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    #[must_use]
98    #[inline(always)]
99    pub const fn kind(&self) -> JsonEncodeErrorKind {
100        match self.failure {
101            JsonEncodeFailure::Budget(_) => JsonEncodeErrorKind::Budget,
102            JsonEncodeFailure::InvalidRawJson(_) => JsonEncodeErrorKind::InvalidRawJson,
103            JsonEncodeFailure::Serialize(_) => JsonEncodeErrorKind::Serialize,
104            JsonEncodeFailure::Write(_) => JsonEncodeErrorKind::Write,
105        }
106    }
107
108    /// Returns the measured budget failure when accounting rejected work.
109    ///
110    /// # Returns
111    ///
112    /// `Some` with the borrowed budget error for
113    /// [`JsonEncodeErrorKind::Budget`], or `None` for every other failure
114    /// kind.
115    #[must_use]
116    #[inline(always)]
117    pub const fn budget_error(&self) -> Option<&MeasuredBudgetError<R, Q>> {
118        match &self.failure {
119            JsonEncodeFailure::Budget(source) => Some(source),
120            _ => None,
121        }
122    }
123
124    /// Returns the syntax error retained for invalid raw JSON.
125    ///
126    /// # Returns
127    ///
128    /// `Some` with the borrowed syntax error for
129    /// [`JsonEncodeErrorKind::InvalidRawJson`], or `None` otherwise.
130    #[must_use]
131    #[inline(always)]
132    pub const fn syntax_error(&self) -> Option<&JsonSyntaxError> {
133        match &self.failure {
134            JsonEncodeFailure::InvalidRawJson(source) => Some(source),
135            _ => None,
136        }
137    }
138
139    /// Returns the privacy-safe Serde serialization error when present.
140    ///
141    /// # Returns
142    ///
143    /// `Some` with the borrowed serialization error for
144    /// [`JsonEncodeErrorKind::Serialize`], or `None` otherwise.
145    #[must_use]
146    #[inline(always)]
147    pub const fn serialization_error(&self) -> Option<&JsonSerializationError> {
148        match &self.failure {
149            JsonEncodeFailure::Serialize(source) => Some(source),
150            _ => None,
151        }
152    }
153
154    /// Returns the destination-writer error when present.
155    ///
156    /// # Returns
157    ///
158    /// `Some` with the borrowed I/O error for [`JsonEncodeErrorKind::Write`],
159    /// or `None` otherwise.
160    #[must_use]
161    #[inline(always)]
162    pub const fn write_error(&self) -> Option<&IoError> {
163        match &self.failure {
164            JsonEncodeFailure::Write(source) => Some(source),
165            _ => None,
166        }
167    }
168
169    /// Consumes this error and returns its measured budget source when present.
170    ///
171    /// # Returns
172    ///
173    /// `Some` with the owned budget error for [`JsonEncodeErrorKind::Budget`],
174    /// or `None` otherwise.
175    #[must_use]
176    pub fn into_budget_error(self) -> Option<MeasuredBudgetError<R, Q>> {
177        match self.failure {
178            JsonEncodeFailure::Budget(source) => Some(source),
179            _ => None,
180        }
181    }
182
183    /// Consumes this error and returns its syntax source when present.
184    ///
185    /// # Returns
186    ///
187    /// `Some` with the owned syntax error for
188    /// [`JsonEncodeErrorKind::InvalidRawJson`], or `None` otherwise.
189    #[must_use]
190    pub fn into_syntax_error(self) -> Option<JsonSyntaxError> {
191        match self.failure {
192            JsonEncodeFailure::InvalidRawJson(source) => Some(source),
193            _ => None,
194        }
195    }
196
197    /// Consumes this error and returns its serialization source when present.
198    ///
199    /// # Returns
200    ///
201    /// `Some` with the owned serialization error for
202    /// [`JsonEncodeErrorKind::Serialize`], or `None` otherwise.
203    #[must_use]
204    pub fn into_serialization_error(self) -> Option<JsonSerializationError> {
205        match self.failure {
206            JsonEncodeFailure::Serialize(source) => Some(source),
207            _ => None,
208        }
209    }
210
211    /// Consumes this error and returns its destination-writer source when
212    /// present.
213    ///
214    /// # Returns
215    ///
216    /// `Some` with the owned I/O error for [`JsonEncodeErrorKind::Write`], or
217    /// `None` otherwise.
218    #[must_use]
219    pub fn into_write_error(self) -> Option<IoError> {
220        match self.failure {
221            JsonEncodeFailure::Write(source) => Some(source),
222            _ => None,
223        }
224    }
225
226    /// Consumes this error and returns its owned underlying source.
227    ///
228    /// Unlike the kind-specific `into_*` methods, this operation never drops
229    /// a non-matching error. Callers can exhaustively map every source with one
230    /// `match` expression.
231    ///
232    /// # Returns
233    ///
234    /// The budget, raw-JSON syntax, serialization, or destination-writer
235    /// source retained by this error.
236    #[inline(always)]
237    pub fn into_source(self) -> JsonEncodeErrorSource<R, Q> {
238        self.failure
239    }
240}
241
242impl<R, Q> From<MeasuredBudgetError<R, Q>> for JsonEncodeError<R, Q>
243where
244    Q: Copy + fmt::Debug,
245{
246    /// Converts a measured-budget failure into an encoding failure.
247    #[inline(always)]
248    fn from(source: MeasuredBudgetError<R, Q>) -> Self {
249        Self::budget(source)
250    }
251}
252
253impl<R, Q> fmt::Display for JsonEncodeError<R, Q>
254where
255    R: fmt::Debug,
256    Q: Copy + fmt::Debug + fmt::Display,
257{
258    /// Formats the retained source without exposing encoder internals.
259    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
260        match &self.failure {
261            JsonEncodeFailure::Budget(source) => fmt::Display::fmt(source, formatter),
262            JsonEncodeFailure::InvalidRawJson(source) => {
263                write!(formatter, "JSON raw value is invalid: {source}")
264            }
265            JsonEncodeFailure::Serialize(source) => {
266                write!(formatter, "JSON serialization failed: {source}")
267            }
268            JsonEncodeFailure::Write(source) => {
269                write!(formatter, "JSON output writer failed: {source}")
270            }
271        }
272    }
273}
274
275impl<R, Q> Error for JsonEncodeError<R, Q>
276where
277    R: fmt::Debug + 'static,
278    Q: Copy + fmt::Debug + fmt::Display + 'static,
279{
280    /// Returns the structured budget, syntax, serialization, or I/O source.
281    fn source(&self) -> Option<&(dyn Error + 'static)> {
282        match &self.failure {
283            JsonEncodeFailure::Budget(source) => Some(source),
284            JsonEncodeFailure::InvalidRawJson(source) => Some(source),
285            JsonEncodeFailure::Serialize(source) => Some(source),
286            JsonEncodeFailure::Write(source) => Some(source),
287        }
288    }
289}