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}