Skip to main content

qubit_json/
json_decode_error.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2026 Haixing Hu.
4 *
5 *    SPDX-License-Identifier: Apache-2.0
6 *
7 *    Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10//! Defines the [`JsonDecodeError`] type used by the public decoder API.
11//!
12
13use std::{
14    fmt,
15    sync::Arc,
16};
17
18use crate::{
19    JsonDecodeErrorKind,
20    JsonDecodeStage,
21    JsonTopLevelKind,
22};
23
24/// Error returned when lenient JSON decoding fails.
25///
26/// This value captures both a stable category in [`JsonDecodeErrorKind`] and
27/// human-readable context that can be logged or surfaced to the caller.
28#[non_exhaustive]
29#[derive(Debug, Clone)]
30pub struct JsonDecodeError {
31    /// Identifies the stable category of this decoding failure.
32    ///
33    /// Callers should match on this field when they need programmatic handling
34    /// that is independent from localized or parser-specific text.
35    pub kind: JsonDecodeErrorKind,
36    /// Identifies which decode stage produced this error.
37    pub stage: JsonDecodeStage,
38    /// Stores a human-readable summary of the decoding failure.
39    ///
40    /// The message is intended for diagnostics and normally includes the
41    /// relevant parsing or deserialization context.
42    pub message: String,
43    /// Stores the top-level JSON kind required by the caller, when applicable.
44    ///
45    /// This field is only populated for errors raised by constrained decoding
46    /// methods such as `decode_object()` and `decode_array()`.
47    pub expected_top_level: Option<JsonTopLevelKind>,
48    /// Stores the top-level JSON kind that was actually parsed, when known.
49    ///
50    /// This field is only populated together with `expected_top_level` for
51    /// top-level contract mismatches.
52    pub actual_top_level: Option<JsonTopLevelKind>,
53    /// Stores the one-based line reported by `serde_json`, when available.
54    ///
55    /// This field is primarily useful for invalid JSON syntax and
56    /// deserialization failures that can be mapped back to a parser location.
57    pub line: Option<usize>,
58    /// Stores the one-based column reported by `serde_json`, when available.
59    ///
60    /// Like `line`, this field is only populated when the lower-level parser
61    /// or deserializer reports a concrete source position.
62    pub column: Option<usize>,
63    /// Stores the input byte length associated with the failure, when known.
64    pub input_bytes: Option<usize>,
65    /// Stores the configured maximum input byte length, when relevant.
66    pub max_input_bytes: Option<usize>,
67    /// Stores the original parser or deserializer error when one exists.
68    source: Option<Arc<serde_json::Error>>,
69}
70
71impl JsonDecodeError {
72    /// Creates an error indicating that the raw input size exceeds a
73    /// configured upper bound.
74    #[inline]
75    pub(crate) fn input_too_large(actual_bytes: usize, max_bytes: usize) -> Self {
76        Self {
77            kind: JsonDecodeErrorKind::InputTooLarge,
78            stage: JsonDecodeStage::Normalize,
79            message: format!(
80                "JSON input is too large: {} bytes exceed configured limit {} bytes",
81                actual_bytes, max_bytes
82            ),
83            expected_top_level: None,
84            actual_top_level: None,
85            line: None,
86            column: None,
87            input_bytes: Some(actual_bytes),
88            max_input_bytes: Some(max_bytes),
89            source: None,
90        }
91    }
92
93    /// Creates an error indicating that the input became empty after
94    /// normalization.
95    #[inline]
96    pub(crate) fn empty_input() -> Self {
97        Self {
98            kind: JsonDecodeErrorKind::EmptyInput,
99            stage: JsonDecodeStage::Normalize,
100            message: "JSON input is empty after normalization".to_string(),
101            expected_top_level: None,
102            actual_top_level: None,
103            line: None,
104            column: None,
105            input_bytes: None,
106            max_input_bytes: None,
107            source: None,
108        }
109    }
110
111    /// Creates an error describing invalid JSON syntax reported by
112    /// `serde_json`.
113    #[inline]
114    pub(crate) fn invalid_json(error: serde_json::Error, input_bytes: Option<usize>) -> Self {
115        let line = error.line();
116        let column = error.column();
117        let message = format!("Failed to parse JSON: {error}");
118        Self {
119            kind: JsonDecodeErrorKind::InvalidJson,
120            stage: JsonDecodeStage::Parse,
121            message,
122            expected_top_level: None,
123            actual_top_level: None,
124            line: (line > 0).then_some(line),
125            column: (column > 0).then_some(column),
126            input_bytes,
127            max_input_bytes: None,
128            source: Some(Arc::new(error)),
129        }
130    }
131
132    /// Creates an error describing a mismatch between expected and actual
133    /// top-level JSON kinds.
134    #[inline]
135    pub(crate) fn unexpected_top_level(
136        expected: JsonTopLevelKind,
137        actual: JsonTopLevelKind,
138    ) -> Self {
139        Self {
140            kind: JsonDecodeErrorKind::UnexpectedTopLevel,
141            stage: JsonDecodeStage::TopLevelCheck,
142            message: format!("Unexpected JSON top-level type: expected {expected}, got {actual}"),
143            expected_top_level: Some(expected),
144            actual_top_level: Some(actual),
145            line: None,
146            column: None,
147            input_bytes: None,
148            max_input_bytes: None,
149            source: None,
150        }
151    }
152
153    /// Creates an error describing a type deserialization failure reported by
154    /// `serde_json`.
155    #[inline]
156    pub(crate) fn deserialize(error: serde_json::Error, input_bytes: Option<usize>) -> Self {
157        let line = error.line();
158        let column = error.column();
159        let message = format!("Failed to deserialize JSON value: {error}");
160        Self {
161            kind: JsonDecodeErrorKind::Deserialize,
162            stage: JsonDecodeStage::Deserialize,
163            message,
164            expected_top_level: None,
165            actual_top_level: None,
166            line: (line > 0).then_some(line),
167            column: (column > 0).then_some(column),
168            input_bytes,
169            max_input_bytes: None,
170            source: Some(Arc::new(error)),
171        }
172    }
173}
174
175impl PartialEq for JsonDecodeError {
176    fn eq(&self, other: &Self) -> bool {
177        self.kind == other.kind
178            && self.stage == other.stage
179            && self.message == other.message
180            && self.expected_top_level == other.expected_top_level
181            && self.actual_top_level == other.actual_top_level
182            && self.line == other.line
183            && self.column == other.column
184            && self.input_bytes == other.input_bytes
185            && self.max_input_bytes == other.max_input_bytes
186    }
187}
188
189impl Eq for JsonDecodeError {}
190
191impl fmt::Display for JsonDecodeError {
192    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
193        match self.kind {
194            JsonDecodeErrorKind::InputTooLarge => f.write_str(&self.message),
195            JsonDecodeErrorKind::EmptyInput => f.write_str(&self.message),
196            JsonDecodeErrorKind::UnexpectedTopLevel => f.write_str(&self.message),
197            JsonDecodeErrorKind::InvalidJson | JsonDecodeErrorKind::Deserialize => {
198                match (self.line, self.column) {
199                    (Some(line), Some(column)) => {
200                        write!(f, "{} (line {}, column {})", self.message, line, column)
201                    }
202                    _ => f.write_str(&self.message),
203                }
204            }
205        }
206    }
207}
208
209impl std::error::Error for JsonDecodeError {
210    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
211        self.source
212            .as_deref()
213            .map(|error| error as &(dyn std::error::Error + 'static))
214    }
215}