Skip to main content

jsonette/parser/
mod.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//! The parsing module contains the strict and tolerant JSON parsers,
20//! as well as fast diagnostics syntax checking and parsing helper utilities.
21
22pub mod strict;
23pub mod tolerant;
24pub mod utils;
25
26pub use strict::parse;
27
28use crate::json_node::JsonNode;
29use crate::types::Diagnostic;
30
31/// Tolerant parsing: Attempts to build a partial AST even if errors are present.
32/// Useful for live IDE feedback while typing, preserving as much of the tree as possible.
33///
34/// # Arguments
35///
36/// * `input` - The raw JSON string slice to parse (potentially incomplete or invalid).
37///
38/// # Returns
39///
40/// A tuple containing:
41/// * `Option<JsonNode>` - The partial AST tree if any structure could be recovered.
42/// * `Vec<Diagnostic>` - A list of errors encountered during parsing.
43pub fn tolerant_parse(input: &str) -> (Option<JsonNode>, Vec<Diagnostic>) {
44    // We opted for a hand-rolled tolerant parser (Option B) instead of tree-sitter.
45    // While tree-sitter provides excellent error recovery, it introduces a C dependency
46    // which complicates the cross-platform UniFFI build (especially for iOS/Android targets),
47    // and slightly increases binary size. A minimal hand-rolled parser is sufficient for
48    // JSON and gives us full control over `JsonNode` span generation without an intermediate AST.
49    tolerant::parse(input)
50}
51
52/// Fast validation path: parses and returns only syntax or structural errors
53/// without fully allocating the resulting AST tree.
54///
55/// # Arguments
56///
57/// * `input` - The raw JSON string slice to validate.
58///
59/// # Returns
60///
61/// A list of `Diagnostic` errors found in the input. If the input is valid JSON, this is empty.
62pub fn diagnostics(input: &str) -> Vec<Diagnostic> {
63    // Fast validation path: for M0, we simply delegate to tolerant_parse
64    // and return the extracted diagnostics. Future performance optimizations
65    // can skip AST allocation entirely if necessary.
66    tolerant_parse(input).1
67}
68
69#[cfg(test)]
70mod stub_tests {
71    use super::*;
72
73    #[test]
74    fn test_tolerant_parse_is_implemented() {
75        let (node, _) = tolerant_parse("{}");
76        assert!(node.is_some());
77    }
78
79    /// **Test Case**: Fast Validation Returns Proper Diagnostics
80    ///
81    /// ### Description
82    /// Verifies that the fast `diagnostics` validation path correctly identifies
83    /// valid and invalid JSON document syntax.
84    ///
85    /// ### Test Procedure
86    /// 1. Call `diagnostics` on a valid JSON document `{}`.
87    /// 2. Call `diagnostics` on an invalid JSON document `{`.
88    ///
89    /// ### Expected Result
90    /// The valid document returns an empty `Vec<Diagnostic>`. The invalid document
91    /// returns a non-empty `Vec<Diagnostic>`.
92    #[test]
93    fn test_diagnostics() {
94        let diags = diagnostics("{}");
95        assert!(diags.is_empty());
96
97        let diags = diagnostics("{");
98        assert!(!diags.is_empty());
99    }
100}