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(expected: JsonTopLevelKind, actual: JsonTopLevelKind) -> Self {
136        Self {
137            kind: JsonDecodeErrorKind::UnexpectedTopLevel,
138            stage: JsonDecodeStage::TopLevelCheck,
139            message: format!("Unexpected JSON top-level type: expected {expected}, got {actual}"),
140            expected_top_level: Some(expected),
141            actual_top_level: Some(actual),
142            line: None,
143            column: None,
144            input_bytes: None,
145            max_input_bytes: None,
146            source: None,
147        }
148    }
149
150    /// Creates an error describing a type deserialization failure reported by
151    /// `serde_json`.
152    #[inline]
153    pub(crate) fn deserialize(error: serde_json::Error, input_bytes: Option<usize>) -> Self {
154        let line = error.line();
155        let column = error.column();
156        let message = format!("Failed to deserialize JSON value: {error}");
157        Self {
158            kind: JsonDecodeErrorKind::Deserialize,
159            stage: JsonDecodeStage::Deserialize,
160            message,
161            expected_top_level: None,
162            actual_top_level: None,
163            line: (line > 0).then_some(line),
164            column: (column > 0).then_some(column),
165            input_bytes,
166            max_input_bytes: None,
167            source: Some(Arc::new(error)),
168        }
169    }
170}
171
172impl PartialEq for JsonDecodeError {
173    fn eq(&self, other: &Self) -> bool {
174        self.kind == other.kind
175            && self.stage == other.stage
176            && self.message == other.message
177            && self.expected_top_level == other.expected_top_level
178            && self.actual_top_level == other.actual_top_level
179            && self.line == other.line
180            && self.column == other.column
181            && self.input_bytes == other.input_bytes
182            && self.max_input_bytes == other.max_input_bytes
183    }
184}
185
186impl Eq for JsonDecodeError {}
187
188impl fmt::Display for JsonDecodeError {
189    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
190        match self.kind {
191            JsonDecodeErrorKind::InputTooLarge => f.write_str(&self.message),
192            JsonDecodeErrorKind::EmptyInput => f.write_str(&self.message),
193            JsonDecodeErrorKind::UnexpectedTopLevel => f.write_str(&self.message),
194            JsonDecodeErrorKind::InvalidJson | JsonDecodeErrorKind::Deserialize => match (self.line, self.column) {
195                (Some(line), Some(column)) => {
196                    write!(f, "{} (line {}, column {})", self.message, line, column)
197                }
198                _ => f.write_str(&self.message),
199            },
200        }
201    }
202}
203
204impl std::error::Error for JsonDecodeError {
205    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
206        self.source
207            .as_deref()
208            .map(|error| error as &(dyn std::error::Error + 'static))
209    }
210}