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 => {
144                Self::budget(error.into_budget_error().expect("budget kind retains a budget source"))
145            }
146            JsonEncodeErrorKind::Serialize => Self::serialization(
147                error
148                    .into_serialization_error()
149                    .expect("serialize kind retains a source"),
150            ),
151            JsonEncodeErrorKind::Write => {
152                Self::write(error.into_write_error().expect("write kind retains an I/O source"))
153            }
154            JsonEncodeErrorKind::InvalidRawJson => Self::serialization(JsonSerializationError::new(
155                JsonSerializationErrorKind::CustomSerialization,
156            )),
157        }
158    }
159
160    /// Returns the stable failure category.
161    ///
162    /// # Returns
163    ///
164    /// The category identifying the retained budget, memory, serialization, or
165    /// writer failure.
166    #[must_use]
167    #[inline]
168    pub const fn kind(&self) -> JcsEncodeErrorKind {
169        match self.failure {
170            JcsEncodeFailure::Budget(_) => JcsEncodeErrorKind::Budget,
171            JcsEncodeFailure::WorkingMemory(_) => JcsEncodeErrorKind::WorkingMemory,
172            JcsEncodeFailure::Serialize(_) => JcsEncodeErrorKind::Serialize,
173            JcsEncodeFailure::Write(_) => JcsEncodeErrorKind::Write,
174        }
175    }
176
177    /// Returns the measured budget failure when resource accounting failed.
178    ///
179    /// `Some` contains the retained accounting source; `None` means the error
180    /// belongs to another category.
181    ///
182    /// # Returns
183    ///
184    /// The retained budget error for a budget failure, or `None` otherwise.
185    #[inline]
186    pub const fn budget_error(&self) -> Option<&MeasuredBudgetError<R, Q>> {
187        match &self.failure {
188            JcsEncodeFailure::Budget(source) => Some(source),
189            _ => None,
190        }
191    }
192
193    /// Returns the working-memory limit failure when present.
194    ///
195    /// `Some` contains the retained limit source; `None` means another failure
196    /// occurred.
197    ///
198    /// # Returns
199    ///
200    /// The retained working-memory failure for that category, or `None`
201    /// otherwise.
202    #[inline]
203    pub const fn working_memory_error(&self) -> Option<&JcsWorkingMemoryError> {
204        match &self.failure {
205            JcsEncodeFailure::WorkingMemory(source) => Some(source),
206            _ => None,
207        }
208    }
209
210    /// Returns the privacy-safe serialization failure when present.
211    ///
212    /// `Some` contains its stable category without canonicalizer details;
213    /// `None` means another failure occurred.
214    ///
215    /// # Returns
216    ///
217    /// The privacy-safe serialization failure for that category, or `None`
218    /// otherwise.
219    #[inline]
220    pub const fn serialization_error(&self) -> Option<&JsonSerializationError> {
221        match &self.failure {
222            JcsEncodeFailure::Serialize(source) => Some(source),
223            _ => None,
224        }
225    }
226
227    /// Returns the destination I/O failure when present.
228    ///
229    /// `Some` contains the writer's I/O source; `None` means another failure
230    /// occurred.
231    ///
232    /// # Returns
233    ///
234    /// The retained writer error for that category, or `None` otherwise.
235    #[inline]
236    pub const fn write_error(&self) -> Option<&io::Error> {
237        match &self.failure {
238            JcsEncodeFailure::Write(source) => Some(source),
239            _ => None,
240        }
241    }
242}
243
244impl<R, Q> fmt::Display for JcsEncodeError<R, Q>
245where
246    R: fmt::Debug,
247    Q: Copy + fmt::Debug + fmt::Display,
248{
249    /// Formats a stable message that does not expose canonicalizer details.
250    ///
251    /// # Parameters
252    ///
253    /// * `formatter` - Destination that receives the error message.
254    ///
255    /// # Returns
256    ///
257    /// `Ok(())` after the message is written.
258    ///
259    /// # Errors
260    ///
261    /// Returns the formatter error if writing the message fails.
262    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
263        match &self.failure {
264            JcsEncodeFailure::Budget(source) => fmt::Display::fmt(source, formatter),
265            JcsEncodeFailure::WorkingMemory(source) => fmt::Display::fmt(source, formatter),
266            JcsEncodeFailure::Serialize(_) => formatter.write_str("JCS serialization failed"),
267            JcsEncodeFailure::Write(_) => formatter.write_str("JCS output writer failed"),
268        }
269    }
270}
271
272impl<R, Q> Error for JcsEncodeError<R, Q>
273where
274    R: fmt::Debug + 'static,
275    Q: Copy + fmt::Debug + fmt::Display + 'static,
276{
277    /// Returns the retained source associated with the error category.
278    ///
279    /// # Returns
280    ///
281    /// The retained budget, working-memory, serialization, or writer source.
282    fn source(&self) -> Option<&(dyn Error + 'static)> {
283        match &self.failure {
284            JcsEncodeFailure::Budget(source) => Some(source),
285            JcsEncodeFailure::WorkingMemory(source) => Some(source),
286            JcsEncodeFailure::Serialize(source) => Some(source),
287            JcsEncodeFailure::Write(source) => Some(source),
288        }
289    }
290}
291
292impl<R, Q> From<MeasuredBudgetError<R, Q>> for JcsEncodeError<R, Q>
293where
294    Q: Copy + fmt::Debug,
295{
296    /// Converts a measured budget failure into a JCS encoding error.
297    ///
298    /// # Parameters
299    ///
300    /// * `source` - Budget failure to preserve.
301    ///
302    /// # Returns
303    ///
304    /// A JCS error classified as a budget failure.
305    fn from(source: MeasuredBudgetError<R, Q>) -> Self {
306        Self::budget(source)
307    }
308}