Skip to main content

jsonette/parser/
strict.rs

1/*
2 * Copyright (c) 2026 DevEtte.
3 *
4 * This project is dual-licensed under both the MIT License and the
5 * Apache License, Version 2.0 (the "License"). You may not use this
6 * file except in compliance with one of these licenses.
7 *
8 * You may obtain a copy of the Licenses at:
9 * - MIT: https://opensource.org
10 * - Apache 2.0: http://apache.org
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18
19//! Strict JSON parser implementation carrying byte-accurate spans for AST nodes.
20
21use super::utils::line_col_to_byte_offset;
22use crate::json_node::{JsonNode, KeyValuePair};
23use crate::types::{Diagnostic, Span};
24
25/// Strict parsing: Fails entirely if the JSON is invalid.
26/// Returns the parsed tree or a list of diagnostic errors.
27/// Primarily used for final validation.
28///
29/// # Arguments
30///
31/// * `input` - The raw JSON string slice to parse.
32///
33/// # Returns
34///
35/// * `Ok(JsonNode)` - The parsed JSON abstract syntax tree (AST) on successful parse.
36/// * `Err(Vec<Diagnostic>)` - A list of syntax or structural errors found during parsing.
37pub fn parse(input: &str) -> Result<JsonNode, Vec<Diagnostic>> {
38    let parser_opts = crate::settings::get_settings().parser;
39    let run_serde_validation = !parser_opts.allow_comments && !parser_opts.allow_trailing_commas;
40
41    if run_serde_validation {
42        // 1. Validate with serde_json to ensure standard compliance
43        if let Err(err) = serde_json::from_str::<serde_json::Value>(input) {
44            let line = err.line();
45            let col = err.column();
46            let offset = line_col_to_byte_offset(input, line, col);
47            let diag = Diagnostic {
48                span: Span {
49                    start: offset,
50                    end: (offset + 1).min(input.len()),
51                },
52                message: err.to_string(),
53            };
54            return Err(vec![diag]);
55        }
56    }
57
58    // 2. Parse with our hand-rolled parser to build the AST with correct spans
59    let mut parser = Parser::new(input, parser_opts);
60
61    match parser.parse_value() {
62        Ok(node) => {
63            parser.skip_whitespace();
64            if parser.cursor < parser.input.len() {
65                Err(vec![parser.error(
66                    parser.cursor,
67                    "Unexpected trailing characters after JSON value",
68                )])
69            } else {
70                Ok(node)
71            }
72        }
73        Err(diag) => Err(vec![diag]),
74    }
75}
76
77/// A stateful recursive-descent parser for strict JSON documents.
78/// Keeps track of byte offset locations to generate AST nodes with accurate `Span` info.
79struct Parser<'a> {
80    /// The input bytes slice of the JSON document.
81    input: &'a [u8],
82    /// The original input string slice for number parsing and error reporting.
83    input_str: &'a str,
84    /// The current byte offset cursor in the input.
85    cursor: usize,
86    /// Cached parser options.
87    opts: crate::types::ParserOptions,
88}
89
90impl<'a> Parser<'a> {
91    /// Creates a new Parser instance for the given JSON input.
92    pub(crate) fn new(input: &'a str, opts: crate::types::ParserOptions) -> Self {
93        Parser {
94            input: input.as_bytes(),
95            input_str: input,
96            cursor: 0,
97            opts,
98        }
99    }
100
101    #[cfg(test)]
102    pub(crate) fn new_test(input: &'a str) -> Self {
103        Self::new(input, crate::types::ParserOptions::default())
104    }
105
106    /// Returns the character byte at the current cursor, or `None` if EOF is reached.
107    fn peek(&self) -> Option<u8> {
108        if self.cursor < self.input.len() {
109            Some(self.input[self.cursor])
110        } else {
111            None
112        }
113    }
114
115    /// Returns the character byte at one position ahead of the current cursor, or `None` if EOF is reached.
116    fn peek_next(&self) -> Option<u8> {
117        if self.cursor + 1 < self.input.len() {
118            Some(self.input[self.cursor + 1])
119        } else {
120            None
121        }
122    }
123
124    /// Advances the cursor by one byte.
125    fn advance(&mut self) {
126        if self.cursor < self.input.len() {
127            self.cursor += 1;
128        }
129    }
130
131    /// Skips any ASCII whitespace characters (spaces, tabs, newlines, carriage returns)
132    /// and single-line/multi-line comments if they are allowed in configuration.
133    fn skip_whitespace(&mut self) {
134        let allow_comments = self.opts.allow_comments;
135        loop {
136            let start = self.cursor;
137            // 1. Skip standard whitespace
138            while let Some(b) = self.peek() {
139                if b == b' ' || b == b'\t' || b == b'\n' || b == b'\r' {
140                    self.advance();
141                } else {
142                    break;
143                }
144            }
145            // 2. Skip comments if enabled
146            if allow_comments && self.peek() == Some(b'/') {
147                match self.peek_next() {
148                    Some(b'/') => {
149                        // Line comment: skip until newline or EOF
150                        self.advance(); // skip '/'
151                        self.advance(); // skip '/'
152                        while let Some(c) = self.peek() {
153                            if c == b'\n' {
154                                self.advance();
155                                break;
156                            }
157                            self.advance();
158                        }
159                        continue;
160                    }
161                    Some(b'*') => {
162                        // Block comment: skip until '*/' or EOF
163                        self.advance(); // skip '/'
164                        self.advance(); // skip '*'
165                        while let Some(c) = self.peek() {
166                            if c == b'*' && self.peek_next() == Some(b'/') {
167                                self.advance(); // skip '*'
168                                self.advance(); // skip '/'
169                                break;
170                            }
171                            self.advance();
172                        }
173                        continue;
174                    }
175                    _ => {}
176                }
177            }
178
179            if self.cursor == start {
180                break;
181            }
182        }
183    }
184
185    /// Helper to create a single-character `Diagnostic` error starting at the given position.
186    fn error(&self, pos: usize, message: impl Into<String>) -> Diagnostic {
187        let end = (pos + 1).min(self.input.len());
188        Diagnostic {
189            span: Span { start: pos, end },
190            message: message.into(),
191        }
192    }
193
194    /// Main entry point to parse a JSON value (null, bool, number, string, array, object).
195    fn parse_value(&mut self) -> Result<JsonNode, Diagnostic> {
196        self.skip_whitespace();
197        let start = self.cursor;
198        let b = match self.peek() {
199            Some(b) => b,
200            None => return Err(self.error(start, "Unexpected end of input")),
201        };
202
203        match b {
204            b'n' => self.parse_null(),
205            b't' | b'f' => self.parse_bool(),
206            b'"' => self.parse_string_node(),
207            b'[' => self.parse_array(),
208            b'{' => self.parse_object(),
209            b'-' | b'0'..=b'9' => self.parse_number(),
210            _ => Err(self.error(start, format!("Unexpected character '{}'", b as char))),
211        }
212    }
213
214    /// Parses a JSON null value.
215    fn parse_null(&mut self) -> Result<JsonNode, Diagnostic> {
216        let start = self.cursor;
217        if self.cursor + 4 <= self.input.len()
218            && &self.input[self.cursor..self.cursor + 4] == b"null"
219        {
220            self.cursor += 4;
221            Ok(JsonNode::Null(Span {
222                start,
223                end: self.cursor,
224            }))
225        } else {
226            Err(self.error(start, "Expected 'null'"))
227        }
228    }
229
230    /// Parses a JSON boolean value (true or false).
231    fn parse_bool(&mut self) -> Result<JsonNode, Diagnostic> {
232        let start = self.cursor;
233        if self.cursor + 4 <= self.input.len()
234            && &self.input[self.cursor..self.cursor + 4] == b"true"
235        {
236            self.cursor += 4;
237            Ok(JsonNode::Bool(
238                true,
239                Span {
240                    start,
241                    end: self.cursor,
242                },
243            ))
244        } else if self.cursor + 5 <= self.input.len()
245            && &self.input[self.cursor..self.cursor + 5] == b"false"
246        {
247            self.cursor += 5;
248            Ok(JsonNode::Bool(
249                false,
250                Span {
251                    start,
252                    end: self.cursor,
253                },
254            ))
255        } else {
256            Err(self.error(start, "Expected boolean value"))
257        }
258    }
259
260    /// Parses a raw string value, decoding escape characters and surrogate pairs,
261    /// and returns the decoded string and its source span.
262    fn parse_string_raw(&mut self) -> Result<(String, Span), Diagnostic> {
263        let start = self.cursor;
264        if self.peek() != Some(b'"') {
265            return Err(self.error(start, "Expected opening quote for string"));
266        }
267        self.advance(); // consume opening quote
268
269        let mut s = String::new();
270        while let Some(b) = self.peek() {
271            match b {
272                b'"' => {
273                    self.advance(); // consume closing quote
274                    return Ok((
275                        s,
276                        Span {
277                            start,
278                            end: self.cursor,
279                        },
280                    ));
281                }
282                b'\\' => {
283                    self.advance(); // consume backslash
284                    let esc = match self.peek() {
285                        Some(esc) => esc,
286                        None => return Err(self.error(self.cursor, "Unterminated string escape")),
287                    };
288                    self.advance(); // consume escape char
289                    match esc {
290                        b'"' => s.push('"'),
291                        b'\\' => s.push('\\'),
292                        b'/' => s.push('/'),
293                        b'b' => s.push('\x08'),
294                        b'f' => s.push('\x0c'),
295                        b'n' => s.push('\n'),
296                        b'r' => s.push('\r'),
297                        b't' => s.push('\t'),
298                        b'u' => {
299                            if self.cursor + 4 > self.input.len() {
300                                return Err(
301                                    self.error(self.cursor, "Invalid unicode escape sequence")
302                                );
303                            }
304                            let hex_str =
305                                std::str::from_utf8(&self.input[self.cursor..self.cursor + 4])
306                                    .map_err(|_| {
307                                        self.error(self.cursor, "Invalid utf-8 in unicode escape")
308                                    })?;
309                            let code_point = u16::from_str_radix(hex_str, 16).map_err(|_| {
310                                self.error(self.cursor, "Invalid hex in unicode escape")
311                            })?;
312                            self.cursor += 4;
313
314                            if (0xD800..=0xDBFF).contains(&code_point) {
315                                if self.cursor + 6 <= self.input.len()
316                                    && &self.input[self.cursor..self.cursor + 2] == b"\\u"
317                                {
318                                    self.cursor += 2;
319                                    let low_hex_str = std::str::from_utf8(
320                                        &self.input[self.cursor..self.cursor + 4],
321                                    )
322                                    .map_err(|_| {
323                                        self.error(self.cursor, "Invalid utf-8 in low surrogate")
324                                    })?;
325                                    let low_code_point = u16::from_str_radix(low_hex_str, 16)
326                                        .map_err(|_| {
327                                            self.error(self.cursor, "Invalid hex in low surrogate")
328                                        })?;
329                                    self.cursor += 4;
330                                    if (0xDC00..=0xDFFF).contains(&low_code_point) {
331                                        let utf32 = (((code_point - 0xD800) as u32) << 10)
332                                            + (low_code_point - 0xDC00) as u32
333                                            + 0x10000;
334                                        if let Some(c) = std::char::from_u32(utf32) {
335                                            s.push(c);
336                                        } else {
337                                            return Err(self.error(
338                                                self.cursor - 12,
339                                                "Invalid surrogate pair",
340                                            ));
341                                        }
342                                    } else {
343                                        return Err(self.error(
344                                            self.cursor - 6,
345                                            "Expected low surrogate after high surrogate",
346                                        ));
347                                    }
348                                } else {
349                                    return Err(self.error(
350                                        self.cursor,
351                                        "Expected low surrogate after high surrogate",
352                                    ));
353                                }
354                            } else if (0xDC00..=0xDFFF).contains(&code_point) {
355                                return Err(self.error(
356                                    self.cursor - 6,
357                                    "Unexpected low surrogate without high surrogate",
358                                ));
359                            } else {
360                                if let Some(c) = std::char::from_u32(code_point as u32) {
361                                    s.push(c);
362                                } else {
363                                    return Err(
364                                        self.error(self.cursor - 6, "Invalid unicode code point")
365                                    );
366                                }
367                            }
368                        }
369                        _ => {
370                            return Err(self.error(
371                                self.cursor - 1,
372                                format!("Invalid escape character '{}'", esc as char),
373                            ));
374                        }
375                    }
376                }
377                b @ 0..=0x1f => {
378                    return Err(self.error(self.cursor, "Control characters must be escaped"));
379                }
380                _ => {
381                    let tail = &self.input_str[self.cursor..];
382                    let c = match tail.chars().next() {
383                        Some(ch) => ch,
384                        None => return Err(self.error(self.cursor, "Unexpected EOF")),
385                    };
386                    self.cursor += c.len_utf8();
387                    s.push(c);
388                }
389            }
390        }
391        Err(self.error(start, "Unterminated string"))
392    }
393
394    /// Parses a JSON string value.
395    fn parse_string_node(&mut self) -> Result<JsonNode, Diagnostic> {
396        let (s, span) = self.parse_string_raw()?;
397        Ok(JsonNode::String(s, span))
398    }
399
400    /// Parses a JSON number value.
401    fn parse_number(&mut self) -> Result<JsonNode, Diagnostic> {
402        let start = self.cursor;
403
404        if self.peek() == Some(b'-') {
405            self.advance();
406        }
407
408        match self.peek() {
409            Some(b'0') => {
410                self.advance();
411            }
412            Some(b) if b.is_ascii_digit() => {
413                while let Some(next_b) = self.peek() {
414                    if next_b.is_ascii_digit() {
415                        self.advance();
416                    } else {
417                        break;
418                    }
419                }
420            }
421            _ => return Err(self.error(start, "Expected digit for number")),
422        }
423
424        if self.peek() == Some(b'.') {
425            self.advance();
426            while let Some(next_b) = self.peek() {
427                if next_b.is_ascii_digit() {
428                    self.advance();
429                } else {
430                    break;
431                }
432            }
433        }
434
435        if let Some(b'e' | b'E') = self.peek() {
436            self.advance();
437            if let Some(b'+' | b'-') = self.peek() {
438                self.advance();
439            }
440            while let Some(next_b) = self.peek() {
441                if next_b.is_ascii_digit() {
442                    self.advance();
443                } else {
444                    break;
445                }
446            }
447        }
448
449        let end = self.cursor;
450        let span = Span { start, end };
451        let raw_str = self.input_str[start..end].to_string();
452
453        let val: f64 = raw_str.parse().unwrap_or(0.0);
454
455        Ok(JsonNode::Number(val, raw_str, span))
456    }
457
458    /// Parses a JSON array value.
459    fn parse_array(&mut self) -> Result<JsonNode, Diagnostic> {
460        let start = self.cursor;
461        if self.peek() != Some(b'[') {
462            return Err(self.error(start, "Expected '['"));
463        }
464        self.advance(); // consume '['
465
466        self.skip_whitespace();
467        if self.peek() == Some(b']') {
468            self.advance(); // consume ']'
469            return Ok(JsonNode::Array(
470                vec![],
471                Span {
472                    start,
473                    end: self.cursor,
474                },
475            ));
476        }
477
478        let mut elements = Vec::new();
479        loop {
480            let val = self.parse_value()?;
481            elements.push(val);
482
483            self.skip_whitespace();
484            match self.peek() {
485                Some(b',') => {
486                    self.advance();
487                    self.skip_whitespace();
488                    if self.peek() == Some(b']') {
489                        if !self.opts.allow_trailing_commas {
490                            return Err(
491                                self.error(self.cursor, "Trailing commas are not allowed in JSON")
492                            );
493                        }
494                        self.advance();
495                        break;
496                    }
497                }
498                Some(b']') => {
499                    self.advance();
500                    break;
501                }
502                Some(b) => {
503                    return Err(self.error(
504                        self.cursor,
505                        format!(
506                            "Expected ',' or ']' after array element, found '{}'",
507                            b as char
508                        ),
509                    ));
510                }
511                None => {
512                    return Err(self.error(self.cursor, "Unterminated array"));
513                }
514            }
515        }
516
517        Ok(JsonNode::Array(
518            elements,
519            Span {
520                start,
521                end: self.cursor,
522            },
523        ))
524    }
525
526    /// Parses a JSON object value.
527    fn parse_object(&mut self) -> Result<JsonNode, Diagnostic> {
528        let start = self.cursor;
529        if self.peek() != Some(b'{') {
530            return Err(self.error(start, "Expected '{'"));
531        }
532        self.advance(); // consume '{'
533
534        self.skip_whitespace();
535        if self.peek() == Some(b'}') {
536            self.advance(); // consume '}'
537            return Ok(JsonNode::Object(
538                vec![],
539                Span {
540                    start,
541                    end: self.cursor,
542                },
543            ));
544        }
545
546        let mut pairs = Vec::new();
547        loop {
548            self.skip_whitespace();
549            let key_start = self.cursor;
550            if self.peek() != Some(b'"') {
551                return Err(self.error(key_start, "Expected string key in object"));
552            }
553            let (key, _) = self.parse_string_raw()?;
554
555            self.skip_whitespace();
556            let colon_pos = self.cursor;
557            if self.peek() != Some(b':') {
558                return Err(self.error(colon_pos, "Expected ':' after key"));
559            }
560            self.advance(); // consume ':'
561
562            let val = self.parse_value()?;
563            pairs.push(KeyValuePair { key, value: val });
564
565            self.skip_whitespace();
566            match self.peek() {
567                Some(b',') => {
568                    self.advance();
569                    self.skip_whitespace();
570                    if self.peek() == Some(b'}') {
571                        if !self.opts.allow_trailing_commas {
572                            return Err(
573                                self.error(self.cursor, "Trailing commas are not allowed in JSON")
574                            );
575                        }
576                        self.advance();
577                        break;
578                    }
579                }
580                Some(b'}') => {
581                    self.advance();
582                    break;
583                }
584                Some(b) => {
585                    return Err(self.error(
586                        self.cursor,
587                        format!(
588                            "Expected ',' or '}}' after object member, found '{}'",
589                            b as char
590                        ),
591                    ));
592                }
593                None => {
594                    return Err(self.error(self.cursor, "Unterminated object"));
595                }
596            }
597        }
598
599        Ok(JsonNode::Object(
600            pairs,
601            Span {
602                start,
603                end: self.cursor,
604            },
605        ))
606    }
607}
608
609#[cfg(test)]
610mod private_tests {
611    use super::*;
612
613    /// **Test Case**: Trailing Characters Check
614    ///
615    /// ### Description
616    /// Verifies that the parser parses a valid JSON value but detects trailing unparsed characters.
617    ///
618    /// ### Test Procedure
619    /// 1. Initialize `Parser` with `"123 abc"`.
620    /// 2. Call `parse_value()` to parse the number `123`.
621    /// 3. Assert that the cursor has not reached the end of the input (trailing characters exist).
622    ///
623    /// ### Expected Result
624    /// The parser parses the number `123` successfully and reports remaining characters at the end.
625    #[test]
626    fn test_parser_trailing_characters() {
627        let mut parser = Parser::new_test("123 abc");
628        let res = parser.parse_value();
629        assert!(res.is_ok());
630        parser.skip_whitespace();
631        assert!(parser.cursor < parser.input.len());
632    }
633
634    /// **Test Case**: Unexpected End of Input Error
635    ///
636    /// ### Description
637    /// Verifies that parsing an empty input string produces an "Unexpected end of input" error.
638    ///
639    /// ### Test Procedure
640    /// 1. Initialize `Parser` with an empty string `""`.
641    /// 2. Call `parse_value()`.
642    ///
643    /// ### Expected Result
644    /// Returns `Err` with the message "Unexpected end of input".
645    #[test]
646    fn test_parser_unexpected_eof() {
647        let mut parser = Parser::new_test("");
648        let res = parser.parse_value();
649        assert!(res.is_err());
650        assert_eq!(res.unwrap_err().message, "Unexpected end of input");
651    }
652
653    /// **Test Case**: Unexpected Character Error
654    ///
655    /// ### Description
656    /// Verifies that an invalid JSON value starting character produces an "Unexpected character" error.
657    ///
658    /// ### Test Procedure
659    /// 1. Initialize `Parser` with `"x"`.
660    /// 2. Call `parse_value()`.
661    ///
662    /// ### Expected Result
663    /// Returns `Err` with the message "Unexpected character 'x'".
664    #[test]
665    fn test_parser_unexpected_char() {
666        let mut parser = Parser::new_test("x");
667        let res = parser.parse_value();
668        assert!(res.is_err());
669        assert_eq!(res.unwrap_err().message, "Unexpected character 'x'");
670    }
671
672    /// **Test Case**: Null Literal Parsing Error
673    ///
674    /// ### Description
675    /// Verifies that a malformed `null` literal results in a parsing error.
676    ///
677    /// ### Test Procedure
678    /// 1. Initialize `Parser` with `"nula"`.
679    /// 2. Call `parse_value()`.
680    ///
681    /// ### Expected Result
682    /// Returns `Err` with the message "Expected 'null'".
683    #[test]
684    fn test_parser_null_error() {
685        let mut parser = Parser::new_test("nula");
686        let res = parser.parse_value();
687        assert!(res.is_err());
688        assert_eq!(res.unwrap_err().message, "Expected 'null'");
689    }
690
691    /// **Test Case**: Boolean Literal Parsing Error
692    ///
693    /// ### Description
694    /// Verifies that malformed boolean literals result in parsing errors.
695    ///
696    /// ### Test Procedure
697    /// 1. Initialize `Parser` with `"truf"` and `"falz"`.
698    /// 2. Call `parse_value()` on both.
699    ///
700    /// ### Expected Result
701    /// Both return `Err` with the message "Expected boolean value".
702    #[test]
703    fn test_parser_bool_error() {
704        let mut parser = Parser::new_test("truf");
705        let res = parser.parse_value();
706        assert!(res.is_err());
707        assert_eq!(res.unwrap_err().message, "Expected boolean value");
708
709        let mut parser = Parser::new_test("falz");
710        let res = parser.parse_value();
711        assert!(res.is_err());
712        assert_eq!(res.unwrap_err().message, "Expected boolean value");
713    }
714
715    /// **Test Case**: String Literal Parsing Errors
716    ///
717    /// ### Description
718    /// Verifies that various malformed string literals (unterminated, unescaped controls, invalid escape sequences) produce correct error messages.
719    ///
720    /// ### Test Procedure
721    /// 1. Test unterminated string `"\"hello"`.
722    /// 2. Test unescaped control character `"\u{08}"`.
723    /// 3. Test invalid escape character `"\x"`.
724    /// 4. Test unterminated string escape `"\`.
725    /// 5. Test invalid unicode escape length `"\u1"`.
726    /// 6. Test invalid hex character in unicode escape `"\u123g"`.
727    /// 7. Test missing low surrogate after high surrogate `"\uD800"`.
728    /// 8. Test invalid low surrogate token after high surrogate `"\uD800\u1234"`.
729    /// 9. Test low surrogate without preceding high surrogate `"\uDC00"`.
730    ///
731    /// ### Expected Result
732    /// All cases return `Err` with their respective parsing/syntax error messages.
733    #[test]
734    fn test_parser_string_errors() {
735        let mut parser = Parser::new_test("\"hello");
736        assert_eq!(
737            parser.parse_value().unwrap_err().message,
738            "Unterminated string"
739        );
740
741        let mut parser = Parser::new_test("\"\u{08}\"");
742        assert_eq!(
743            parser.parse_value().unwrap_err().message,
744            "Control characters must be escaped"
745        );
746
747        let mut parser = Parser::new_test("\"\\x\"");
748        assert_eq!(
749            parser.parse_value().unwrap_err().message,
750            "Invalid escape character 'x'"
751        );
752
753        let mut parser = Parser::new_test("\"\\");
754        assert_eq!(
755            parser.parse_value().unwrap_err().message,
756            "Unterminated string escape"
757        );
758
759        let mut parser = Parser::new_test("\"\\u1\"");
760        assert_eq!(
761            parser.parse_value().unwrap_err().message,
762            "Invalid unicode escape sequence"
763        );
764
765        let mut parser = Parser::new_test("\"\\u123g\"");
766        assert_eq!(
767            parser.parse_value().unwrap_err().message,
768            "Invalid hex in unicode escape"
769        );
770
771        let mut parser = Parser::new_test("\"\\uD800\"");
772        assert_eq!(
773            parser.parse_value().unwrap_err().message,
774            "Expected low surrogate after high surrogate"
775        );
776
777        let mut parser = Parser::new_test("\"\\uD800\\u1234\"");
778        assert_eq!(
779            parser.parse_value().unwrap_err().message,
780            "Expected low surrogate after high surrogate"
781        );
782
783        let mut parser = Parser::new_test("\"\\uDC00\"");
784        assert_eq!(
785            parser.parse_value().unwrap_err().message,
786            "Unexpected low surrogate without high surrogate"
787        );
788    }
789
790    /// **Test Case**: Number Parsing Errors
791    ///
792    /// ### Description
793    /// Verifies that invalid number formats (e.g., negative sign with no digits) result in a parsing error.
794    ///
795    /// ### Test Procedure
796    /// 1. Initialize `Parser` with `"-"`.
797    /// 2. Call `parse_value()`.
798    ///
799    /// ### Expected Result
800    /// Returns `Err` with the message "Expected digit for number".
801    #[test]
802    fn test_parser_number_errors() {
803        let mut parser = Parser::new_test("-");
804        assert_eq!(
805            parser.parse_value().unwrap_err().message,
806            "Expected digit for number"
807        );
808    }
809
810    /// **Test Case**: Array Parsing Errors
811    ///
812    /// ### Description
813    /// Verifies error detection for malformed array declarations (unterminated arrays, trailing commas, missing separators).
814    ///
815    /// ### Test Procedure
816    /// 1. Test unterminated array `"[1"`.
817    /// 2. Test trailing comma `"[1, ]"`.
818    /// 3. Test missing separator `"[1 2]"`.
819    ///
820    /// ### Expected Result
821    /// All cases return `Err` with their respective parsing/syntax error messages.
822    #[test]
823    fn test_parser_array_errors() {
824        let mut parser = Parser::new_test("[1");
825        assert_eq!(
826            parser.parse_value().unwrap_err().message,
827            "Unterminated array"
828        );
829
830        let mut parser = Parser::new_test("[1, ]");
831        assert_eq!(
832            parser.parse_value().unwrap_err().message,
833            "Trailing commas are not allowed in JSON"
834        );
835
836        let mut parser = Parser::new_test("[1 2]");
837        assert_eq!(
838            parser.parse_value().unwrap_err().message,
839            "Expected ',' or ']' after array element, found '2'"
840        );
841    }
842
843    /// **Test Case**: Object Parsing Errors
844    ///
845    /// ### Description
846    /// Verifies error detection for malformed object declarations (non-string keys, missing colons, trailing commas, missing separators, unterminated objects).
847    ///
848    /// ### Test Procedure
849    /// 1. Test non-string key `"{1: 2}"`.
850    /// 2. Test missing colon `{"key" 1}`.
851    /// 3. Test trailing comma `{"key": 1, }`.
852    /// 4. Test missing separator `{"key": 1 "other": 2}`.
853    /// 5. Test unterminated object `{"key": 1`.
854    ///
855    /// ### Expected Result
856    /// All cases return `Err` with their respective parsing/syntax error messages.
857    #[test]
858    fn test_parser_object_errors() {
859        let mut parser = Parser::new_test("{1: 2}");
860        assert_eq!(
861            parser.parse_value().unwrap_err().message,
862            "Expected string key in object"
863        );
864
865        let mut parser = Parser::new_test("{\"key\" 1}");
866        assert_eq!(
867            parser.parse_value().unwrap_err().message,
868            "Expected ':' after key"
869        );
870
871        let mut parser = Parser::new_test("{\"key\": 1, }");
872        assert_eq!(
873            parser.parse_value().unwrap_err().message,
874            "Trailing commas are not allowed in JSON"
875        );
876
877        let mut parser = Parser::new_test("{\"key\": 1 \"other\": 2}");
878        assert_eq!(
879            parser.parse_value().unwrap_err().message,
880            "Expected ',' or '}' after object member, found '\"'"
881        );
882
883        let mut parser = Parser::new_test("{\"key\": 1");
884        assert_eq!(
885            parser.parse_value().unwrap_err().message,
886            "Unterminated object"
887        );
888    }
889
890    /// **Test Case**: Offset Mapping Edge Cases
891    ///
892    /// ### Description
893    /// Validates the robustness of the coordinate-to-byte-offset mapping utility
894    /// under common boundary inputs.
895    ///
896    /// ### Test Procedure
897    /// 1. Query an empty input string with line 0, column 0.
898    /// 2. Query a valid multiline string with coordinates referencing the character 'e'.
899    /// 3. Query an out-of-bounds line and column number.
900    ///
901    /// ### Expected Result
902    /// 1. Line 0 returns 0.
903    /// 2. Valid coordinates return the exact byte offset of the character 'e' (5).
904    /// 3. Out-of-bounds queries fall back gracefully to the total input string length.
905    #[test]
906    fn test_line_col_to_byte_offset_edge_cases() {
907        assert_eq!(line_col_to_byte_offset("", 0, 0), 0);
908        assert_eq!(line_col_to_byte_offset("abc\ndef\n", 2, 2), 5); // 'e' is at index 5
909        assert_eq!(line_col_to_byte_offset("abc", 5, 5), 3); // out of bounds
910    }
911
912    /// **Test Case**: String Escape Decoding
913    ///
914    /// ### Description
915    /// Verifies that all standard character escapes (quote, backslash, slash, backspace, formfeed, newline, carriage return, tab) are correctly decoded.
916    ///
917    /// ### Test Procedure
918    /// 1. Initialize `Parser` with a string containing all escaped control characters: `\"\\\"\\\\\\/\\b\\f\\n\\r\\t\"`.
919    /// 2. Call `parse_value()`.
920    ///
921    /// ### Expected Result
922    /// Returns `JsonNode::String` containing the correct unescaped string and span.
923    #[test]
924    fn test_parser_valid_escapes() {
925        let mut parser = Parser::new_test("\"\\\"\\\\\\/\\b\\f\\n\\r\\t\"");
926        let res = parser.parse_value().unwrap();
927        assert_eq!(
928            res,
929            JsonNode::String(
930                "\"\\/\x08\x0c\n\r\t".to_string(),
931                Span { start: 0, end: 18 }
932            )
933        );
934    }
935
936    /// **Test Case**: Unicode Surrogate Pair Decoding
937    ///
938    /// ### Description
939    /// Verifies that Unicode surrogate pairs (e.g., `\uD83D\uDE00` representing 😀) are successfully parsed and decoded into a UTF-8 character.
940    ///
941    /// ### Test Procedure
942    /// 1. Initialize `Parser` with high and low surrogates: `\"\\uD83D\\uDE00\"`.
943    /// 2. Call `parse_value()`.
944    ///
945    /// ### Expected Result
946    /// Returns `JsonNode::String` containing "😀" and span `0..14`.
947    #[test]
948    fn test_parser_surrogate_pair() {
949        let mut parser = Parser::new_test("\"\\uD83D\\uDE00\"");
950        let res = parser.parse_value().unwrap();
951        assert_eq!(
952            res,
953            JsonNode::String("😀".to_string(), Span { start: 0, end: 14 })
954        );
955    }
956
957    /// **Test Case**: Number Formats Parsing
958    ///
959    /// ### Description
960    /// Verifies successful parsing of various valid numeric formats (integers, decimals, and scientific exponents).
961    ///
962    /// ### Test Procedure
963    /// 1. Test parsing `"0"`.
964    /// 2. Test parsing `"0.1"`.
965    /// 3. Test parsing `"123e4"`.
966    ///
967    /// ### Expected Result
968    /// All cases return correct `JsonNode::Number` containing the correct float value, raw text representation, and span.
969    #[test]
970    fn test_parser_numbers() {
971        let mut parser = Parser::new_test("0");
972        assert_eq!(
973            parser.parse_value().unwrap(),
974            JsonNode::Number(0.0, "0".to_string(), Span { start: 0, end: 1 })
975        );
976
977        let mut parser = Parser::new_test("0.1");
978        assert_eq!(
979            parser.parse_value().unwrap(),
980            JsonNode::Number(0.1, "0.1".to_string(), Span { start: 0, end: 3 })
981        );
982
983        let mut parser = Parser::new_test("123e4 ");
984        assert_eq!(
985            parser.parse_value().unwrap(),
986            JsonNode::Number(1230000.0, "123e4".to_string(), Span { start: 0, end: 5 })
987        );
988    }
989
990    /// **Test Case**: Empty Array and Object Parsing
991    ///
992    /// ### Description
993    /// Verifies that empty arrays `[]` and empty objects `{}` are correctly parsed with precise spans.
994    ///
995    /// ### Test Procedure
996    /// 1. Test parsing `"[]"`.
997    /// 2. Test parsing `"{}"`.
998    ///
999    /// ### Expected Result
1000    /// Returns correct empty container nodes with spans starting at 0 and ending at 2.
1001    #[test]
1002    fn test_parser_empty_array_and_object() {
1003        let mut parser = Parser::new_test("[]");
1004        assert_eq!(
1005            parser.parse_value().unwrap(),
1006            JsonNode::Array(vec![], Span { start: 0, end: 2 })
1007        );
1008
1009        let mut parser = Parser::new_test("{}");
1010        assert_eq!(
1011            parser.parse_value().unwrap(),
1012            JsonNode::Object(vec![], Span { start: 0, end: 2 })
1013        );
1014    }
1015}