Skip to main content

json_bourne/
error.rs

1use core::fmt;
2
3/// Byte offset into the input where an event was emitted or an error fired.
4///
5/// `Position` used to carry `(offset, line, column)` (16 bytes), which
6/// pushed `Result<Option<Event>, Error>` to 24 bytes — too big to return
7/// in two GP registers, forcing every `next_event` call to spill through
8/// stack memory. Now it's just an offset (4 bytes); line and column are
9/// reconstructed on demand by `Position::resolve(input)`, which scans
10/// `input[..offset]` for newlines. The scan only fires at error sites
11/// or when the consumer explicitly asks for them.
12#[derive(Copy, Clone, Debug, PartialEq, Eq)]
13pub struct Position {
14    pub offset: u32,
15}
16
17/// Line and column reconstructed from a `Position` against its input.
18///
19/// 1-based on both axes (matches the convention used by most editors).
20#[derive(Copy, Clone, Debug, PartialEq, Eq)]
21pub struct LineColumn {
22    pub line: u32,
23    pub column: u32,
24}
25
26impl Position {
27    pub const START: Self = Self { offset: 0 };
28
29    #[must_use]
30    pub const fn new(offset: u32) -> Self {
31        Self { offset }
32    }
33
34    /// Compute 1-based line and column by scanning the input up to this
35    /// position. O(offset) — only call when actually rendering an error.
36    #[must_use]
37    pub fn resolve(&self, input: &[u8]) -> LineColumn {
38        let upto = core::cmp::min(self.offset as usize, input.len());
39        let mut line: u32 = 1;
40        let mut last_newline: usize = 0;
41        let mut had_newline = false;
42        let mut i = 0;
43        while i < upto {
44            if input[i] == b'\n' {
45                line += 1;
46                last_newline = i + 1;
47                had_newline = true;
48            }
49            i += 1;
50        }
51        let col_start = if had_newline { last_newline } else { 0 };
52        let column = u32::try_from(upto - col_start + 1).unwrap_or(u32::MAX);
53        LineColumn { line, column }
54    }
55}
56
57impl fmt::Display for Position {
58    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59        write!(f, "byte {}", self.offset)
60    }
61}
62
63#[derive(Copy, Clone, Debug, PartialEq, Eq)]
64pub enum ErrorKind {
65    UnexpectedByte(u8),
66    UnexpectedEof,
67    InvalidEscape,
68    InvalidUnicodeEscape,
69    UnpairedSurrogate,
70    InvalidUtf8,
71    InvalidNumber,
72    NumberOutOfRange,
73    ControlCharInString,
74    TrailingData,
75    DepthLimitExceeded,
76    ExpectedValue,
77    ExpectedString,
78    ExpectedNumber,
79    ExpectedBool,
80    ExpectedNull,
81    ExpectedArray,
82    ExpectedObject,
83    TypeMismatch,
84    DuplicateKey,
85    MissingField,
86    UnknownField,
87    NonFiniteFloat,
88}
89
90impl ErrorKind {
91    /// Lexer / parser–level errors.
92    #[must_use]
93    const fn lexical_msg(self) -> Option<&'static str> {
94        let msg = match self {
95            Self::UnexpectedByte(_) => "unexpected byte",
96            Self::UnexpectedEof => "unexpected end of input",
97            Self::InvalidEscape => "invalid string escape",
98            Self::InvalidUnicodeEscape => "invalid \\u escape",
99            Self::UnpairedSurrogate => "unpaired UTF-16 surrogate in \\u escape",
100            Self::InvalidUtf8 => "invalid UTF-8",
101            Self::InvalidNumber => "invalid number literal",
102            Self::NumberOutOfRange => "number does not fit target type",
103            Self::ControlCharInString => "control character in string literal",
104            Self::TrailingData => "trailing data after JSON value",
105            Self::DepthLimitExceeded => "nesting depth limit exceeded",
106            _ => return None,
107        };
108        Some(msg)
109    }
110
111    /// Static human-readable message for every variant except
112    /// `UnexpectedByte`, which is formatted dynamically by `Display`.
113    #[must_use]
114    const fn static_msg(self) -> &'static str {
115        if let Some(s) = self.lexical_msg() {
116            return s;
117        }
118        match self {
119            Self::ExpectedValue => "expected JSON value",
120            Self::ExpectedString => "expected string",
121            Self::ExpectedNumber => "expected number",
122            Self::ExpectedBool => "expected boolean",
123            Self::ExpectedNull => "expected null",
124            Self::ExpectedArray => "expected array",
125            Self::ExpectedObject => "expected object",
126            Self::TypeMismatch => "type mismatch",
127            Self::DuplicateKey => "duplicate object key",
128            Self::MissingField => "missing required field",
129            Self::UnknownField => "unknown field",
130            Self::NonFiniteFloat => "non-finite float not representable in JSON",
131            _ => "error",
132        }
133    }
134}
135
136impl fmt::Display for ErrorKind {
137    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138        if let Self::UnexpectedByte(b) = self {
139            return write!(f, "unexpected byte 0x{b:02x}");
140        }
141        f.write_str(self.static_msg())
142    }
143}
144
145#[derive(Copy, Clone, Debug, PartialEq, Eq)]
146pub struct Error {
147    pub kind: ErrorKind,
148    pub position: Position,
149}
150
151impl Error {
152    #[must_use]
153    pub const fn new(kind: ErrorKind, position: Position) -> Self {
154        Self { kind, position }
155    }
156}
157
158impl fmt::Display for Error {
159    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
160        write!(f, "{} at {}", self.kind, self.position)
161    }
162}
163
164#[cfg(feature = "std")]
165impl std::error::Error for Error {}