diff --git a/crates/common/src/error.rs b/crates/common/src/error.rs index 014ef44..12c6059 100644 --- a/crates/common/src/error.rs +++ b/crates/common/src/error.rs @@ -51,6 +51,7 @@ pub enum CiError { } impl CiError { + #[must_use] pub fn is_disk_full(&self) -> bool { let msg = self.to_string().to_lowercase(); msg.contains("no space left on device") @@ -78,7 +79,7 @@ pub fn check_disk_space(path: &std::path::Path) -> Result { })?; let mut statfs: libc::statfs = unsafe { std::mem::zeroed() }; - if unsafe { libc::statfs(cpath.as_ptr(), &mut statfs) } != 0 { + if unsafe { libc::statfs(cpath.as_ptr(), &raw mut statfs) } != 0 { return Err(CiError::Io(std::io::Error::last_os_error())); } @@ -185,16 +186,19 @@ pub struct DiskSpaceInfo { impl DiskSpaceInfo { /// Check if disk space is critically low (less than 1GB available) + #[must_use] pub fn is_critical(&self) -> bool { self.available_gb < 1.0 } /// Check if disk space is low (less than 5GB available) + #[must_use] pub fn is_low(&self) -> bool { self.available_gb < 5.0 } /// Get a human-readable summary + #[must_use] pub fn summary(&self) -> String { format!( "Total: {:.1}GB, Free: {:.1}GB ({:.1}%), Available: {:.1}GB", diff --git a/crates/common/src/gc_roots.rs b/crates/common/src/gc_roots.rs index 255107a..9b843f2 100644 --- a/crates/common/src/gc_roots.rs +++ b/crates/common/src/gc_roots.rs @@ -8,7 +8,7 @@ use std::{ use tracing::{info, warn}; -/// Remove GC root symlinks with mtime older than max_age. Returns count +/// Remove GC root symlinks with mtime older than `max_age`. Returns count /// removed. pub fn cleanup_old_roots( roots_dir: &Path, diff --git a/crates/common/src/log_storage.rs b/crates/common/src/log_storage.rs index 5ba4dcd..3b9e181 100644 --- a/crates/common/src/log_storage.rs +++ b/crates/common/src/log_storage.rs @@ -15,13 +15,15 @@ impl LogStorage { } /// Returns the filesystem path where a build's log should be stored + #[must_use] pub fn log_path(&self, build_id: &Uuid) -> PathBuf { - self.log_dir.join(format!("{}.log", build_id)) + self.log_dir.join(format!("{build_id}.log")) } /// Returns the filesystem path for an active (in-progress) build log + #[must_use] pub fn log_path_for_active(&self, build_id: &Uuid) -> PathBuf { - self.log_dir.join(format!("{}.active.log", build_id)) + self.log_dir.join(format!("{build_id}.active.log")) } /// Write build log content to file diff --git a/crates/common/src/nix_probe.rs b/crates/common/src/nix_probe.rs index 42d1d8f..481b10a 100644 --- a/crates/common/src/nix_probe.rs +++ b/crates/common/src/nix_probe.rs @@ -96,7 +96,7 @@ pub async fn probe_flake( }; let output = - tokio::time::timeout(std::time::Duration::from_secs(60), async { + tokio::time::timeout(std::time::Duration::from_mins(1), async { tokio::process::Command::new("nix") .args([ "--extra-experimental-features", @@ -230,7 +230,7 @@ pub async fn probe_flake( description: top .get("description") .and_then(|v| v.as_str()) - .map(|s| s.to_string()), + .map(std::string::ToString::to_string), url: Some(repo_url.to_string()), }; diff --git a/crates/common/src/notifications.rs b/crates/common/src/notifications.rs index febc218..4030f47 100644 --- a/crates/common/src/notifications.rs +++ b/crates/common/src/notifications.rs @@ -72,11 +72,11 @@ async fn run_command_notification(cmd: &str, build: &Build, project: &Project) { match result { Ok(output) => { - if !output.status.success() { + if output.status.success() { + info!(build_id = %build.id, "RunCommand completed successfully"); + } else { let stderr = String::from_utf8_lossy(&output.stderr); warn!(build_id = %build.id, "RunCommand failed: {stderr}"); - } else { - info!(build_id = %build.id, "RunCommand completed successfully"); } }, Err(e) => error!(build_id = %build.id, "RunCommand execution failed: {e}"), @@ -90,12 +90,9 @@ async fn set_github_status( build: &Build, ) { // Parse owner/repo from URL - let (owner, repo) = match parse_github_repo(repo_url) { - Some(v) => v, - None => { - warn!("Cannot parse GitHub owner/repo from {repo_url}"); - return; - }, + let (owner, repo) = if let Some(v) = parse_github_repo(repo_url) { v } else { + warn!("Cannot parse GitHub owner/repo from {repo_url}"); + return; }; let (state, description) = match build.status { @@ -125,12 +122,12 @@ async fn set_github_status( .await { Ok(resp) => { - if !resp.status().is_success() { + if resp.status().is_success() { + info!(build_id = %build.id, "Set GitHub commit status: {state}"); + } else { let status = resp.status(); let text = resp.text().await.unwrap_or_default(); warn!("GitHub status API returned {status}: {text}"); - } else { - info!(build_id = %build.id, "Set GitHub commit status: {state}"); } }, Err(e) => error!("GitHub status API request failed: {e}"), @@ -145,12 +142,9 @@ async fn set_gitea_status( build: &Build, ) { // Parse owner/repo from URL (try to extract from the gitea URL) - let (owner, repo) = match parse_gitea_repo(repo_url, base_url) { - Some(v) => v, - None => { - warn!("Cannot parse Gitea owner/repo from {repo_url}"); - return; - }, + let (owner, repo) = if let Some(v) = parse_gitea_repo(repo_url, base_url) { v } else { + warn!("Cannot parse Gitea owner/repo from {repo_url}"); + return; }; let (state, description) = match build.status { @@ -177,12 +171,12 @@ async fn set_gitea_status( .await { Ok(resp) => { - if !resp.status().is_success() { + if resp.status().is_success() { + info!(build_id = %build.id, "Set Gitea commit status: {state}"); + } else { let status = resp.status(); let text = resp.text().await.unwrap_or_default(); warn!("Gitea status API returned {status}: {text}"); - } else { - info!(build_id = %build.id, "Set Gitea commit status: {state}"); } }, Err(e) => error!("Gitea status API request failed: {e}"), @@ -197,12 +191,9 @@ async fn set_gitlab_status( build: &Build, ) { // Parse project path from URL - let project_path = match parse_gitlab_project(repo_url, base_url) { - Some(p) => p, - None => { - warn!("Cannot parse GitLab project from {repo_url}"); - return; - }, + let project_path = if let Some(p) = parse_gitlab_project(repo_url, base_url) { p } else { + warn!("Cannot parse GitLab project from {repo_url}"); + return; }; // GitLab uses different state names @@ -238,12 +229,12 @@ async fn set_gitlab_status( .await { Ok(resp) => { - if !resp.status().is_success() { + if resp.status().is_success() { + info!(build_id = %build.id, "Set GitLab commit status: {state}"); + } else { let status = resp.status(); let text = resp.text().await.unwrap_or_default(); warn!("GitLab status API returned {status}: {text}"); - } else { - info!(build_id = %build.id, "Set GitLab commit status: {state}"); } }, Err(e) => error!("GitLab status API request failed: {e}"), diff --git a/crates/common/src/repo/build_dependencies.rs b/crates/common/src/repo/build_dependencies.rs index 324fbc3..b612d44 100644 --- a/crates/common/src/repo/build_dependencies.rs +++ b/crates/common/src/repo/build_dependencies.rs @@ -45,7 +45,7 @@ pub async fn list_for_build( } /// Batch check if all dependency builds are completed for multiple builds at -/// once. Returns a map from build_id to whether all deps are completed. +/// once. Returns a map from `build_id` to whether all deps are completed. pub async fn check_deps_for_builds( pool: &PgPool, build_ids: &[Uuid], diff --git a/crates/common/src/repo/builds.rs b/crates/common/src/repo/builds.rs index 6087d60..4e6eda4 100644 --- a/crates/common/src/repo/builds.rs +++ b/crates/common/src/repo/builds.rs @@ -179,7 +179,7 @@ pub async fn reset_orphaned( Ok(result.rows_affected()) } -/// List builds with optional evaluation_id, status, system, and job_name +/// List builds with optional `evaluation_id`, status, system, and `job_name` /// filters, with pagination. pub async fn list_filtered( pool: &PgPool, @@ -305,7 +305,7 @@ pub async fn mark_signed(pool: &PgPool, id: Uuid) -> Result<()> { } /// Batch-fetch completed builds by derivation paths. -/// Returns a map from drv_path to Build for deduplication. +/// Returns a map from `drv_path` to Build for deduplication. pub async fn get_completed_by_drv_paths( pool: &PgPool, drv_paths: &[String], @@ -330,7 +330,7 @@ pub async fn get_completed_by_drv_paths( ) } -/// Set the builder_id for a build. +/// Set the `builder_id` for a build. pub async fn set_builder( pool: &PgPool, id: Uuid, diff --git a/crates/common/src/repo/evaluations.rs b/crates/common/src/repo/evaluations.rs index 6c502d9..b339f87 100644 --- a/crates/common/src/repo/evaluations.rs +++ b/crates/common/src/repo/evaluations.rs @@ -58,7 +58,7 @@ pub async fn list_for_jobset( .map_err(CiError::Database) } -/// List evaluations with optional jobset_id and status filters, with +/// List evaluations with optional `jobset_id` and status filters, with /// pagination. pub async fn list_filtered( pool: &PgPool, @@ -145,7 +145,7 @@ pub async fn set_inputs_hash( Ok(()) } -/// Check if an evaluation with the same inputs_hash already exists for this +/// Check if an evaluation with the same `inputs_hash` already exists for this /// jobset. pub async fn get_by_inputs_hash( pool: &PgPool, diff --git a/crates/common/src/repo/project_members.rs b/crates/common/src/repo/project_members.rs index 2f3b9f1..1e0ded0 100644 --- a/crates/common/src/repo/project_members.rs +++ b/crates/common/src/repo/project_members.rs @@ -52,7 +52,7 @@ pub async fn get(pool: &PgPool, id: Uuid) -> Result { .map_err(|e| { match e { sqlx::Error::RowNotFound => { - CiError::NotFound(format!("Project member {} not found", id)) + CiError::NotFound(format!("Project member {id} not found")) }, _ => CiError::Database(e), } @@ -123,7 +123,7 @@ pub async fn update( .map_err(|e| { match e { sqlx::Error::RowNotFound => { - CiError::NotFound(format!("Project member {} not found", id)) + CiError::NotFound(format!("Project member {id} not found")) }, _ => CiError::Database(e), } @@ -141,8 +141,7 @@ pub async fn delete(pool: &PgPool, id: Uuid) -> Result<()> { .await?; if result.rows_affected() == 0 { return Err(CiError::NotFound(format!( - "Project member {} not found", - id + "Project member {id} not found" ))); } Ok(()) diff --git a/crates/common/src/repo/search.rs b/crates/common/src/repo/search.rs index a381d54..7710951 100644 --- a/crates/common/src/repo/search.rs +++ b/crates/common/src/repo/search.rs @@ -9,7 +9,7 @@ use crate::{ }; /// Search entity types -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SearchEntity { Projects, Jobsets, @@ -18,14 +18,14 @@ pub enum SearchEntity { } /// Sort order for search results -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SortOrder { Asc, Desc, } /// Sort field for builds -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum BuildSortField { CreatedAt, JobName, @@ -34,7 +34,7 @@ pub enum BuildSortField { } /// Sort field for projects -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ProjectSortField { Name, CreatedAt, @@ -42,7 +42,7 @@ pub enum ProjectSortField { } /// Build status filter -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum BuildStatusFilter { Pending, Running, @@ -492,7 +492,7 @@ pub async fn quick_search( query: &str, limit: i64, ) -> Result<(Vec, Vec)> { - let pattern = format!("%{}%", query); + let pattern = format!("%{query}%"); let projects = sqlx::query_as::<_, Project>( "SELECT * FROM projects WHERE name ILIKE $1 OR description ILIKE $1 ORDER \ diff --git a/crates/common/src/repo/starred_jobs.rs b/crates/common/src/repo/starred_jobs.rs index 3186b29..a720eef 100644 --- a/crates/common/src/repo/starred_jobs.rs +++ b/crates/common/src/repo/starred_jobs.rs @@ -43,7 +43,7 @@ pub async fn get(pool: &PgPool, id: Uuid) -> Result { .map_err(|e| { match e { sqlx::Error::RowNotFound => { - CiError::NotFound(format!("Starred job {} not found", id)) + CiError::NotFound(format!("Starred job {id} not found")) }, _ => CiError::Database(e), } @@ -107,7 +107,7 @@ pub async fn delete(pool: &PgPool, id: Uuid) -> Result<()> { .execute(pool) .await?; if result.rows_affected() == 0 { - return Err(CiError::NotFound(format!("Starred job {} not found", id))); + return Err(CiError::NotFound(format!("Starred job {id} not found"))); } Ok(()) } diff --git a/crates/common/src/repo/users.rs b/crates/common/src/repo/users.rs index b557271..83c4312 100644 --- a/crates/common/src/repo/users.rs +++ b/crates/common/src/repo/users.rs @@ -29,7 +29,7 @@ pub fn hash_password(password: &str) -> Result { argon2 .hash_password(password.as_bytes(), &salt) .map(|h| h.to_string()) - .map_err(|e| CiError::Internal(format!("Password hashing failed: {}", e))) + .map_err(|e| CiError::Internal(format!("Password hashing failed: {e}"))) } /// Verify a password against a hash @@ -37,7 +37,7 @@ pub fn verify_password(password: &str, hash: &str) -> Result { use argon2::{Argon2, PasswordHash, PasswordVerifier}; let parsed_hash = PasswordHash::new(hash) - .map_err(|e| CiError::Internal(format!("Invalid password hash: {}", e)))?; + .map_err(|e| CiError::Internal(format!("Invalid password hash: {e}")))?; let argon2 = Argon2::default(); Ok( argon2 @@ -134,7 +134,7 @@ pub async fn get(pool: &PgPool, id: Uuid) -> Result { .map_err(|e| { match e { sqlx::Error::RowNotFound => { - CiError::NotFound(format!("User {} not found", id)) + CiError::NotFound(format!("User {id} not found")) }, _ => CiError::Database(e), } @@ -321,7 +321,7 @@ pub async fn delete(pool: &PgPool, id: Uuid) -> Result<()> { .execute(pool) .await?; if result.rows_affected() == 0 { - return Err(CiError::NotFound(format!("User {} not found", id))); + return Err(CiError::NotFound(format!("User {id} not found"))); } Ok(()) } @@ -335,7 +335,7 @@ pub async fn upsert_oauth_user( oauth_provider_id: &str, ) -> Result { // Use provider ID in username to avoid collisions - let unique_username = format!("{}_{}", username, oauth_provider_id); + let unique_username = format!("{username}_{oauth_provider_id}"); // Check if user exists by OAuth provider ID pattern let existing = @@ -381,7 +381,7 @@ pub async fn upsert_oauth_user( VALUES ($1, $2, $3, NULL, 'read-only') RETURNING *", ) .bind(&unique_username) - .bind(email.unwrap_or(&format!("{}@oauth.local", unique_username))) + .bind(email.unwrap_or(&format!("{unique_username}@oauth.local"))) .bind(user_type_str) .fetch_one(pool) .await @@ -395,7 +395,7 @@ pub async fn upsert_oauth_user( }) } -/// Create a new session for a user. Returns (session_token, session_id). +/// Create a new session for a user. Returns (`session_token`, `session_id`). pub async fn create_session( pool: &PgPool, user_id: Uuid, diff --git a/crates/common/src/roles.rs b/crates/common/src/roles.rs index 7e42281..28c279e 100644 --- a/crates/common/src/roles.rs +++ b/crates/common/src/roles.rs @@ -49,16 +49,19 @@ pub const VALID_PROJECT_ROLES: &[&str] = &[ ]; /// Check if a global role is valid +#[must_use] pub fn is_valid_role(role: &str) -> bool { VALID_ROLES.contains(&role) } /// Check if a project role is valid +#[must_use] pub fn is_valid_project_role(role: &str) -> bool { VALID_PROJECT_ROLES.contains(&role) } /// Get the highest project role (for permission checks) +#[must_use] pub fn project_role_level(role: &str) -> i32 { match role { PROJECT_ROLE_ADMIN => 3, @@ -70,6 +73,7 @@ pub fn project_role_level(role: &str) -> i32 { /// Check if user has required project permission /// Higher level roles automatically have lower level permissions +#[must_use] pub fn has_project_permission(user_role: &str, required: &str) -> bool { let user_level = project_role_level(user_role); let required_level = project_role_level(required); diff --git a/crates/common/src/validation.rs b/crates/common/src/validation.rs index 241983e..81246f1 100644 --- a/crates/common/src/validation.rs +++ b/crates/common/src/validation.rs @@ -160,7 +160,7 @@ pub fn validate_full_name(name: &str) -> Result<(), ValidationError> { }); } - if name.chars().any(|c| c.is_control()) { + if name.chars().any(char::is_control) { return Err(ValidationError { field: "full_name".to_string(), message: "Full name cannot contain control characters".to_string(), diff --git a/crates/evaluator/src/git.rs b/crates/evaluator/src/git.rs index 5da6615..1c0fc79 100644 --- a/crates/evaluator/src/git.rs +++ b/crates/evaluator/src/git.rs @@ -3,7 +3,7 @@ use std::path::{Path, PathBuf}; use fc_common::error::Result; use git2::Repository; -/// Clone or fetch a repository. Returns (repo_path, commit_hash). +/// Clone or fetch a repository. Returns (`repo_path`, `commit_hash`). /// /// If `branch` is `Some`, resolve `refs/remotes/origin/` instead of /// HEAD. diff --git a/crates/evaluator/src/nix.rs b/crates/evaluator/src/nix.rs index e31112b..2d2e79a 100644 --- a/crates/evaluator/src/nix.rs +++ b/crates/evaluator/src/nix.rs @@ -72,8 +72,8 @@ pub fn parse_eval_output(stdout: &str) -> EvalResult { } /// Evaluate nix expressions and return discovered jobs. -/// If flake_mode is true, uses nix-eval-jobs with --flake flag. -/// If flake_mode is false, evaluates a legacy expression file. +/// If `flake_mode` is true, uses nix-eval-jobs with --flake flag. +/// If `flake_mode` is false, evaluates a legacy expression file. #[tracing::instrument(skip(config, inputs), fields(flake_mode, nix_expression))] pub async fn evaluate( repo_path: &Path, @@ -286,7 +286,7 @@ async fn evaluate_with_nix_eval( let system = drv_val .get("system") .and_then(|v| v.as_str()) - .map(|s| s.to_string()); + .map(std::string::ToString::to_string); jobs.push(NixJob { name: name.clone(), drv_path: drv_path.clone(), diff --git a/crates/queue-runner/src/builder.rs b/crates/queue-runner/src/builder.rs index d7442ad..86b3084 100644 --- a/crates/queue-runner/src/builder.rs +++ b/crates/queue-runner/src/builder.rs @@ -68,7 +68,7 @@ pub async fn run_nix_build_remote( buf }); - let live_log_path_owned = live_log_path.map(|p| p.to_path_buf()); + let live_log_path_owned = live_log_path.map(std::path::Path::to_path_buf); let stderr_task = tokio::spawn(async move { let mut buf = String::new(); let steps: Vec = Vec::new(); @@ -146,6 +146,7 @@ pub struct SubStep { /// Parse a single nix internal JSON log line (`@nix {...}`). /// Returns `Some(action, drv_path)` if the line contains a derivation action. +#[must_use] pub fn parse_nix_log_line(line: &str) -> Option<(&'static str, String)> { let json_str = line.strip_prefix("@nix ")?.trim(); let parsed: serde_json::Value = serde_json::from_str(json_str).ok()?; @@ -209,7 +210,7 @@ pub async fn run_nix_build( }); // Read stderr (logs + internal JSON) - let live_log_path_owned = live_log_path.map(|p| p.to_path_buf()); + let live_log_path_owned = live_log_path.map(std::path::Path::to_path_buf); let stderr_task = tokio::spawn(async move { let mut buf = String::new(); let mut steps: Vec = Vec::new(); diff --git a/crates/queue-runner/src/worker.rs b/crates/queue-runner/src/worker.rs index 5825e8f..781a9fe 100644 --- a/crates/queue-runner/src/worker.rs +++ b/crates/queue-runner/src/worker.rs @@ -31,6 +31,7 @@ pub struct WorkerPool { impl WorkerPool { #[allow(clippy::too_many_arguments)] + #[must_use] pub fn new( db_pool: PgPool, workers: usize, @@ -348,7 +349,7 @@ async fn run_build( match result { Ok(build_result) => { // Complete the build step - let exit_code = if build_result.success { 0 } else { 1 }; + let exit_code = i32::from(!build_result.success); repo::build_steps::complete( pool, step.id, @@ -366,7 +367,7 @@ async fn run_build( command: format!("nix build {}", sub_step.drv_path), }) .await?; - let sub_exit = if sub_step.success { 0 } else { 1 }; + let sub_exit = i32::from(!sub_step.success); repo::build_steps::complete(pool, sub.id, sub_exit, None, None).await?; } @@ -476,7 +477,7 @@ async fn run_build( } let primary_output = - build_result.output_paths.first().map(|s| s.as_str()); + build_result.output_paths.first().map(std::string::String::as_str); repo::builds::complete( pool, diff --git a/crates/server/src/auth_middleware.rs b/crates/server/src/auth_middleware.rs index 280e06c..a43b025 100644 --- a/crates/server/src/auth_middleware.rs +++ b/crates/server/src/auth_middleware.rs @@ -77,7 +77,7 @@ pub async fn require_api_key( { // Check session expiry (24 hours) if session.created_at.elapsed() - < std::time::Duration::from_secs(24 * 60 * 60) + < std::time::Duration::from_hours(24) { // Insert both user and session data if let Some(ref user) = session.user { @@ -87,11 +87,10 @@ pub async fn require_api_key( request.extensions_mut().insert(api_key.clone()); } return Ok(next.run(request).await); - } else { - // Expired, remove it - drop(session); - state.sessions.remove(&session_id); } + // Expired, remove it + drop(session); + state.sessions.remove(&session_id); } // Try legacy API key session (fc_session cookie) @@ -100,17 +99,16 @@ pub async fn require_api_key( { // Check session expiry (24 hours) if session.created_at.elapsed() - < std::time::Duration::from_secs(24 * 60 * 60) + < std::time::Duration::from_hours(24) { if let Some(ref api_key) = session.api_key { request.extensions_mut().insert(api_key.clone()); } return Ok(next.run(request).await); - } else { - // Expired, remove it - drop(session); - state.sessions.remove(&session_id); } + // Expired, remove it + drop(session); + state.sessions.remove(&session_id); } } @@ -138,7 +136,7 @@ impl FromRequestParts for RequireAdmin { && user.role == "admin" { // Create a synthetic API key for compatibility - return Ok(RequireAdmin(ApiKey { + return Ok(Self(ApiKey { id: user.id, name: user.username.clone(), key_hash: String::new(), @@ -157,7 +155,7 @@ impl FromRequestParts for RequireAdmin { .ok_or(StatusCode::UNAUTHORIZED)?; if key.role == "admin" { - Ok(RequireAdmin(key)) + Ok(Self(key)) } else { Err(StatusCode::FORBIDDEN) } @@ -216,7 +214,7 @@ pub async fn extract_session( .headers() .get("cookie") .and_then(|v| v.to_str().ok()) - .map(|s| s.to_string()); + .map(std::string::ToString::to_string); if let Some(cookie_header) = cookie_header { // Try user session first @@ -225,7 +223,7 @@ pub async fn extract_session( { // Check session expiry if session.created_at.elapsed() - < std::time::Duration::from_secs(24 * 60 * 60) + < std::time::Duration::from_hours(24) { if let Some(ref user) = session.user { request.extensions_mut().insert(user.clone()); @@ -245,7 +243,7 @@ pub async fn extract_session( { // Check session expiry if session.created_at.elapsed() - < std::time::Duration::from_secs(24 * 60 * 60) + < std::time::Duration::from_hours(24) { if let Some(ref api_key) = session.api_key { request.extensions_mut().insert(api_key.clone()); diff --git a/crates/server/src/error.rs b/crates/server/src/error.rs index 18c61c9..9c3d23e 100644 --- a/crates/server/src/error.rs +++ b/crates/server/src/error.rs @@ -9,7 +9,7 @@ pub struct ApiError(pub CiError); impl From for ApiError { fn from(err: CiError) -> Self { - ApiError(err) + Self(err) } } @@ -45,12 +45,11 @@ impl IntoResponse for ApiError { StatusCode::INSUFFICIENT_STORAGE, "DISK_FULL", format!( - "{}\n\nDISK SPACE ISSUE DETECTED:\nThe server has run out of \ + "{msg}\n\nDISK SPACE ISSUE DETECTED:\nThe server has run out of \ disk space. Please free up space:\n- Run `nix-collect-garbage \ -d` to clean the Nix store\n- Clear the evaluator work \ directory: `rm -rf /tmp/fc-evaluator/*`\n- Clear build logs if \ - configured", - msg + configured" ), ) } else { @@ -66,11 +65,10 @@ impl IntoResponse for ApiError { StatusCode::INSUFFICIENT_STORAGE, "DISK_FULL", format!( - "{}\n\nDISK SPACE ISSUE:\nThe server is running low on disk \ + "{msg}\n\nDISK SPACE ISSUE:\nThe server is running low on disk \ space. Please free up space:\n- Run `nix-collect-garbage -d` to \ clean the Nix store\n- Clear the evaluator work directory\n- \ - Clear build logs if configured", - msg + Clear build logs if configured" ), ) }, @@ -92,9 +90,8 @@ impl IntoResponse for ApiError { StatusCode::INSUFFICIENT_STORAGE, "DISK_FULL", format!( - "Database error: {}\n\nDISK SPACE ISSUE:\nThe server is running \ - low on disk space.", - e + "Database error: {e}\n\nDISK SPACE ISSUE:\nThe server is running \ + low on disk space." ), ) } else { @@ -133,12 +130,11 @@ impl IntoResponse for ApiError { StatusCode::INSUFFICIENT_STORAGE, "DISK_FULL", format!( - "IO error: {}\n\nDISK SPACE ISSUE DETECTED:\nThe server has run \ + "IO error: {msg}\n\nDISK SPACE ISSUE DETECTED:\nThe server has run \ out of disk space. Please free up space:\n- Run \ `nix-collect-garbage -d` to clean the Nix store\n- Clear the \ evaluator work directory: `rm -rf /tmp/fc-evaluator/*`\n- \ - Clear build logs if configured", - msg + Clear build logs if configured" ), ) } else { @@ -160,9 +156,8 @@ impl IntoResponse for ApiError { StatusCode::INSUFFICIENT_STORAGE, "DISK_FULL", format!( - "{}\n\nDISK SPACE ISSUE:\nThe server is running low on disk \ - space. Please free up space.", - msg + "{msg}\n\nDISK SPACE ISSUE:\nThe server is running low on disk \ + space. Please free up space." ), ) } else { diff --git a/crates/server/src/routes/auth.rs b/crates/server/src/routes/auth.rs index 5847602..67f0388 100644 --- a/crates/server/src/routes/auth.rs +++ b/crates/server/src/routes/auth.rs @@ -29,6 +29,7 @@ pub struct ApiKeyInfo { pub last_used_at: Option>, } +#[must_use] pub fn hash_api_key(key: &str) -> String { let mut hasher = Sha256::new(); hasher.update(key.as_bytes()); diff --git a/crates/server/src/routes/cache.rs b/crates/server/src/routes/cache.rs index b23308b..4a697fd 100644 --- a/crates/server/src/routes/cache.rs +++ b/crates/server/src/routes/cache.rs @@ -10,7 +10,7 @@ use tokio::process::Command; use crate::{error::ApiError, state::AppState}; -/// Serve NARInfo for a store path hash. +/// Serve `NARInfo` for a store path hash. /// GET /nix-cache/{hash}.narinfo async fn narinfo( State(state): State, @@ -68,7 +68,7 @@ async fn narinfo( }; let nar_hash = entry.get("narHash").and_then(|v| v.as_str()).unwrap_or(""); - let nar_size = entry.get("narSize").and_then(|v| v.as_u64()).unwrap_or(0); + let nar_size = entry.get("narSize").and_then(serde_json::Value::as_u64).unwrap_or(0); let store_path = entry .get("path") .and_then(|v| v.as_str()) diff --git a/crates/server/src/routes/evaluations.rs b/crates/server/src/routes/evaluations.rs index 794d3d0..69402fd 100644 --- a/crates/server/src/routes/evaluations.rs +++ b/crates/server/src/routes/evaluations.rs @@ -186,7 +186,9 @@ async fn compare_evaluations( // Jobs in both: compare derivation paths for (name, from_build) in &from_map { if let Some(to_build) = to_map.get(name) { - if from_build.drv_path != to_build.drv_path { + if from_build.drv_path == to_build.drv_path { + unchanged_count += 1; + } else { changed_jobs.push(JobChange { job_name: name.to_string(), system: to_build.system.clone(), @@ -195,8 +197,6 @@ async fn compare_evaluations( old_status: format!("{:?}", from_build.status), new_status: format!("{:?}", to_build.status), }); - } else { - unchanged_count += 1; } } } diff --git a/crates/server/src/routes/logs.rs b/crates/server/src/routes/logs.rs index 73e29d8..7105490 100644 --- a/crates/server/src/routes/logs.rs +++ b/crates/server/src/routes/logs.rs @@ -93,12 +93,9 @@ async fn stream_build_log( if active_path.exists() { active_path.clone() } else { final_path.clone() } }; - let file = match tokio::fs::File::open(&path).await { - Ok(f) => f, - Err(_) => { - yield Ok(Event::default().data("Failed to open log file")); - return; - } + let file = if let Ok(f) = tokio::fs::File::open(&path).await { f } else { + yield Ok(Event::default().data("Failed to open log file")); + return; }; let mut reader = BufReader::new(file); diff --git a/crates/server/src/routes/metrics.rs b/crates/server/src/routes/metrics.rs index 6865423..48f650a 100644 --- a/crates/server/src/routes/metrics.rs +++ b/crates/server/src/routes/metrics.rs @@ -130,7 +130,7 @@ async fn prometheus_metrics(State(state): State) -> Response { output .push_str("\n# HELP fc_evaluations_total Total number of evaluations\n"); output.push_str("# TYPE fc_evaluations_total gauge\n"); - output.push_str(&format!("fc_evaluations_total {}\n", eval_count)); + output.push_str(&format!("fc_evaluations_total {eval_count}\n")); output.push_str("\n# HELP fc_evaluations_by_status Evaluations by status\n"); output.push_str("# TYPE fc_evaluations_by_status gauge\n"); diff --git a/crates/server/src/routes/oauth.rs b/crates/server/src/routes/oauth.rs index be3700c..f2d4467 100644 --- a/crates/server/src/routes/oauth.rs +++ b/crates/server/src/routes/oauth.rs @@ -270,8 +270,7 @@ async fn github_callback( "" }; let clear_state = format!( - "fc_oauth_state=; HttpOnly; SameSite=Lax; Path=/; Max-Age=0{}", - secure_flag + "fc_oauth_state=; HttpOnly; SameSite=Lax; Path=/; Max-Age=0{secure_flag}" ); let session_cookie = format!( "fc_user_session={}; HttpOnly; SameSite=Lax; Path=/; Max-Age={}{}", diff --git a/crates/server/src/routes/search.rs b/crates/server/src/routes/search.rs index b0d2803..20befea 100644 --- a/crates/server/src/routes/search.rs +++ b/crates/server/src/routes/search.rs @@ -130,8 +130,8 @@ struct SearchRequest { eval_before: Option>, // Sorting - /// Sort builds by: created_at, job_name, status, priority (default: - /// created_at) + /// Sort builds by: `created_at`, `job_name`, status, priority (default: + /// `created_at`) #[serde(rename = "build_sort")] build_sort: Option, @@ -139,12 +139,12 @@ struct SearchRequest { #[serde(rename = "order")] order: Option, - /// Sort projects by: name, created_at (default: name) + /// Sort projects by: name, `created_at` (default: name) #[serde(rename = "project_sort")] project_sort: Option, } -fn default_limit() -> i64 { +const fn default_limit() -> i64 { 20 } diff --git a/crates/server/src/routes/users.rs b/crates/server/src/routes/users.rs index 9ff3cc8..e41a843 100644 --- a/crates/server/src/routes/users.rs +++ b/crates/server/src/routes/users.rs @@ -45,7 +45,7 @@ pub struct UserResponse { impl From for UserResponse { fn from(u: User) -> Self { - UserResponse { + Self { id: u.id, username: u.username, email: u.email, diff --git a/crates/server/src/routes/webhooks.rs b/crates/server/src/routes/webhooks.rs index 2a91144..14ed304 100644 --- a/crates/server/src/routes/webhooks.rs +++ b/crates/server/src/routes/webhooks.rs @@ -403,35 +403,32 @@ async fn handle_gitea_push( .map_err(ApiError)?; // Fall back to the other type if not found - let webhook_config = match webhook_config { - Some(c) => c, - None => { - let alt = if forge_type == "gitea" { - "forgejo" - } else { - "gitea" - }; - match repo::webhook_configs::get_by_project_and_forge( - &state.pool, - project_id, - alt, - ) - .await - .map_err(ApiError)? - { - Some(c) => c, - None => { - return Ok(( - StatusCode::NOT_FOUND, - Json(WebhookResponse { - accepted: false, - message: "No Gitea/Forgejo webhook configured for this project" - .to_string(), - }), - )); - }, - } - }, + let webhook_config = if let Some(c) = webhook_config { c } else { + let alt = if forge_type == "gitea" { + "forgejo" + } else { + "gitea" + }; + match repo::webhook_configs::get_by_project_and_forge( + &state.pool, + project_id, + alt, + ) + .await + .map_err(ApiError)? + { + Some(c) => c, + None => { + return Ok(( + StatusCode::NOT_FOUND, + Json(WebhookResponse { + accepted: false, + message: "No Gitea/Forgejo webhook configured for this project" + .to_string(), + }), + )); + }, + } }; // Verify signature if configured diff --git a/crates/server/src/state.rs b/crates/server/src/state.rs index 679aadf..c341c82 100644 --- a/crates/server/src/state.rs +++ b/crates/server/src/state.rs @@ -17,6 +17,7 @@ pub struct SessionData { impl SessionData { /// Check if the session has admin role + #[must_use] pub fn is_admin(&self) -> bool { if let Some(ref user) = self.user { user.role == "admin" @@ -28,6 +29,7 @@ impl SessionData { } /// Check if the session has a specific role + #[must_use] pub fn has_role(&self, role: &str) -> bool { if self.is_admin() { return true; @@ -42,6 +44,7 @@ impl SessionData { } /// Get the display name for the session (username or api key name) + #[must_use] pub fn display_name(&self) -> String { if let Some(ref user) = self.user { user.username.clone() @@ -53,7 +56,8 @@ impl SessionData { } /// Check if this is a user session (not just API key) - pub fn is_user_session(&self) -> bool { + #[must_use] + pub const fn is_user_session(&self) -> bool { self.user.is_some() } }