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 #[must_use = "the retained budget failure should be inspected"]
186 #[inline]
187 pub const fn budget_error(&self) -> Option<&MeasuredBudgetError<R, Q>> {
188 match &self.failure {
189 JcsEncodeFailure::Budget(source) => Some(source),
190 _ => None,
191 }
192 }
193
194 /// Returns the working-memory limit failure when present.
195 ///
196 /// `Some` contains the retained limit source; `None` means another failure
197 /// occurred.
198 ///
199 /// # Returns
200 ///
201 /// The retained working-memory failure for that category, or `None`
202 /// otherwise.
203 #[must_use = "the retained working-memory failure should be inspected"]
204 #[inline]
205 pub const fn working_memory_error(&self) -> Option<&JcsWorkingMemoryError> {
206 match &self.failure {
207 JcsEncodeFailure::WorkingMemory(source) => Some(source),
208 _ => None,
209 }
210 }
211
212 /// Returns the privacy-safe serialization failure when present.
213 ///
214 /// `Some` contains its stable category without canonicalizer details;
215 /// `None` means another failure occurred.
216 ///
217 /// # Returns
218 ///
219 /// The privacy-safe serialization failure for that category, or `None`
220 /// otherwise.
221 #[must_use = "the retained serialization failure should be inspected"]
222 #[inline]
223 pub const fn serialization_error(&self) -> Option<&JsonSerializationError> {
224 match &self.failure {
225 JcsEncodeFailure::Serialize(source) => Some(source),
226 _ => None,
227 }
228 }
229
230 /// Returns the destination I/O failure when present.
231 ///
232 /// `Some` contains the writer's I/O source; `None` means another failure
233 /// occurred.
234 ///
235 /// # Returns
236 ///
237 /// The retained writer error for that category, or `None` otherwise.
238 #[must_use = "the retained writer failure should be inspected"]
239 #[inline]
240 pub const fn write_error(&self) -> Option<&io::Error> {
241 match &self.failure {
242 JcsEncodeFailure::Write(source) => Some(source),
243 _ => None,
244 }
245 }
246}
247
248impl<R, Q> fmt::Display for JcsEncodeError<R, Q>
249where
250 R: fmt::Debug,
251 Q: Copy + fmt::Debug + fmt::Display,
252{
253 /// Formats a stable message that does not expose canonicalizer details.
254 ///
255 /// # Parameters
256 ///
257 /// * `formatter` - Destination that receives the error message.
258 ///
259 /// # Returns
260 ///
261 /// `Ok(())` after the message is written.
262 ///
263 /// # Errors
264 ///
265 /// Returns the formatter error if writing the message fails.
266 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
267 match &self.failure {
268 JcsEncodeFailure::Budget(source) => fmt::Display::fmt(source, formatter),
269 JcsEncodeFailure::WorkingMemory(source) => fmt::Display::fmt(source, formatter),
270 JcsEncodeFailure::Serialize(_) => formatter.write_str("JCS serialization failed"),
271 JcsEncodeFailure::Write(_) => formatter.write_str("JCS output writer failed"),
272 }
273 }
274}
275
276impl<R, Q> Error for JcsEncodeError<R, Q>
277where
278 R: fmt::Debug + 'static,
279 Q: Copy + fmt::Debug + fmt::Display + 'static,
280{
281 /// Returns the retained source associated with the error category.
282 ///
283 /// # Returns
284 ///
285 /// The retained budget, working-memory, serialization, or writer source.
286 fn source(&self) -> Option<&(dyn Error + 'static)> {
287 match &self.failure {
288 JcsEncodeFailure::Budget(source) => Some(source),
289 JcsEncodeFailure::WorkingMemory(source) => Some(source),
290 JcsEncodeFailure::Serialize(source) => Some(source),
291 JcsEncodeFailure::Write(source) => Some(source),
292 }
293 }
294}
295
296impl<R, Q> From<MeasuredBudgetError<R, Q>> for JcsEncodeError<R, Q>
297where
298 Q: Copy + fmt::Debug,
299{
300 /// Converts a measured budget failure into a JCS encoding error.
301 ///
302 /// # Parameters
303 ///
304 /// * `source` - Budget failure to preserve.
305 ///
306 /// # Returns
307 ///
308 /// A JCS error classified as a budget failure.
309 fn from(source: MeasuredBudgetError<R, Q>) -> Self {
310 Self::budget(source)
311 }
312}