vika_cli/generator/
schema_resolver.rs

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            // Circular reference detected
45            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                    // Cycle detected
257                    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}