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