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}