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}