diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f9c9c3..2fa3b78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +## [0.24.9] - 2025-10-25 + +### Fixed +- Treat compile-time and runtime custom `AppCode` values as equal by comparing + their canonical string representation, restoring successful JSON roundtrips + for `AppCode::new("…")` literals. + +### Changed +- Equality for `AppCode` is now string-based; prefer `==` checks instead of + pattern matching on `AppCode::Variant` constants. + ## [0.24.8] - 2025-10-24 ### Changed diff --git a/Cargo.toml b/Cargo.toml index eb9d659..0cc6195 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "masterror" -version = "0.24.8" +version = "0.24.9" rust-version = "1.90" edition = "2024" license = "MIT OR Apache-2.0" diff --git a/src/code/app_code.rs b/src/code/app_code.rs index 5971a3f..0d7722c 100644 --- a/src/code/app_code.rs +++ b/src/code/app_code.rs @@ -1,7 +1,11 @@ -use alloc::{borrow::ToOwned, boxed::Box, string::String}; +use alloc::{ + borrow::{Cow, ToOwned}, + string::String +}; use core::{ error::Error as CoreError, fmt::{self, Display}, + hash::{Hash, Hasher}, str::FromStr }; @@ -44,15 +48,9 @@ impl CoreError for ParseAppCodeError {} /// - Validate custom codes using [`AppCode::try_new`] before exposing them /// publicly. #[non_exhaustive] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone)] pub struct AppCode { - repr: CodeRepr -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -enum CodeRepr { - Static(&'static str), - Owned(Box) + repr: Cow<'static, str> } #[allow(non_upper_case_globals)] @@ -108,13 +106,13 @@ impl AppCode { const fn from_static(code: &'static str) -> Self { Self { - repr: CodeRepr::Static(code) + repr: Cow::Borrowed(code) } } fn from_owned(code: String) -> Self { Self { - repr: CodeRepr::Owned(code.into_boxed_str()) + repr: Cow::Owned(code) } } @@ -169,10 +167,21 @@ impl AppCode { /// This matches the JSON serialization. #[must_use] pub fn as_str(&self) -> &str { - match &self.repr { - CodeRepr::Static(value) => value, - CodeRepr::Owned(value) => value - } + self.repr.as_ref() + } +} + +impl PartialEq for AppCode { + fn eq(&self, other: &Self) -> bool { + self.as_str() == other.as_str() + } +} + +impl Eq for AppCode {} + +impl Hash for AppCode { + fn hash(&self, state: &mut H) { + self.as_str().hash(state); } } diff --git a/src/lib.rs b/src/lib.rs index 83fbd13..86e8f0f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -263,7 +263,7 @@ //! let app_err = AppError::new(AppErrorKind::NotFound, "user_not_found"); //! let resp: ErrorResponse = (&app_err).into(); //! assert_eq!(resp.status, 404); -//! assert!(matches!(resp.code, AppCode::NotFound)); +//! assert_eq!(resp.code, AppCode::NotFound); //! ``` //! //! # Typed control-flow macros @@ -393,7 +393,7 @@ pub use kind::AppErrorKind; /// name: "other" /// } /// .into(); -/// assert!(matches!(code, AppCode::BadRequest)); +/// assert_eq!(code, AppCode::BadRequest); /// ``` pub use masterror_derive::{Error, Masterror}; pub use response::{ diff --git a/src/response/tests.rs b/src/response/tests.rs index bd9cbdb..30ba794 100644 --- a/src/response/tests.rs +++ b/src/response/tests.rs @@ -9,7 +9,7 @@ use crate::{AppCode, AppError, AppErrorKind, ProblemJson}; fn new_sets_status_code_and_message() { let e = ErrorResponse::new(404, AppCode::NotFound, "missing").expect("status"); assert_eq!(e.status, 404); - assert!(matches!(e.code, AppCode::NotFound)); + assert_eq!(e.code, AppCode::NotFound); assert_eq!(e.message, "missing"); assert!(e.retry.is_none()); assert!(e.www_authenticate.is_none()); @@ -246,7 +246,7 @@ fn from_app_error_preserves_status_and_sets_code() { let app = AppError::new(AppErrorKind::NotFound, "user"); let e: ErrorResponse = (&app).into(); assert_eq!(e.status, 404); - assert!(matches!(e.code, AppCode::NotFound)); + assert_eq!(e.code, AppCode::NotFound); assert_eq!(e.message, "user"); assert!(e.retry.is_none()); } @@ -256,7 +256,7 @@ fn from_app_error_uses_default_message_when_none() { let app = AppError::bare(AppErrorKind::Internal); let e: ErrorResponse = (&app).into(); assert_eq!(e.status, 500); - assert!(matches!(e.code, AppCode::Internal)); + assert_eq!(e.code, AppCode::Internal); assert_eq!(e.message, AppErrorKind::Internal.label()); } @@ -269,7 +269,7 @@ fn from_owned_app_error_moves_message_and_metadata() { let resp: ErrorResponse = err.into(); assert_eq!(resp.status, 401); - assert!(matches!(resp.code, AppCode::Unauthorized)); + assert_eq!(resp.code, AppCode::Unauthorized); assert_eq!(resp.message, "owned message"); assert_eq!(resp.retry.unwrap().after_seconds, 5); assert_eq!(resp.www_authenticate.as_deref(), Some("Bearer")); @@ -280,7 +280,7 @@ fn from_owned_app_error_defaults_message_when_absent() { let resp: ErrorResponse = AppError::bare(AppErrorKind::Internal).into(); assert_eq!(resp.status, 500); - assert!(matches!(resp.code, AppCode::Internal)); + assert_eq!(resp.code, AppCode::Internal); assert_eq!(resp.message, AppErrorKind::Internal.label()); } @@ -290,7 +290,7 @@ fn from_app_error_bare_uses_kind_display_as_message() { let resp: ErrorResponse = app.into(); assert_eq!(resp.status, 504); - assert!(matches!(resp.code, AppCode::Timeout)); + assert_eq!(resp.code, AppCode::Timeout); assert_eq!(resp.message, AppErrorKind::Timeout.label()); } @@ -367,7 +367,7 @@ fn display_is_concise_and_does_not_leak_details() { fn new_legacy_defaults_to_internal_code() { let e = ErrorResponse::new_legacy(404, "boom"); assert_eq!(e.status, 404); - assert!(matches!(e.code, AppCode::Internal)); + assert_eq!(e.code, AppCode::Internal); assert_eq!(e.message, "boom"); } @@ -376,7 +376,7 @@ fn new_legacy_defaults_to_internal_code() { fn new_legacy_invalid_status_falls_back_to_internal_error() { let e = ErrorResponse::new_legacy(0, "boom"); assert_eq!(e.status, 500); - assert!(matches!(e.code, AppCode::Internal)); + assert_eq!(e.code, AppCode::Internal); assert_eq!(e.message, "boom"); } diff --git a/tests/ui/app_error/pass/enum.rs b/tests/ui/app_error/pass/enum.rs index 2b2a22e..0324d8f 100644 --- a/tests/ui/app_error/pass/enum.rs +++ b/tests/ui/app_error/pass/enum.rs @@ -26,5 +26,5 @@ fn main() { assert!(app_backend.message.is_none()); let code: AppCode = ApiError::Backend.into(); - assert!(matches!(code, AppCode::Service)); + assert_eq!(code, AppCode::Service); } diff --git a/tests/ui/app_error/pass/struct.rs b/tests/ui/app_error/pass/struct.rs index 05b64aa..b668a62 100644 --- a/tests/ui/app_error/pass/struct.rs +++ b/tests/ui/app_error/pass/struct.rs @@ -14,5 +14,5 @@ fn main() { assert_eq!(app.message.as_deref(), Some("missing flag: feature")); let code: AppCode = MissingFlag { name: "other" }.into(); - assert!(matches!(code, AppCode::BadRequest)); + assert_eq!(code, AppCode::BadRequest); }