Skip to main content

qubit_json/encode/
json_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 strict JSON encoding.
9
10use std::error::Error;
11use std::fmt;
12use std::io::Error as IoError;
13
14use qubit_budget::MeasuredBudgetError;
15
16use super::JsonEncodeErrorKind;
17use super::JsonEncodeErrorSource;
18use super::JsonSerializationError;
19use super::internal::JsonEncodeFailure;
20use crate::decode::JsonSyntaxError;
21
22/// Failure produced while encoding one JSON document.
23///
24/// Internal failure variants remain private. Callers branch through
25/// [`JsonEncodeErrorKind`] and inspect sources through stable accessors rather
26/// than depending on the encoder's representation.
27///
28/// # Type Parameters
29///
30/// * `R` - Resource identity attached to budget failures.
31/// * `Q` - Quantity representation attached to budget failures.
32///
33/// # Examples
34///
35/// ```
36/// use qubit_json::encode::JsonEncodeErrorKind;
37/// use qubit_json::encode::JsonEncoder;
38///
39/// let mut encoder = JsonEncoder::unlimited();
40/// let error = encoder
41///     .to_vec(&u128::MAX)
42///     .expect_err("wide integer must not serialize as JSON");
43/// assert_eq!(error.kind(), JsonEncodeErrorKind::Serialize);
44/// assert!(error.serialization_error().is_some());
45/// ```
46#[must_use]
47#[derive(Debug)]
48pub struct JsonEncodeError<R, Q = usize>
49where
50    Q: Copy + fmt::Debug,
51{
52    /// Mutually exclusive structured failure retained by this error.
53    failure: JsonEncodeFailure<R, Q>,
54}
55
56impl<R, Q> JsonEncodeError<R, Q>
57where
58    Q: Copy + fmt::Debug,
59{
60    /// Creates a resource-accounting failure.
61    #[must_use = "return or inspect the constructed encoding error"]
62    pub(crate) const fn budget(source: MeasuredBudgetError<R, Q>) -> Self {
63        Self {
64            failure: JsonEncodeFailure::Budget(source),
65        }
66    }
67
68    /// Creates a failure for invalid `RawValue` JSON text.
69    #[must_use = "return or inspect the constructed encoding error"]
70    pub(crate) const fn invalid_raw_json(source: JsonSyntaxError) -> Self {
71        Self {
72            failure: JsonEncodeFailure::InvalidRawJson(source),
73        }
74    }
75
76    /// Creates a privacy-safe Serde serialization failure.
77    #[must_use = "return or inspect the constructed encoding error"]
78    pub(crate) const fn serialization(source: JsonSerializationError) -> Self {
79        Self {
80            failure: JsonEncodeFailure::Serialize(source),
81        }
82    }
83
84    /// Creates a destination-writer failure.
85    #[must_use = "return or inspect the constructed encoding error"]
86    pub(crate) fn write(source: IoError) -> Self {
87        Self {
88            failure: JsonEncodeFailure::Write(source),
89        }
90    }
91
92    /// Returns the stable failure category.
93    ///
94    /// # Returns
95    ///
96    /// The category describing which encoding operation failed.
97    #[inline(always)]
98    pub const fn kind(&self) -> JsonEncodeErrorKind {
99        match self.failure {
100            JsonEncodeFailure::Budget(_) => JsonEncodeErrorKind::Budget,
101            JsonEncodeFailure::InvalidRawJson(_) => JsonEncodeErrorKind::InvalidRawJson,
102            JsonEncodeFailure::Serialize(_) => JsonEncodeErrorKind::Serialize,
103            JsonEncodeFailure::Write(_) => JsonEncodeErrorKind::Write,
104        }
105    }
106
107    /// Returns the measured budget failure when accounting rejected work.
108    ///
109    /// # Returns
110    ///
111    /// `Some` with the borrowed budget error for
112    /// [`JsonEncodeErrorKind::Budget`], or `None` for every other failure
113    /// kind.
114    #[must_use]
115    #[inline(always)]
116    pub const fn budget_error(&self) -> Option<&MeasuredBudgetError<R, Q>> {
117        match &self.failure {
118            JsonEncodeFailure::Budget(source) => Some(source),
119            _ => None,
120        }
121    }
122
123    /// Returns the syntax error retained for invalid raw JSON.
124    ///
125    /// # Returns
126    ///
127    /// `Some` with the borrowed syntax error for
128    /// [`JsonEncodeErrorKind::InvalidRawJson`], or `None` otherwise.
129    #[must_use]
130    #[inline(always)]
131    pub const fn syntax_error(&self) -> Option<&JsonSyntaxError> {
132        match &self.failure {
133            JsonEncodeFailure::InvalidRawJson(source) => Some(source),
134            _ => None,
135        }
136    }
137
138    /// Returns the privacy-safe Serde serialization error when present.
139    ///
140    /// # Returns
141    ///
142    /// `Some` with the borrowed serialization error for
143    /// [`JsonEncodeErrorKind::Serialize`], or `None` otherwise.
144    #[must_use]
145    #[inline(always)]
146    pub const fn serialization_error(&self) -> Option<&JsonSerializationError> {
147        match &self.failure {
148            JsonEncodeFailure::Serialize(source) => Some(source),
149            _ => None,
150        }
151    }
152
153    /// Returns the destination-writer error when present.
154    ///
155    /// # Returns
156    ///
157    /// `Some` with the borrowed I/O error for [`JsonEncodeErrorKind::Write`],
158    /// or `None` otherwise.
159    #[must_use]
160    #[inline(always)]
161    pub const fn write_error(&self) -> Option<&IoError> {
162        match &self.failure {
163            JsonEncodeFailure::Write(source) => Some(source),
164            _ => None,
165        }
166    }
167
168    /// Consumes this error and returns its measured budget source when present.
169    ///
170    /// # Returns
171    ///
172    /// `Some` with the owned budget error for [`JsonEncodeErrorKind::Budget`],
173    /// or `None` otherwise.
174    #[must_use]
175    pub fn into_budget_error(self) -> Option<MeasuredBudgetError<R, Q>> {
176        match self.failure {
177            JsonEncodeFailure::Budget(source) => Some(source),
178            _ => None,
179        }
180    }
181
182    /// Consumes this error and returns its syntax source when present.
183    ///
184    /// # Returns
185    ///
186    /// `Some` with the owned syntax error for
187    /// [`JsonEncodeErrorKind::InvalidRawJson`], or `None` otherwise.
188    #[must_use]
189    pub fn into_syntax_error(self) -> Option<JsonSyntaxError> {
190        match self.failure {
191            JsonEncodeFailure::InvalidRawJson(source) => Some(source),
192            _ => None,
193        }
194    }
195
196    /// Consumes this error and returns its serialization source when present.
197    ///
198    /// # Returns
199    ///
200    /// `Some` with the owned serialization error for
201    /// [`JsonEncodeErrorKind::Serialize`], or `None` otherwise.
202    #[must_use]
203    pub fn into_serialization_error(self) -> Option<JsonSerializationError> {
204        match self.failure {
205            JsonEncodeFailure::Serialize(source) => Some(source),
206            _ => None,
207        }
208    }
209
210    /// Consumes this error and returns its destination-writer source when
211    /// present.
212    ///
213    /// # Returns
214    ///
215    /// `Some` with the owned I/O error for [`JsonEncodeErrorKind::Write`], or
216    /// `None` otherwise.
217    #[must_use]
218    pub fn into_write_error(self) -> Option<IoError> {
219        match self.failure {
220            JsonEncodeFailure::Write(source) => Some(source),
221            _ => None,
222        }
223    }
224
225    /// Consumes this error and returns its owned underlying source.
226    ///
227    /// Unlike the kind-specific `into_*` methods, this operation never drops
228    /// a non-matching error. Callers can exhaustively map every source with one
229    /// `match` expression.
230    ///
231    /// # Returns
232    ///
233    /// The budget, raw-JSON syntax, serialization, or destination-writer
234    /// source retained by this error.
235    #[inline(always)]
236    pub fn into_source(self) -> JsonEncodeErrorSource<R, Q> {
237        self.failure
238    }
239}
240
241impl<R, Q> From<MeasuredBudgetError<R, Q>> for JsonEncodeError<R, Q>
242where
243    Q: Copy + fmt::Debug,
244{
245    /// Converts a measured-budget failure into an encoding failure.
246    #[inline(always)]
247    fn from(source: MeasuredBudgetError<R, Q>) -> Self {
248        Self::budget(source)
249    }
250}
251
252impl<R, Q> fmt::Display for JsonEncodeError<R, Q>
253where
254    R: fmt::Debug,
255    Q: Copy + fmt::Debug + fmt::Display,
256{
257    /// Formats the retained source without exposing encoder internals.
258    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
259        match &self.failure {
260            JsonEncodeFailure::Budget(source) => fmt::Display::fmt(source, formatter),
261            JsonEncodeFailure::InvalidRawJson(source) => {
262                write!(formatter, "JSON raw value is invalid: {source}")
263            }
264            JsonEncodeFailure::Serialize(source) => {
265                write!(formatter, "JSON serialization failed: {source}")
266            }
267            JsonEncodeFailure::Write(source) => {
268                write!(formatter, "JSON output writer failed: {source}")
269            }
270        }
271    }
272}
273
274impl<R, Q> Error for JsonEncodeError<R, Q>
275where
276    R: fmt::Debug + 'static,
277    Q: Copy + fmt::Debug + fmt::Display + 'static,
278{
279    /// Returns the structured budget, syntax, serialization, or I/O source.
280    fn source(&self) -> Option<&(dyn Error + 'static)> {
281        match &self.failure {
282            JsonEncodeFailure::Budget(source) => Some(source),
283            JsonEncodeFailure::InvalidRawJson(source) => Some(source),
284            JsonEncodeFailure::Serialize(source) => Some(source),
285            JsonEncodeFailure::Write(source) => Some(source),
286        }
287    }
288}