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}