diff --git a/CHANGELOG.md b/CHANGELOG.md index e453f73..51e36e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,21 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +## [0.11.0] - 2025-10-26 + +### Changed +- Updated `AppError::database` to accept `Option>`, allowing + bare `None` calls without type annotations, and added the helper + `AppError::database_with_message` for the common message-bearing path. + +### Documentation +- Refreshed the `AppError::database` docs to illustrate the new constructor + behavior and helper usage. + +### Tests +- Expanded database constructor tests to cover both the helper and bare `None` + scenario. + ## [0.10.9] - 2025-10-26 ### Fixed diff --git a/Cargo.lock b/Cargo.lock index ef7cb8f..78d4eb7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1606,7 +1606,7 @@ dependencies = [ [[package]] name = "masterror" -version = "0.10.9" +version = "0.11.0" dependencies = [ "actix-web", "axum", diff --git a/Cargo.toml b/Cargo.toml index 6b49254..e539f93 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "masterror" -version = "0.10.9" +version = "0.11.0" rust-version = "1.90" edition = "2024" license = "MIT OR Apache-2.0" diff --git a/README.md b/README.md index f629ac7..cfa1978 100644 --- a/README.md +++ b/README.md @@ -29,9 +29,9 @@ Stable categories, conservative HTTP mapping, no `unsafe`. ~~~toml [dependencies] -masterror = { version = "0.10.9", default-features = false } +masterror = { version = "0.11.0", default-features = false } # or with features: -# masterror = { version = "0.10.9", features = [ +# masterror = { version = "0.11.0", features = [ # "axum", "actix", "openapi", "serde_json", # "sqlx", "sqlx-migrate", "reqwest", "redis", # "validator", "config", "tokio", "multipart", @@ -66,10 +66,11 @@ masterror = { version = "0.10.9", default-features = false } ~~~toml [dependencies] # lean core -masterror = { version = "0.10.9", default-features = false } +masterror = { version = "0.11.0", default-features = false } # with Axum/Actix + JSON + integrations -# masterror = { version = "0.10.9", features = [ +# masterror = { version = "0.11.0", features = [ + # "axum", "actix", "openapi", "serde_json", # "sqlx", "sqlx-migrate", "reqwest", "redis", # "validator", "config", "tokio", "multipart", @@ -623,13 +624,13 @@ assert_eq!(resp.status, 401); Minimal core: ~~~toml -masterror = { version = "0.10.9", default-features = false } +masterror = { version = "0.11.0", default-features = false } ~~~ API (Axum + JSON + deps): ~~~toml -masterror = { version = "0.10.9", features = [ +masterror = { version = "0.11.0", features = [ "axum", "serde_json", "openapi", "sqlx", "reqwest", "redis", "validator", "config", "tokio" ] } @@ -638,7 +639,7 @@ masterror = { version = "0.10.9", features = [ API (Actix + JSON + deps): ~~~toml -masterror = { version = "0.10.9", features = [ +masterror = { version = "0.11.0", features = [ "actix", "serde_json", "openapi", "sqlx", "reqwest", "redis", "validator", "config", "tokio" ] } diff --git a/src/app_error/constructors.rs b/src/app_error/constructors.rs index 5dc80a2..c95d421 100644 --- a/src/app_error/constructors.rs +++ b/src/app_error/constructors.rs @@ -51,16 +51,39 @@ impl AppError { } /// Build a `Database` error with an optional message. /// - /// Accepts `Option` to avoid gratuitous `.map(|...| ...)` at call sites - /// when you may or may not have a safe-to-print string at hand. - pub fn database(msg: Option>>) -> Self { + /// This constructor accepts a pre-built [`Cow`] so callers that already + /// manage ownership can pass either borrowed or owned strings. When you + /// have plain string data, prefer [`AppError::database_with_message`]. + /// + /// ```rust + /// use masterror::AppError; + /// + /// let err = AppError::database(None); + /// assert!(err.message.is_none()); + /// ``` + pub fn database(msg: Option>) -> Self { Self { kind: AppErrorKind::Database, - message: msg.map(Into::into), + message: msg, retry: None, www_authenticate: None } } + + /// Build a `Database` error with a message. + /// + /// Convenience wrapper around [`AppError::database`] for the common case + /// where you start from a plain string-like value. + /// + /// ```rust + /// use masterror::AppError; + /// + /// let err = AppError::database_with_message("db down"); + /// assert_eq!(err.message.as_deref(), Some("db down")); + /// ``` + pub fn database_with_message(msg: impl Into>) -> Self { + Self::database(Some(msg.into())) + } /// Build a `Config` error. pub fn config(msg: impl Into>) -> Self { Self::with(AppErrorKind::Config, msg) diff --git a/src/app_error/tests.rs b/src/app_error/tests.rs index feebc54..7d4d6c3 100644 --- a/src/app_error/tests.rs +++ b/src/app_error/tests.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use super::{AppResult, core::AppError}; use crate::AppErrorKind; @@ -108,10 +110,13 @@ fn constructors_match_kinds() { #[test] fn database_accepts_optional_message() { - let with_msg = AppError::database(Some("db down")); + let with_msg = AppError::database_with_message("db down"); assert_err_with_msg(with_msg, AppErrorKind::Database, "db down"); - let without = AppError::database(None::<&str>); + let via_option = AppError::database(Some(Cow::Borrowed("db down"))); + assert_err_with_msg(via_option, AppErrorKind::Database, "db down"); + + let without = AppError::database(None); assert_err_bare(without, AppErrorKind::Database); } diff --git a/src/convert/sqlx.rs b/src/convert/sqlx.rs index df9be12..89fd254 100644 --- a/src/convert/sqlx.rs +++ b/src/convert/sqlx.rs @@ -50,7 +50,7 @@ impl From for AppError { fn from(err: SqlxError) -> Self { match err { SqlxError::RowNotFound => AppError::not_found("Record not found"), - other => AppError::database(Some(other.to_string())) + other => AppError::database_with_message(other.to_string()) } } } @@ -63,7 +63,7 @@ impl From for AppError { #[cfg_attr(docsrs, doc(cfg(feature = "sqlx-migrate")))] impl From for AppError { fn from(err: MigrateError) -> Self { - AppError::database(Some(err.to_string())) + AppError::database_with_message(err.to_string()) } }