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