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
7pub struct SchemaGenerator {
9 type_resolver: TypeResolver,
11 schemas: HashMap<String, Schema>,
13}
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct Schema {
18 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
20 pub schema_type: Option<String>,
21 #[serde(skip_serializing_if = "Option::is_none")]
23 pub properties: Option<HashMap<String, Property>>,
24 #[serde(skip_serializing_if = "Option::is_none")]
26 pub required: Option<Vec<String>>,
27 #[serde(skip_serializing_if = "Option::is_none")]
29 pub items: Option<Box<Schema>>,
30 #[serde(rename = "enum", skip_serializing_if = "Option::is_none")]
32 pub enum_values: Option<Vec<String>>,
33 #[serde(rename = "$ref", skip_serializing_if = "Option::is_none")]
35 pub reference: Option<String>,
36 #[serde(skip_serializing_if = "Option::is_none")]
38 pub format: Option<String>,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct Property {
44 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
46 pub property_type: Option<String>,
47 #[serde(rename = "$ref", skip_serializing_if = "Option::is_none")]
49 pub reference: Option<String>,
50 #[serde(skip_serializing_if = "Option::is_none")]
52 pub items: Option<Box<Schema>>,
53 #[serde(skip_serializing_if = "Option::is_none")]
55 pub format: Option<String>,
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct ParameterSchema {
61 pub name: String,
63 #[serde(rename = "in")]
65 pub location: String,
66 pub required: bool,
68 pub schema: Schema,
70}
71
72impl SchemaGenerator {
73 pub fn new(type_resolver: TypeResolver) -> Self {
75 debug!("Initializing SchemaGenerator");
76 Self {
77 type_resolver,
78 schemas: HashMap::new(),
79 }
80 }
81
82 pub fn generate_schema(&mut self, type_info: &TypeInfo) -> Schema {
84 debug!("Generating schema for type: {}", type_info.name);
85
86 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 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 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 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 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 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 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 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 fn generate_struct_schema(&mut self, type_name: &str) {
200 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 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 if field.serde_attrs.skip {
224 continue;
225 }
226
227 let field_name = field
229 .serde_attrs
230 .rename
231 .as_ref()
232 .unwrap_or(&field.name)
233 .clone();
234
235 let property = self.type_info_to_property(&field.type_info);
237 properties.insert(field_name.clone(), property);
238
239 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 fn generate_enum_schema(&mut self, type_name: &str) {
265 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 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 fn type_info_to_property(&mut self, type_info: &TypeInfo) -> Property {
299 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 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 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 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 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 Property {
364 property_type: Some("object".to_string()),
365 reference: None,
366 items: None,
367 format: None,
368 }
369 }
370
371 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(¶m.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 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 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 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 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 assert!(schema.reference.is_some());
526 assert_eq!(
527 schema.reference.unwrap(),
528 "#/components/schemas/User".to_string()
529 );
530
531 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 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 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 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 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 assert!(schema.reference.is_some());
666 assert_eq!(
667 schema.reference.unwrap(),
668 "#/components/schemas/Status".to_string()
669 );
670
671 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 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 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(¶m);
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(¶m);
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(¶m);
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 assert_eq!(data_property.property_type, Some("array".to_string()));
802 assert!(data_property.items.is_some());
803
804 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 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 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 let type_info = TypeInfo::new("User".to_string());
841 generator.generate_schema(&type_info);
842 generator.generate_schema(&type_info);
843
844 let schemas = generator.get_schemas();
846 assert_eq!(schemas.len(), 1);
847 assert!(schemas.contains_key("User"));
848 }
849}