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}
85
86/// One segment of a [`JsonPath`].
87#[derive(Debug, Clone, PartialEq, Eq)]
88pub enum JsonPathSegment {
89    /// An object member name.
90    Key(String),
91    /// An array index.
92    Index(usize),
93}
94
95/// The location of an error within the JSON document, as a path of object keys
96/// and array indices from the document root (`$`).
97#[derive(Debug, Clone, PartialEq, Eq, Default)]
98pub struct JsonPath {
99    segments: Vec<JsonPathSegment>,
100}
101
102impl JsonPath {
103    pub(crate) fn from_segments(segments: Vec<JsonPathSegment>) -> Self {
104        Self { segments }
105    }
106
107    /// Returns the path segments from the root.
108    pub fn segments(&self) -> &[JsonPathSegment] {
109        &self.segments
110    }
111}
112
113impl fmt::Display for JsonPath {
114    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115        f.write_str("$")?;
116        for segment in &self.segments {
117            match segment {
118                JsonPathSegment::Key(key) => write!(f, ".{key}")?,
119                JsonPathSegment::Index(index) => write!(f, "[{index}]")?,
120            }
121        }
122        Ok(())
123    }
124}
125
126/// An error produced while parsing or serializing JSON.
127///
128/// Carries a stable [`kind`](Self::kind), the byte `offset`, 1-based `line` and
129/// `column`, and the [`JsonPath`] being processed when known.
130#[derive(Debug, Clone, PartialEq, Eq)]
131pub struct JsonError {
132    kind: JsonErrorKind,
133    offset: usize,
134    line: usize,
135    column: usize,
136    path: Option<JsonPath>,
137}
138
139impl JsonError {
140    pub(crate) fn new(kind: JsonErrorKind, offset: usize, line: usize, column: usize) -> Self {
141        Self {
142            kind,
143            offset,
144            line,
145            column,
146            path: None,
147        }
148    }
149
150    pub(crate) fn with_path(mut self, path: JsonPath) -> Self {
151        self.path = Some(path);
152        self
153    }
154
155    /// The stable error category.
156    pub fn kind(&self) -> &JsonErrorKind {
157        &self.kind
158    }
159
160    /// The byte offset of the error in the input.
161    pub fn offset(&self) -> usize {
162        self.offset
163    }
164
165    /// The 1-based line of the error.
166    pub fn line(&self) -> usize {
167        self.line
168    }
169
170    /// The 1-based column of the error.
171    pub fn column(&self) -> usize {
172        self.column
173    }
174
175    /// The JSON path being processed when the error occurred, if known.
176    pub fn path(&self) -> Option<&JsonPath> {
177        self.path.as_ref()
178    }
179}
180
181impl fmt::Display for JsonError {
182    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183        let message = match &self.kind {
184            JsonErrorKind::UnexpectedEof => "unexpected end of input",
185            JsonErrorKind::UnexpectedByte => "unexpected byte",
186            JsonErrorKind::InvalidUtf8 => "invalid UTF-8",
187            JsonErrorKind::InvalidEscape => "invalid escape sequence",
188            JsonErrorKind::InvalidUnicodeEscape => "invalid unicode escape",
189            JsonErrorKind::LoneSurrogate => "unpaired UTF-16 surrogate",
190            JsonErrorKind::UnescapedControlCharacter => "unescaped control character in string",
191            JsonErrorKind::InvalidNumber => "invalid number",
192            JsonErrorKind::DuplicateKey => "duplicate object key",
193            JsonErrorKind::TrailingData => "trailing data after JSON value",
194            JsonErrorKind::LimitExceeded(limit) => {
195                write!(
196                    f,
197                    "limit exceeded: {} at byte {}, line {}, column {}",
198                    limit.as_str(),
199                    self.offset,
200                    self.line,
201                    self.column
202                )?;
203                if let Some(path) = &self.path {
204                    write!(f, ", path: {path}")?;
205                }
206                return Ok(());
207            }
208            JsonErrorKind::WriteFailure => "failed to write output",
209        };
210        write!(
211            f,
212            "{message} at byte {}, line {}, column {}",
213            self.offset, self.line, self.column
214        )?;
215        if let Some(path) = &self.path {
216            write!(f, ", path: {path}")?;
217        }
218        Ok(())
219    }
220}
221
222#[cfg(feature = "std")]
223impl std::error::Error for JsonError {}
224
225/// An error from a [`JsonNumber`](crate::JsonNumber) conversion.
226///
227/// `#[non_exhaustive]`: new kinds may be added in a future release.
228#[non_exhaustive]
229#[derive(Debug, Clone, Copy, PartialEq, Eq)]
230pub enum JsonNumberError {
231    /// The value does not fit the target integer type.
232    OutOfRange,
233    /// The value is not an integer (it has a fraction or exponent).
234    NotAnInteger,
235    /// The value is not a finite number (e.g. an exponent that overflows `f64`).
236    NotFinite,
237    /// The string is not a valid JSON number.
238    InvalidNumber,
239}
240
241impl fmt::Display for JsonNumberError {
242    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
243        f.write_str(match self {
244            Self::OutOfRange => "number out of range for target type",
245            Self::NotAnInteger => "number is not an integer",
246            Self::NotFinite => "number is not finite",
247            Self::InvalidNumber => "not a valid JSON number",
248        })
249    }
250}
251
252#[cfg(feature = "std")]
253impl std::error::Error for JsonNumberError {}