diff --git a/CHANGELOG.md b/CHANGELOG.md index db0b209..f6db523 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,16 @@ All notable changes to this project will be documented in this file. ## [Unreleased] -## [0.14.1] - 2025-09-24 +## [0.14.1] - 2025-09-25 + +### Changed +- Boxed the internal `AppError` payload inside a new `ErrorInner` allocation, + keeping public field access via `Deref` while shrinking the error to a + pointer-sized handle that shares metadata, retry hints, and backtrace state. + +### Removed +- Dropped `clippy::result_large_err` allowances in response helpers and tests + now that `AppError` is pointer-sized and lint-clean without suppressions. ### Fixed - Removed the unused `BacktraceSlot::get` helper to restore builds with `-D warnings`. diff --git a/src/app_error/core.rs b/src/app_error/core.rs index bcf43f2..c6b6055 100644 --- a/src/app_error/core.rs +++ b/src/app_error/core.rs @@ -5,6 +5,7 @@ use std::{ borrow::Cow, error::Error as StdError, fmt::{Display, Formatter, Result as FmtResult}, + ops::{Deref, DerefMut}, sync::atomic::{AtomicBool, Ordering} }; @@ -65,9 +66,9 @@ impl Default for BacktraceSlot { #[cfg(not(feature = "backtrace"))] type BacktraceSlot = Option; -/// Rich application error preserving domain code, taxonomy and metadata. #[derive(Debug)] -pub struct Error { +#[doc(hidden)] +pub struct ErrorInner { /// Stable machine-readable error code. pub code: AppCode, /// Semantic error category. @@ -87,6 +88,26 @@ pub struct Error { telemetry_dirty: AtomicBool } +/// Rich application error preserving domain code, taxonomy and metadata. +#[derive(Debug)] +pub struct Error { + inner: Box +} + +impl Deref for Error { + type Target = ErrorInner; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl DerefMut for Error { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + impl Display for Error { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { Display::fmt(&self.kind, f) @@ -129,16 +150,18 @@ pub type AppResult = Result; impl Error { pub(crate) fn new_raw(kind: AppErrorKind, message: Option>) -> Self { Self { - code: AppCode::from(kind), - kind, - message, - metadata: Metadata::new(), - edit_policy: MessageEditPolicy::Preserve, - retry: None, - www_authenticate: None, - source: None, - backtrace: BacktraceSlot::default(), - telemetry_dirty: AtomicBool::new(true) + inner: Box::new(ErrorInner { + code: AppCode::from(kind), + kind, + message, + metadata: Metadata::new(), + edit_policy: MessageEditPolicy::Preserve, + retry: None, + www_authenticate: None, + source: None, + backtrace: BacktraceSlot::default(), + telemetry_dirty: AtomicBool::new(true) + }) } } diff --git a/src/app_error/tests.rs b/src/app_error/tests.rs index d86af65..03f2c66 100644 --- a/src/app_error/tests.rs +++ b/src/app_error/tests.rs @@ -427,8 +427,6 @@ fn metrics_counter_is_incremented_once() { #[test] fn result_alias_is_generic() { - // The alias intentionally preserves the full AppError payload size. - #[allow(clippy::result_large_err)] fn app() -> super::AppResult { Ok(1) } diff --git a/src/response/details.rs b/src/response/details.rs index 92472ca..ceca799 100644 --- a/src/response/details.rs +++ b/src/response/details.rs @@ -54,8 +54,6 @@ impl ErrorResponse { /// assert!(resp.details.is_some()); /// # } /// ``` - // AppError carries telemetry metadata; keep the rich payload despite the lint. - #[allow(clippy::result_large_err)] pub fn with_details(self, payload: T) -> AppResult where T: Serialize diff --git a/src/response/mapping.rs b/src/response/mapping.rs index e907581..cc522c5 100644 --- a/src/response/mapping.rs +++ b/src/response/mapping.rs @@ -11,18 +11,14 @@ impl Display for ErrorResponse { } impl From for ErrorResponse { - fn from(err: AppError) -> Self { - let AppError { - code, - kind, - message, - retry, - www_authenticate, - .. - } = err; + fn from(mut err: AppError) -> Self { + let kind = err.kind; + let code = err.code; + let retry = err.retry.take(); + let www_authenticate = err.www_authenticate.take(); let status = kind.http_status(); - let message = match message { + let message = match err.message.take() { Some(msg) => msg.into_owned(), None => kind.to_string() };