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}