1mod ast;
4mod json_schema;
5pub(crate) mod regex;
6mod resources;
7
8pub use json_schema::{schema_to_ir, schema_to_ir_timed};
9pub(crate) use json_schema::{JSON_STRING_BODY, JSON_STRING_CODEPOINT};
10pub use regex::regex_to_ir;
11#[cfg(all(test, feature = "alloc-probe"))]
12pub(crate) use resources::discover_memory_for_probe;
13pub use resources::{SchemaRegistry, SchemaResourceLimits};
14
15use crate::error::CompileError;
16use crate::ir::{CompileOptions, SchemaIR};
17
18#[doc(hidden)]
19#[cfg(feature = "bench-internals")]
20#[derive(Clone, Copy, Debug)]
21pub struct SchemaSetProfile {
22 pub registry_insert_ns: u64,
23 pub document_parse_ns: u64,
24 pub resource_uri_anchor_index_ns: u64,
25 pub resource_canonicalize_ns: u64,
26 pub reference_resolution_ns: u64,
27 pub graph_freeze_ns: u64,
28 pub ir_lowering_and_construction_validation_ns: u64,
29 pub ir_revalidation_ns: u64,
30 pub retained_graph_bytes: usize,
31 pub peak_resource_build_bytes: usize,
32}
33
34pub fn schema_to_ir_with_external_refs(
35 text: &str,
36 options: CompileOptions,
37 resolver: &rustc_hash::FxHashMap<String, String>,
38) -> Result<SchemaIR, CompileError> {
39 let limits = SchemaResourceLimits::default();
40 let mut registry = SchemaRegistry::new(limits);
41 for (uri, schema) in resolver {
42 registry.insert(uri, std::sync::Arc::<str>::from(schema.as_str()))?;
43 }
44 json_schema::schema_set_to_ir(text, None, options, limits, registry)
45}
46
47pub fn schema_to_ir_with_resources(
48 text: &str,
49 retrieval_uri: Option<&str>,
50 options: CompileOptions,
51 limits: SchemaResourceLimits,
52 registry: SchemaRegistry,
53) -> Result<SchemaIR, CompileError> {
54 json_schema::schema_set_to_ir(text, retrieval_uri, options, limits, registry)
55}
56
57#[doc(hidden)]
58#[cfg(feature = "bench-internals")]
59pub fn schema_to_ir_with_resources_profiled(
60 text: &str,
61 retrieval_uri: Option<&str>,
62 options: CompileOptions,
63 limits: SchemaResourceLimits,
64 registry: SchemaRegistry,
65) -> Result<(SchemaIR, SchemaSetProfile), CompileError> {
66 json_schema::schema_set_to_ir_profiled(text, retrieval_uri, options, limits, registry)
67}
68
69#[cfg(test)]
70mod resource_lowering_tests {
71 use super::*;
72
73 fn compile(root: &str, retrieval: Option<&str>, resources: &[(&str, &str)]) -> SchemaIR {
74 let limits = SchemaResourceLimits::default();
75 let mut registry = SchemaRegistry::new(limits);
76 for (uri, schema) in resources {
77 registry.insert(uri, *schema).expect("resource");
78 }
79 schema_to_ir_with_resources(root, retrieval, CompileOptions::default(), limits, registry)
80 .expect("schema set")
81 }
82
83 #[test]
84 fn external_static_root_pointer_anchor_and_transitive_refs_lower_once() {
85 let ir = compile(
86 r##"{
87 "type":"object",
88 "properties":{
89 "a":{"$ref":"defs.json#/$defs/value"},
90 "b":{"$ref":"defs.json#value"}
91 },
92 "required":["a","b"],
93 "additionalProperties":false
94 }"##,
95 Some("https://example.com/root.json"),
96 &[(
97 "https://example.com/defs.json",
98 r##"{"$defs":{"value":{"$anchor":"value","type":"integer","minimum":5}}}"##,
99 )],
100 );
101 assert_eq!(ir.resources().len(), 2);
102 assert!(crate::structured::try_accepts(
103 std::sync::Arc::new(ir.clone()),
104 br#"{"a":5,"b":6}"#
105 )
106 .expect("accept"));
107 assert!(
108 !crate::structured::try_accepts(std::sync::Arc::new(ir), br#"{"a":4,"b":6}"#)
109 .expect("reject")
110 );
111 }
112
113 #[test]
114 fn productive_external_mutual_recursion_is_not_a_resolver_cycle() {
115 let ir = compile(
116 r##"{"$ref":"a.json"}"##,
117 Some("https://example.com/root.json"),
118 &[
119 (
120 "https://example.com/a.json",
121 r##"{"type":"object","properties":{"b":{"$ref":"b.json"}},"additionalProperties":false}"##,
122 ),
123 (
124 "https://example.com/b.json",
125 r##"{"type":"object","properties":{"a":{"$ref":"a.json"}},"additionalProperties":false}"##,
126 ),
127 ],
128 );
129 assert!(
130 crate::structured::try_accepts(std::sync::Arc::new(ir), br#"{"b":{"a":{}}}"#)
131 .expect("accept")
132 );
133 }
134
135 #[test]
136 fn nested_id_changes_the_active_reference_base() {
137 let ir = compile(
138 r##"{
139 "$defs":{"nested":{"$id":"folder/child.json","$ref":"value.json"}},
140 "$ref":"folder/child.json"
141 }"##,
142 Some("https://example.com/root.json"),
143 &[(
144 "https://example.com/folder/value.json",
145 r##"{"type":"string"}"##,
146 )],
147 );
148 assert!(
149 crate::structured::try_accepts(std::sync::Arc::new(ir), br#""ok""#).expect("accept")
150 );
151 }
152
153 #[test]
154 fn registry_insertion_order_does_not_change_the_frozen_hash() {
155 fn build(reverse: bool) -> [u8; 32] {
156 let limits = SchemaResourceLimits::default();
157 let mut registry = SchemaRegistry::new(limits);
158 let resources = [
159 (
160 "https://example.com/a",
161 r##"{"$anchor":"a","type":"string"}"##,
162 ),
163 (
164 "https://example.com/b",
165 r##"{"$anchor":"b","type":"integer"}"##,
166 ),
167 ];
168 for index in if reverse { [1, 0] } else { [0, 1] } {
169 registry
170 .insert(resources[index].0, resources[index].1)
171 .expect("resource");
172 }
173 schema_to_ir_with_resources(
174 r##"{"allOf":[{"$ref":"https://example.com/a#a"},{"$ref":"https://example.com/b#b"}]}"##,
175 Some("https://example.com/root"),
176 CompileOptions::default(),
177 limits,
178 registry,
179 )
180 .expect("schema set")
181 .canonical_hash()
182 }
183 assert_eq!(build(false), build(true));
184 }
185
186 #[test]
187 fn anonymous_root_resolves_empty_pointer_and_plain_name_fragments() {
188 let ir = compile(
189 r##"{
190 "$defs":{"value":{"$anchor":"value","type":"integer"}},
191 "allOf":[{"$ref":"#/$defs/value"},{"$ref":"#value"}]
192 }"##,
193 None,
194 &[],
195 );
196 assert!(crate::structured::try_accepts(std::sync::Arc::new(ir.clone()), b"7").unwrap());
197 assert!(!crate::structured::try_accepts(std::sync::Arc::new(ir), br#""x""#).unwrap());
198 }
199}