json_schema_rs/json_schema/
ref_resolver.rs1use crate::json_schema::JsonSchema;
11use std::collections::HashSet;
12
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum RefResolutionError {
15 UnsupportedRef { ref_str: String },
17 UnsupportedFragment { ref_str: String },
19 DefsMissing { ref_str: String },
21 DefinitionsMissing { ref_str: String },
23 DefNotFound { ref_str: String, name: String },
25 DefinitionNotFound { ref_str: String, name: String },
27 RefCycle { ref_str: String },
29 InvalidPointerEscape { ref_str: String },
31}
32
33#[derive(Debug, Clone, PartialEq, Eq, Hash)]
34pub enum ParsedRef {
35 Root,
36 Defs(String),
37 Definitions(String),
38}
39
40fn decode_json_pointer_segment(seg: &str, ref_str: &str) -> Result<String, RefResolutionError> {
41 if !seg.contains('~') {
42 return Ok(seg.to_string());
43 }
44
45 let mut out: String = String::with_capacity(seg.len());
47 let mut chars = seg.chars();
48 while let Some(c) = chars.next() {
49 if c != '~' {
50 out.push(c);
51 continue;
52 }
53 match chars.next() {
54 Some('0') => out.push('~'),
55 Some('1') => out.push('/'),
56 _ => {
57 return Err(RefResolutionError::InvalidPointerEscape {
58 ref_str: ref_str.to_string(),
59 });
60 }
61 }
62 }
63 Ok(out)
64}
65
66pub fn parse_ref(ref_str: &str) -> Result<ParsedRef, RefResolutionError> {
73 if ref_str.is_empty() || ref_str == "#" {
74 return Ok(ParsedRef::Root);
75 }
76 if !ref_str.starts_with('#') {
77 return Err(RefResolutionError::UnsupportedRef {
78 ref_str: ref_str.to_string(),
79 });
80 }
81 let frag = &ref_str[1..];
82 if frag.is_empty() {
83 return Ok(ParsedRef::Root);
84 }
85 if !frag.starts_with('/') {
86 return Err(RefResolutionError::UnsupportedFragment {
87 ref_str: ref_str.to_string(),
88 });
89 }
90
91 let mut parts = frag[1..].split('/');
93 let container = parts.next().unwrap_or_default();
94 let raw_name = parts.next().unwrap_or_default();
95 if container.is_empty() || raw_name.is_empty() || parts.next().is_some() {
97 return Err(RefResolutionError::UnsupportedFragment {
98 ref_str: ref_str.to_string(),
99 });
100 }
101
102 let name = decode_json_pointer_segment(raw_name, ref_str)?;
103 match container {
104 "$defs" => Ok(ParsedRef::Defs(name)),
105 "definitions" => Ok(ParsedRef::Definitions(name)),
106 _ => Err(RefResolutionError::UnsupportedFragment {
107 ref_str: ref_str.to_string(),
108 }),
109 }
110}
111
112pub fn resolve_ref<'a>(
119 root: &'a JsonSchema,
120 ref_str: &str,
121) -> Result<&'a JsonSchema, RefResolutionError> {
122 match parse_ref(ref_str)? {
123 ParsedRef::Root => Ok(root),
124 ParsedRef::Defs(name) => {
125 let defs = root
126 .defs
127 .as_ref()
128 .ok_or_else(|| RefResolutionError::DefsMissing {
129 ref_str: ref_str.to_string(),
130 })?;
131 let target = defs
132 .get(&name)
133 .ok_or_else(|| RefResolutionError::DefNotFound {
134 ref_str: ref_str.to_string(),
135 name,
136 })?;
137 Ok(target)
138 }
139 ParsedRef::Definitions(name) => {
140 let definitions = root.definitions.as_ref().ok_or_else(|| {
141 RefResolutionError::DefinitionsMissing {
142 ref_str: ref_str.to_string(),
143 }
144 })?;
145 let target =
146 definitions
147 .get(&name)
148 .ok_or_else(|| RefResolutionError::DefinitionNotFound {
149 ref_str: ref_str.to_string(),
150 name,
151 })?;
152 Ok(target)
153 }
154 }
155}
156
157pub fn resolve_schema_ref_transitive<'a>(
165 root: &'a JsonSchema,
166 schema: &'a JsonSchema,
167) -> Result<&'a JsonSchema, RefResolutionError> {
168 let mut current: &'a JsonSchema = schema;
169 let mut visited: HashSet<&'a str> = HashSet::new();
170
171 while let Some(ref_str) = current.ref_.as_deref() {
172 if visited.contains(ref_str) {
173 return Err(RefResolutionError::RefCycle {
174 ref_str: ref_str.to_string(),
175 });
176 }
177 visited.insert(ref_str);
178 current = resolve_ref(root, ref_str)?;
179 }
180
181 Ok(current)
182}
183
184#[cfg(test)]
185mod tests {
186 use super::{
187 ParsedRef, RefResolutionError, parse_ref, resolve_ref, resolve_schema_ref_transitive,
188 };
189 use crate::json_schema::JsonSchema;
190
191 #[test]
192 fn parse_ref_defs() {
193 let actual = parse_ref("#/$defs/Foo").unwrap();
194 let expected = ParsedRef::Defs("Foo".to_string());
195 assert_eq!(expected, actual);
196 }
197
198 #[test]
199 fn parse_ref_definitions() {
200 let actual = parse_ref("#/definitions/Foo").unwrap();
201 let expected = ParsedRef::Definitions("Foo".to_string());
202 assert_eq!(expected, actual);
203 }
204
205 #[test]
206 fn parse_ref_root_hash() {
207 let actual = parse_ref("#").unwrap();
208 let expected = ParsedRef::Root;
209 assert_eq!(expected, actual);
210 }
211
212 #[test]
213 fn parse_ref_root_empty_string() {
214 let actual = parse_ref("").unwrap();
215 let expected = ParsedRef::Root;
216 assert_eq!(expected, actual);
217 }
218
219 #[test]
220 fn parse_ref_unsupported_non_fragment() {
221 let actual = parse_ref("http://example.com/schema.json").unwrap_err();
222 let expected = RefResolutionError::UnsupportedRef {
223 ref_str: "http://example.com/schema.json".to_string(),
224 };
225 assert_eq!(expected, actual);
226 }
227
228 #[test]
229 fn parse_ref_unsupported_extra_segments() {
230 let actual = parse_ref("#/$defs/Foo/bar").unwrap_err();
231 let expected = RefResolutionError::UnsupportedFragment {
232 ref_str: "#/$defs/Foo/bar".to_string(),
233 };
234 assert_eq!(expected, actual);
235 }
236
237 #[test]
238 fn parse_ref_invalid_pointer_escape() {
239 let actual = parse_ref("#/$defs/Foo~").unwrap_err();
240 let expected = RefResolutionError::InvalidPointerEscape {
241 ref_str: "#/$defs/Foo~".to_string(),
242 };
243 assert_eq!(expected, actual);
244 }
245
246 #[test]
247 fn resolve_ref_defs_success() {
248 let root: JsonSchema = serde_json::from_str(
249 r#"{
250 "$defs": {
251 "Foo": { "type": "string", "title": "FooType" }
252 }
253}"#,
254 )
255 .unwrap();
256 let actual: &JsonSchema = resolve_ref(&root, "#/$defs/Foo").expect("resolve Foo");
257 let expected: JsonSchema = JsonSchema {
258 type_: Some("string".to_string()),
259 title: Some("FooType".to_string()),
260 ..Default::default()
261 };
262 assert_eq!(expected, *actual);
263 }
264
265 #[test]
266 fn resolve_ref_definitions_success() {
267 let root: JsonSchema = serde_json::from_str(
268 r#"{
269 "definitions": {
270 "Bar": { "type": "integer", "title": "BarType" }
271 }
272}"#,
273 )
274 .unwrap();
275 let actual: &JsonSchema = resolve_ref(&root, "#/definitions/Bar").expect("resolve Bar");
276 let expected: JsonSchema = JsonSchema {
277 type_: Some("integer".to_string()),
278 title: Some("BarType".to_string()),
279 ..Default::default()
280 };
281 assert_eq!(expected, *actual);
282 }
283
284 #[test]
285 fn resolve_ref_root_returns_root() {
286 let root: JsonSchema =
287 serde_json::from_str(r#"{"type":"object","properties":{"x":{"type":"string"}}}"#)
288 .unwrap();
289 let actual: &JsonSchema = resolve_ref(&root, "#").expect("resolve root");
290 let expected: &JsonSchema = &root;
291 assert_eq!(expected, actual);
292 }
293
294 #[test]
295 fn resolve_ref_decodes_pointer_segment() {
296 let root: JsonSchema = serde_json::from_str(
297 r#"{
298 "$defs": {
299 "Foo/bar": { "type": "string" }
300 }
301}"#,
302 )
303 .unwrap();
304 let actual: &JsonSchema = resolve_ref(&root, "#/$defs/Foo~1bar").expect("resolve Foo/bar");
305 let expected: JsonSchema = JsonSchema {
306 type_: Some("string".to_string()),
307 ..Default::default()
308 };
309 assert_eq!(expected, *actual);
310 }
311
312 #[test]
313 fn resolve_ref_missing_defs_errors() {
314 let root: JsonSchema =
315 serde_json::from_str(r#"{"type":"object","properties":{}}"#).unwrap();
316 let actual = resolve_ref(&root, "#/$defs/Foo").unwrap_err();
317 let expected = RefResolutionError::DefsMissing {
318 ref_str: "#/$defs/Foo".to_string(),
319 };
320 assert_eq!(expected, actual);
321 }
322
323 #[test]
324 fn resolve_ref_not_found_errors() {
325 let root: JsonSchema = serde_json::from_str(
326 r#"{"$defs":{"Bar":{"type":"string"}},"type":"object","properties":{}}"#,
327 )
328 .unwrap();
329 let actual = resolve_ref(&root, "#/$defs/Foo").unwrap_err();
330 let expected = RefResolutionError::DefNotFound {
331 ref_str: "#/$defs/Foo".to_string(),
332 name: "Foo".to_string(),
333 };
334 assert_eq!(expected, actual);
335 }
336
337 #[test]
338 fn resolve_schema_ref_transitive_cycle_errors() {
339 let root: JsonSchema = serde_json::from_str(
340 r##"{
341 "$defs": {
342 "A": { "$ref": "#/$defs/B" },
343 "B": { "$ref": "#/$defs/A" }
344 },
345 "$ref": "#/$defs/A"
346}"##,
347 )
348 .unwrap();
349 let actual = resolve_schema_ref_transitive(&root, &root).unwrap_err();
350 let expected = RefResolutionError::RefCycle {
351 ref_str: "#/$defs/A".to_string(),
352 };
353 assert_eq!(expected, actual);
354 }
355}