Skip to content

Commit 73b4457

Browse files
authored
Merge pull request #153 from RAprogramm/eye-of-ra/fix-custom_codes_roundtrip_via_json-test
Normalize AppCode equality for custom codes
2 parents ed7877d + 352a217 commit 73b4457

File tree

7 files changed

+48
-28
lines changed

7 files changed

+48
-28
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file.
33

44
## [Unreleased]
55

6+
## [0.24.9] - 2025-10-25
7+
8+
### Fixed
9+
- Treat compile-time and runtime custom `AppCode` values as equal by comparing
10+
their canonical string representation, restoring successful JSON roundtrips
11+
for `AppCode::new("…")` literals.
12+
13+
### Changed
14+
- Equality for `AppCode` is now string-based; prefer `==` checks instead of
15+
pattern matching on `AppCode::Variant` constants.
16+
617
## [0.24.8] - 2025-10-24
718

819
### Changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "masterror"
3-
version = "0.24.8"
3+
version = "0.24.9"
44
rust-version = "1.90"
55
edition = "2024"
66
license = "MIT OR Apache-2.0"

src/code/app_code.rs

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
use alloc::{borrow::ToOwned, boxed::Box, string::String};
1+
use alloc::{
2+
borrow::{Cow, ToOwned},
3+
string::String
4+
};
25
use core::{
36
error::Error as CoreError,
47
fmt::{self, Display},
8+
hash::{Hash, Hasher},
59
str::FromStr
610
};
711

@@ -44,15 +48,9 @@ impl CoreError for ParseAppCodeError {}
4448
/// - Validate custom codes using [`AppCode::try_new`] before exposing them
4549
/// publicly.
4650
#[non_exhaustive]
47-
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
51+
#[derive(Debug, Clone)]
4852
pub struct AppCode {
49-
repr: CodeRepr
50-
}
51-
52-
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
53-
enum CodeRepr {
54-
Static(&'static str),
55-
Owned(Box<str>)
53+
repr: Cow<'static, str>
5654
}
5755

5856
#[allow(non_upper_case_globals)]
@@ -108,13 +106,13 @@ impl AppCode {
108106

109107
const fn from_static(code: &'static str) -> Self {
110108
Self {
111-
repr: CodeRepr::Static(code)
109+
repr: Cow::Borrowed(code)
112110
}
113111
}
114112

115113
fn from_owned(code: String) -> Self {
116114
Self {
117-
repr: CodeRepr::Owned(code.into_boxed_str())
115+
repr: Cow::Owned(code)
118116
}
119117
}
120118

@@ -169,10 +167,21 @@ impl AppCode {
169167
/// This matches the JSON serialization.
170168
#[must_use]
171169
pub fn as_str(&self) -> &str {
172-
match &self.repr {
173-
CodeRepr::Static(value) => value,
174-
CodeRepr::Owned(value) => value
175-
}
170+
self.repr.as_ref()
171+
}
172+
}
173+
174+
impl PartialEq for AppCode {
175+
fn eq(&self, other: &Self) -> bool {
176+
self.as_str() == other.as_str()
177+
}
178+
}
179+
180+
impl Eq for AppCode {}
181+
182+
impl Hash for AppCode {
183+
fn hash<H: Hasher>(&self, state: &mut H) {
184+
self.as_str().hash(state);
176185
}
177186
}
178187

src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@
263263
//! let app_err = AppError::new(AppErrorKind::NotFound, "user_not_found");
264264
//! let resp: ErrorResponse = (&app_err).into();
265265
//! assert_eq!(resp.status, 404);
266-
//! assert!(matches!(resp.code, AppCode::NotFound));
266+
//! assert_eq!(resp.code, AppCode::NotFound);
267267
//! ```
268268
//!
269269
//! # Typed control-flow macros
@@ -393,7 +393,7 @@ pub use kind::AppErrorKind;
393393
/// name: "other"
394394
/// }
395395
/// .into();
396-
/// assert!(matches!(code, AppCode::BadRequest));
396+
/// assert_eq!(code, AppCode::BadRequest);
397397
/// ```
398398
pub use masterror_derive::{Error, Masterror};
399399
pub use response::{

src/response/tests.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::{AppCode, AppError, AppErrorKind, ProblemJson};
99
fn new_sets_status_code_and_message() {
1010
let e = ErrorResponse::new(404, AppCode::NotFound, "missing").expect("status");
1111
assert_eq!(e.status, 404);
12-
assert!(matches!(e.code, AppCode::NotFound));
12+
assert_eq!(e.code, AppCode::NotFound);
1313
assert_eq!(e.message, "missing");
1414
assert!(e.retry.is_none());
1515
assert!(e.www_authenticate.is_none());
@@ -246,7 +246,7 @@ fn from_app_error_preserves_status_and_sets_code() {
246246
let app = AppError::new(AppErrorKind::NotFound, "user");
247247
let e: ErrorResponse = (&app).into();
248248
assert_eq!(e.status, 404);
249-
assert!(matches!(e.code, AppCode::NotFound));
249+
assert_eq!(e.code, AppCode::NotFound);
250250
assert_eq!(e.message, "user");
251251
assert!(e.retry.is_none());
252252
}
@@ -256,7 +256,7 @@ fn from_app_error_uses_default_message_when_none() {
256256
let app = AppError::bare(AppErrorKind::Internal);
257257
let e: ErrorResponse = (&app).into();
258258
assert_eq!(e.status, 500);
259-
assert!(matches!(e.code, AppCode::Internal));
259+
assert_eq!(e.code, AppCode::Internal);
260260
assert_eq!(e.message, AppErrorKind::Internal.label());
261261
}
262262

@@ -269,7 +269,7 @@ fn from_owned_app_error_moves_message_and_metadata() {
269269
let resp: ErrorResponse = err.into();
270270

271271
assert_eq!(resp.status, 401);
272-
assert!(matches!(resp.code, AppCode::Unauthorized));
272+
assert_eq!(resp.code, AppCode::Unauthorized);
273273
assert_eq!(resp.message, "owned message");
274274
assert_eq!(resp.retry.unwrap().after_seconds, 5);
275275
assert_eq!(resp.www_authenticate.as_deref(), Some("Bearer"));
@@ -280,7 +280,7 @@ fn from_owned_app_error_defaults_message_when_absent() {
280280
let resp: ErrorResponse = AppError::bare(AppErrorKind::Internal).into();
281281

282282
assert_eq!(resp.status, 500);
283-
assert!(matches!(resp.code, AppCode::Internal));
283+
assert_eq!(resp.code, AppCode::Internal);
284284
assert_eq!(resp.message, AppErrorKind::Internal.label());
285285
}
286286

@@ -290,7 +290,7 @@ fn from_app_error_bare_uses_kind_display_as_message() {
290290
let resp: ErrorResponse = app.into();
291291

292292
assert_eq!(resp.status, 504);
293-
assert!(matches!(resp.code, AppCode::Timeout));
293+
assert_eq!(resp.code, AppCode::Timeout);
294294
assert_eq!(resp.message, AppErrorKind::Timeout.label());
295295
}
296296

@@ -367,7 +367,7 @@ fn display_is_concise_and_does_not_leak_details() {
367367
fn new_legacy_defaults_to_internal_code() {
368368
let e = ErrorResponse::new_legacy(404, "boom");
369369
assert_eq!(e.status, 404);
370-
assert!(matches!(e.code, AppCode::Internal));
370+
assert_eq!(e.code, AppCode::Internal);
371371
assert_eq!(e.message, "boom");
372372
}
373373

@@ -376,7 +376,7 @@ fn new_legacy_defaults_to_internal_code() {
376376
fn new_legacy_invalid_status_falls_back_to_internal_error() {
377377
let e = ErrorResponse::new_legacy(0, "boom");
378378
assert_eq!(e.status, 500);
379-
assert!(matches!(e.code, AppCode::Internal));
379+
assert_eq!(e.code, AppCode::Internal);
380380
assert_eq!(e.message, "boom");
381381
}
382382

tests/ui/app_error/pass/enum.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,5 @@ fn main() {
2626
assert!(app_backend.message.is_none());
2727

2828
let code: AppCode = ApiError::Backend.into();
29-
assert!(matches!(code, AppCode::Service));
29+
assert_eq!(code, AppCode::Service);
3030
}

tests/ui/app_error/pass/struct.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,5 @@ fn main() {
1414
assert_eq!(app.message.as_deref(), Some("missing flag: feature"));
1515

1616
let code: AppCode = MissingFlag { name: "other" }.into();
17-
assert!(matches!(code, AppCode::BadRequest));
17+
assert_eq!(code, AppCode::BadRequest);
1818
}

0 commit comments

Comments
 (0)