Skip to main content

qubit_json/decode/
json_decode_error_source.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 owned sources retained by JSON decoding errors.
9
10use std::error::Error;
11use std::fmt;
12use std::io;
13use std::sync::Arc;
14
15use qubit_budget::MeasuredBudgetError;
16
17use super::JsonDecodeStage;
18use super::JsonRootKind;
19use super::JsonSyntaxError;
20
21/// An owned semantic source extracted from a JSON decoding failure.
22///
23/// This enum contains the owned operation-level source for one failure. It lets
24/// downstream adapters move that source into their own models with one
25/// exhaustive `match`, without pairing [`super::JsonDecodeError::kind`] with
26/// fallible source accessors. A materialization reason is stored separately:
27/// read it with [`super::JsonDecodeError::materialization_error_reason`] before
28/// consuming the error with [`super::JsonDecodeError::into_source`]. Parser and
29/// Serde sources remain present only when decoding used
30/// [`super::DiagnosticPolicy::Detailed`].
31///
32/// # Type Parameters
33///
34/// * `R` - Resource identity attached to budget failures.
35/// * `Q` - Quantity representation attached to budget failures.
36///
37/// # Examples
38///
39/// ```
40/// use qubit_json::decode::{JsonDecodeErrorSource, JsonDecoder};
41///
42/// let mut decoder = JsonDecoder::unlimited();
43/// let error = decoder.validate_str("{").expect_err("invalid JSON must fail");
44/// match error.into_source() {
45///     JsonDecodeErrorSource::InvalidJson { syntax, .. } => {
46///         assert_eq!(syntax.offset(), 1);
47///     }
48///     source => panic!("unexpected decoding source: {source:?}"),
49/// }
50/// ```
51#[must_use]
52#[derive(Debug, Clone)]
53pub enum JsonDecodeErrorSource<R, Q = usize>
54where
55    Q: Copy + fmt::Debug,
56{
57    /// Reading input failed before text decoding began.
58    Read {
59        /// Number of bytes observed as read before the failure; this is not
60        /// necessarily the amount charged by every input path.
61        raw_input_bytes: usize,
62        /// Stable standard I/O failure category.
63        kind: io::ErrorKind,
64        /// Original error retained only under detailed diagnostics.
65        source: Option<Arc<io::Error>>,
66    },
67    /// A resource measurement was rejected.
68    Budget {
69        /// Semantic stage where the measurement was rejected.
70        stage: JsonDecodeStage,
71        /// Original input length observed at the decoding boundary; not
72        /// necessarily the amount charged to an input-byte budget.
73        raw_input_bytes: usize,
74        /// Predicted normalized target length when available; it does not
75        /// prove that normalized text was allocated or rewritten.
76        normalized_input_bytes: Option<usize>,
77        /// Complete measured-budget failure.
78        source: MeasuredBudgetError<R, Q>,
79    },
80    /// Input was empty at a public boundary.
81    EmptyInput {
82        /// Semantic stage where emptiness was detected.
83        stage: JsonDecodeStage,
84        /// Original input length observed at the decoding boundary.
85        raw_input_bytes: usize,
86        /// Predicted normalized target length when available; it does not
87        /// prove that normalized text was produced.
88        normalized_input_bytes: Option<usize>,
89    },
90    /// Raw bytes were not valid UTF-8.
91    InvalidUtf8 {
92        /// Original input length observed at the decoding boundary.
93        raw_input_bytes: usize,
94        /// Valid prefix length reported by UTF-8 validation.
95        valid_up_to: usize,
96        /// Invalid sequence length when known.
97        error_len: Option<usize>,
98        /// Detailed source retained only under detailed diagnostics.
99        source: Option<std::str::Utf8Error>,
100    },
101    /// Text was not one valid JSON document.
102    InvalidJson {
103        /// Original input length observed at the decoding boundary.
104        raw_input_bytes: usize,
105        /// Predicted normalized target length when available; it does not
106        /// prove that normalized text was allocated or rewritten.
107        normalized_input_bytes: Option<usize>,
108        /// Stable scanner reason and source coordinates.
109        syntax: JsonSyntaxError,
110        /// Detailed parser source retained only under detailed diagnostics.
111        source: Option<Arc<dyn Error + Send + Sync>>,
112    },
113    /// A valid document had an unexpected top-level kind.
114    UnexpectedTopLevel {
115        /// Original input length observed at the decoding boundary.
116        raw_input_bytes: usize,
117        /// Predicted normalized target length when available; it does not
118        /// prove that normalized text was produced.
119        normalized_input_bytes: Option<usize>,
120        /// Required top-level kind.
121        expected: JsonRootKind,
122        /// Observed top-level kind.
123        actual: JsonRootKind,
124    },
125    /// A valid admitted document could not materialize the target type.
126    Deserialize {
127        /// Original input length observed at the decoding boundary.
128        raw_input_bytes: usize,
129        /// Predicted normalized target length when available; it does not
130        /// prove that normalized text was produced.
131        normalized_input_bytes: Option<usize>,
132        /// One-based Serde line, or zero when unavailable.
133        line: usize,
134        /// One-based Serde column, or zero when unavailable.
135        column: usize,
136        /// Detailed source retained only under detailed diagnostics.
137        source: Option<Arc<dyn Error + Send + Sync>>,
138    },
139}