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