oa_forge_parser/
resolver.rs1use std::collections::HashSet;
2
3use crate::openapi::{Components, SchemaOrRef};
4
5pub fn resolve_ref<'a>(
7 ref_path: &str,
8 components: Option<&'a Components>,
9) -> Option<&'a SchemaOrRef> {
10 let parts: Vec<&str> = ref_path
11 .trim_start_matches('#')
12 .trim_start_matches('/')
13 .split('/')
14 .collect();
15
16 match parts.as_slice() {
17 ["components", "schemas", name] => components?.schemas.get(*name),
18 _ => None,
19 }
20}
21
22pub fn detect_circular_refs(
24 schema: &SchemaOrRef,
25 components: Option<&Components>,
26 visited: &mut HashSet<String>,
27) -> bool {
28 match schema {
29 SchemaOrRef::Ref { ref_path } => {
30 if visited.contains(ref_path) {
31 return true;
32 }
33 visited.insert(ref_path.clone());
34 if let Some(resolved) = resolve_ref(ref_path, components) {
35 let is_circular = detect_circular_refs(resolved, components, visited);
36 visited.remove(ref_path);
37 is_circular
38 } else {
39 visited.remove(ref_path);
40 false
41 }
42 }
43 SchemaOrRef::Schema(schema) => {
44 for prop in schema.properties.values() {
45 if detect_circular_refs(prop, components, visited) {
46 return true;
47 }
48 }
49 if let Some(items) = &schema.items
50 && detect_circular_refs(items, components, visited)
51 {
52 return true;
53 }
54 if let Some(all_of) = &schema.all_of {
55 for s in all_of {
56 if detect_circular_refs(s, components, visited) {
57 return true;
58 }
59 }
60 }
61 if let Some(one_of) = &schema.one_of {
62 for s in one_of {
63 if detect_circular_refs(s, components, visited) {
64 return true;
65 }
66 }
67 }
68 false
69 }
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76 use crate::parse;
77
78 #[test]
79 fn resolve_simple_ref() {
80 let yaml = r#"
81openapi: "3.0.3"
82info:
83 title: Test
84 version: "1.0.0"
85paths: {}
86components:
87 schemas:
88 Pet:
89 type: object
90 properties:
91 name:
92 type: string
93"#;
94 let spec = parse(yaml).unwrap();
95 let result = resolve_ref("#/components/schemas/Pet", spec.components.as_ref());
96 assert!(result.is_some());
97 }
98
99 #[test]
100 fn detect_self_reference() {
101 let yaml = r##"
102openapi: "3.0.3"
103info:
104 title: Test
105 version: "1.0.0"
106paths: {}
107components:
108 schemas:
109 Node:
110 type: object
111 properties:
112 child:
113 $ref: "#/components/schemas/Node"
114"##;
115 let spec = parse(yaml).unwrap();
116 let schema = spec
117 .components
118 .as_ref()
119 .unwrap()
120 .schemas
121 .get("Node")
122 .unwrap();
123 let mut visited = HashSet::new();
124 assert!(detect_circular_refs(
125 schema,
126 spec.components.as_ref(),
127 &mut visited
128 ));
129 }
130}