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