Skip to main content

qubit_json/encode/
jcs_encode_error.rs

1// =============================================================================
2//    Copyright (c) 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8//! Defines errors returned by JCS encoding.
9
10use std::error::Error;
11use std::fmt;
12use std::io;
13
14use qubit_budget::MeasuredBudgetError;
15
16use super::JcsEncodeErrorKind;
17use super::JcsWorkingMemoryError;
18use super::JsonEncodeError;
19use super::JsonEncodeErrorKind;
20use super::JsonSerializationError;
21use super::JsonSerializationErrorKind;
22use super::internal::JcsEncodeFailure;
23
24/// Failure produced while encoding a JSON value as RFC 8785 JCS.
25///
26/// Serialization diagnostics are deliberately fixed text and never expose
27/// details returned by the canonicalizer.
28///
29/// # Type Parameters
30///
31/// * `R` is the resource type used by the encoder's budget accounting.
32/// * `Q` is the quantity type used by that accounting.
33///
34/// # Examples
35///
36/// ```
37/// use qubit_budget::json::JsonEncodeLimits;
38/// use qubit_json::encode::JcsEncodeErrorKind;
39/// use qubit_json::encode::JcsEncoder;
40///
41/// let limits: JsonEncodeLimits = JsonEncodeLimits::new();
42/// let mut encoder = JcsEncoder::with_limits(limits, 0);
43/// let error = encoder
44///     .to_vec(&serde_json::json!({"key": "value"}))
45///     .expect_err("zero working memory cannot render an object");
46/// assert_eq!(error.kind(), JcsEncodeErrorKind::WorkingMemory);
47/// ```
48#[must_use]
49#[derive(Debug)]
50pub struct JcsEncodeError<R, Q = usize>
51where
52    Q: Copy + fmt::Debug,
53{
54    /// Retains the detailed source behind the stable public failure category.
55    failure: JcsEncodeFailure<R, Q>,
56}
57
58impl<R, Q> JcsEncodeError<R, Q>
59where
60    Q: Copy + fmt::Debug,
61{
62    /// Wraps a budget-accounting failure without changing its source.
63    ///
64    /// # Parameters
65    ///
66    /// * `source` - Measured budget failure to retain.
67    ///
68    /// # Returns
69    ///
70    /// A JCS encoding error classified as a budget failure.
71    #[inline]
72    pub(crate) const fn budget(source: MeasuredBudgetError<R, Q>) -> Self {
73        Self {
74            failure: JcsEncodeFailure::Budget(source),
75        }
76    }
77
78    /// Wraps a canonicalization failure while preserving its safe category.
79    ///
80    /// # Parameters
81    ///
82    /// * `source` - Privacy-safe serialization failure to retain.
83    ///
84    /// # Returns
85    ///
86    /// A JCS encoding error classified as a serialization failure.
87    #[inline]
88    pub(crate) const fn serialization(source: JsonSerializationError) -> Self {
89        Self {
90            failure: JcsEncodeFailure::Serialize(source),
91        }
92    }
93
94    /// Wraps a working-memory limit failure.
95    ///
96    /// # Parameters
97    ///
98    /// * `source` - Working-memory limit failure to retain.
99    ///
100    /// # Returns
101    ///
102    /// A JCS encoding error classified as a working-memory failure.
103    #[inline]
104    pub(crate) const fn working_memory(source: JcsWorkingMemoryError) -> Self {
105        Self {
106            failure: JcsEncodeFailure::WorkingMemory(source),
107        }
108    }
109
110    /// Wraps an output destination failure.
111    ///
112    /// # Parameters
113    ///
114    /// * `source` - I/O failure returned by the output destination.
115    ///
116    /// # Returns
117    ///
118    /// A JCS encoding error classified as a writer failure.
119    #[inline]
120    pub(crate) fn write(source: io::Error) -> Self {
121        Self {
122            failure: JcsEncodeFailure::Write(source),
123        }
124    }
125
126    /// Converts a strict JSON encoder failure to its corresponding JCS class.
127    ///
128    /// # Parameters
129    ///
130    /// * `error` - Strict encoder failure to classify as a JCS error.
131    ///
132    /// # Returns
133    ///
134    /// The corresponding budget, serialization, or writer failure; invalid raw
135    /// JSON becomes a privacy-safe serialization failure.
136    ///
137    /// # Panics
138    ///
139    /// Panics if the strict encoder reports a category without its required
140    /// retained source, which violates the encoder error invariant.
141    pub(crate) fn from_json_encode(error: JsonEncodeError<R, Q>) -> Self {
142        match error.kind() {
143            JsonEncodeErrorKind::Budget => Self::budget(
144                error
145                    .into_budget_error()
146                    .expect("budget kind retains a budget source"),
147            ),
148            JsonEncodeErrorKind::Serialize => Self::serialization(
149                error
150                    .into_serialization_error()
151                    .expect("serialize kind retains a source"),
152            ),
153            JsonEncodeErrorKind::Write => Self::write(
154                error
155                    .into_write_error()
156                    .expect("write kind retains an I/O source"),
157            ),
158            JsonEncodeErrorKind::InvalidRawJson => Self::serialization(
159                JsonSerializationError::new(JsonSerializationErrorKind::CustomSerialization),
160            ),
161        }
162    }
163
164    /// Returns the stable failure category.
165    ///
166    /// # Returns
167    ///
168    /// The category identifying the retained budget, memory, serialization, or
169    /// writer failure.
170    #[must_use]
171    #[inline]
172    pub const fn kind(&self) -> JcsEncodeErrorKind {
173        match self.failure {
174            JcsEncodeFailure::Budget(_) => JcsEncodeErrorKind::Budget,
175            JcsEncodeFailure::WorkingMemory(_) => JcsEncodeErrorKind::WorkingMemory,
176            JcsEncodeFailure::Serialize(_) => JcsEncodeErrorKind::Serialize,
177            JcsEncodeFailure::Write(_) => JcsEncodeErrorKind::Write,
178        }
179    }
180
181    /// Returns the measured budget failure when resource accounting failed.
182    ///
183    /// `Some` contains the retained accounting source; `None` means the error
184    /// belongs to another category.
185    ///
186    /// # Returns
187    ///
188    /// The retained budget error for a budget failure, or `None` otherwise.
189    #[must_use = "the retained budget failure should be inspected"]
190    #[inline]
191    pub const fn budget_error(&self) -> Option<&MeasuredBudgetError<R, Q>> {
192        match &self.failure {
193            JcsEncodeFailure::Budget(source) => Some(source),
194            _ => None,
195        }
196    }
197
198    /// Returns the working-memory limit failure when present.
199    ///
200    /// `Some` contains the retained limit source; `None` means another failure
201    /// occurred.
202    ///
203    /// # Returns
204    ///
205    /// The retained working-memory failure for that category, or `None`
206    /// otherwise.
207    #[must_use = "the retained working-memory failure should be inspected"]
208    #[inline]
209    pub const fn working_memory_error(&self) -> Option<&JcsWorkingMemoryError> {
210        match &self.failure {
211            JcsEncodeFailure::WorkingMemory(source) => Some(source),
212            _ => None,
213        }
214    }
215
216    /// Returns the privacy-safe serialization failure when present.
217    ///
218    /// `Some` contains its stable category without canonicalizer details;
219    /// `None` means another failure occurred.
220    ///
221    /// # Returns
222    ///
223    /// The privacy-safe serialization failure for that category, or `None`
224    /// otherwise.
225    #[must_use = "the retained serialization failure should be inspected"]
226    #[inline]
227    pub const fn serialization_error(&self) -> Option<&JsonSerializationError> {
228        match &self.failure {
229            JcsEncodeFailure::Serialize(source) => Some(source),
230            _ => None,
231        }
232    }
233
234    /// Returns the destination I/O failure when present.
235    ///
236    /// `Some` contains the writer's I/O source; `None` means another failure
237    /// occurred.
238    ///
239    /// # Returns
240    ///
241    /// The retained writer error for that category, or `None` otherwise.
242    #[must_use = "the retained writer failure should be inspected"]
243    #[inline]
244    pub const fn write_error(&self) -> Option<&io::Error> {
245        match &self.failure {
246            JcsEncodeFailure::Write(source) => Some(source),
247            _ => None,
248        }
249    }
250}
251
252impl<R, Q> fmt::Display for JcsEncodeError<R, Q>
253where
254    R: fmt::Debug,
255    Q: Copy + fmt::Debug + fmt::Display,
256{
257    /// Formats a stable message that does not expose canonicalizer details.
258    ///
259    /// # Parameters
260    ///
261    /// * `formatter` - Destination that receives the error message.
262    ///
263    /// # Returns
264    ///
265    /// `Ok(())` after the message is written.
266    ///
267    /// # Errors
268    ///
269    /// Returns the formatter error if writing the message fails.
270    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
271        match &self.failure {
272            JcsEncodeFailure::Budget(source) => fmt::Display::fmt(source, formatter),
273            JcsEncodeFailure::WorkingMemory(source) => fmt::Display::fmt(source, formatter),
274            JcsEncodeFailure::Serialize(_) => formatter.write_str("JCS serialization failed"),
275            JcsEncodeFailure::Write(_) => formatter.write_str("JCS output writer failed"),
276        }
277    }
278}
279
280impl<R, Q> Error for JcsEncodeError<R, Q>
281where
282    R: fmt::Debug + 'static,
283    Q: Copy + fmt::Debug + fmt::Display + 'static,
284{
285    /// Returns the retained source associated with the error category.
286    ///
287    /// # Returns
288    ///
289    /// The retained budget, working-memory, serialization, or writer source.
290    fn source(&self) -> Option<&(dyn Error + 'static)> {
291        match &self.failure {
292            JcsEncodeFailure::Budget(source) => Some(source),
293            JcsEncodeFailure::WorkingMemory(source) => Some(source),
294            JcsEncodeFailure::Serialize(source) => Some(source),
295            JcsEncodeFailure::Write(source) => Some(source),
296        }
297    }
298}
299
300impl<R, Q> From<MeasuredBudgetError<R, Q>> for JcsEncodeError<R, Q>
301where
302    Q: Copy + fmt::Debug,
303{
304    /// Converts a measured budget failure into a JCS encoding error.
305    ///
306    /// # Parameters
307    ///
308    /// * `source` - Budget failure to preserve.
309    ///
310    /// # Returns
311    ///
312    /// A JCS error classified as a budget failure.
313    fn from(source: MeasuredBudgetError<R, Q>) -> Self {
314        Self::budget(source)
315    }
316}