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}