• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

facet-rs / facet / 14434945618

14 Apr 2025 12:35AM UTC coverage: 19.561% (-12.1%) from 31.623%
14434945618

Pull #188

github

web-flow
Merge 8230131c0 into 046ca7ecc
Pull Request #188: Rewrite facet-reflect for safety

421 of 969 new or added lines in 44 files covered. (43.45%)

679 existing lines in 19 files now uncovered.

1149 of 5874 relevant lines covered (19.56%)

8.61 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/facet-json/src/parser.rs
1
//! The facet-json parser.
2
//!
3
//! For now it is extremely naive, it's just a proof of concept, it doesn't use SIMD or anything,
4
//! it's not fast, it's nothing, it's just proving that we can use facet types to deserialize something.
5

6
#[derive(Debug)]
7
pub struct JsonParseError {
8
    pub kind: JsonParseErrorKind,
9
    pub position: usize,
10
}
11

12
#[derive(Debug)]
13
#[non_exhaustive]
14
pub enum JsonParseErrorKind {
15
    ExpectedOpeningQuote,
16
    UnterminatedString,
17
    InvalidEscapeSequence(char),
18
    IncompleteUnicodeEscape,
19
    InvalidUnicodeEscape,
20
    ExpectedNumber,
21
    InvalidNumberFormat,
22
    ExpectedOpeningBrace,
23
    ExpectedOpeningBracket,
24
    ExpectedColon,
25
    UnexpectedEndOfInput,
26
    InvalidValue,
27
    ExpectedClosingBrace,
28
    ExpectedClosingBracket,
29
    UnknownField(String),
30
    Custom(String),
31
}
32

33
impl JsonParseError {
34
    pub fn new(kind: JsonParseErrorKind, position: usize) -> Self {
35
        JsonParseError { kind, position }
36
    }
37
}
38

39
#[derive(Debug)]
40
pub struct JsonParseErrorWithContext<'input> {
41
    pub error: JsonParseError,
42
    pub input: &'input str,
43
}
44

45
impl JsonParseErrorWithContext<'_> {
46
    pub fn strip_context(self) -> JsonParseError {
47
        self.error
48
    }
49
}
50

51
impl core::fmt::Display for JsonParseError {
52
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
53
        let error_message = match &self.kind {
54
            JsonParseErrorKind::ExpectedOpeningQuote => "Expected opening quote for string",
55
            JsonParseErrorKind::UnterminatedString => "Unterminated string",
56
            JsonParseErrorKind::InvalidEscapeSequence(ch) => {
57
                return write!(f, "Invalid escape sequence: \\{}", ch);
58
            }
59
            JsonParseErrorKind::IncompleteUnicodeEscape => "Incomplete Unicode escape sequence",
60
            JsonParseErrorKind::InvalidUnicodeEscape => "Invalid Unicode escape sequence",
61
            JsonParseErrorKind::ExpectedNumber => "Expected a number",
62
            JsonParseErrorKind::InvalidNumberFormat => "Invalid number format",
63
            JsonParseErrorKind::ExpectedOpeningBrace => "Expected opening brace for object",
64
            JsonParseErrorKind::ExpectedOpeningBracket => "Expected opening bracket for array",
65
            JsonParseErrorKind::ExpectedColon => "Expected ':' after object key",
66
            JsonParseErrorKind::UnexpectedEndOfInput => "Unexpected end of input",
67
            JsonParseErrorKind::InvalidValue => "Invalid value",
68
            JsonParseErrorKind::ExpectedClosingBrace => "Expected closing brace for object",
69
            JsonParseErrorKind::ExpectedClosingBracket => "Expected closing bracket for array",
70
            JsonParseErrorKind::UnknownField(field) => {
71
                return write!(f, "Unknown field: {}", field);
72
            }
73
            JsonParseErrorKind::Custom(msg) => msg,
74
        };
75

76
        write!(f, "{} at position {}", error_message, self.position)
77
    }
78
}
79

80
impl core::fmt::Display for JsonParseErrorWithContext<'_> {
81
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
82
        let context_start = self.error.position.saturating_sub(20);
83
        let context_end = (self.error.position + 20).min(self.input.len());
84
        let context = &self.input[context_start..context_end];
85
        let arrow_position = self.error.position - context_start;
86

87
        writeln!(f, "{}", self.error)?;
88
        writeln!(f, "\x1b[36m{}\x1b[0m", context)?;
89
        write!(f, "{}\x1b[31m^\x1b[0m", " ".repeat(arrow_position))
90
    }
91
}
92

93
impl core::error::Error for JsonParseError {}
94

95
pub struct JsonParser<'input> {
96
    pub input: &'input str,
97
    pub position: usize,
98
}
99

100
impl<'a> JsonParser<'a> {
UNCOV
101
    pub fn new(input: &'a str) -> Self {
×
102
        JsonParser { input, position: 0 }
103
    }
104

UNCOV
105
    pub fn make_error(&self, kind: JsonParseErrorKind) -> JsonParseErrorWithContext<'a> {
×
106
        JsonParseErrorWithContext {
UNCOV
107
            error: JsonParseError::new(kind, self.position),
×
UNCOV
108
            input: self.input,
×
109
        }
110
    }
111

UNCOV
112
    pub fn parse_string(&mut self) -> Result<String, JsonParseErrorWithContext<'a>> {
×
UNCOV
113
        self.skip_whitespace();
×
UNCOV
114
        if self.position >= self.input.len() || self.input.as_bytes()[self.position] != b'"' {
×
115
            return Err(self.make_error(JsonParseErrorKind::ExpectedOpeningQuote));
×
116
        }
UNCOV
117
        self.position += 1;
×
118

UNCOV
119
        let mut result = String::new();
×
UNCOV
120
        let mut escaped = false;
×
121

UNCOV
122
        while self.position < self.input.len() {
×
UNCOV
123
            let ch = self.input.as_bytes()[self.position];
×
UNCOV
124
            self.position += 1;
×
125

UNCOV
126
            if escaped {
×
127
                match ch {
×
128
                    b'"' | b'\\' | b'/' => result.push(ch as char),
×
129
                    b'b' => result.push('\x08'),
×
130
                    b'f' => result.push('\x0C'),
×
131
                    b'n' => result.push('\n'),
×
132
                    b'r' => result.push('\r'),
×
133
                    b't' => result.push('\t'),
×
134
                    b'u' => {
×
135
                        // Parse 4-digit hex code
136
                        if self.position + 4 > self.input.len() {
×
137
                            return Err(
×
138
                                self.make_error(JsonParseErrorKind::IncompleteUnicodeEscape)
×
139
                            );
140
                        }
141
                        let hex = &self.input[self.position..self.position + 4];
×
142
                        self.position += 4;
×
143
                        if let Ok(code) = u16::from_str_radix(hex, 16) {
×
144
                            result.push(char::from_u32(code as u32).unwrap_or('\u{FFFD}'));
×
145
                        } else {
146
                            return Err(self.make_error(JsonParseErrorKind::InvalidUnicodeEscape));
×
147
                        }
148
                    }
149
                    _ => {
×
150
                        return Err(
×
151
                            self.make_error(JsonParseErrorKind::InvalidEscapeSequence(ch as char))
×
152
                        );
153
                    }
154
                }
155
                escaped = false;
×
UNCOV
156
            } else if ch == b'\\' {
×
157
                escaped = true;
×
UNCOV
158
            } else if ch == b'"' {
×
UNCOV
159
                return Ok(result);
×
160
            } else {
UNCOV
161
                result.push(ch as char);
×
162
            }
163
        }
164

165
        Err(self.make_error(JsonParseErrorKind::UnterminatedString))
×
166
    }
167

UNCOV
168
    pub fn parse_u64(&mut self) -> Result<u64, JsonParseErrorWithContext<'a>> {
×
UNCOV
169
        self.skip_whitespace();
×
UNCOV
170
        let start = self.position;
×
UNCOV
171
        while self.position < self.input.len()
×
UNCOV
172
            && self.input.as_bytes()[self.position].is_ascii_digit()
×
173
        {
UNCOV
174
            self.position += 1;
×
175
        }
UNCOV
176
        if start == self.position {
×
177
            return Err(self.make_error(JsonParseErrorKind::ExpectedNumber));
×
178
        }
UNCOV
179
        let num_str = &self.input[start..self.position];
×
UNCOV
180
        num_str
×
181
            .parse::<u64>()
UNCOV
182
            .map_err(|_| self.make_error(JsonParseErrorKind::InvalidNumberFormat))
×
183
    }
184

UNCOV
185
    pub fn parse_i64(&mut self) -> Result<i64, JsonParseErrorWithContext<'a>> {
×
UNCOV
186
        self.skip_whitespace();
×
UNCOV
187
        let start = self.position;
×
188

189
        // Allow leading minus sign
UNCOV
190
        if self.position < self.input.len() && self.input.as_bytes()[self.position] == b'-' {
×
UNCOV
191
            self.position += 1;
×
192
        }
193

194
        // Parse digits
UNCOV
195
        while self.position < self.input.len()
×
UNCOV
196
            && self.input.as_bytes()[self.position].is_ascii_digit()
×
197
        {
UNCOV
198
            self.position += 1;
×
199
        }
200

UNCOV
201
        if start == self.position
×
UNCOV
202
            || (self.position == start + 1 && self.input.as_bytes()[start] == b'-')
×
203
        {
204
            // Handle case where only '-' was found or nothing was parsed
205
            return Err(self.make_error(JsonParseErrorKind::ExpectedNumber));
×
206
        }
207

UNCOV
208
        let num_str = &self.input[start..self.position];
×
UNCOV
209
        num_str
×
210
            .parse::<i64>()
UNCOV
211
            .map_err(|_| self.make_error(JsonParseErrorKind::InvalidNumberFormat))
×
212
    }
213

214
    // Generic number parsing helper
215
    fn parse_number<T>(&mut self) -> Result<T, JsonParseErrorWithContext<'a>>
216
    where
217
        T: core::str::FromStr,
218
        <T as core::str::FromStr>::Err: core::fmt::Debug, // Ensure the error type can be debug printed
219
    {
UNCOV
220
        self.skip_whitespace();
×
UNCOV
221
        let start = self.position;
×
222
        // Allow leading minus sign
UNCOV
223
        if self.position < self.input.len() && self.input.as_bytes()[self.position] == b'-' {
×
224
            self.position += 1;
×
225
        }
226
        // Allow digits and decimal point
UNCOV
227
        while self.position < self.input.len() {
×
UNCOV
228
            let byte = self.input.as_bytes()[self.position];
×
UNCOV
229
            if byte.is_ascii_digit() || byte == b'.' {
×
UNCOV
230
                self.position += 1;
×
231
            } else {
UNCOV
232
                break;
×
233
            }
234
        }
235

UNCOV
236
        if start == self.position
×
UNCOV
237
            || (self.position == start + 1 && self.input.as_bytes()[start] == b'-')
×
238
        {
239
            // Handle case where only '-' was found or nothing was parsed
240
            return Err(self.make_error(JsonParseErrorKind::ExpectedNumber));
×
241
        }
242

UNCOV
243
        let num_str = &self.input[start..self.position];
×
UNCOV
244
        num_str
×
245
            .parse::<T>()
UNCOV
246
            .map_err(|_| self.make_error(JsonParseErrorKind::InvalidNumberFormat))
×
247
    }
248

UNCOV
249
    pub fn parse_f64(&mut self) -> Result<f64, JsonParseErrorWithContext<'a>> {
×
UNCOV
250
        self.parse_number()
×
251
    }
252

253
    pub fn parse_bool(&mut self) -> Result<bool, JsonParseErrorWithContext<'a>> {
×
254
        self.skip_whitespace();
×
255
        if self.position + 4 <= self.input.len()
×
256
            && &self.input[self.position..self.position + 4] == "true"
×
257
        {
258
            self.position += 4;
×
259
            return Ok(true);
×
260
        }
261
        if self.position + 5 <= self.input.len()
×
262
            && &self.input[self.position..self.position + 5] == "false"
×
263
        {
264
            self.position += 5;
×
265
            return Ok(false);
×
266
        }
267
        Err(self.make_error(JsonParseErrorKind::InvalidValue))
×
268
    }
269

UNCOV
270
    pub fn parse_null(&mut self) -> Result<(), JsonParseErrorWithContext<'a>> {
×
UNCOV
271
        self.skip_whitespace();
×
UNCOV
272
        if self.position + 4 <= self.input.len()
×
UNCOV
273
            && &self.input[self.position..self.position + 4] == "null"
×
274
        {
UNCOV
275
            self.position += 4;
×
UNCOV
276
            return Ok(());
×
277
        }
UNCOV
278
        Err(self.make_error(JsonParseErrorKind::InvalidValue))
×
279
    }
280

UNCOV
281
    pub fn skip_whitespace(&mut self) {
×
UNCOV
282
        while self.position < self.input.len() {
×
UNCOV
283
            match self.input.as_bytes()[self.position] {
×
UNCOV
284
                b' ' | b'\t' | b'\n' | b'\r' => self.position += 1,
×
UNCOV
285
                _ => break,
×
286
            }
287
        }
288
    }
289

290
    /// Expects the start of an array.
UNCOV
291
    pub fn expect_array_start(&mut self) -> Result<(), JsonParseErrorWithContext<'a>> {
×
UNCOV
292
        self.skip_whitespace();
×
UNCOV
293
        if self.position >= self.input.len() || self.input.as_bytes()[self.position] != b'[' {
×
294
            return Err(self.make_error(JsonParseErrorKind::ExpectedOpeningBracket));
×
295
        }
UNCOV
296
        self.position += 1;
×
UNCOV
297
        Ok(())
×
298
    }
299

300
    /// Expects the end of an array or a comma followed by the next element.
301
    /// Returns Some(true) if there's another element, Some(false) if the array has ended,
302
    /// or an error if the JSON is malformed.
UNCOV
303
    pub fn parse_array_element(&mut self) -> Result<Option<bool>, JsonParseErrorWithContext<'a>> {
×
UNCOV
304
        self.skip_whitespace();
×
UNCOV
305
        if self.position >= self.input.len() {
×
306
            return Err(self.make_error(JsonParseErrorKind::UnexpectedEndOfInput));
×
307
        }
308

UNCOV
309
        match self.input.as_bytes()[self.position] {
×
310
            b',' => {
×
UNCOV
311
                self.position += 1;
×
UNCOV
312
                self.skip_whitespace();
×
313
                Ok(Some(true)) // There's another element
314
            }
315
            b']' => {
×
UNCOV
316
                self.position += 1;
×
317
                Ok(Some(false)) // End of array
318
            }
319
            _ => {
×
320
                // First element doesn't need a comma
UNCOV
321
                Ok(Some(true))
×
322
            }
323
        }
324
    }
325

326
    /// Expects the start of an object and returns the first key if present.
327
    /// Returns None if the object is empty.
UNCOV
328
    pub fn expect_object_start(&mut self) -> Result<Option<String>, JsonParseErrorWithContext<'a>> {
×
UNCOV
329
        self.skip_whitespace();
×
UNCOV
330
        if self.position >= self.input.len() || self.input.as_bytes()[self.position] != b'{' {
×
331
            return Err(self.make_error(JsonParseErrorKind::ExpectedOpeningBrace));
×
332
        }
UNCOV
333
        self.position += 1;
×
UNCOV
334
        self.skip_whitespace();
×
335

UNCOV
336
        if self.position < self.input.len() && self.input.as_bytes()[self.position] == b'"' {
×
UNCOV
337
            let key = self.parse_string()?;
×
338
            self.skip_whitespace();
×
UNCOV
339
            if self.position < self.input.len() && self.input.as_bytes()[self.position] == b':' {
×
UNCOV
340
                self.position += 1;
×
UNCOV
341
                Ok(Some(key))
×
342
            } else {
343
                Err(self.make_error(JsonParseErrorKind::ExpectedColon))
×
344
            }
UNCOV
345
        } else if self.position < self.input.len() && self.input.as_bytes()[self.position] == b'}' {
×
UNCOV
346
            self.position += 1;
×
UNCOV
347
            Ok(None)
×
348
        } else {
349
            Err(self.make_error(JsonParseErrorKind::InvalidValue))
×
350
        }
351
    }
352

353
    /// Expects the end of an object or a comma followed by the next key.
354
    /// Returns None if the object has ended, or Some(key) if there's another key-value pair.
355
    ///
356
    /// This function is used to parse the end of an object or to move to the next key-value pair.
357
    /// It handles three cases:
358
    /// 1. If it encounters a comma, it expects the next key-value pair and returns Some(key).
359
    /// 2. If it encounters a closing brace, it returns None to indicate the end of the object.
360
    /// 3. If it encounters anything else, it returns an error.
361
    ///
362
    /// The function also takes care of skipping whitespace before and after tokens.
363
    /// If it reaches the end of input unexpectedly, it returns an appropriate error.
UNCOV
364
    pub fn parse_object_key(&mut self) -> Result<Option<String>, JsonParseErrorWithContext<'a>> {
×
UNCOV
365
        self.skip_whitespace();
×
UNCOV
366
        if self.position >= self.input.len() {
×
367
            return Err(self.make_error(JsonParseErrorKind::UnexpectedEndOfInput));
×
368
        }
UNCOV
369
        match self.input.as_bytes()[self.position] {
×
370
            b',' => {
×
UNCOV
371
                self.position += 1;
×
UNCOV
372
                self.skip_whitespace();
×
UNCOV
373
                if self.position < self.input.len() && self.input.as_bytes()[self.position] == b'"'
×
374
                {
UNCOV
375
                    let key = self.parse_string()?;
×
376
                    self.skip_whitespace();
×
377
                    if self.position < self.input.len()
×
UNCOV
378
                        && self.input.as_bytes()[self.position] == b':'
×
379
                    {
UNCOV
380
                        self.position += 1;
×
UNCOV
381
                        Ok(Some(key))
×
382
                    } else {
383
                        Err(self.make_error(JsonParseErrorKind::ExpectedColon))
×
384
                    }
385
                } else {
386
                    Err(self.make_error(JsonParseErrorKind::InvalidValue))
×
387
                }
388
            }
389
            b'}' => {
×
UNCOV
390
                self.position += 1;
×
UNCOV
391
                Ok(None)
×
392
            }
393
            _ => Err(self.make_error(JsonParseErrorKind::InvalidValue)),
×
394
        }
395
    }
396
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc