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}