ftl_jiter/
errors.rs

1/// Enum representing all possible errors in JSON syntax.
2///
3/// Almost all of `JsonErrorType` is copied from [serde_json](https://github.com/serde-rs) so errors match
4/// those expected from `serde_json`.
5#[derive(Debug, PartialEq, Eq, Clone)]
6pub enum JsonErrorType {
7    /// float value was found where an int was expected
8    FloatExpectingInt,
9
10    /// duplicate keys in an object
11    DuplicateKey(String),
12
13    /// happens when getting the `Decimal` type or constructing a decimal fails
14    InternalError(String),
15
16    /// NOTE: all errors from here on are copied from serde_json
17    /// [src/error.rs](https://github.com/serde-rs/json/blob/v1.0.107/src/error.rs#L236)
18    /// with `Io` and `Message` removed
19    ///
20    /// EOF while parsing a list.
21    EofWhileParsingList,
22
23    /// EOF while parsing an object.
24    EofWhileParsingObject,
25
26    /// EOF while parsing a string.
27    EofWhileParsingString,
28
29    /// EOF while parsing a JSON value.
30    EofWhileParsingValue,
31
32    /// Expected this character to be a `':'`.
33    ExpectedColon,
34
35    /// Expected this character to be either a `','` or a `']'`.
36    ExpectedListCommaOrEnd,
37
38    /// Expected this character to be either a `','` or a `'}'`.
39    ExpectedObjectCommaOrEnd,
40
41    /// Expected to parse either a `true`, `false`, or a `null`.
42    ExpectedSomeIdent,
43
44    /// Expected this character to start a JSON value.
45    ExpectedSomeValue,
46
47    /// Invalid hex escape code.
48    InvalidEscape,
49
50    /// Invalid number.
51    InvalidNumber,
52
53    /// Number is bigger than the maximum value of its type.
54    NumberOutOfRange,
55
56    /// Invalid unicode code point.
57    InvalidUnicodeCodePoint,
58
59    /// Control character found while parsing a string.
60    ControlCharacterWhileParsingString,
61
62    /// Object key is not a string.
63    KeyMustBeAString,
64
65    /// Lone leading surrogate in hex escape.
66    LoneLeadingSurrogateInHexEscape,
67
68    /// JSON has a comma after the last value in an array or map.
69    TrailingComma,
70
71    /// JSON has non-whitespace trailing characters after the value.
72    TrailingCharacters,
73
74    /// Unexpected end of hex escape.
75    UnexpectedEndOfHexEscape,
76
77    /// Encountered nesting of JSON maps and arrays more than 128 layers deep.
78    RecursionLimitExceeded,
79}
80
81impl std::fmt::Display for JsonErrorType {
82    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83        // Messages for enum members copied from serde_json are unchanged
84        match self {
85            Self::FloatExpectingInt => f.write_str("float value was found where an int was expected"),
86            Self::DuplicateKey(s) => write!(f, "Detected duplicate key {s:?}"),
87            Self::InternalError(s) => write!(f, "Internal error: {s:?}"),
88            Self::EofWhileParsingList => f.write_str("EOF while parsing a list"),
89            Self::EofWhileParsingObject => f.write_str("EOF while parsing an object"),
90            Self::EofWhileParsingString => f.write_str("EOF while parsing a string"),
91            Self::EofWhileParsingValue => f.write_str("EOF while parsing a value"),
92            Self::ExpectedColon => f.write_str("expected `:`"),
93            Self::ExpectedListCommaOrEnd => f.write_str("expected `,` or `]`"),
94            Self::ExpectedObjectCommaOrEnd => f.write_str("expected `,` or `}`"),
95            Self::ExpectedSomeIdent => f.write_str("expected ident"),
96            Self::ExpectedSomeValue => f.write_str("expected value"),
97            Self::InvalidEscape => f.write_str("invalid escape"),
98            Self::InvalidNumber => f.write_str("invalid number"),
99            Self::NumberOutOfRange => f.write_str("number out of range"),
100            Self::InvalidUnicodeCodePoint => f.write_str("invalid unicode code point"),
101            Self::ControlCharacterWhileParsingString => {
102                f.write_str("control character (\\u0000-\\u001F) found while parsing a string")
103            }
104            Self::KeyMustBeAString => f.write_str("key must be a string"),
105            Self::LoneLeadingSurrogateInHexEscape => f.write_str("lone leading surrogate in hex escape"),
106            Self::TrailingComma => f.write_str("trailing comma"),
107            Self::TrailingCharacters => f.write_str("trailing characters"),
108            Self::UnexpectedEndOfHexEscape => f.write_str("unexpected end of hex escape"),
109            Self::RecursionLimitExceeded => f.write_str("recursion limit exceeded"),
110        }
111    }
112}
113
114pub type JsonResult<T> = Result<T, JsonError>;
115
116/// Represents an error from parsing JSON
117#[derive(Debug, Clone, Eq, PartialEq)]
118pub struct JsonError {
119    /// The type of error.
120    pub error_type: JsonErrorType,
121    /// The index in the data where the error occurred.
122    pub index: usize,
123}
124
125impl JsonError {
126    pub(crate) fn new(error_type: JsonErrorType, index: usize) -> Self {
127        Self { error_type, index }
128    }
129
130    pub fn get_position(&self, json_data: &[u8]) -> LinePosition {
131        LinePosition::find(json_data, self.index)
132    }
133
134    pub fn description(&self, json_data: &[u8]) -> String {
135        let position = self.get_position(json_data);
136        format!("{} at {}", self.error_type, position)
137    }
138}
139
140impl std::fmt::Display for JsonError {
141    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
142        write!(f, "{} at index {}", self.error_type, self.index)
143    }
144}
145
146macro_rules! json_error {
147    ($error_type:ident, $index:expr) => {
148        crate::errors::JsonError::new(crate::errors::JsonErrorType::$error_type, $index)
149    };
150}
151
152pub(crate) use json_error;
153
154macro_rules! json_err {
155    ($error_type:ident, $index:expr) => {
156        Err(crate::errors::json_error!($error_type, $index))
157    };
158}
159
160use crate::Jiter;
161pub(crate) use json_err;
162
163pub(crate) const DEFAULT_RECURSION_LIMIT: u8 = 200;
164
165/// Enum representing all JSON types.
166#[derive(Debug, Clone, Eq, PartialEq)]
167pub enum JsonType {
168    Null,
169    Bool,
170    Int,
171    Float,
172    String,
173    Array,
174    Object,
175}
176
177impl std::fmt::Display for JsonType {
178    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
179        match self {
180            Self::Null => f.write_str("null"),
181            Self::Bool => f.write_str("bool"),
182            Self::Int => f.write_str("int"),
183            Self::Float => f.write_str("float"),
184            Self::String => f.write_str("string"),
185            Self::Array => f.write_str("array"),
186            Self::Object => f.write_str("object"),
187        }
188    }
189}
190
191/// Enum representing either a [JsonErrorType] or a WrongType error.
192#[derive(Debug, Clone, Eq, PartialEq)]
193pub enum JiterErrorType {
194    JsonError(JsonErrorType),
195    WrongType { expected: JsonType, actual: JsonType },
196}
197
198impl std::fmt::Display for JiterErrorType {
199    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
200        match self {
201            Self::JsonError(error_type) => write!(f, "{error_type}"),
202            Self::WrongType { expected, actual } => {
203                write!(f, "expected {expected} but found {actual}")
204            }
205        }
206    }
207}
208
209/// An error from the Jiter iterator.
210#[derive(Debug, Clone, Eq, PartialEq)]
211pub struct JiterError {
212    pub error_type: JiterErrorType,
213    pub index: usize,
214}
215
216impl std::fmt::Display for JiterError {
217    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
218        write!(f, "{} at index {}", self.error_type, self.index)
219    }
220}
221
222impl JiterError {
223    pub(crate) fn new(error_type: JiterErrorType, index: usize) -> Self {
224        Self { error_type, index }
225    }
226
227    pub fn get_position(&self, jiter: &Jiter) -> LinePosition {
228        jiter.error_position(self.index)
229    }
230
231    pub fn description(&self, jiter: &Jiter) -> String {
232        let position = self.get_position(jiter);
233        format!("{} at {}", self.error_type, position)
234    }
235
236    pub(crate) fn wrong_type(expected: JsonType, actual: JsonType, index: usize) -> Self {
237        Self::new(JiterErrorType::WrongType { expected, actual }, index)
238    }
239}
240
241impl From<JsonError> for JiterError {
242    fn from(error: JsonError) -> Self {
243        Self {
244            error_type: JiterErrorType::JsonError(error.error_type),
245            index: error.index,
246        }
247    }
248}
249
250/// Represents a line and column in a file or input string, used for both errors and value positions.
251#[derive(Debug, Clone, PartialEq, Eq)]
252pub struct LinePosition {
253    /// Line number, starting at 1.
254    pub line: usize,
255    /// Column number, starting at 1.
256    pub column: usize,
257}
258
259impl std::fmt::Display for LinePosition {
260    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
261        write!(f, "line {} column {}", self.line, self.column)
262    }
263}
264
265impl LinePosition {
266    pub fn new(line: usize, column: usize) -> Self {
267        Self { line, column }
268    }
269
270    /// Find the line and column of a byte index in a string.
271    pub fn find(json_data: &[u8], find: usize) -> Self {
272        let mut line = 1;
273        let mut last_line_start = 0;
274        let mut index = 0;
275        while let Some(next) = json_data.get(index) {
276            if *next == b'\n' {
277                line += 1;
278                last_line_start = index + 1;
279            }
280            if index == find {
281                return Self {
282                    line,
283                    column: index + 1 - last_line_start,
284                };
285            }
286            index += 1;
287        }
288        Self {
289            line,
290            column: index.saturating_sub(last_line_start),
291        }
292    }
293
294    pub fn short(&self) -> String {
295        format!("{}:{}", self.line, self.column)
296    }
297}