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}