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}