openapi_from_source/
schema_generator.rs

1use crate::extractor::{Parameter, ParameterLocation, TypeInfo};
2use crate::type_resolver::{PrimitiveType, TypeKind, TypeResolver};
3use log::debug;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7/// Schema generator - converts Rust types to OpenAPI schemas
8pub struct SchemaGenerator {
9    /// Type resolver for looking up type definitions
10    type_resolver: TypeResolver,
11    /// Cache of generated schemas to avoid duplication
12    schemas: HashMap<String, Schema>,
13}
14
15/// OpenAPI Schema definition
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct Schema {
18    /// The type of the schema (string, integer, object, array, etc.)
19    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
20    pub schema_type: Option<String>,
21    /// Properties for object types
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub properties: Option<HashMap<String, Property>>,
24    /// Required field names for object types
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub required: Option<Vec<String>>,
27    /// Items schema for array types
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub items: Option<Box<Schema>>,
30    /// Enum values for enum types
31    #[serde(rename = "enum", skip_serializing_if = "Option::is_none")]
32    pub enum_values: Option<Vec<String>>,
33    /// Reference to another schema
34    #[serde(rename = "$ref", skip_serializing_if = "Option::is_none")]
35    pub reference: Option<String>,
36    /// Format for primitive types (e.g., "int32", "int64", "float", "double")
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub format: Option<String>,
39}
40
41/// Property definition for object schemas
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct Property {
44    /// The type of the property
45    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
46    pub property_type: Option<String>,
47    /// Reference to another schema
48    #[serde(rename = "$ref", skip_serializing_if = "Option::is_none")]
49    pub reference: Option<String>,
50    /// Items schema for array properties
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub items: Option<Box<Schema>>,
53    /// Format for primitive types
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub format: Option<String>,
56}
57
58/// Parameter schema for OpenAPI parameters
59#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct ParameterSchema {
61    /// Parameter name
62    pub name: String,
63    /// Parameter location (path, query, header)
64    #[serde(rename = "in")]
65    pub location: String,
66    /// Whether the parameter is required
67    pub required: bool,
68    /// Schema for the parameter
69    pub schema: Schema,
70}
71
72impl SchemaGenerator {
73    /// Create a new SchemaGenerator with a TypeResolver
74    pub fn new(type_resolver: TypeResolver) -> Self {
75        debug!("Initializing SchemaGenerator");
76        Self {
77            type_resolver,
78            schemas: HashMap::new(),
79        }
80    }
81
82    /// Generate a schema for a TypeInfo
83    pub fn generate_schema(&mut self, type_info: &TypeInfo) -> Schema {
84        debug!("Generating schema for type: {}", type_info.name);
85
86        // Handle Option<T> - unwrap and generate schema for inner type
87        if type_info.is_option {
88            if let Some(inner) = type_info.generic_args.first() {
89                return self.generate_schema(inner);
90            }
91        }
92
93        // Handle Vec<T> - generate array schema
94        if type_info.is_vec {
95            if let Some(inner) = type_info.generic_args.first() {
96                let items_schema = self.generate_schema(inner);
97                return Schema {
98                    schema_type: Some("array".to_string()),
99                    properties: None,
100                    required: None,
101                    items: Some(Box::new(items_schema)),
102                    enum_values: None,
103                    reference: None,
104                    format: None,
105                };
106            }
107        }
108
109        // Try to resolve as a primitive type first
110        if let Some(resolved) = self.type_resolver.resolve_type(&type_info.name) {
111            match resolved.kind {
112                TypeKind::Primitive(prim) => {
113                    return self.primitive_to_schema(&prim);
114                }
115                TypeKind::Struct(_) => {
116                    // For structs, return a reference and ensure the schema is generated
117                    self.generate_struct_schema(&type_info.name);
118                    return Schema {
119                        schema_type: None,
120                        properties: None,
121                        required: None,
122                        items: None,
123                        enum_values: None,
124                        reference: Some(format!("#/components/schemas/{}", type_info.name)),
125                        format: None,
126                    };
127                }
128                TypeKind::Enum(_) => {
129                    // For enums, return a reference and ensure the schema is generated
130                    self.generate_enum_schema(&type_info.name);
131                    return Schema {
132                        schema_type: None,
133                        properties: None,
134                        required: None,
135                        items: None,
136                        enum_values: None,
137                        reference: Some(format!("#/components/schemas/{}", type_info.name)),
138                        format: None,
139                    };
140                }
141                TypeKind::Generic(_) => {
142                    // Generic types - use a placeholder
143                    return Schema {
144                        schema_type: Some("object".to_string()),
145                        properties: None,
146                        required: None,
147                        items: None,
148                        enum_values: None,
149                        reference: None,
150                        format: None,
151                    };
152                }
153            }
154        }
155
156        // Fallback for unknown types
157        debug!("Unknown type: {}, using object placeholder", type_info.name);
158        Schema {
159            schema_type: Some("object".to_string()),
160            properties: None,
161            required: None,
162            items: None,
163            enum_values: None,
164            reference: None,
165            format: None,
166        }
167    }
168
169    /// Convert a primitive type to an OpenAPI schema
170    fn primitive_to_schema(&self, primitive: &PrimitiveType) -> Schema {
171        let (schema_type, format) = match primitive {
172            PrimitiveType::String => ("string", None),
173            PrimitiveType::I8 | PrimitiveType::I16 | PrimitiveType::I32 => {
174                ("integer", Some("int32"))
175            }
176            PrimitiveType::I64 | PrimitiveType::I128 => ("integer", Some("int64")),
177            PrimitiveType::U8 | PrimitiveType::U16 | PrimitiveType::U32 => {
178                ("integer", Some("int32"))
179            }
180            PrimitiveType::U64 | PrimitiveType::U128 => ("integer", Some("int64")),
181            PrimitiveType::F32 => ("number", Some("float")),
182            PrimitiveType::F64 => ("number", Some("double")),
183            PrimitiveType::Bool => ("boolean", None),
184            PrimitiveType::Char => ("string", None),
185        };
186
187        Schema {
188            schema_type: Some(schema_type.to_string()),
189            properties: None,
190            required: None,
191            items: None,
192            enum_values: None,
193            reference: None,
194            format: format.map(|s| s.to_string()),
195        }
196    }
197
198    /// Generate a schema for a struct type and add it to the schemas collection
199    fn generate_struct_schema(&mut self, type_name: &str) {
200        // Check if already generated
201        if self.schemas.contains_key(type_name) {
202            debug!("Schema for {} already exists", type_name);
203            return;
204        }
205
206        debug!("Generating struct schema for: {}", type_name);
207
208        // Resolve the type
209        let resolved = match self.type_resolver.resolve_type(type_name) {
210            Some(r) => r,
211            None => {
212                debug!("Could not resolve type: {}", type_name);
213                return;
214            }
215        };
216
217        if let TypeKind::Struct(struct_def) = resolved.kind {
218            let mut properties = HashMap::new();
219            let mut required = Vec::new();
220
221            for field in &struct_def.fields {
222                // Skip fields marked with #[serde(skip)]
223                if field.serde_attrs.skip {
224                    continue;
225                }
226
227                // Use the renamed field name if specified
228                let field_name = field
229                    .serde_attrs
230                    .rename
231                    .as_ref()
232                    .unwrap_or(&field.name)
233                    .clone();
234
235                // Generate property schema
236                let property = self.type_info_to_property(&field.type_info);
237                properties.insert(field_name.clone(), property);
238
239                // Add to required list if not optional
240                if !field.optional && !field.type_info.is_option {
241                    required.push(field_name);
242                }
243            }
244
245            let schema = Schema {
246                schema_type: Some("object".to_string()),
247                properties: Some(properties),
248                required: if required.is_empty() {
249                    None
250                } else {
251                    Some(required)
252                },
253                items: None,
254                enum_values: None,
255                reference: None,
256                format: None,
257            };
258
259            self.schemas.insert(type_name.to_string(), schema);
260        }
261    }
262
263    /// Generate a schema for an enum type and add it to the schemas collection
264    fn generate_enum_schema(&mut self, type_name: &str) {
265        // Check if already generated
266        if self.schemas.contains_key(type_name) {
267            debug!("Schema for {} already exists", type_name);
268            return;
269        }
270
271        debug!("Generating enum schema for: {}", type_name);
272
273        // Resolve the type
274        let resolved = match self.type_resolver.resolve_type(type_name) {
275            Some(r) => r,
276            None => {
277                debug!("Could not resolve type: {}", type_name);
278                return;
279            }
280        };
281
282        if let TypeKind::Enum(enum_def) = resolved.kind {
283            let schema = Schema {
284                schema_type: Some("string".to_string()),
285                properties: None,
286                required: None,
287                items: None,
288                enum_values: Some(enum_def.variants),
289                reference: None,
290                format: None,
291            };
292
293            self.schemas.insert(type_name.to_string(), schema);
294        }
295    }
296
297    /// Convert a TypeInfo to a Property
298    fn type_info_to_property(&mut self, type_info: &TypeInfo) -> Property {
299        // Handle Option<T> - unwrap and generate property for inner type
300        if type_info.is_option {
301            if let Some(inner) = type_info.generic_args.first() {
302                return self.type_info_to_property(inner);
303            }
304        }
305
306        // Handle Vec<T> - generate array property
307        if type_info.is_vec {
308            if let Some(inner) = type_info.generic_args.first() {
309                let items_schema = self.generate_schema(inner);
310                return Property {
311                    property_type: Some("array".to_string()),
312                    reference: None,
313                    items: Some(Box::new(items_schema)),
314                    format: None,
315                };
316            }
317        }
318
319        // Try to resolve the type
320        if let Some(resolved) = self.type_resolver.resolve_type(&type_info.name) {
321            match resolved.kind {
322                TypeKind::Primitive(prim) => {
323                    let schema = self.primitive_to_schema(&prim);
324                    return Property {
325                        property_type: schema.schema_type,
326                        reference: None,
327                        items: None,
328                        format: schema.format,
329                    };
330                }
331                TypeKind::Struct(_) => {
332                    // Generate the struct schema if not already done
333                    self.generate_struct_schema(&type_info.name);
334                    return Property {
335                        property_type: None,
336                        reference: Some(format!("#/components/schemas/{}", type_info.name)),
337                        items: None,
338                        format: None,
339                    };
340                }
341                TypeKind::Enum(_) => {
342                    // Generate the enum schema if not already done
343                    self.generate_enum_schema(&type_info.name);
344                    return Property {
345                        property_type: None,
346                        reference: Some(format!("#/components/schemas/{}", type_info.name)),
347                        items: None,
348                        format: None,
349                    };
350                }
351                TypeKind::Generic(_) => {
352                    return Property {
353                        property_type: Some("object".to_string()),
354                        reference: None,
355                        items: None,
356                        format: None,
357                    };
358                }
359            }
360        }
361
362        // Fallback for unknown types
363        Property {
364            property_type: Some("object".to_string()),
365            reference: None,
366            items: None,
367            format: None,
368        }
369    }
370
371    /// Generate a parameter schema from a Parameter
372    pub fn generate_parameter_schema(&mut self, param: &Parameter) -> ParameterSchema {
373        debug!("Generating parameter schema for: {}", param.name);
374
375        let location = match param.location {
376            ParameterLocation::Path => "path",
377            ParameterLocation::Query => "query",
378            ParameterLocation::Header => "header",
379        };
380
381        let schema = self.generate_schema(&param.type_info);
382
383        ParameterSchema {
384            name: param.name.clone(),
385            location: location.to_string(),
386            required: param.required,
387            schema,
388        }
389    }
390
391    /// Get all generated schemas
392    pub fn get_schemas(&self) -> &HashMap<String, Schema> {
393        &self.schemas
394    }
395}
396
397#[cfg(test)]
398mod tests {
399    use super::*;
400    use crate::parser::AstParser;
401    use std::fs;
402    use std::io::Write;
403    use tempfile::TempDir;
404
405    /// Helper function to create a temporary file with content
406    fn create_temp_file(dir: &TempDir, name: &str, content: &str) -> std::path::PathBuf {
407        let file_path = dir.path().join(name);
408        let mut file = fs::File::create(&file_path).unwrap();
409        file.write_all(content.as_bytes()).unwrap();
410        file_path
411    }
412
413    /// Helper function to create a SchemaGenerator from code
414    fn create_generator_from_code(code: &str) -> SchemaGenerator {
415        let temp_dir = TempDir::new().unwrap();
416        let file_path = create_temp_file(&temp_dir, "test.rs", code);
417        let parsed = AstParser::parse_file(&file_path).unwrap();
418        let type_resolver = TypeResolver::new(vec![parsed]);
419        SchemaGenerator::new(type_resolver)
420    }
421
422    #[test]
423    fn test_primitive_type_string() {
424        let mut generator = create_generator_from_code("");
425        let type_info = TypeInfo::new("String".to_string());
426        let schema = generator.generate_schema(&type_info);
427
428        assert_eq!(schema.schema_type, Some("string".to_string()));
429        assert!(schema.format.is_none());
430        assert!(schema.reference.is_none());
431    }
432
433    #[test]
434    fn test_primitive_type_i32() {
435        let mut generator = create_generator_from_code("");
436        let type_info = TypeInfo::new("i32".to_string());
437        let schema = generator.generate_schema(&type_info);
438
439        assert_eq!(schema.schema_type, Some("integer".to_string()));
440        assert_eq!(schema.format, Some("int32".to_string()));
441        assert!(schema.reference.is_none());
442    }
443
444    #[test]
445    fn test_primitive_type_i64() {
446        let mut generator = create_generator_from_code("");
447        let type_info = TypeInfo::new("i64".to_string());
448        let schema = generator.generate_schema(&type_info);
449
450        assert_eq!(schema.schema_type, Some("integer".to_string()));
451        assert_eq!(schema.format, Some("int64".to_string()));
452    }
453
454    #[test]
455    fn test_primitive_type_f32() {
456        let mut generator = create_generator_from_code("");
457        let type_info = TypeInfo::new("f32".to_string());
458        let schema = generator.generate_schema(&type_info);
459
460        assert_eq!(schema.schema_type, Some("number".to_string()));
461        assert_eq!(schema.format, Some("float".to_string()));
462    }
463
464    #[test]
465    fn test_primitive_type_f64() {
466        let mut generator = create_generator_from_code("");
467        let type_info = TypeInfo::new("f64".to_string());
468        let schema = generator.generate_schema(&type_info);
469
470        assert_eq!(schema.schema_type, Some("number".to_string()));
471        assert_eq!(schema.format, Some("double".to_string()));
472    }
473
474    #[test]
475    fn test_primitive_type_bool() {
476        let mut generator = create_generator_from_code("");
477        let type_info = TypeInfo::new("bool".to_string());
478        let schema = generator.generate_schema(&type_info);
479
480        assert_eq!(schema.schema_type, Some("boolean".to_string()));
481        assert!(schema.format.is_none());
482    }
483
484    #[test]
485    fn test_vec_type() {
486        let mut generator = create_generator_from_code("");
487        let inner = TypeInfo::new("String".to_string());
488        let type_info = TypeInfo::vec(inner);
489        let schema = generator.generate_schema(&type_info);
490
491        assert_eq!(schema.schema_type, Some("array".to_string()));
492        assert!(schema.items.is_some());
493
494        let items = schema.items.unwrap();
495        assert_eq!(items.schema_type, Some("string".to_string()));
496    }
497
498    #[test]
499    fn test_option_type() {
500        let mut generator = create_generator_from_code("");
501        let inner = TypeInfo::new("i32".to_string());
502        let type_info = TypeInfo::option(inner);
503        let schema = generator.generate_schema(&type_info);
504
505        // Option<T> should unwrap to T's schema
506        assert_eq!(schema.schema_type, Some("integer".to_string()));
507        assert_eq!(schema.format, Some("int32".to_string()));
508    }
509
510    #[test]
511    fn test_struct_schema_generation() {
512        let code = r#"
513            pub struct User {
514                pub id: u32,
515                pub name: String,
516                pub active: bool,
517            }
518        "#;
519
520        let mut generator = create_generator_from_code(code);
521        let type_info = TypeInfo::new("User".to_string());
522        let schema = generator.generate_schema(&type_info);
523
524        // Should return a reference
525        assert!(schema.reference.is_some());
526        assert_eq!(
527            schema.reference.unwrap(),
528            "#/components/schemas/User".to_string()
529        );
530
531        // Check that the schema was added to the collection
532        let schemas = generator.get_schemas();
533        assert!(schemas.contains_key("User"));
534
535        let user_schema = &schemas["User"];
536        assert_eq!(user_schema.schema_type, Some("object".to_string()));
537        assert!(user_schema.properties.is_some());
538
539        let properties = user_schema.properties.as_ref().unwrap();
540        assert_eq!(properties.len(), 3);
541        assert!(properties.contains_key("id"));
542        assert!(properties.contains_key("name"));
543        assert!(properties.contains_key("active"));
544
545        // All fields should be required
546        assert!(user_schema.required.is_some());
547        let required = user_schema.required.as_ref().unwrap();
548        assert_eq!(required.len(), 3);
549    }
550
551    #[test]
552    fn test_struct_with_optional_field() {
553        let code = r#"
554            pub struct User {
555                pub id: u32,
556                pub email: Option<String>,
557            }
558        "#;
559
560        let mut generator = create_generator_from_code(code);
561        let type_info = TypeInfo::new("User".to_string());
562        generator.generate_schema(&type_info);
563
564        let schemas = generator.get_schemas();
565        let user_schema = &schemas["User"];
566
567        // Only id should be required
568        assert!(user_schema.required.is_some());
569        let required = user_schema.required.as_ref().unwrap();
570        assert_eq!(required.len(), 1);
571        assert_eq!(required[0], "id");
572    }
573
574    #[test]
575    fn test_struct_with_vec_field() {
576        let code = r#"
577            pub struct Post {
578                pub id: u32,
579                pub tags: Vec<String>,
580            }
581        "#;
582
583        let mut generator = create_generator_from_code(code);
584        let type_info = TypeInfo::new("Post".to_string());
585        generator.generate_schema(&type_info);
586
587        let schemas = generator.get_schemas();
588        let post_schema = &schemas["Post"];
589
590        let properties = post_schema.properties.as_ref().unwrap();
591        let tags_property = &properties["tags"];
592
593        assert_eq!(tags_property.property_type, Some("array".to_string()));
594        assert!(tags_property.items.is_some());
595    }
596
597    #[test]
598    fn test_struct_with_serde_rename() {
599        let code = r#"
600            use serde::{Deserialize, Serialize};
601            
602            #[derive(Serialize, Deserialize)]
603            pub struct User {
604                pub id: u32,
605                #[serde(rename = "userName")]
606                pub name: String,
607            }
608        "#;
609
610        let mut generator = create_generator_from_code(code);
611        let type_info = TypeInfo::new("User".to_string());
612        generator.generate_schema(&type_info);
613
614        let schemas = generator.get_schemas();
615        let user_schema = &schemas["User"];
616
617        let properties = user_schema.properties.as_ref().unwrap();
618        // Should use the renamed field name
619        assert!(properties.contains_key("userName"));
620        assert!(!properties.contains_key("name"));
621    }
622
623    #[test]
624    fn test_struct_with_serde_skip() {
625        let code = r#"
626            use serde::{Deserialize, Serialize};
627            
628            #[derive(Serialize, Deserialize)]
629            pub struct User {
630                pub id: u32,
631                #[serde(skip)]
632                pub password: String,
633            }
634        "#;
635
636        let mut generator = create_generator_from_code(code);
637        let type_info = TypeInfo::new("User".to_string());
638        generator.generate_schema(&type_info);
639
640        let schemas = generator.get_schemas();
641        let user_schema = &schemas["User"];
642
643        let properties = user_schema.properties.as_ref().unwrap();
644        // Skipped field should not be in properties
645        assert_eq!(properties.len(), 1);
646        assert!(properties.contains_key("id"));
647        assert!(!properties.contains_key("password"));
648    }
649
650    #[test]
651    fn test_enum_schema_generation() {
652        let code = r#"
653            pub enum Status {
654                Active,
655                Inactive,
656                Pending,
657            }
658        "#;
659
660        let mut generator = create_generator_from_code(code);
661        let type_info = TypeInfo::new("Status".to_string());
662        let schema = generator.generate_schema(&type_info);
663
664        // Should return a reference
665        assert!(schema.reference.is_some());
666        assert_eq!(
667            schema.reference.unwrap(),
668            "#/components/schemas/Status".to_string()
669        );
670
671        // Check the enum schema
672        let schemas = generator.get_schemas();
673        let status_schema = &schemas["Status"];
674
675        assert_eq!(status_schema.schema_type, Some("string".to_string()));
676        assert!(status_schema.enum_values.is_some());
677
678        let variants = status_schema.enum_values.as_ref().unwrap();
679        assert_eq!(variants.len(), 3);
680        assert!(variants.contains(&"Active".to_string()));
681        assert!(variants.contains(&"Inactive".to_string()));
682        assert!(variants.contains(&"Pending".to_string()));
683    }
684
685    #[test]
686    fn test_nested_struct_schema() {
687        let code = r#"
688            pub struct User {
689                pub id: u32,
690                pub profile: Profile,
691            }
692            
693            pub struct Profile {
694                pub bio: String,
695                pub avatar: String,
696            }
697        "#;
698
699        let mut generator = create_generator_from_code(code);
700        let type_info = TypeInfo::new("User".to_string());
701        generator.generate_schema(&type_info);
702
703        let schemas = generator.get_schemas();
704        
705        // Both User and Profile should be in schemas
706        assert!(schemas.contains_key("User"));
707        assert!(schemas.contains_key("Profile"));
708
709        let user_schema = &schemas["User"];
710        let properties = user_schema.properties.as_ref().unwrap();
711        let profile_property = &properties["profile"];
712
713        // Profile field should be a reference
714        assert!(profile_property.reference.is_some());
715        assert_eq!(
716            profile_property.reference.as_ref().unwrap(),
717            "#/components/schemas/Profile"
718        );
719    }
720
721    #[test]
722    fn test_parameter_schema_path() {
723        let mut generator = create_generator_from_code("");
724        let param = Parameter::new(
725            "id".to_string(),
726            ParameterLocation::Path,
727            TypeInfo::new("u32".to_string()),
728            true,
729        );
730
731        let param_schema = generator.generate_parameter_schema(&param);
732
733        assert_eq!(param_schema.name, "id");
734        assert_eq!(param_schema.location, "path");
735        assert!(param_schema.required);
736        assert_eq!(param_schema.schema.schema_type, Some("integer".to_string()));
737    }
738
739    #[test]
740    fn test_parameter_schema_query() {
741        let mut generator = create_generator_from_code("");
742        let param = Parameter::new(
743            "page".to_string(),
744            ParameterLocation::Query,
745            TypeInfo::new("i32".to_string()),
746            false,
747        );
748
749        let param_schema = generator.generate_parameter_schema(&param);
750
751        assert_eq!(param_schema.name, "page");
752        assert_eq!(param_schema.location, "query");
753        assert!(!param_schema.required);
754        assert_eq!(param_schema.schema.schema_type, Some("integer".to_string()));
755    }
756
757    #[test]
758    fn test_parameter_schema_header() {
759        let mut generator = create_generator_from_code("");
760        let param = Parameter::new(
761            "Authorization".to_string(),
762            ParameterLocation::Header,
763            TypeInfo::new("String".to_string()),
764            true,
765        );
766
767        let param_schema = generator.generate_parameter_schema(&param);
768
769        assert_eq!(param_schema.name, "Authorization");
770        assert_eq!(param_schema.location, "header");
771        assert!(param_schema.required);
772        assert_eq!(param_schema.schema.schema_type, Some("string".to_string()));
773    }
774
775    #[test]
776    fn test_complex_nested_type() {
777        let code = r#"
778            pub struct Response {
779                pub data: Option<Vec<User>>,
780            }
781            
782            pub struct User {
783                pub id: u32,
784                pub name: String,
785            }
786        "#;
787
788        let mut generator = create_generator_from_code(code);
789        let type_info = TypeInfo::new("Response".to_string());
790        generator.generate_schema(&type_info);
791
792        let schemas = generator.get_schemas();
793        assert!(schemas.contains_key("Response"));
794        assert!(schemas.contains_key("User"));
795
796        let response_schema = &schemas["Response"];
797        let properties = response_schema.properties.as_ref().unwrap();
798        let data_property = &properties["data"];
799
800        // data is Option<Vec<User>>, so it should be an array
801        assert_eq!(data_property.property_type, Some("array".to_string()));
802        assert!(data_property.items.is_some());
803
804        // The items should reference User
805        let items = data_property.items.as_ref().unwrap();
806        assert!(items.reference.is_some());
807        assert_eq!(
808            items.reference.as_ref().unwrap(),
809            "#/components/schemas/User"
810        );
811
812        // data field should not be required (it's Option)
813        let required = response_schema.required.as_ref();
814        assert!(required.is_none() || !required.unwrap().contains(&"data".to_string()));
815    }
816
817    #[test]
818    fn test_unknown_type_fallback() {
819        let mut generator = create_generator_from_code("");
820        let type_info = TypeInfo::new("UnknownType".to_string());
821        let schema = generator.generate_schema(&type_info);
822
823        // Should fallback to object type
824        assert_eq!(schema.schema_type, Some("object".to_string()));
825        assert!(schema.reference.is_none());
826    }
827
828    #[test]
829    fn test_schema_caching() {
830        let code = r#"
831            pub struct User {
832                pub id: u32,
833                pub name: String,
834            }
835        "#;
836
837        let mut generator = create_generator_from_code(code);
838        
839        // Generate schema twice
840        let type_info = TypeInfo::new("User".to_string());
841        generator.generate_schema(&type_info);
842        generator.generate_schema(&type_info);
843
844        // Should only have one entry in schemas
845        let schemas = generator.get_schemas();
846        assert_eq!(schemas.len(), 1);
847        assert!(schemas.contains_key("User"));
848    }
849}