1use crate::error::Result;
2use crate::generator::swagger_parser::{get_schema_name_from_ref, resolve_ref};
3use openapiv3::{OpenAPI, ReferenceOr, Schema, SchemaKind, Type};
4use std::collections::{HashMap, HashSet};
5
6#[allow(dead_code)]
7pub struct SchemaResolver {
8 openapi: OpenAPI,
9 resolved_cache: HashMap<String, ReferenceOr<Schema>>,
10 dependency_graph: HashMap<String, Vec<String>>,
11 circular_refs: HashSet<String>,
12}
13
14#[allow(dead_code)]
15impl SchemaResolver {
16 pub fn new(openapi: OpenAPI) -> Self {
17 Self {
18 openapi,
19 resolved_cache: HashMap::new(),
20 dependency_graph: HashMap::new(),
21 circular_refs: HashSet::new(),
22 }
23 }
24
25 pub fn build_dependency_graph(&mut self) -> Result<()> {
26 let schema_names: Vec<String> = if let Some(components) = &self.openapi.components {
27 components.schemas.keys().cloned().collect()
28 } else {
29 return Ok(());
30 };
31
32 for schema_name in schema_names {
33 self.extract_dependencies(&schema_name, &mut HashSet::new())?;
34 }
35 Ok(())
36 }
37
38 fn extract_dependencies(
39 &mut self,
40 schema_name: &str,
41 visited: &mut HashSet<String>,
42 ) -> Result<Vec<String>> {
43 if visited.contains(schema_name) {
44 self.circular_refs.insert(schema_name.to_string());
46 return Ok(vec![]);
47 }
48
49 if let Some(deps) = self.dependency_graph.get(schema_name) {
50 return Ok(deps.clone());
51 }
52
53 visited.insert(schema_name.to_string());
54 let mut dependencies = Vec::new();
55
56 if let Some(components) = &self.openapi.components {
57 if let Some(ReferenceOr::Item(schema)) = components.schemas.get(schema_name) {
58 dependencies.extend(self.extract_schema_dependencies(schema, visited)?);
59 }
60 }
61
62 visited.remove(schema_name);
63 self.dependency_graph
64 .insert(schema_name.to_string(), dependencies.clone());
65 Ok(dependencies)
66 }
67
68 fn extract_schema_dependencies(
69 &self,
70 schema: &Schema,
71 visited: &mut HashSet<String>,
72 ) -> Result<Vec<String>> {
73 let mut deps = Vec::new();
74
75 match &schema.schema_kind {
76 SchemaKind::Type(type_) => match type_ {
77 Type::Array(array) => {
78 if let Some(items) = &array.items {
79 if let ReferenceOr::Reference { reference } = items {
80 if let Some(ref_name) = get_schema_name_from_ref(reference) {
81 deps.push(ref_name.clone());
82 if !visited.contains(&ref_name) {
83 deps.extend(self.extract_schema_dependencies_from_ref(
84 &ref_name, visited,
85 )?);
86 }
87 }
88 } else if let ReferenceOr::Item(item_schema) = items {
89 deps.extend(self.extract_schema_dependencies(item_schema, visited)?);
90 }
91 }
92 }
93 Type::Object(object_type) => {
94 for (_, prop_schema_ref) in object_type.properties.iter() {
95 match prop_schema_ref {
96 ReferenceOr::Reference { reference } => {
97 if let Some(ref_name) = get_schema_name_from_ref(reference) {
98 deps.push(ref_name.clone());
99 if !visited.contains(&ref_name) {
100 deps.extend(self.extract_schema_dependencies_from_ref(
101 &ref_name, visited,
102 )?);
103 }
104 }
105 }
106 ReferenceOr::Item(prop_schema) => {
107 deps.extend(
108 self.extract_schema_dependencies(prop_schema, visited)?,
109 );
110 }
111 }
112 }
113 }
114 _ => {}
115 },
116 SchemaKind::OneOf { one_of, .. } => {
117 for item in one_of {
118 if let ReferenceOr::Reference { reference } = item {
119 if let Some(ref_name) = get_schema_name_from_ref(reference) {
120 deps.push(ref_name.clone());
121 if !visited.contains(&ref_name) {
122 deps.extend(
123 self.extract_schema_dependencies_from_ref(&ref_name, visited)?,
124 );
125 }
126 }
127 } else if let ReferenceOr::Item(item_schema) = item {
128 deps.extend(self.extract_schema_dependencies(item_schema, visited)?);
129 }
130 }
131 }
132 SchemaKind::AllOf { all_of, .. } => {
133 for item in all_of {
134 if let ReferenceOr::Reference { reference } = item {
135 if let Some(ref_name) = get_schema_name_from_ref(reference) {
136 deps.push(ref_name.clone());
137 if !visited.contains(&ref_name) {
138 deps.extend(
139 self.extract_schema_dependencies_from_ref(&ref_name, visited)?,
140 );
141 }
142 }
143 } else if let ReferenceOr::Item(item_schema) = item {
144 deps.extend(self.extract_schema_dependencies(item_schema, visited)?);
145 }
146 }
147 }
148 SchemaKind::AnyOf { any_of, .. } => {
149 for item in any_of {
150 if let ReferenceOr::Reference { reference } = item {
151 if let Some(ref_name) = get_schema_name_from_ref(reference) {
152 deps.push(ref_name.clone());
153 if !visited.contains(&ref_name) {
154 deps.extend(
155 self.extract_schema_dependencies_from_ref(&ref_name, visited)?,
156 );
157 }
158 }
159 } else if let ReferenceOr::Item(item_schema) = item {
160 deps.extend(self.extract_schema_dependencies(item_schema, visited)?);
161 }
162 }
163 }
164 _ => {}
165 }
166
167 Ok(deps)
168 }
169
170 fn extract_schema_dependencies_from_ref(
171 &self,
172 ref_name: &str,
173 visited: &mut HashSet<String>,
174 ) -> Result<Vec<String>> {
175 if visited.contains(ref_name) {
176 return Ok(vec![]);
177 }
178
179 visited.insert(ref_name.to_string());
180 let mut deps = vec![ref_name.to_string()];
181
182 if let Some(components) = &self.openapi.components {
183 if let Some(ReferenceOr::Item(schema)) = components.schemas.get(ref_name) {
184 deps.extend(self.extract_schema_dependencies(schema, visited)?);
185 }
186 }
187
188 visited.remove(ref_name);
189 Ok(deps)
190 }
191
192 pub fn resolve_schema_ref(&mut self, ref_path: &str) -> Result<ReferenceOr<Schema>> {
193 if let Some(cached) = self.resolved_cache.get(ref_path) {
194 return Ok(cached.clone());
195 }
196
197 let resolved = resolve_ref(&self.openapi, ref_path)?;
198 self.resolved_cache
199 .insert(ref_path.to_string(), resolved.clone());
200 Ok(resolved)
201 }
202
203 pub fn resolve_with_dependencies(&mut self, schema_name: &str) -> Result<Vec<String>> {
204 let mut all_schemas = vec![schema_name.to_string()];
205
206 if let Some(deps) = self.dependency_graph.get(schema_name) {
207 for dep in deps {
208 if !all_schemas.contains(dep) {
209 all_schemas.push(dep.clone());
210 }
211 }
212 }
213
214 Ok(all_schemas)
215 }
216
217 pub fn detect_circular_dependencies(&self) -> Result<Vec<Vec<String>>> {
218 let mut cycles = Vec::new();
219 let mut visited = HashSet::new();
220 let mut rec_stack = HashSet::new();
221
222 if let Some(components) = &self.openapi.components {
223 for schema_name in components.schemas.keys() {
224 if !visited.contains(schema_name) {
225 self.dfs_cycle_detection(
226 schema_name,
227 &mut visited,
228 &mut rec_stack,
229 &mut Vec::new(),
230 &mut cycles,
231 )?;
232 }
233 }
234 }
235
236 Ok(cycles)
237 }
238
239 fn dfs_cycle_detection(
240 &self,
241 schema_name: &str,
242 visited: &mut HashSet<String>,
243 rec_stack: &mut HashSet<String>,
244 path: &mut Vec<String>,
245 cycles: &mut Vec<Vec<String>>,
246 ) -> Result<()> {
247 visited.insert(schema_name.to_string());
248 rec_stack.insert(schema_name.to_string());
249 path.push(schema_name.to_string());
250
251 if let Some(deps) = self.dependency_graph.get(schema_name) {
252 for dep in deps {
253 if !visited.contains(dep) {
254 self.dfs_cycle_detection(dep, visited, rec_stack, path, cycles)?;
255 } else if rec_stack.contains(dep) {
256 let cycle_start = path.iter().position(|x| x == dep).unwrap_or(0);
258 cycles.push(path[cycle_start..].to_vec());
259 }
260 }
261 }
262
263 rec_stack.remove(schema_name);
264 path.pop();
265 Ok(())
266 }
267
268 pub fn is_circular(&self, schema_name: &str) -> bool {
269 self.circular_refs.contains(schema_name)
270 }
271
272 pub fn get_openapi(&self) -> &OpenAPI {
273 &self.openapi
274 }
275
276 pub fn classify_schema(&self, schema: &Schema) -> SchemaType {
277 match &schema.schema_kind {
278 SchemaKind::Type(type_) => match type_ {
279 Type::String(string_type) => {
280 if !string_type.enumeration.is_empty() {
281 let enum_values: Vec<String> = string_type
282 .enumeration
283 .iter()
284 .filter_map(|v| v.as_ref().cloned())
285 .collect();
286 if !enum_values.is_empty() {
287 return SchemaType::Enum {
288 values: enum_values,
289 };
290 }
291 }
292 SchemaType::Primitive(PrimitiveType::String)
293 }
294 Type::Number(_) => SchemaType::Primitive(PrimitiveType::Number),
295 Type::Integer(_) => SchemaType::Primitive(PrimitiveType::Integer),
296 Type::Boolean(_) => SchemaType::Primitive(PrimitiveType::Boolean),
297 Type::Array(_) => SchemaType::Array {
298 item_type: Box::new(SchemaType::Primitive(PrimitiveType::Any)),
299 },
300 Type::Object(_) => SchemaType::Object {
301 properties: HashMap::new(),
302 },
303 },
304 SchemaKind::OneOf { .. } => SchemaType::OneOf { variants: vec![] },
305 SchemaKind::AllOf { .. } => SchemaType::AllOf { all: vec![] },
306 SchemaKind::AnyOf { .. } => SchemaType::AnyOf { any: vec![] },
307 SchemaKind::Any(_) => SchemaType::Primitive(PrimitiveType::Any),
308 SchemaKind::Not { .. } => SchemaType::Primitive(PrimitiveType::Any),
309 }
310 }
311}
312
313#[allow(dead_code)]
314#[derive(Debug, Clone)]
315pub enum SchemaType {
316 Primitive(PrimitiveType),
317 Array {
318 item_type: Box<SchemaType>,
319 },
320 Object {
321 properties: HashMap<String, SchemaType>,
322 },
323 Enum {
324 values: Vec<String>,
325 },
326 Reference {
327 ref_path: String,
328 },
329 OneOf {
330 variants: Vec<SchemaType>,
331 },
332 AllOf {
333 all: Vec<SchemaType>,
334 },
335 AnyOf {
336 any: Vec<SchemaType>,
337 },
338 Nullable(Box<SchemaType>),
339}
340
341#[allow(dead_code)]
342#[derive(Debug, Clone)]
343pub enum PrimitiveType {
344 String,
345 Number,
346 Integer,
347 Boolean,
348 Any,
349}