From 11dd0f006cc5c3d9e214604d44ed1dc475ac32a9 Mon Sep 17 00:00:00 2001 From: Malcolm Greaves Date: Thu, 26 Mar 2026 17:21:46 -0700 Subject: [PATCH] Documenting & simplifying error variants Adding docstrings to all `OxenError` variants. Docstring explains what the variant is and, if applicable, the situations and code paths where it is encountered. Audited all `OxenError` variants and identified variants that are never used in code. These variants have been removed. Additionally, some variants only ever use statically allocated strings as their inner value. These have been replaced with non-value bearing enum variants. --- crates/lib/src/api/client/repositories.rs | 2 +- crates/lib/src/core/v_latest/commits.rs | 12 +- crates/lib/src/core/v_latest/entries.rs | 4 +- crates/lib/src/core/v_latest/pull.rs | 6 +- crates/lib/src/core/v_latest/push.rs | 4 +- .../src/core/v_latest/workspaces/commit.rs | 26 +- .../core/v_latest/workspaces/data_frames.rs | 6 +- .../lib/src/core/v_latest/workspaces/diff.rs | 2 +- .../lib/src/core/v_latest/workspaces/files.rs | 48 ++- crates/lib/src/core/v_old/v0_19_0/pull.rs | 2 +- crates/lib/src/error.rs | 364 +++++++++--------- crates/lib/src/repositories.rs | 6 +- crates/lib/src/repositories/checkout.rs | 4 +- crates/lib/src/repositories/diffs.rs | 6 +- crates/lib/src/repositories/fork.rs | 4 +- crates/lib/src/repositories/tree.rs | 2 +- crates/lib/src/repositories/workspaces.rs | 4 +- .../workspaces/data_frames/embeddings.rs | 2 +- crates/lib/src/util/fs.rs | 5 +- crates/oxen-py/src/py_remote_data_frame.rs | 6 +- crates/server/src/controllers/commits.rs | 16 +- crates/server/src/controllers/diff.rs | 34 +- crates/server/src/controllers/entries.rs | 2 +- crates/server/src/controllers/fork.rs | 7 +- crates/server/src/controllers/merger.rs | 20 +- crates/server/src/controllers/metadata.rs | 2 +- crates/server/src/controllers/repositories.rs | 4 +- crates/server/src/controllers/schemas.rs | 2 +- crates/server/src/controllers/tree.rs | 2 +- crates/server/src/errors.rs | 23 +- 30 files changed, 310 insertions(+), 317 deletions(-) diff --git a/crates/lib/src/api/client/repositories.rs b/crates/lib/src/api/client/repositories.rs index 67acf625f..5de3a3ba7 100644 --- a/crates/lib/src/api/client/repositories.rs +++ b/crates/lib/src/api/client/repositories.rs @@ -113,7 +113,7 @@ pub async fn get_by_remote(remote: &Remote) -> Result Result { latest_commit = Some(commit); } } - latest_commit.ok_or(OxenError::no_commits_found()) + latest_commit.ok_or_else(|| OxenError::NoCommitsFound) } fn head_commit_id(repo: &LocalRepository) -> Result { let commit_id = with_ref_manager(repo, |manager| manager.head_commit_id())?; match commit_id { Some(commit_id) => Ok(commit_id.parse()?), - None => Err(OxenError::head_not_found()), + None => Err(OxenError::HeadNotFound), } } @@ -257,7 +257,7 @@ pub fn create_empty_commit( ) -> Result { let branch_name = branch_name.as_ref(); let Some(existing_commit) = repositories::revisions::get(repo, branch_name)? else { - return Err(OxenError::revision_not_found(branch_name.into())); + return Err(OxenError::RevisionNotFound(branch_name.into())); }; let existing_commit_id = existing_commit.id.parse()?; let existing_node = @@ -677,9 +677,9 @@ pub fn list_from_paginated_impl( let base = split[0]; let head = split[1]; let base_commit = repositories::commits::get_by_id(repo, base)? - .ok_or(OxenError::revision_not_found(base.into()))?; + .ok_or(OxenError::RevisionNotFound(base.into()))?; let head_commit = repositories::commits::get_by_id(repo, head)? - .ok_or(OxenError::revision_not_found(head.into()))?; + .ok_or(OxenError::RevisionNotFound(head.into()))?; let (commits, total_count) = list_recursive_paginated(repo, head_commit, skip, limit, Some(&base_commit), None)?; @@ -791,7 +791,7 @@ pub fn count_from( let revision = revision.as_ref(); let commit = repositories::revisions::get(repo, revision)? - .ok_or_else(|| OxenError::revision_not_found(revision.into()))?; + .ok_or_else(|| OxenError::RevisionNotFound(revision.into()))?; let db = open_commit_count_db(repo)?; diff --git a/crates/lib/src/core/v_latest/entries.rs b/crates/lib/src/core/v_latest/entries.rs index 80680e021..f67d2624c 100644 --- a/crates/lib/src/core/v_latest/entries.rs +++ b/crates/lib/src/core/v_latest/entries.rs @@ -72,7 +72,7 @@ pub fn list_directory_with_depth( let commit = parsed_resource .commit .clone() - .ok_or(OxenError::revision_not_found(revision.into()))?; + .ok_or(OxenError::RevisionNotFound(revision.into()))?; log::debug!("list_directory commit {commit}"); @@ -537,7 +537,7 @@ pub fn list_for_commit( pub fn update_metadata(repo: &LocalRepository, revision: impl AsRef) -> Result<(), OxenError> { let commit = repositories::revisions::get(repo, revision.as_ref())? - .ok_or_else(|| OxenError::revision_not_found(revision.as_ref().to_string().into()))?; + .ok_or_else(|| OxenError::RevisionNotFound(revision.as_ref().to_string().into()))?; let tree: CommitMerkleTree = CommitMerkleTree::from_commit(repo, &commit)?; let mut node = tree.root; diff --git a/crates/lib/src/core/v_latest/pull.rs b/crates/lib/src/core/v_latest/pull.rs index 58b06643e..809c61c64 100644 --- a/crates/lib/src/core/v_latest/pull.rs +++ b/crates/lib/src/core/v_latest/pull.rs @@ -48,7 +48,7 @@ pub async fn pull_remote_branch( let remote_branch = fetch::fetch_remote_branch(repo, &remote_repo, &fetch_opts).await?; let new_head_commit = repositories::revisions::get(repo, &remote_branch.commit_id)?.ok_or( - OxenError::revision_not_found(remote_branch.commit_id.to_owned().into()), + OxenError::RevisionNotFound(remote_branch.commit_id.to_owned().into()), )?; match previous_head_commit { @@ -72,8 +72,8 @@ pub async fn pull_remote_branch( } Ok(None) => { // Merge conflict, keep the previous commit - return Err(OxenError::merge_conflict( - "There was a merge conflict, please resolve it before pulling", + return Err(OxenError::UpstreamMergeConflict( + "There was a merge conflict, please resolve it before pulling.".into(), )); } Err(e) => return Err(e), diff --git a/crates/lib/src/core/v_latest/push.rs b/crates/lib/src/core/v_latest/push.rs index c3edee1b2..6a451b48a 100644 --- a/crates/lib/src/core/v_latest/push.rs +++ b/crates/lib/src/core/v_latest/push.rs @@ -72,7 +72,7 @@ async fn push_local_branch_to_remote_repo( ) -> Result<(), OxenError> { // Get the commit from the branch let Some(commit) = repositories::commits::get_by_id(repo, &local_branch.commit_id)? else { - return Err(OxenError::revision_not_found( + return Err(OxenError::RevisionNotFound( local_branch.commit_id.clone().into(), )); }; @@ -136,7 +136,7 @@ async fn push_to_existing_branch( let latest_remote_commit = repositories::commits::get_by_id(repo, &remote_branch.commit_id)?.ok_or_else( - || OxenError::revision_not_found(remote_branch.commit_id.clone().into()), + || OxenError::RevisionNotFound(remote_branch.commit_id.clone().into()), )?; let mut commits = diff --git a/crates/lib/src/core/v_latest/workspaces/commit.rs b/crates/lib/src/core/v_latest/workspaces/commit.rs index 28ab599a5..4a142ffe9 100644 --- a/crates/lib/src/core/v_latest/workspaces/commit.rs +++ b/crates/lib/src/core/v_latest/workspaces/commit.rs @@ -61,7 +61,7 @@ pub async fn commit( let conflicts = list_conflicts(workspace, &dir_entries, &branch)?; if !conflicts.is_empty() { - return Err(OxenError::workspace_behind(workspace)); + return Err(OxenError::WorkspaceBehind(Box::new(workspace.clone()))); } let dir_entries = export_tabular_data_frames(workspace, dir_entries).await?; @@ -110,16 +110,14 @@ pub fn mergeability( let branch_name = branch_name.as_ref(); let Some(branch) = repositories::branches::get_by_name(&workspace.base_repo, branch_name)? else { - return Err(OxenError::revision_not_found( - branch_name.to_string().into(), - )); + return Err(OxenError::BranchNotFound(branch_name.into())); }; let base = &workspace.commit; let Some(head) = repositories::commits::get_by_id(&workspace.base_repo, &branch.commit_id)? else { - return Err(OxenError::revision_not_found( - branch.commit_id.clone().into(), + return Err(OxenError::RevisionNotFound( + branch.commit_id.as_str().into(), )); }; @@ -174,8 +172,8 @@ fn list_conflicts( let Some(branch_commit) = repositories::commits::get_by_id(&workspace.base_repo, &branch.commit_id)? else { - return Err(OxenError::revision_not_found( - branch.commit_id.clone().into(), + return Err(OxenError::RevisionNotFound( + branch.commit_id.as_str().into(), )); }; log::debug!( @@ -195,22 +193,22 @@ fn list_conflicts( let Some(branch_commit) = repositories::commits::get_by_id(&workspace.base_repo, &branch.commit_id)? else { - return Err(OxenError::revision_not_found( - branch.commit_id.clone().into(), + return Err(OxenError::RevisionNotFound( + branch.commit_id.as_str().into(), )); }; let Some(branch_tree) = repositories::tree::get_root_with_children(&workspace.base_repo, &branch_commit)? else { - return Err(OxenError::revision_not_found( - branch.commit_id.clone().into(), + return Err(OxenError::RevisionNotFound( + branch.commit_id.as_str().into(), )); }; let Some(workspace_tree) = repositories::tree::get_root_with_children(&workspace.base_repo, workspace_commit)? else { - return Err(OxenError::revision_not_found( - workspace.commit.id.clone().into(), + return Err(OxenError::RevisionNotFound( + workspace.commit.id.as_str().into(), )); }; diff --git a/crates/lib/src/core/v_latest/workspaces/data_frames.rs b/crates/lib/src/core/v_latest/workspaces/data_frames.rs index b5996f87f..ee67fd2a5 100644 --- a/crates/lib/src/core/v_latest/workspaces/data_frames.rs +++ b/crates/lib/src/core/v_latest/workspaces/data_frames.rs @@ -31,7 +31,7 @@ pub fn is_queryable_data_frame_indexed( match get_queryable_data_frame_workspace(repo, path, commit) { Ok(_workspace) => Ok(true), Err(e) => match e { - OxenError::QueryableWorkspaceNotFound() => Ok(false), + OxenError::QueryableWorkspaceNotFound => Ok(false), _ => Err(e), }, } @@ -47,7 +47,7 @@ pub fn is_queryable_data_frame_indexed_from_file_node( { Ok(_workspace) => Ok(true), Err(e) => match e { - OxenError::QueryableWorkspaceNotFound() => Ok(false), + OxenError::QueryableWorkspaceNotFound => Ok(false), _ => Err(e), }, } @@ -78,7 +78,7 @@ pub fn get_queryable_data_frame_workspace_from_file_node( } } - Err(OxenError::QueryableWorkspaceNotFound()) + Err(OxenError::QueryableWorkspaceNotFound) } pub fn get_queryable_data_frame_workspace( diff --git a/crates/lib/src/core/v_latest/workspaces/diff.rs b/crates/lib/src/core/v_latest/workspaces/diff.rs index bf907b8cb..b92c16870 100644 --- a/crates/lib/src/core/v_latest/workspaces/diff.rs +++ b/crates/lib/src/core/v_latest/workspaces/diff.rs @@ -25,7 +25,7 @@ pub fn diff(workspace: &Workspace, path: impl AsRef) -> Result bool { false } +#[derive(Debug, thiserror::Error)] +enum InvalidUrlError { + #[error("URL has no host")] + NoHost, + + #[error("DNS resolution failed for {host}: {source}")] + DnsResolutionFailed { + host: String, + source: std::io::Error, + }, + + #[error("URL resolves to a private/reserved IP address: {0}")] + PrivateIp(std::net::IpAddr), +} + /// Resolves a URL's hostname via DNS and rejects it if any resolved address is /// private/reserved. This prevents SSRF attacks where a user-supplied URL could reach /// internal services (e.g. cloud metadata at 169.254.169.254, internal APIs, etc.). -async fn validate_url_target(url: &Url) -> Result<(), OxenError> { - let host = url - .host_str() - .ok_or_else(|| OxenError::file_import_error("URL has no host"))?; +async fn validate_url_target(url: &Url) -> Result<(), InvalidUrlError> { + let host = url.host_str().ok_or(InvalidUrlError::NoHost)?; let port = url.port_or_known_default().unwrap_or(443); let addr = format!("{host}:{port}"); - let resolved = tokio::net::lookup_host(&addr).await.map_err(|e| { - OxenError::file_import_error(format!("DNS resolution failed for {host}: {e}")) - })?; + let resolved = + tokio::net::lookup_host(&addr) + .await + .map_err(|e| InvalidUrlError::DnsResolutionFailed { + host: host.to_string(), + source: e, + })?; for socket_addr in resolved { if is_private_ip(&socket_addr.ip()) { - return Err(OxenError::file_import_error(format!( - "URL resolves to a private/reserved IP address: {}", - socket_addr.ip() - ))); + return Err(InvalidUrlError::PrivateIp(socket_addr.ip())); } } @@ -353,7 +367,9 @@ pub async fn import( )); } - validate_url_target(&parsed_url).await?; + validate_url_target(&parsed_url) + .await + .map_err(|e| OxenError::ImportFileError(format!("{e}").into()))?; let auth_header_value = HeaderValue::from_str(auth) .map_err(|_e| OxenError::file_import_error(format!("Invalid header auth value {auth}")))?; @@ -407,12 +423,12 @@ pub async fn upload_zip( log::debug!("workspace::commit ✅ success! commit {commit:?}"); Ok(commit) } - Err(OxenError::WorkspaceBehind(workspace)) => { + workspace_behind_error @ Err(OxenError::WorkspaceBehind(_)) => { log::error!( "unable to commit branch {:?}. Workspace behind", branch.name ); - Err(OxenError::WorkspaceBehind(workspace)) + workspace_behind_error } Err(err) => { log::error!("unable to commit branch {:?}. Err: {}", branch.name, err); @@ -479,7 +495,9 @@ async fn fetch_file( "Redirect to non-HTTP(S) URL is not allowed", )); } - validate_url_target(&next_url).await?; + validate_url_target(&next_url) + .await + .map_err(|e| OxenError::ImportFileError(format!("{e}").into()))?; current_url = next_url; continue; diff --git a/crates/lib/src/core/v_old/v0_19_0/pull.rs b/crates/lib/src/core/v_old/v0_19_0/pull.rs index cd4e69e1b..6c8803028 100644 --- a/crates/lib/src/core/v_old/v0_19_0/pull.rs +++ b/crates/lib/src/core/v_old/v0_19_0/pull.rs @@ -44,7 +44,7 @@ pub async fn pull_remote_branch( fetch::fetch_remote_branch(repo, &remote_repo, fetch_opts).await?; let new_head_commit = repositories::revisions::get(repo, branch)? - .ok_or(OxenError::revision_not_found(branch.to_owned().into()))?; + .ok_or(OxenError::RevisionNotFound(branch.to_owned().into()))?; // Merge if there are changes if let Some(previous_head_commit) = &previous_head_commit { diff --git a/crates/lib/src/error.rs b/crates/lib/src/error.rs index 353fb34e5..0397aa1ed 100644 --- a/crates/lib/src/error.rs +++ b/crates/lib/src/error.rs @@ -8,13 +8,12 @@ use std::io; use std::num::ParseIntError; use std::path::Path; use std::path::PathBuf; -use std::path::StripPrefixError; use tokio::task::JoinError; +use crate::model::ParsedResource; use crate::model::RepoNew; use crate::model::Schema; use crate::model::Workspace; -use crate::model::{Commit, ParsedResource}; pub mod path_buf_error; pub mod string_error; @@ -22,179 +21,293 @@ pub mod string_error; pub use crate::error::path_buf_error::PathBufError; pub use crate::error::string_error::StringError; -pub const HEAD_NOT_FOUND: &str = "HEAD not found"; - -pub const EMAIL_AND_NAME_NOT_FOUND: &str = "oxen not configured, set email and name with:\n\noxen config --name YOUR_NAME --email YOUR_EMAIL\n"; - pub const AUTH_TOKEN_NOT_FOUND: &str = "oxen authentication token not found, obtain one from your administrator and configure with:\n\noxen config --auth \n"; #[derive(thiserror::Error, Debug)] pub enum OxenError { - // User - #[error("{0}")] - UserConfigNotFound(Box), - + // + // Configuration + // + /// The user configuration file cannot be found at $HOME/.config/oxen/user_config.toml + #[error( + "oxen not configured, set email and name with:\n\noxen config --name YOUR_NAME --email YOUR_EMAIL\n" + )] + UserConfigNotFound, + + // // Repo + // + /// When an operation assumes a repository exists but it cannot be found. #[error("Repository '{0}' not found")] RepoNotFound(Box), + + /// When a local repository cannot be found at the given path. #[error("No oxen repository found at {0}")] - LocalRepoNotFound(Box), + LocalRepoNotFound(PathBufError), + + /// Error during repository creation: attempt to create a repository that already exists. #[error("Repository '{0}' already exists")] RepoAlreadyExists(Box), - #[error("Repository already exists at destination: {0}")] - RepoAlreadyExistsAtDestination(Box), + + /// Error when creating a repository: repo names are restricted. #[error("Invalid repository or namespace name '{0}'. Must match [a-zA-Z0-9][a-zA-Z0-9_.-]+")] InvalidRepoName(StringError), - // Fork - #[error("{0}")] - ForkStatusNotFound(StringError), + /// When `get_fork_status` cannot obtain the fork status for a repository. + #[error("No fork status found.")] + ForkStatusNotFound, + // // Remotes + // + /// A remote repository with a given name was not located on the server. #[error("Remote repository not found: {0}")] - RemoteRepoNotFound(Box), - #[error("{0}")] - RemoteAheadOfLocal(StringError), - #[error("{0}")] - IncompleteLocalHistory(StringError), - #[error("{0}")] - RemoteBranchLocked(StringError), + RemoteRepoNotFound(StringError), + + /// A merge cannot occur because there's a conflict with its upstream tracking branch. #[error("{0}")] UpstreamMergeConflict(StringError), + // // Branches/Commits + // + /// A branch with a given name was not found in the repository. #[error("{0}")] - BranchNotFound(Box), + BranchNotFound(StringError), + + /// A given revision (commit hash) was not found in the repository. #[error("Revision not found: {0}")] - RevisionNotFound(Box), - #[error("Root commit does not match: {0}")] - RootCommitDoesNotMatch(Box), - #[error("{0}")] - NothingToCommit(StringError), - #[error("{0}")] - NoCommitsFound(StringError), - #[error("{0}")] - HeadNotFound(StringError), + RevisionNotFound(StringError), + /// The repository is empty: it has no commits. + #[error("No commits found.")] + NoCommitsFound, + + /// The repository's current branch (head) cannot be located. + #[error("HEAD not found.")] + HeadNotFound, + + // // Workspaces + // + /// The workspace wasn't found (either locally or on a remote server). #[error("Workspace not found: {0}")] - WorkspaceNotFound(Box), + WorkspaceNotFound(StringError), + + /// No queryable workspace was found. #[error("No queryable workspace found")] - QueryableWorkspaceNotFound(), + QueryableWorkspaceNotFound, + + /// The workspace is behind the remote repository and cannot be automatically updated. #[error("Workspace is behind: {0}")] WorkspaceBehind(Box), + // // Resources (paths, uris, etc.) + // + /// A resource (entry, path, commit, etc.) was not found. #[error("Resource not found: {0}")] ResourceNotFound(StringError), + + /// The given path was not found, either in the repository or in the filesystem. #[error("Path does not exist: {0}")] - PathDoesNotExist(Box), + PathDoesNotExist(PathBufError), + + /// A parsed resource was not found. #[error("Resource not found: {0}")] - ParsedResourceNotFound(Box), + ParsedResourceNotFound(PathBufError), + // // Versioning + // + /// The repository must be migrated before it can be used. + /// This is due to the repository being at an older version than the oxen server or client + /// being used on it. #[error("{0}")] MigrationRequired(StringError), + + /// The oxen client or server must be updated before it can be used. #[error("{0}")] OxenUpdateRequired(StringError), + + /// The version is invalid or unsupported. #[error("Invalid version: {0}")] InvalidVersion(StringError), - // Entry + /// A commit entry is not present in the repository. #[error("{0}")] CommitEntryNotFound(StringError), - // Schema + // + // Schema (dataframes) + // + /// The schema is invalid or unsupported for dataframe operations. #[error("Invalid schema: {0}")] InvalidSchema(Box), + + /// The schemas of the data frames are incompatible. #[error("Incompatible schemas: {0}")] IncompatibleSchemas(Box), + + /// The file type is unsupported for data frame operations. #[error("{0}")] InvalidFileType(StringError), + + /// A column name already exists in the dataframe's schema and cannot be added again. #[error("{0}")] ColumnNameAlreadyExists(StringError), + + /// A column name was requested in a dataframe, but no such column exists. #[error("{0}")] ColumnNameNotFound(StringError), + + /// An operation is not supported for the the dataframe. #[error("{0}")] UnsupportedOperation(StringError), + // // Metadata + // + /// Thumbnails can only be created when the ffmpeg feature is enabled. + #[error( + "Video thumbnail generation requires the 'ffmpeg' feature to be enabled. Build with --features liboxen/ffmpeg to enable this functionality." + )] + ThumbnailingNotEnabled, + + // + // Dataframes + // + /// No rows were found for a given SQL query. + #[error("Query returned no rows")] + NoRowsFound, + + /// An error encountered during dataframe operations. + /// Contains a human-readable description of the error. #[error("{0}")] - ImageMetadataParseError(StringError), - #[error("{0}")] - ThumbnailingNotEnabled(StringError), - - // SQL - #[error("SQL parse error: {0}")] - SQLParseError(StringError), - #[error("{0}")] - NoRowsFound(StringError), - - // CLI Interaction - #[error("{0}")] - OperationCancelled(StringError), + DataFrameError(StringError), - // fs / io + /// Adding a file into a workspace #[error("{0}")] - StripPrefixError(StringError), + ImportFileError(StringError), - // Dataframe Errors + /// An error encountered during SQL parsing. #[error("{0}")] - DataFrameError(StringError), + SQLParseError(StringError), - // File Import Error - #[error("{0}")] - ImportFileError(StringError), + // + // + // Wrappers + // + // + /// Wraps the error from std::path::strip_prefix. + #[error("Error stripping prefix: {0}")] + StripPrefixError(#[from] std::path::StripPrefixError), - // External Library Errors + /// Wraps errors encotunered from file reading & writing operations. #[error("{0}")] IO(#[from] io::Error), + + /// Encountered when authentication fails. Contains the authentication error message. #[error("Authentication failed: {0}")] Authentication(StringError), + + /// Wraps errors from the Arrow library, which are encountered in dataframe operations. #[error("{0}")] ArrowError(#[from] ArrowError), + + /// Wraps errors from bincode when serializing and deserializing Rust objects into binary data. #[error("{0}")] BinCodeError(#[from] bincode::Error), + + /// Wraps errors encountered when trying to serialize TOML data. #[error("Configuration error: {0}")] TomlSer(#[from] toml::ser::Error), + + /// Wraps errors encountered when deserializing invalid TOML data. #[error("Configuration error: {0}")] TomlDe(#[from] toml::de::Error), + + /// Wraps errors encountered when parsing malformed URIs. #[error("Invalid URI: {0}")] URI(#[from] http::uri::InvalidUri), + + /// Wraps errors encountered when parsing malformed URLs. #[error("Invalid URL: {0}")] URL(#[from] url::ParseError), + + /// Wraps JSON serialization and deserialization errors. #[error("JSON error: {0}")] JSON(#[from] serde_json::Error), + + /// Wraps any HTTP client errors we encounter. #[error("Network error: {0}")] HTTP(#[from] reqwest::Error), + + /// Wraps any error we encounter from handling non-UTF-8 strings. + /// + /// Most often occurs when interacting with filesystem paths as much + /// of the oxen codebase relies on paths being valid UTF-8 strings. #[error("UTF-8 encoding error: {0}")] UTF8Error(#[from] std::str::Utf8Error), + + /// Wraps any error we encounter from converting a byte slice to a UTF-8 string. + #[error("UTF-8 conversion error: {0}")] + Utf8ConvError(#[from] std::string::FromUtf8Error), + + /// Wraps any error we encounter from interacting with RocksDB. #[error("Database error: {0}")] DB(#[from] rocksdb::Error), + + /// Wraps any error we encounter from interacting with DuckDB. #[error("Query error: {0}")] DUCKDB(#[from] duckdb::Error), + + /// Wraps any error we encounter from interacting with environment variables. #[error("Environment variable error: {0}")] ENV(#[from] std::env::VarError), + + /// Wraps any error that we get from using the image crate (image processing). #[error("Image processing error: {0}")] ImageError(#[from] image::ImageError), + + /// Wraps any error that we get from Redis client use. #[error("Redis error: {0}")] RedisError(#[from] redis::RedisError), + + /// Wraps any error that we get from using r2d2 (the connection pool). #[error("Connection pool error: {0}")] R2D2Error(#[from] r2d2::Error), + + /// Wraps any error that we get from using jwalk (directory traversal). #[error("Directory traversal error: {0}")] JwalkError(#[from] jwalk::Error), + + /// Wraps any error that we get from parsing malformed glob patterns. #[error("Pattern error: {0}")] PatternError(#[from] glob::PatternError), + + /// Wraps any error that we encounter when walking paths emitted from a glob pattern. #[error("Glob error: {0}")] GlobError(#[from] glob::GlobError), + + /// Wraps any error that we get from using polars (dataframe operations). #[error("DataFrame error: {0}")] PolarsError(#[from] polars::prelude::PolarsError), + + /// Wraps any error that we get from parsing integers from strings. #[error("Invalid integer: {0}")] ParseIntError(#[from] ParseIntError), + + /// Wraps any error that we get from decoding message pack data. #[error("Decode error: {0}")] RmpDecodeError(#[from] rmp_serde::decode::Error), + /// Wraps any error that we get from joining tasks. + #[error("{0}")] + JoinError(#[from] JoinError), + // Fallback + // TODO: remove all uses of `Basic` and replace with specific errors. #[error("{0}")] Basic(StringError), } @@ -217,8 +330,7 @@ impl OxenError { RevisionNotFound(_) => { "Check available branches with `oxen branch --all` or commits with `oxen log`." } - NothingToCommit(_) => "Stage changes with `oxen add ` before committing.", - HeadNotFound(_) | NoCommitsFound(_) => { + HeadNotFound | NoCommitsFound => { "This repository has no commits yet. Add files and create your first commit." } PathDoesNotExist(_) @@ -255,18 +367,10 @@ impl OxenError { OxenError::Basic(StringError::from(s.as_ref())) } - pub fn thumbnailing_not_enabled(s: impl AsRef) -> Self { - OxenError::ThumbnailingNotEnabled(StringError::from(s.as_ref())) - } - pub fn authentication(s: impl AsRef) -> Self { OxenError::Authentication(StringError::from(s.as_ref())) } - pub fn migration_required(s: impl AsRef) -> Self { - OxenError::MigrationRequired(StringError::from(s.as_ref())) - } - pub fn invalid_version(s: impl AsRef) -> Self { OxenError::InvalidVersion(StringError::from(s.as_ref())) } @@ -275,10 +379,6 @@ impl OxenError { OxenError::OxenUpdateRequired(StringError::from(s.as_ref())) } - pub fn user_config_not_found(value: StringError) -> Self { - OxenError::UserConfigNotFound(Box::new(value)) - } - pub fn repo_not_found(repo: RepoNew) -> Self { OxenError::RepoNotFound(Box::new(repo)) } @@ -294,60 +394,16 @@ impl OxenError { )) } - pub fn remote_ahead_of_local() -> Self { - OxenError::RemoteAheadOfLocal(StringError::from( - "\nRemote ahead of local, must pull changes. To fix run:\n\n oxen pull\n", - )) - } - - pub fn upstream_merge_conflict() -> Self { - OxenError::UpstreamMergeConflict(StringError::from( - "\nRemote has conflicts with local branch. To fix run:\n\n oxen pull\n\nThen resolve conflicts and commit changes.\n", - )) - } - - pub fn merge_conflict(desc: impl AsRef) -> Self { - OxenError::UpstreamMergeConflict(StringError::from(desc.as_ref())) - } - - pub fn incomplete_local_history() -> Self { - OxenError::IncompleteLocalHistory(StringError::from( - "\nCannot push to an empty repository with an incomplete local history. To fix, pull the complete history from your remote:\n\n oxen pull --all\n", - )) - } - - pub fn remote_branch_locked() -> Self { - OxenError::RemoteBranchLocked(StringError::from( - "\nRemote branch is locked - another push is in progress. Wait a bit before pushing again, or try pushing to a new branch.\n", - )) - } - - pub fn operation_cancelled() -> Self { - OxenError::OperationCancelled(StringError::from("\nOperation cancelled.\n")) - } - pub fn resource_not_found(value: impl AsRef) -> Self { OxenError::ResourceNotFound(StringError::from(value.as_ref())) } pub fn path_does_not_exist(path: impl AsRef) -> Self { - OxenError::PathDoesNotExist(Box::new(path.as_ref().into())) - } - - pub fn image_metadata_error(s: impl AsRef) -> Self { - OxenError::ImageMetadataParseError(StringError::from(s.as_ref())) - } - - pub fn sql_parse_error(s: impl AsRef) -> Self { - OxenError::SQLParseError(StringError::from(s.as_ref())) + OxenError::PathDoesNotExist(path.as_ref().into()) } pub fn parsed_resource_not_found(resource: ParsedResource) -> Self { - OxenError::ParsedResourceNotFound(Box::new(resource.resource.into())) - } - - pub fn invalid_repo_name(s: impl AsRef) -> Self { - OxenError::InvalidRepoName(StringError::from(s.as_ref())) + OxenError::ParsedResourceNotFound(resource.resource.into()) } pub fn is_auth_error(&self) -> bool { @@ -363,52 +419,12 @@ impl OxenError { ) } - pub fn repo_already_exists(repo: RepoNew) -> Self { - OxenError::RepoAlreadyExists(Box::new(repo)) - } - - pub fn repo_already_exists_at_destination(value: StringError) -> Self { - OxenError::RepoAlreadyExistsAtDestination(Box::new(value)) - } - - pub fn fork_status_not_found() -> Self { - OxenError::ForkStatusNotFound(StringError::from("No fork status found")) - } - - pub fn revision_not_found(value: StringError) -> Self { - OxenError::RevisionNotFound(Box::new(value)) - } - - pub fn workspace_not_found(value: StringError) -> Self { - OxenError::WorkspaceNotFound(Box::new(value)) - } - - pub fn workspace_behind(workspace: &Workspace) -> Self { - OxenError::WorkspaceBehind(Box::new(workspace.clone())) - } - - pub fn root_commit_does_not_match(commit: Commit) -> Self { - OxenError::RootCommitDoesNotMatch(Box::new(commit)) - } - - pub fn no_commits_found() -> Self { - OxenError::NoCommitsFound(StringError::from("\n No commits found.\n")) - } - pub fn local_repo_not_found(dir: impl AsRef) -> OxenError { - OxenError::LocalRepoNotFound(Box::new(dir.as_ref().into())) + OxenError::LocalRepoNotFound(dir.as_ref().into()) } pub fn email_and_name_not_set() -> OxenError { - OxenError::user_config_not_found(EMAIL_AND_NAME_NOT_FOUND.to_string().into()) - } - - pub fn remote_repo_not_found(url: impl AsRef) -> OxenError { - OxenError::RemoteRepoNotFound(Box::new(StringError::from(url.as_ref()))) - } - - pub fn head_not_found() -> OxenError { - OxenError::HeadNotFound(StringError::from(HEAD_NOT_FOUND)) + OxenError::UserConfigNotFound } pub fn home_dir_not_found() -> OxenError { @@ -460,12 +476,12 @@ impl OxenError { pub fn remote_branch_not_found(name: impl AsRef) -> OxenError { let err = format!("Remote branch '{}' not found", name.as_ref()); - OxenError::BranchNotFound(Box::new(StringError::from(err))) + OxenError::BranchNotFound(err.into()) } pub fn local_branch_not_found(name: impl AsRef) -> OxenError { let err = format!("Branch '{}' not found", name.as_ref()); - OxenError::BranchNotFound(Box::new(StringError::from(err))) + OxenError::BranchNotFound(err.into()) } pub fn commit_db_corrupted(commit_id: impl AsRef) -> OxenError { @@ -487,7 +503,7 @@ impl OxenError { } pub fn entry_does_not_exist(path: impl AsRef) -> OxenError { - OxenError::ParsedResourceNotFound(Box::new(path.as_ref().into())) + OxenError::ParsedResourceNotFound(path.as_ref().into()) } pub fn file_error(path: impl AsRef, error: std::io::Error) -> OxenError { @@ -679,21 +695,3 @@ impl From for OxenError { OxenError::Basic(StringError::from(error)) } } - -impl From for OxenError { - fn from(error: StripPrefixError) -> Self { - OxenError::basic_str(format!("Error stripping prefix: {error}")) - } -} - -impl From for OxenError { - fn from(error: JoinError) -> Self { - OxenError::basic_str(error.to_string()) - } -} - -impl From for OxenError { - fn from(error: std::string::FromUtf8Error) -> Self { - OxenError::basic_str(format!("UTF8 conversion error: {error}")) - } -} diff --git a/crates/lib/src/repositories.rs b/crates/lib/src/repositories.rs index a3693a81b..3241bdd83 100644 --- a/crates/lib/src/repositories.rs +++ b/crates/lib/src/repositories.rs @@ -218,12 +218,12 @@ pub async fn create( ) -> Result { // Validate repo name if !is_valid_repo_name(&new_repo.name) { - return Err(OxenError::invalid_repo_name(&new_repo.name)); + return Err(OxenError::InvalidRepoName(new_repo.name.into())); } // Validate namespace if !is_valid_repo_name(&new_repo.namespace) { - return Err(OxenError::invalid_repo_name(&new_repo.namespace)); + return Err(OxenError::InvalidRepoName(new_repo.namespace.into())); } let repo_dir = root_dir @@ -231,7 +231,7 @@ pub async fn create( .join(Path::new(&new_repo.name)); if repo_dir.exists() { log::error!("Repository already exists {repo_dir:?}"); - return Err(OxenError::repo_already_exists(new_repo)); + return Err(OxenError::RepoAlreadyExists(Box::new(new_repo))); } // Create the repo dir diff --git a/crates/lib/src/repositories/checkout.rs b/crates/lib/src/repositories/checkout.rs index 562563213..51e718be2 100644 --- a/crates/lib/src/repositories/checkout.rs +++ b/crates/lib/src/repositories/checkout.rs @@ -28,7 +28,7 @@ pub async fn checkout( println!("Checkout branch: {value}"); let commit = repositories::revisions::get(repo, value)? - .ok_or(OxenError::revision_not_found(value.into()))?; + .ok_or(OxenError::RevisionNotFound(value.into()))?; let subtree_paths = match repo.subtree_paths() { Some(paths_vec) => paths_vec, // If Some(vec), take the inner vector None => vec![Path::new("").to_path_buf()], @@ -46,7 +46,7 @@ pub async fn checkout( } let commit = repositories::revisions::get(repo, value)? - .ok_or(OxenError::revision_not_found(value.into()))?; + .ok_or(OxenError::RevisionNotFound(value.into()))?; let previous_head_commit = repositories::commits::head_commit_maybe(repo)?; repositories::branches::checkout_commit_from_commit(repo, &commit, &previous_head_commit) diff --git a/crates/lib/src/repositories/diffs.rs b/crates/lib/src/repositories/diffs.rs index 6c28e947d..4d3be8c9e 100644 --- a/crates/lib/src/repositories/diffs.rs +++ b/crates/lib/src/repositories/diffs.rs @@ -174,7 +174,7 @@ pub async fn diff_uncommitted( let unstaged_files = status.unstaged_files(); log::debug!("unstaged_files: {unstaged_files:?}"); let commit_1 = repositories::revisions::get(repo, rev_1)? - .ok_or_else(|| OxenError::revision_not_found(rev_1.to_string().into()))?; + .ok_or_else(|| OxenError::RevisionNotFound(rev_1.into()))?; log::debug!("commit_1: {commit_1:?}"); let mut diff_result = Vec::new(); log::debug!("diff_result: {diff_result:?}"); @@ -224,9 +224,9 @@ pub async fn diff_revs( path_2.display() ); let commit_1 = repositories::revisions::get(repo, rev_1)? - .ok_or_else(|| OxenError::revision_not_found(rev_1.to_string().into()))?; + .ok_or_else(|| OxenError::RevisionNotFound(rev_1.into()))?; let commit_2 = repositories::revisions::get(repo, rev_2)? - .ok_or_else(|| OxenError::revision_not_found(rev_2.to_string().into()))?; + .ok_or_else(|| OxenError::RevisionNotFound(rev_2.into()))?; let dir_diff = diff_path(repo, &commit_1, &commit_2, path_1, path_2, opts).await?; log::debug!( diff --git a/crates/lib/src/repositories/fork.rs b/crates/lib/src/repositories/fork.rs index 51c82673d..4a9c07314 100644 --- a/crates/lib/src/repositories/fork.rs +++ b/crates/lib/src/repositories/fork.rs @@ -102,7 +102,7 @@ pub fn start_fork( } pub fn get_fork_status(repo_path: &Path) -> Result { - let status = read_status(repo_path)?.ok_or_else(OxenError::fork_status_not_found)?; + let status = read_status(repo_path)?.ok_or_else(|| OxenError::ForkStatusNotFound)?; Ok(ForkStatusResponse { repository: repo_path.to_string_lossy().to_string(), @@ -222,7 +222,7 @@ mod tests { current_status = match get_fork_status(&forked_repo_path) { Ok(status) => status.status, Err(e) => { - if let OxenError::ForkStatusNotFound(_) = e { + if matches!(e, OxenError::ForkStatusNotFound) { "in_progress".to_string() } else { return Err(e); diff --git a/crates/lib/src/repositories/tree.rs b/crates/lib/src/repositories/tree.rs index d62a0ce01..6657b7b21 100644 --- a/crates/lib/src/repositories/tree.rs +++ b/crates/lib/src/repositories/tree.rs @@ -550,7 +550,7 @@ pub async fn list_missing_file_hashes_from_commits( let commit_id_str = commit_id.to_string(); let Some(commit) = repositories::commits::get_by_id(repo, &commit_id_str)? else { log::error!("list_missing_file_hashes_from_commits Commit {commit_id_str} not found"); - return Err(OxenError::revision_not_found(commit_id_str.into())); + return Err(OxenError::RevisionNotFound(commit_id_str.into())); }; // Handle the case where we are given a list of subtrees to check // It is much faster to check the subtree directly than to walk the entire tree diff --git a/crates/lib/src/repositories/workspaces.rs b/crates/lib/src/repositories/workspaces.rs index f22c9f73e..47470fe2d 100644 --- a/crates/lib/src/repositories/workspaces.rs +++ b/crates/lib/src/repositories/workspaces.rs @@ -307,7 +307,7 @@ pub fn delete(workspace: &Workspace) -> Result<(), OxenError> { let workspace_id = workspace.id.to_string(); let workspace_dir = workspace.dir(); if !workspace_dir.exists() { - return Err(OxenError::workspace_not_found(workspace_id.into())); + return Err(OxenError::WorkspaceNotFound(workspace_id.into())); } log::debug!("workspace::delete cleaning up workspace dir: {workspace_dir:?}"); @@ -338,7 +338,7 @@ pub fn update_commit(workspace: &Workspace, new_commit_id: &str) -> Result<(), O if !config_path.exists() { log::error!("Workspace config not found: {config_path:?}"); - return Err(OxenError::workspace_not_found(workspace.id.clone().into())); + return Err(OxenError::WorkspaceNotFound(workspace.id.as_str().into())); } let config_contents = util::fs::read_from_path(&config_path)?; diff --git a/crates/lib/src/repositories/workspaces/data_frames/embeddings.rs b/crates/lib/src/repositories/workspaces/data_frames/embeddings.rs index f1b7ab23d..bbf84c918 100644 --- a/crates/lib/src/repositories/workspaces/data_frames/embeddings.rs +++ b/crates/lib/src/repositories/workspaces/data_frames/embeddings.rs @@ -563,7 +563,7 @@ fn get_avg_embedding(result_set: Vec) -> Result, OxenError } if embeddings.is_empty() { - return Err(OxenError::NoRowsFound("Query returned no rows".into())); + return Err(OxenError::NoRowsFound); } if vector_length == 0 { diff --git a/crates/lib/src/util/fs.rs b/crates/lib/src/util/fs.rs index 27faefba5..502707059 100644 --- a/crates/lib/src/util/fs.rs +++ b/crates/lib/src/util/fs.rs @@ -1719,10 +1719,7 @@ pub async fn handle_video_thumbnail( #[cfg(not(feature = "ffmpeg"))] { let _ = (version_store, file_hash, video_thumbnail); - Err(OxenError::thumbnailing_not_enabled( - "Video thumbnail generation requires the 'ffmpeg' feature to be enabled. \ - Build with --features liboxen/ffmpeg to enable this functionality.", - )) + Err(OxenError::ThumbnailingNotEnabled) } #[cfg(feature = "ffmpeg")] diff --git a/crates/oxen-py/src/py_remote_data_frame.rs b/crates/oxen-py/src/py_remote_data_frame.rs index 08b6f339f..aed90dc6d 100644 --- a/crates/oxen-py/src/py_remote_data_frame.rs +++ b/crates/oxen-py/src/py_remote_data_frame.rs @@ -26,7 +26,7 @@ impl PyRemoteDataFrame { fn size(&self) -> Result<(usize, usize), PyOxenError> { let Some(revision) = &self.repo.revision else { - return Err(OxenError::no_commits_found().into()); + return Err(OxenError::NoCommitsFound.into()); }; pyo3_async_runtimes::tokio::get_runtime().block_on(async { @@ -50,7 +50,7 @@ impl PyRemoteDataFrame { fn get_row_by_index(&self, row: usize) -> Result { let Some(revision) = &self.repo.revision else { - return Err(OxenError::no_commits_found().into()); + return Err(OxenError::NoCommitsFound.into()); }; let data = pyo3_async_runtimes::tokio::get_runtime().block_on(async { @@ -78,7 +78,7 @@ impl PyRemoteDataFrame { columns: Vec, ) -> Result { let Some(revision) = &self.repo.revision else { - return Err(OxenError::no_commits_found().into()); + return Err(OxenError::NoCommitsFound.into()); }; let data = pyo3_async_runtimes::tokio::get_runtime().block_on(async { diff --git a/crates/server/src/controllers/commits.rs b/crates/server/src/controllers/commits.rs index 3222302dc..979d1b440 100644 --- a/crates/server/src/controllers/commits.rs +++ b/crates/server/src/controllers/commits.rs @@ -306,7 +306,7 @@ pub async fn list_missing_files( }; let head_commit = repositories::commits::get_by_id(&repo, &query.head)? - .ok_or(OxenError::revision_not_found(query.head.clone().into()))?; + .ok_or(OxenError::RevisionNotFound(query.head.as_str().into()))?; let missing_files = repositories::entries::list_missing_files_in_commit_range( &repo, @@ -399,7 +399,7 @@ pub async fn show(req: HttpRequest) -> actix_web::Result actix_web::Result { - log::error!("Err create_commit: RootCommitDoesNotMatch {commit_id}"); - Err(OxenHttpError::BadRequest("Remote commit history does not match local commit history. Make sure you are pushing to the correct remote.".into())) - } Err(err) => { log::error!("Err create_commit: {err}"); Err(OxenHttpError::InternalServerError) diff --git a/crates/server/src/controllers/diff.rs b/crates/server/src/controllers/diff.rs index 74180081a..63a23fd7f 100644 --- a/crates/server/src/controllers/diff.rs +++ b/crates/server/src/controllers/diff.rs @@ -73,8 +73,8 @@ pub async fn commits( let (base, head) = parse_base_head(&base_head)?; let (base_commit, head_commit) = resolve_base_head(&repository, &base, &head)?; - let base_commit = base_commit.ok_or(OxenError::revision_not_found(base.into()))?; - let head_commit = head_commit.ok_or(OxenError::revision_not_found(head.into()))?; + let base_commit = base_commit.ok_or_else(|| OxenError::RevisionNotFound(base.into()))?; + let head_commit = head_commit.ok_or_else(|| OxenError::RevisionNotFound(head.into()))?; let commits = repositories::commits::list_between(&repository, &base_commit, &head_commit)?; let (paginated, pagination) = util::paginate(commits, page, page_size); @@ -132,8 +132,8 @@ pub async fn entries( let (base, head) = parse_base_head(&base_head)?; let (base_commit, head_commit) = resolve_base_head(&repository, &base, &head)?; - let base_commit = base_commit.ok_or(OxenError::revision_not_found(base.into()))?; - let head_commit = head_commit.ok_or(OxenError::revision_not_found(head.into()))?; + let base_commit = base_commit.ok_or_else(|| OxenError::RevisionNotFound(base.into()))?; + let head_commit = head_commit.ok_or_else(|| OxenError::RevisionNotFound(head.into()))?; let entries_diff = repositories::diffs::list_diff_entries( &repository, @@ -207,8 +207,8 @@ pub async fn dir_tree(req: HttpRequest) -> actix_web::Result { - log::debug!("Repo already exists: {path:?}"); - Ok(HttpResponse::Conflict() - .json(StatusMessage::error("Repo already exists at destination."))) - } Err(err) => { log::error!("Failed to fork repository: {err:?}"); Err(OxenHttpError::from(err)) @@ -90,7 +85,7 @@ pub async fn get_status(req: HttpRequest) -> Result match repositories::fork::get_fork_status(&repo_path) { Ok(status) => Ok(HttpResponse::Ok().json(status)), - Err(OxenError::ForkStatusNotFound(_)) => { + Err(OxenError::ForkStatusNotFound) => { Ok(HttpResponse::NotFound().json(StatusMessage::error("Fork status not found"))) } Err(e) => { diff --git a/crates/server/src/controllers/merger.rs b/crates/server/src/controllers/merger.rs index 9f35bc628..dcacdb65e 100644 --- a/crates/server/src/controllers/merger.rs +++ b/crates/server/src/controllers/merger.rs @@ -39,8 +39,8 @@ pub async fn show(req: HttpRequest) -> actix_web::Result actix_web::Result actix_web::Result { log::debug!("Merge has conflicts"); - Err(OxenError::merge_conflict(format!( - "Unable to merge {head} into {base} due to conflicts" - )))? + Err(OxenError::UpstreamMergeConflict( + format!("Unable to merge {head} into {base} due to conflicts.").into(), + ))? } Err(err) => { log::debug!("Err merging branches {err:?}"); diff --git a/crates/server/src/controllers/metadata.rs b/crates/server/src/controllers/metadata.rs index c42ba5e90..902eae456 100644 --- a/crates/server/src/controllers/metadata.rs +++ b/crates/server/src/controllers/metadata.rs @@ -51,7 +51,7 @@ pub async fn file(req: HttpRequest) -> actix_web::Result '{}'", diff --git a/crates/server/src/controllers/repositories.rs b/crates/server/src/controllers/repositories.rs index 2358eacb8..1420a8798 100644 --- a/crates/server/src/controllers/repositories.rs +++ b/crates/server/src/controllers/repositories.rs @@ -307,7 +307,7 @@ async fn handle_json_creation( }, metadata_entries: None, })), - Err(OxenError::NoCommitsFound(_)) => { + Err(OxenError::NoCommitsFound) => { Ok(HttpResponse::Ok().json(RepositoryCreationResponse { status: STATUS_SUCCESS.to_string(), status_message: MSG_RESOURCE_FOUND.to_string(), @@ -453,7 +453,7 @@ async fn handle_multipart_creation( }, metadata_entries: repo.entries, })), - Err(OxenError::NoCommitsFound(_)) => { + Err(OxenError::NoCommitsFound) => { Ok(HttpResponse::Ok().json(RepositoryCreationResponse { status: STATUS_SUCCESS.to_string(), status_message: MSG_RESOURCE_FOUND.to_string(), diff --git a/crates/server/src/controllers/schemas.rs b/crates/server/src/controllers/schemas.rs index eb0a49aff..12bac687d 100644 --- a/crates/server/src/controllers/schemas.rs +++ b/crates/server/src/controllers/schemas.rs @@ -59,7 +59,7 @@ pub async fn list_or_get(req: HttpRequest) -> actix_web::Result { - log::error!("Remote ahead of local: {desc}"); - HttpResponse::BadRequest() - .json(StatusMessageDescription::bad_request(format!("{desc}"))) - } - OxenError::IncompleteLocalHistory(desc) => { - log::error!("Cannot push repo with incomplete local history: {desc}"); - - HttpResponse::BadRequest() - .json(StatusMessageDescription::bad_request(format!("{desc}"))) - } OxenError::IncompatibleSchemas(schema) => { log::error!("Incompatible schemas: {schema}"); @@ -505,13 +494,13 @@ impl error::ResponseError for OxenHttpError { }); HttpResponse::InternalServerError().json(error_json) } - OxenError::ThumbnailingNotEnabled(error) => { - log::error!("Thumbnailing not enabled: {error}"); + thumbnail_error @ OxenError::ThumbnailingNotEnabled => { + log::error!("Thumbnailing not enabled: {thumbnail_error}"); let error_json = json!({ "error": { "type": "thumbnailing_not_enabled", "title": "Thumbnailing Not Enabled", - "detail": format!("{}", error), + "detail": format!("{error}"), }, "status": STATUS_ERROR, "status_message": MSG_INTERNAL_SERVER_ERROR, @@ -529,13 +518,13 @@ impl error::ResponseError for OxenHttpError { }); HttpResponse::InternalServerError().json(error_json) } - OxenError::NoRowsFound(msg) => { - log::error!("No rows found: {msg}"); + e @ OxenError::NoRowsFound => { + log::error!("No rows found: {e}"); let error_json = json!({ "error": { "type": "no_rows_found", "title": "No rows found", - "detail": format!("{}", msg), + "detail": format!("{e}"), }, "status": STATUS_ERROR, "status_message": MSG_INTERNAL_SERVER_ERROR,