Skip to main content

reliakit_json/
error.rs

1//! Error types for parsing and serialization.
2
3use alloc::string::String;
4use alloc::vec::Vec;
5use core::fmt;
6
7/// A resource limit that was exceeded while parsing.
8///
9/// `#[non_exhaustive]`: new limit kinds may be added in a future release.
10#[non_exhaustive]
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub enum JsonLimitKind {
13    /// `max_input_bytes` exceeded.
14    InputBytes,
15    /// `max_depth` exceeded.
16    Depth,
17    /// `max_string_bytes` exceeded for a single string value.
18    StringBytes,
19    /// `max_key_bytes` exceeded for a single object key.
20    KeyBytes,
21    /// `max_number_bytes` exceeded for a single number token.
22    NumberBytes,
23    /// `max_array_items` exceeded.
24    ArrayItems,
25    /// `max_object_members` exceeded.
26    ObjectMembers,
27    /// `max_total_nodes` exceeded.
28    TotalNodes,
29    /// `max_total_decoded_string_bytes` exceeded across the document.
30    TotalDecodedStringBytes,
31}
32
33impl JsonLimitKind {
34    /// A short, stable description of the limit.
35    pub const fn as_str(self) -> &'static str {
36        match self {
37            Self::InputBytes => "input bytes",
38            Self::Depth => "nesting depth",
39            Self::StringBytes => "string bytes",
40            Self::KeyBytes => "key bytes",
41            Self::NumberBytes => "number bytes",
42            Self::ArrayItems => "array items",
43            Self::ObjectMembers => "object members",
44            Self::TotalNodes => "total nodes",
45            Self::TotalDecodedStringBytes => "total decoded string bytes",
46        }
47    }
48}
49
50/// The category of a parse or serialization failure.
51///
52/// This is a stable, machine-readable classification: match on it for
53/// programmatic handling rather than on [`Display`](fmt::Display) text.
54///
55/// `#[non_exhaustive]`: new kinds may be added in a future release, so match
56/// with a wildcard arm.
57#[non_exhaustive]
58#[derive(Debug, Clone, PartialEq, Eq)]
59pub enum JsonErrorKind {
60    /// Input ended before a complete value was parsed.
61    UnexpectedEof,
62    /// A byte was found that is not valid at this position.
63    UnexpectedByte,
64    /// The input was not valid UTF-8 (including a leading byte-order mark).
65    InvalidUtf8,
66    /// An invalid string escape such as `\x`.
67    InvalidEscape,
68    /// A malformed `\uXXXX` escape.
69    InvalidUnicodeEscape,
70    /// An unpaired UTF-16 surrogate escape.
71    LoneSurrogate,
72    /// A raw control character (`U+0000..=U+001F`) inside a string.
73    UnescapedControlCharacter,
74    /// A number that does not match the strict JSON grammar.
75    InvalidNumber,
76    /// A duplicate object member name.
77    DuplicateKey,
78    /// Non-whitespace bytes after the top-level value.
79    TrailingData,
80    /// A configured [`JsonLimits`](crate::JsonLimits) value was exceeded.
81    LimitExceeded(JsonLimitKind),
82    /// The output sink failed to accept bytes (serialization).
83    WriteFailure,
84    /// A number could not be represented as a finite IEEE-754 `f64` during
85    /// canonical serialization (e.g. a magnitude that overflows to infinity).
86    NonFiniteNumber,
87}
88
89/// One segment of a [`JsonPath`].
90#[derive(Debug, Clone, PartialEq, Eq)]
91pub enum JsonPathSegment {
92    /// An object member name.
93    Key(String),
94    /// An array index.
95    Index(usize),
96}
97
98/// The location of an error within the JSON document, as a path of object keys
99/// and array indices from the document root (`$`).
100#[derive(Debug, Clone, PartialEq, Eq, Default)]
101pub struct JsonPath {
102    segments: Vec<JsonPathSegment>,
103}
104
105impl JsonPath {
106    pub(crate) fn from_segments(segments: Vec<JsonPathSegment>) -> Self {
107        Self { segments }
108    }
109
110    /// Returns the path segments from the root.
111    pub fn segments(&self) -> &[JsonPathSegment] {
112        &self.segments
113    }
114}
115
116impl fmt::Display for JsonPath {
117    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118        f.write_str("$")?;
119        for segment in &self.segments {
120            match segment {
121                JsonPathSegment::Key(key) => write!(f, ".{key}")?,
122                JsonPathSegment::Index(index) => write!(f, "[{index}]")?,
123            }
124        }
125        Ok(())
126    }
127}
128
129/// An error produced while parsing or serializing JSON.
130///
131/// Carries a stable [`kind`](Self::kind), the byte `offset`, 1-based `line` and
132/// `column`, and the [`JsonPath`] being processed when known.
133#[derive(Debug, Clone, PartialEq, Eq)]
134pub struct JsonError {
135    kind: JsonErrorKind,
136    offset: usize,
137    line: usize,
138    column: usize,
139    path: Option<JsonPath>,
140}
141
142impl JsonError {
143    pub(crate) fn new(kind: JsonErrorKind, offset: usize, line: usize, column: usize) -> Self {
144        Self {
145            kind,
146            offset,
147            line,
148            column,
149            path: None,
150        }
151    }
152
153    pub(crate) fn with_path(mut self, path: JsonPath) -> Self {
154        self.path = Some(path);
155        self
156    }
157
158    /// Builds an error that occurred during serialization, where there is no
159    /// source position to report. `offset`, `line`, and `column` are `0`.
160    #[cfg(feature = "canonical")]
161    pub(crate) fn serialization(kind: JsonErrorKind) -> Self {
162        Self {
163            kind,
164            offset: 0,
165            line: 0,
166            column: 0,
167            path: None,
168        }
169    }
170
171    /// The stable error category.
172    pub fn kind(&self) -> &JsonErrorKind {
173        &self.kind
174    }
175
176    /// The byte offset of the error in the input.
177    pub fn offset(&self) -> usize {
178        self.offset
179    }
180
181    /// The 1-based line of the error.
182    pub fn line(&self) -> usize {
183        self.line
184    }
185
186    /// The 1-based column of the error.
187    pub fn column(&self) -> usize {
188        self.column
189    }
190
191    /// The JSON path being processed when the error occurred, if known.
192    pub fn path(&self) -> Option<&JsonPath> {
193        self.path.as_ref()
194    }
195}
196
197impl fmt::Display for JsonError {
198    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199        let message = match &self.kind {
200            JsonErrorKind::UnexpectedEof => "unexpected end of input",
201            JsonErrorKind::UnexpectedByte => "unexpected byte",
202            JsonErrorKind::InvalidUtf8 => "invalid UTF-8",
203            JsonErrorKind::InvalidEscape => "invalid escape sequence",
204            JsonErrorKind::InvalidUnicodeEscape => "invalid unicode escape",
205            JsonErrorKind::LoneSurrogate => "unpaired UTF-16 surrogate",
206            JsonErrorKind::UnescapedControlCharacter => "unescaped control character in string",
207            JsonErrorKind::InvalidNumber => "invalid number",
208            JsonErrorKind::DuplicateKey => "duplicate object key",
209            JsonErrorKind::TrailingData => "trailing data after JSON value",
210            JsonErrorKind::LimitExceeded(limit) => {
211                write!(
212                    f,
213                    "limit exceeded: {} at byte {}, line {}, column {}",
214                    limit.as_str(),
215                    self.offset,
216                    self.line,
217                    self.column
218                )?;
219                if let Some(path) = &self.path {
220                    write!(f, ", path: {path}")?;
221                }
222                return Ok(());
223            }
224            // Serialization-side errors have no source position to report.
225            JsonErrorKind::WriteFailure => return f.write_str("failed to write output"),
226            JsonErrorKind::NonFiniteNumber => {
227                return f.write_str("number is not representable as a finite f64");
228            }
229        };
230        write!(
231            f,
232            "{message} at byte {}, line {}, column {}",
233            self.offset, self.line, self.column
234        )?;
235        if let Some(path) = &self.path {
236            write!(f, ", path: {path}")?;
237        }
238        Ok(())
239    }
240}
241
242#[cfg(feature = "std")]
243impl std::error::Error for JsonError {}
244
245/// An error from a [`JsonNumber`](crate::JsonNumber) conversion.
246///
247/// `#[non_exhaustive]`: new kinds may be added in a future release.
248#[non_exhaustive]
249#[derive(Debug, Clone, Copy, PartialEq, Eq)]
250pub enum JsonNumberError {
251    /// The value does not fit the target integer type.
252    OutOfRange,
253    /// The value is not an integer (it has a fraction or exponent).
254    NotAnInteger,
255    /// The value is not a finite number (e.g. an exponent that overflows `f64`).
256    NotFinite,
257    /// The string is not a valid JSON number.
258    InvalidNumber,
259}
260
261impl fmt::Display for JsonNumberError {
262    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
263        f.write_str(match self {
264            Self::OutOfRange => "number out of range for target type",
265            Self::NotAnInteger => "number is not an integer",
266            Self::NotFinite => "number is not finite",
267            Self::InvalidNumber => "not a valid JSON number",
268        })
269    }
270}
271
272#[cfg(feature = "std")]
273impl std::error::Error for JsonNumberError {}