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}