qubit_json/decode/json_decode_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 the shared error returned by all JSON decoding facades.
9
10use std::error::Error;
11use std::fmt;
12use std::io;
13use std::sync::Arc;
14
15use qubit_budget::MeasuredBudgetError;
16use qubit_budget::ResourceQuantity;
17use qubit_budget::json::JsonResource;
18use serde_json::Error as JsonError;
19
20use super::DiagnosticPolicy;
21use super::JsonDecodeErrorKind;
22use super::JsonDecodeErrorSource;
23use super::JsonDecodeStage;
24use super::JsonMaterializationErrorReason;
25use super::JsonRootKind;
26use super::JsonSyntaxError;
27use crate::lexical::JsonLexicalFailure;
28
29/// Failure produced by either strict or normalizing JSON decoding.
30///
31/// Internal variants are private so callers branch through stable semantic
32/// accessors rather than depending on scanner, normalizer, or Serde details.
33///
34/// # Type Parameters
35///
36/// * `R` - Resource identity attached to budget failures.
37/// * `Q` - Quantity representation attached to budget failures.
38///
39/// # Examples
40///
41/// ```
42/// use qubit_json::decode::{JsonDecodeError, JsonDecodeErrorKind, JsonDecoder};
43/// use serde_json::Value;
44///
45/// let mut decoder = JsonDecoder::unlimited();
46/// let error: JsonDecodeError = decoder
47/// .decode_str::<Value>("")
48/// .expect_err("empty input must be rejected");
49/// assert_eq!(error.kind(), JsonDecodeErrorKind::InvalidJson);
50/// ```
51#[must_use]
52#[derive(Clone)]
53pub struct JsonDecodeError<R = JsonResource, Q = usize>
54where
55 Q: Copy + fmt::Debug,
56{
57 /// Policy controlling input-derived source retention.
58 diagnostic_policy: DiagnosticPolicy,
59 /// Safe reason for a unique-key intermediate-tree construction failure.
60 materialization_reason: Option<JsonMaterializationErrorReason>,
61 /// Mutually exclusive structured failure.
62 failure: JsonDecodeErrorSource<R, Q>,
63}
64
65impl<R, Q> JsonDecodeError<R, Q>
66where
67 Q: Copy + fmt::Debug,
68{
69 /// Creates a stream-read failure and conditionally retains its source.
70 ///
71 /// # Parameters
72 ///
73 /// * `source` - I/O error produced while reading the input.
74 /// * `raw_input_bytes` - Number of input bytes read before the failure.
75 /// * `diagnostic_policy` - Whether to retain the original error details.
76 ///
77 /// # Returns
78 ///
79 /// A read failure containing the I/O category and, under detailed
80 /// diagnostics, the original error.
81 pub(crate) fn read(source: io::Error, raw_input_bytes: usize, diagnostic_policy: DiagnosticPolicy) -> Self {
82 let kind = source.kind();
83 let source = (diagnostic_policy == DiagnosticPolicy::Detailed).then(|| Arc::new(source));
84 Self {
85 diagnostic_policy,
86 materialization_reason: None,
87 failure: JsonDecodeErrorSource::Read {
88 raw_input_bytes,
89 kind,
90 source,
91 },
92 }
93 }
94
95 /// Creates a measured-budget failure at a semantic stage.
96 ///
97 /// # Parameters
98 ///
99 /// * `source` - Resource measurement that exceeded its budget.
100 /// * `stage` - Decode stage where the budget was exceeded.
101 /// * `raw_input_bytes` - Original input length in bytes.
102 /// * `normalized_input_bytes` - Normalized input length, when available.
103 /// * `diagnostic_policy` - Policy controlling retained input details.
104 ///
105 /// # Returns
106 ///
107 /// A budget failure retaining the measured resource error and decode
108 /// stage.
109 pub(crate) const fn budget(
110 source: MeasuredBudgetError<R, Q>,
111 stage: JsonDecodeStage,
112 raw_input_bytes: usize,
113 normalized_input_bytes: Option<usize>,
114 diagnostic_policy: DiagnosticPolicy,
115 ) -> Self {
116 Self {
117 diagnostic_policy,
118 materialization_reason: None,
119 failure: JsonDecodeErrorSource::Budget {
120 stage,
121 raw_input_bytes,
122 normalized_input_bytes,
123 source,
124 },
125 }
126 }
127
128 /// Creates an empty-input failure at a semantic stage.
129 ///
130 /// # Parameters
131 ///
132 /// * `stage` - Decode stage where the empty input was detected.
133 /// * `raw_input_bytes` - Original input length in bytes.
134 /// * `normalized_input_bytes` - Normalized input length, when available.
135 /// * `diagnostic_policy` - Policy controlling retained input details.
136 ///
137 /// # Returns
138 ///
139 /// An empty-input failure retaining the stage and input lengths.
140 pub(crate) const fn empty_input(
141 stage: JsonDecodeStage,
142 raw_input_bytes: usize,
143 normalized_input_bytes: Option<usize>,
144 diagnostic_policy: DiagnosticPolicy,
145 ) -> Self {
146 Self {
147 diagnostic_policy,
148 materialization_reason: None,
149 failure: JsonDecodeErrorSource::EmptyInput {
150 stage,
151 raw_input_bytes,
152 normalized_input_bytes,
153 },
154 }
155 }
156
157 /// Creates an invalid-UTF-8 failure and conditionally retains its source.
158 ///
159 /// # Parameters
160 ///
161 /// * `source` - UTF-8 validation failure for the input bytes.
162 /// * `raw_input_bytes` - Original input length in bytes.
163 /// * `diagnostic_policy` - Whether to retain the original error details.
164 ///
165 /// # Returns
166 ///
167 /// An invalid-UTF-8 failure retaining the invalid byte position and, under
168 /// detailed diagnostics, the original validation error.
169 pub(crate) fn invalid_utf8(
170 source: std::str::Utf8Error,
171 raw_input_bytes: usize,
172 diagnostic_policy: DiagnosticPolicy,
173 ) -> Self {
174 let valid_up_to = source.valid_up_to();
175 let error_len = source.error_len();
176 let source = (diagnostic_policy == DiagnosticPolicy::Detailed).then_some(source);
177 Self {
178 diagnostic_policy,
179 materialization_reason: None,
180 failure: JsonDecodeErrorSource::InvalidUtf8 {
181 raw_input_bytes,
182 valid_up_to,
183 error_len,
184 source,
185 },
186 }
187 }
188
189 /// Creates a stable invalid-JSON failure from lexical admission.
190 ///
191 /// # Parameters
192 ///
193 /// * `syntax_source` - Lexical failure with stable syntax details.
194 /// * `detailed_source` - Optional parser error retained for detailed
195 /// diagnostics.
196 /// * `raw_input_bytes` - Original input length in bytes.
197 /// * `normalized_input_bytes` - Normalized input length, when available.
198 /// * `diagnostic_policy` - Policy controlling retained input details.
199 ///
200 /// # Returns
201 ///
202 /// An invalid-JSON failure containing stable syntax details and the
203 /// optional detailed parser source.
204 pub(crate) fn invalid_json(
205 syntax_source: JsonLexicalFailure,
206 detailed_source: Option<Arc<dyn Error + Send + Sync>>,
207 raw_input_bytes: usize,
208 normalized_input_bytes: Option<usize>,
209 diagnostic_policy: DiagnosticPolicy,
210 ) -> Self {
211 Self {
212 diagnostic_policy,
213 materialization_reason: None,
214 failure: JsonDecodeErrorSource::InvalidJson {
215 raw_input_bytes,
216 normalized_input_bytes,
217 syntax: JsonSyntaxError::from_lexical(syntax_source),
218 source: detailed_source,
219 },
220 }
221 }
222
223 /// Creates an unexpected-top-level failure.
224 ///
225 /// # Parameters
226 ///
227 /// * `expected` - Required top-level JSON kind.
228 /// * `actual` - Observed top-level JSON kind.
229 /// * `raw_input_bytes` - Original input length in bytes.
230 /// * `normalized_input_bytes` - Normalized input length, when available.
231 /// * `diagnostic_policy` - Policy controlling retained input details.
232 ///
233 /// # Returns
234 ///
235 /// A top-level-kind failure retaining the expected and observed kinds.
236 pub(crate) const fn unexpected_top_level(
237 expected: JsonRootKind,
238 actual: JsonRootKind,
239 raw_input_bytes: usize,
240 normalized_input_bytes: Option<usize>,
241 diagnostic_policy: DiagnosticPolicy,
242 ) -> Self {
243 Self {
244 diagnostic_policy,
245 materialization_reason: None,
246 failure: JsonDecodeErrorSource::UnexpectedTopLevel {
247 raw_input_bytes,
248 normalized_input_bytes,
249 expected,
250 actual,
251 },
252 }
253 }
254
255 /// Creates a target-deserialization failure and conditionally retains its
256 /// input-derived source.
257 ///
258 /// # Parameters
259 ///
260 /// * `source` - Serde error produced while materializing the target value.
261 /// * `raw_input_bytes` - Original input length in bytes.
262 /// * `normalized_input_bytes` - Normalized input length, when available.
263 /// * `diagnostic_policy` - Whether to retain the original error details.
264 ///
265 /// # Returns
266 ///
267 /// A deserialization failure retaining the source location and, under
268 /// detailed diagnostics, the original Serde error.
269 pub(crate) fn deserialize(
270 source: JsonError,
271 raw_input_bytes: usize,
272 normalized_input_bytes: Option<usize>,
273 diagnostic_policy: DiagnosticPolicy,
274 ) -> Self {
275 let line = source.line();
276 let column = source.column();
277 let source =
278 (diagnostic_policy == DiagnosticPolicy::Detailed).then(|| Arc::new(source) as Arc<dyn Error + Send + Sync>);
279 Self {
280 diagnostic_policy,
281 materialization_reason: None,
282 failure: JsonDecodeErrorSource::Deserialize {
283 raw_input_bytes,
284 normalized_input_bytes,
285 line,
286 column,
287 source,
288 },
289 }
290 }
291
292 /// Creates a unique-key materialization failure with a safe subreason.
293 pub(crate) fn materialization(
294 source: JsonError,
295 reason: JsonMaterializationErrorReason,
296 raw_input_bytes: usize,
297 diagnostic_policy: DiagnosticPolicy,
298 ) -> Self {
299 let mut error = Self::deserialize(source, raw_input_bytes, None, diagnostic_policy);
300 error.materialization_reason = Some(reason);
301 error
302 }
303
304 /// Returns the stable failure category.
305 ///
306 /// # Returns
307 ///
308 /// The category describing which kind of decode operation failed.
309 #[must_use = "inspect or propagate the underlying decode error source"]
310 #[inline(always)]
311 pub const fn kind(&self) -> JsonDecodeErrorKind {
312 match self.failure {
313 JsonDecodeErrorSource::Read { .. } => JsonDecodeErrorKind::Read,
314 JsonDecodeErrorSource::Budget { .. } => JsonDecodeErrorKind::Budget,
315 JsonDecodeErrorSource::EmptyInput { .. } => JsonDecodeErrorKind::EmptyInput,
316 JsonDecodeErrorSource::InvalidUtf8 { .. } => JsonDecodeErrorKind::InvalidUtf8,
317 JsonDecodeErrorSource::InvalidJson { .. } => JsonDecodeErrorKind::InvalidJson,
318 JsonDecodeErrorSource::UnexpectedTopLevel { .. } => JsonDecodeErrorKind::UnexpectedTopLevel,
319 JsonDecodeErrorSource::Deserialize { .. } => JsonDecodeErrorKind::Deserialize,
320 }
321 }
322
323 /// Returns the safe reason for a unique-key intermediate-tree failure.
324 ///
325 /// Returns `Some(reason)` when unique-key tree construction failed because
326 /// of a duplicate key or a defensive unrepresentable admitted number.
327 /// The legacy depth-limit reason remains for source compatibility, but the
328 /// shared builder has no fixed container cap and configured depth failures
329 /// are reported as budget errors. Returns `None` for lexical, input,
330 /// budget, and target-deserialization
331 /// failures. The reason remains available under redacted diagnostics and
332 /// contains no input-derived text. Read it before consuming the error with
333 /// [`Self::into_source`].
334 ///
335 /// # Examples
336 ///
337 /// ```
338 /// use qubit_json::decode::{JsonDecoder, JsonMaterializationErrorReason};
339 /// use serde_json::Value;
340 ///
341 /// let mut decoder = JsonDecoder::unlimited();
342 /// let error = decoder
343 /// .decode_unique_str::<Value>(r#"{"id":1,"id":2}"#)
344 /// .expect_err("duplicate keys must be rejected");
345 /// assert_eq!(
346 /// error.materialization_error_reason(),
347 /// Some(JsonMaterializationErrorReason::DuplicateKey),
348 /// );
349 /// ```
350 #[inline(always)]
351 pub const fn materialization_error_reason(&self) -> Option<JsonMaterializationErrorReason> {
352 self.materialization_reason
353 }
354
355 /// Returns the semantic stage that produced the failure.
356 ///
357 /// # Returns
358 ///
359 /// The pipeline stage at which the failure was recorded.
360 #[must_use]
361 #[inline(always)]
362 pub const fn stage(&self) -> JsonDecodeStage {
363 match self.failure {
364 JsonDecodeErrorSource::Budget { stage, .. } | JsonDecodeErrorSource::EmptyInput { stage, .. } => stage,
365 JsonDecodeErrorSource::Read { .. } => JsonDecodeStage::Input,
366 JsonDecodeErrorSource::InvalidUtf8 { .. } => JsonDecodeStage::DecodeText,
367 JsonDecodeErrorSource::InvalidJson { .. } => JsonDecodeStage::Parse,
368 JsonDecodeErrorSource::UnexpectedTopLevel { .. } => JsonDecodeStage::TopLevelCheck,
369 JsonDecodeErrorSource::Deserialize { .. } => JsonDecodeStage::Deserialize,
370 }
371 }
372
373 /// Returns the diagnostic policy applied while constructing this error.
374 ///
375 /// # Returns
376 ///
377 /// The policy that determines whether input-derived details are retained.
378 #[must_use]
379 #[inline(always)]
380 pub const fn diagnostic_policy(&self) -> DiagnosticPolicy {
381 self.diagnostic_policy
382 }
383
384 /// Returns the raw input length observed at the decoding boundary.
385 ///
386 /// For slice-based entry points, this is the complete input slice length,
387 /// including when input admission rejects the slice before processing it.
388 /// For `decode_input`, this is the length of the prefix read and retained
389 /// before the error; a byte consumed only to probe past the input budget is
390 /// excluded. The value therefore does not universally represent bytes
391 /// charged to the budget or the session's cumulative input usage.
392 ///
393 /// # Returns
394 ///
395 /// The raw input length observed for the input that caused this error.
396 #[must_use]
397 #[inline(always)]
398 pub const fn raw_input_bytes(&self) -> usize {
399 match self.failure {
400 JsonDecodeErrorSource::Read { raw_input_bytes, .. }
401 | JsonDecodeErrorSource::Budget { raw_input_bytes, .. }
402 | JsonDecodeErrorSource::EmptyInput { raw_input_bytes, .. }
403 | JsonDecodeErrorSource::InvalidUtf8 { raw_input_bytes, .. }
404 | JsonDecodeErrorSource::InvalidJson { raw_input_bytes, .. }
405 | JsonDecodeErrorSource::UnexpectedTopLevel { raw_input_bytes, .. }
406 | JsonDecodeErrorSource::Deserialize { raw_input_bytes, .. } => raw_input_bytes,
407 }
408 }
409
410 /// Returns the predicted normalized text length when it is available.
411 ///
412 /// A value may be present even when a budget rejected the input before
413 /// normalized text was allocated or rewritten. This reports a target
414 /// length, not proof that a normalized document was produced or charged.
415 ///
416 /// # Returns
417 ///
418 /// `Some(length)` when normalization determined a target length, or
419 /// `None` when the failure occurred before that length was available.
420 #[inline(always)]
421 pub const fn normalized_input_bytes(&self) -> Option<usize> {
422 match self.failure {
423 JsonDecodeErrorSource::Budget {
424 normalized_input_bytes, ..
425 }
426 | JsonDecodeErrorSource::EmptyInput {
427 normalized_input_bytes, ..
428 }
429 | JsonDecodeErrorSource::InvalidJson {
430 normalized_input_bytes, ..
431 }
432 | JsonDecodeErrorSource::UnexpectedTopLevel {
433 normalized_input_bytes, ..
434 }
435 | JsonDecodeErrorSource::Deserialize {
436 normalized_input_bytes, ..
437 } => normalized_input_bytes,
438 JsonDecodeErrorSource::InvalidUtf8 { .. } | JsonDecodeErrorSource::Read { .. } => None,
439 }
440 }
441
442 /// Returns the standard I/O error category for a read failure.
443 ///
444 /// # Returns
445 ///
446 /// `Some(kind)` for a read failure, or `None` for other failure kinds.
447 #[inline(always)]
448 pub const fn read_error_kind(&self) -> Option<io::ErrorKind> {
449 match self.failure {
450 JsonDecodeErrorSource::Read { kind, .. } => Some(kind),
451 _ => None,
452 }
453 }
454
455 /// Returns the retained original I/O error for detailed read diagnostics.
456 ///
457 /// # Returns
458 ///
459 /// The original read error when detailed diagnostics retained it, or `None`
460 /// when the error is not a read failure or its source was not retained.
461 #[inline(always)]
462 pub fn read_error(&self) -> Option<&io::Error> {
463 match &self.failure {
464 JsonDecodeErrorSource::Read {
465 source: Some(source), ..
466 } => Some(source.as_ref()),
467 _ => None,
468 }
469 }
470
471 /// Returns the one-based error line when available.
472 ///
473 /// Invalid-JSON coordinates come from the lexical scanner. Deserialize
474 /// coordinates preserve `serde_json`'s one-based line values, including
475 /// unique-key materialization errors. For a deserialize error, a backend
476 /// line of zero is reported as `None`.
477 ///
478 /// # Returns
479 ///
480 /// `Some(line)` for failures with source coordinates, otherwise `None`.
481 #[inline(always)]
482 pub const fn line(&self) -> Option<usize> {
483 match &self.failure {
484 JsonDecodeErrorSource::InvalidJson { syntax, .. } => Some(syntax.line()),
485 JsonDecodeErrorSource::Deserialize { line, .. } if *line > 0 => Some(*line),
486 _ => None,
487 }
488 }
489
490 /// Returns the one-based error column when available.
491 ///
492 /// Invalid-JSON columns count UTF-8 characters, while deserialize columns
493 /// preserve `serde_json`'s byte-based column. Unique-key materialization
494 /// columns count bytes before the offending token, with only LF starting a
495 /// new line. For a deserialize error, a backend column of zero is reported
496 /// as `None`.
497 ///
498 /// # Returns
499 ///
500 /// `Some(column)` for failures with source coordinates, otherwise `None`.
501 #[inline(always)]
502 pub const fn column(&self) -> Option<usize> {
503 match &self.failure {
504 JsonDecodeErrorSource::InvalidJson { syntax, .. } => Some(syntax.column()),
505 JsonDecodeErrorSource::Deserialize { column, .. } if *column > 0 => Some(*column),
506 _ => None,
507 }
508 }
509
510 /// Returns the structured syntax failure for invalid JSON.
511 ///
512 /// # Returns
513 ///
514 /// A borrowed syntax error when parsing failed, otherwise `None`.
515 #[inline(always)]
516 pub const fn syntax_error(&self) -> Option<&JsonSyntaxError> {
517 match &self.failure {
518 JsonDecodeErrorSource::InvalidJson { syntax, .. } => Some(syntax),
519 _ => None,
520 }
521 }
522
523 /// Returns the complete measured-budget failure when present.
524 ///
525 /// # Returns
526 ///
527 /// A borrowed budget error when resource accounting rejected the input,
528 /// otherwise `None`.
529 #[inline(always)]
530 pub const fn budget_error(&self) -> Option<&MeasuredBudgetError<R, Q>> {
531 match &self.failure {
532 JsonDecodeErrorSource::Budget { source, .. } => Some(source),
533 _ => None,
534 }
535 }
536
537 /// Returns the valid UTF-8 prefix length for invalid byte input.
538 ///
539 /// # Returns
540 ///
541 /// `Some(bytes)` for an invalid UTF-8 failure, or `None` for other failure
542 /// kinds.
543 #[inline(always)]
544 pub const fn utf8_valid_up_to(&self) -> Option<usize> {
545 match self.failure {
546 JsonDecodeErrorSource::InvalidUtf8 { valid_up_to, .. } => Some(valid_up_to),
547 _ => None,
548 }
549 }
550
551 /// Returns the invalid UTF-8 sequence length when known.
552 ///
553 /// # Returns
554 ///
555 /// The length of the invalid sequence when the decoder can determine it,
556 /// otherwise `None`.
557 #[inline(always)]
558 pub const fn utf8_error_len(&self) -> Option<usize> {
559 match self.failure {
560 JsonDecodeErrorSource::InvalidUtf8 { error_len, .. } => error_len,
561 _ => None,
562 }
563 }
564
565 /// Returns the expected top-level kind for a constrained decode failure.
566 ///
567 /// # Returns
568 ///
569 /// The required root kind when a constrained operation failed, otherwise
570 /// `None`.
571 #[inline(always)]
572 pub const fn expected_top_level(&self) -> Option<JsonRootKind> {
573 match self.failure {
574 JsonDecodeErrorSource::UnexpectedTopLevel { expected, .. } => Some(expected),
575 _ => None,
576 }
577 }
578
579 /// Returns the observed top-level kind for a constrained decode failure.
580 ///
581 /// # Returns
582 ///
583 /// The root kind observed in the valid document, otherwise `None`.
584 #[inline(always)]
585 pub const fn actual_top_level(&self) -> Option<JsonRootKind> {
586 match self.failure {
587 JsonDecodeErrorSource::UnexpectedTopLevel { actual, .. } => Some(actual),
588 _ => None,
589 }
590 }
591
592 /// Consumes this error and returns its owned operation-level source.
593 ///
594 /// The materialization reason returned by
595 /// [`Self::materialization_error_reason`] is stored separately and is not
596 /// included in the returned source. Read it before consuming this error.
597 /// Input-derived third-party sources remain present only when the decoder
598 /// used [`DiagnosticPolicy::Detailed`].
599 ///
600 /// # Returns
601 ///
602 /// The owned budget, input, syntax, top-level, or deserialization source
603 /// retained by this error. Any materialization reason is not returned.
604 ///
605 /// # Examples
606 ///
607 /// ```
608 /// use qubit_json::decode::{
609 /// JsonDecodeErrorSource, JsonDecoder, JsonMaterializationErrorReason,
610 /// };
611 ///
612 /// let error = JsonDecoder::unlimited()
613 /// .decode_value_str(r#"{"id":1,"id":2}"#)
614 /// .expect_err("duplicate keys must be rejected");
615 /// let reason = error.materialization_error_reason();
616 /// let source = error.into_source();
617 ///
618 /// assert_eq!(reason, Some(JsonMaterializationErrorReason::DuplicateKey));
619 /// assert!(matches!(source, JsonDecodeErrorSource::Deserialize { .. }));
620 /// ```
621 #[inline(always)]
622 pub fn into_source(self) -> JsonDecodeErrorSource<R, Q> {
623 self.failure
624 }
625}
626
627impl<R, Q> fmt::Debug for JsonDecodeError<R, Q>
628where
629 R: fmt::Debug,
630 Q: Copy + fmt::Debug,
631{
632 /// Formats structured diagnostics retained under the active policy.
633 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
634 formatter
635 .debug_struct("JsonDecodeError")
636 .field("diagnostic_policy", &self.diagnostic_policy)
637 .field("materialization_reason", &self.materialization_reason)
638 .field("failure", &self.failure)
639 .finish()
640 }
641}
642
643impl<R, Q> fmt::Display for JsonDecodeError<R, Q>
644where
645 R: fmt::Debug,
646 Q: ResourceQuantity,
647{
648 /// Formats a privacy-safe or detailed message according to the policy.
649 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
650 match &self.failure {
651 JsonDecodeErrorSource::Read { kind, .. } => {
652 write!(formatter, "JSON input read failed ({kind:?})")
653 }
654 JsonDecodeErrorSource::Budget { source, .. } => {
655 write!(formatter, "JSON resource budget rejected input: {source}")
656 }
657 JsonDecodeErrorSource::EmptyInput { .. } => formatter.write_str("JSON input is empty after normalization"),
658 JsonDecodeErrorSource::InvalidUtf8 { source, .. } => match source {
659 Some(source) => write!(formatter, "Failed to decode JSON input as UTF-8: {source}"),
660 None => formatter.write_str("Failed to decode JSON input as UTF-8"),
661 },
662 JsonDecodeErrorSource::InvalidJson { syntax, source, .. } => match source {
663 Some(source) => {
664 write!(formatter, "Failed to parse JSON: {source}")
665 }
666 None => write!(formatter, "Failed to parse JSON: {syntax}"),
667 },
668 JsonDecodeErrorSource::UnexpectedTopLevel { expected, actual, .. } => {
669 write!(
670 formatter,
671 "Unexpected JSON top-level type: expected {expected}, got {actual}"
672 )
673 }
674 JsonDecodeErrorSource::Deserialize {
675 normalized_input_bytes,
676 line,
677 column,
678 source,
679 ..
680 } => match source {
681 Some(source) => write!(formatter, "Failed to deserialize JSON value: {source}"),
682 None if *line == 0 || *column == 0 => formatter.write_str("Failed to deserialize JSON value"),
683 None if normalized_input_bytes.is_some() => write!(
684 formatter,
685 "Failed to deserialize JSON value at normalized line {line} column {column}"
686 ),
687 None => write!(
688 formatter,
689 "Failed to deserialize JSON value at line {line} column {column}"
690 ),
691 },
692 }
693 }
694}
695
696impl<R, Q> Error for JsonDecodeError<R, Q>
697where
698 R: fmt::Debug + 'static,
699 Q: ResourceQuantity + 'static,
700{
701 /// Returns budget sources unconditionally and input-derived sources only
702 /// when detailed diagnostics retained them.
703 fn source(&self) -> Option<&(dyn Error + 'static)> {
704 match &self.failure {
705 JsonDecodeErrorSource::Read {
706 source: Some(source), ..
707 } => Some(source.as_ref()),
708 JsonDecodeErrorSource::Budget { source, .. } => Some(source),
709 JsonDecodeErrorSource::InvalidUtf8 {
710 source: Some(source), ..
711 } => Some(source),
712 JsonDecodeErrorSource::InvalidJson {
713 source: Some(source), ..
714 } => Some(source.as_ref()),
715 JsonDecodeErrorSource::Deserialize {
716 source: Some(source), ..
717 } => Some(source.as_ref()),
718 _ => None,
719 }
720 }
721}