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