Skip to main content

rocketmq_error/
error.rs

1// Copyright 2026 The RocketMQ Rust Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::backtrace::Backtrace;
16use std::backtrace::BacktraceStatus;
17use std::error::Error as StdError;
18use std::fmt;
19use std::panic::Location;
20use std::sync::Arc;
21
22use crate::BacktracePolicy;
23use crate::CanonicalCondition;
24use crate::ComponentId;
25use crate::DiagnosticView;
26use crate::ErrorClass;
27use crate::ErrorCode;
28use crate::ErrorContext;
29use crate::ErrorDescriptor;
30use crate::ErrorSeverity;
31use crate::Exposure;
32use crate::FaultAttribution;
33use crate::ProjectionSpec;
34use crate::PublicErrorView;
35use crate::RecoveryHint;
36use crate::ViewContextViolation;
37
38type BoxError = Box<dyn StdError + Send + Sync + 'static>;
39
40/// Opaque canonical RocketMQ error.
41///
42/// Identity and policy are owned by a reviewed catalog descriptor. Each value
43/// retains typed context, at most one direct typed source, its first-promotion
44/// caller location, and an optional catalog-controlled backtrace behind one
45/// boxed pointer.
46///
47/// `Error` is intentionally not cloneable. Use [`SharedError`] when multiple
48/// owners must observe the same error instance.
49///
50/// ```compile_fail
51/// use rocketmq_error::{Error, CORE_INTERNAL_FAILURE};
52///
53/// let error = Error::new(&CORE_INTERNAL_FAILURE);
54/// let _copy = error.clone();
55/// ```
56pub struct Error {
57    inner: Box<ErrorInner>,
58}
59
60struct ErrorInner {
61    descriptor: &'static ErrorDescriptor,
62    context: ErrorContext,
63    source: Option<BoxError>,
64    location: &'static Location<'static>,
65    backtrace: Option<Backtrace>,
66}
67
68/// Result whose error is the canonical [`Error`].
69pub type Result<T> = std::result::Result<T, Error>;
70
71/// Shared ownership of one canonical [`Error`] instance.
72pub type SharedError = Arc<Error>;
73
74impl Error {
75    /// Creates a source-free canonical error from a catalog descriptor.
76    #[must_use]
77    #[track_caller]
78    pub fn new(descriptor: &'static ErrorDescriptor) -> Self {
79        Self::from_parts(descriptor, None)
80    }
81
82    /// Creates a canonical error while retaining the direct typed source.
83    #[must_use]
84    #[track_caller]
85    pub fn caused_by(descriptor: &'static ErrorDescriptor, source: impl StdError + Send + Sync + 'static) -> Self {
86        Self::from_parts(descriptor, Some(Box::new(source)))
87    }
88
89    #[track_caller]
90    fn from_parts(descriptor: &'static ErrorDescriptor, source: Option<BoxError>) -> Self {
91        Self {
92            inner: Box::new(ErrorInner {
93                descriptor,
94                context: ErrorContext::new(),
95                source,
96                location: Location::caller(),
97                backtrace: capture_backtrace(descriptor.backtrace_policy()),
98            }),
99        }
100    }
101
102    /// Replaces the typed context without changing source or provenance.
103    #[must_use]
104    pub fn with_context(mut self, context: ErrorContext) -> Self {
105        self.inner.context = context;
106        self
107    }
108
109    /// Replaces the direct typed source without changing context or provenance.
110    #[must_use]
111    pub fn with_source(mut self, source: impl StdError + Send + Sync + 'static) -> Self {
112        self.inner.source = Some(Box::new(source));
113        self
114    }
115
116    /// Replaces the direct boxed source without adding another causal layer.
117    #[must_use]
118    pub fn with_boxed_source(mut self, source: Box<dyn StdError + Send + Sync + 'static>) -> Self {
119        self.inner.source = Some(source);
120        self
121    }
122
123    /// Returns the catalog descriptor that owns identity and policy.
124    #[must_use]
125    pub const fn descriptor(&self) -> &'static ErrorDescriptor {
126        self.inner.descriptor
127    }
128
129    /// Returns the stable catalog code.
130    #[must_use]
131    pub const fn code(&self) -> ErrorCode {
132        self.descriptor().code()
133    }
134
135    /// Returns the broad catalog-owned class.
136    #[must_use]
137    pub const fn class(&self) -> ErrorClass {
138        self.descriptor().class()
139    }
140
141    /// Returns the protocol-independent canonical condition.
142    #[must_use]
143    pub const fn condition(&self) -> CanonicalCondition {
144        self.descriptor().condition()
145    }
146
147    /// Returns the catalog-owned fault attribution.
148    #[must_use]
149    pub const fn fault(&self) -> FaultAttribution {
150        self.descriptor().fault()
151    }
152
153    /// Returns the catalog component that owns this identity.
154    #[must_use]
155    pub const fn component(&self) -> ComponentId {
156        self.descriptor().component()
157    }
158
159    /// Returns the catalog-owned severity.
160    #[must_use]
161    pub const fn severity(&self) -> ErrorSeverity {
162        self.descriptor().severity()
163    }
164
165    /// Returns the catalog-owned recovery hint.
166    #[must_use]
167    pub const fn recovery_hint(&self) -> RecoveryHint {
168        self.descriptor().recovery_hint()
169    }
170
171    /// Returns the public-context exposure policy.
172    #[must_use]
173    pub const fn exposure(&self) -> Exposure {
174        self.descriptor().exposure()
175    }
176
177    /// Returns the catalog-controlled backtrace policy.
178    #[must_use]
179    pub const fn backtrace_policy(&self) -> BacktracePolicy {
180        self.descriptor().backtrace_policy()
181    }
182
183    /// Returns descriptor-owned boundary projections.
184    #[must_use]
185    pub const fn projection(&self) -> ProjectionSpec {
186        self.descriptor().projection()
187    }
188
189    /// Returns the typed context retained by this error.
190    #[must_use]
191    pub const fn context(&self) -> &ErrorContext {
192        &self.inner.context
193    }
194
195    /// Creates a descriptor-validated public projection.
196    ///
197    /// # Errors
198    ///
199    /// Returns an error when the retained context does not match the selected
200    /// catalog descriptor.
201    pub fn public_view(&self) -> std::result::Result<PublicErrorView<'_>, ViewContextViolation> {
202        PublicErrorView::try_new(self.descriptor(), self.context())
203    }
204
205    /// Creates a descriptor-validated diagnostic projection.
206    ///
207    /// The view never includes the source, caller location, or backtrace.
208    ///
209    /// # Errors
210    ///
211    /// Returns an error when the retained context does not match the selected
212    /// catalog descriptor.
213    pub fn diagnostic_view(&self) -> std::result::Result<DiagnosticView<'_>, ViewContextViolation> {
214        DiagnosticView::try_new(self.descriptor(), self.context())
215    }
216
217    /// Returns the first-promotion caller location.
218    #[must_use]
219    pub const fn location(&self) -> &'static Location<'static> {
220        self.inner.location
221    }
222
223    /// Returns the captured backtrace, if standard-library capture was enabled.
224    #[must_use]
225    pub const fn backtrace(&self) -> Option<&Backtrace> {
226        self.inner.backtrace.as_ref()
227    }
228}
229
230fn capture_backtrace(policy: BacktracePolicy) -> Option<Backtrace> {
231    match policy {
232        BacktracePolicy::Never => None,
233        BacktracePolicy::OnDemand => {
234            let backtrace = Backtrace::capture();
235            matches!(backtrace.status(), BacktraceStatus::Captured).then_some(backtrace)
236        }
237    }
238}
239
240impl fmt::Display for Error {
241    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
242        write!(formatter, "{}: {}", self.code(), self.descriptor().public_message())
243    }
244}
245
246impl fmt::Debug for Error {
247    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
248        formatter
249            .debug_struct("Error")
250            .field("code", &self.code())
251            .field("class", &self.class())
252            .field("condition", &self.condition())
253            .field("fault", &self.fault())
254            .field("component", &self.component())
255            .field("severity", &self.severity())
256            .field("recovery_hint", &self.recovery_hint())
257            .field("exposure", &self.exposure())
258            .field("backtrace_policy", &self.backtrace_policy())
259            .field("context", &self.context())
260            .field("source_present", &self.inner.source.is_some())
261            .field("backtrace_captured", &self.inner.backtrace.is_some())
262            .finish()
263    }
264}
265
266impl StdError for Error {
267    fn source(&self) -> Option<&(dyn StdError + 'static)> {
268        self.inner
269            .source
270            .as_deref()
271            .map(|source| source as &(dyn StdError + 'static))
272    }
273}