1use std::collections::HashSet;
47use std::fs;
48use std::path::Path;
49
50use quote::{format_ident, quote};
51
52use crate::config::CodegenConfig;
53use crate::fhir_types::StructureDefinition;
54use crate::generators::binding_generator::BindingGenerator;
55use crate::generators::enum_generator::EnumGenerator;
56use crate::generators::import_manager::ImportManager;
57use crate::generators::primitive_generator::PrimitiveGenerator;
58use crate::generators::token_generator::TokenGenerator;
59use crate::generators::trait_impl_generator::TraitImplGenerator;
60use crate::generators::utils::GeneratorUtils;
61use crate::rust_types::{RustStruct, RustTrait};
62use crate::{CodegenError, CodegenResult};
63
64#[derive(Debug, Clone, PartialEq)]
66pub enum FhirTypeCategory {
67 Resource,
68 Profile,
69 DataType,
70 Extension,
71 Primitive,
72}
73
74pub struct FileGenerator<'a> {
76 config: &'a CodegenConfig,
77 token_generator: &'a TokenGenerator,
78}
79
80impl<'a> FileGenerator<'a> {
81 pub fn new(config: &'a CodegenConfig, token_generator: &'a TokenGenerator) -> Self {
83 Self {
84 config,
85 token_generator,
86 }
87 }
88
89 pub fn generate_macros_file<P: AsRef<Path>>(&self, output_path: P) -> CodegenResult<()> {
91 let macros_content = include_str!("../macros.rs");
92
93 let syntax_tree =
95 syn::parse_file(macros_content).map_err(|e| CodegenError::Generation {
96 message: format!("Failed to parse macros file: {e}"),
97 })?;
98
99 let formatted_code = prettyplease::unparse(&syntax_tree);
100
101 fs::write(output_path, formatted_code)?;
103
104 Ok(())
105 }
106
107 pub fn generate_lib_file<P: AsRef<Path>>(&self, output_path: P) -> CodegenResult<()> {
109 let lib_tokens = quote! {
110 #![allow(clippy::derivable_impls)]
143
144 pub mod macros;
145 pub mod primitives;
146 pub mod datatypes;
147 pub mod extensions;
148 pub mod resources;
149 pub mod traits;
150 pub mod bindings;
151
152 pub use macros::*;
154 pub use serde::{Deserialize, Serialize};
155 };
156
157 let syntax_tree = syn::parse2(lib_tokens).map_err(|e| CodegenError::Generation {
159 message: format!("Failed to parse generated lib tokens: {e}"),
160 })?;
161
162 let formatted_code = prettyplease::unparse(&syntax_tree);
163
164 fs::write(output_path, formatted_code)?;
166
167 Ok(())
168 }
169
170 pub fn generate_module_file<P: AsRef<Path>>(
172 &self,
173 module_dir: P,
174 module_names: &[String],
175 ) -> CodegenResult<()> {
176 let module_dir = module_dir.as_ref();
177 let mod_file_path = module_dir.join("mod.rs");
178
179 let mut mod_tokens = proc_macro2::TokenStream::new();
180
181 for module_name in module_names {
183 let mod_ident = format_ident!("{}", module_name);
184 mod_tokens.extend(quote! {
185 pub mod #mod_ident;
186 });
187 }
188
189 let syntax_tree = syn::parse2(mod_tokens).map_err(|e| CodegenError::Generation {
191 message: format!("Failed to parse generated mod tokens: {e}"),
192 })?;
193
194 let formatted_code = prettyplease::unparse(&syntax_tree);
195
196 fs::write(mod_file_path, formatted_code)?;
198
199 Ok(())
200 }
201
202 pub fn generate_combined_primitives_file<P: AsRef<Path>>(
204 &self,
205 primitive_structure_defs: &[StructureDefinition],
206 output_path: P,
207 ) -> CodegenResult<()> {
208 let mut all_tokens = proc_macro2::TokenStream::new();
209
210 let doc_comment = quote! {
212 };
217 all_tokens.extend(doc_comment);
218
219 let mut type_cache = std::collections::HashMap::new();
221 let primitive_generator = PrimitiveGenerator::new(self.config, &mut type_cache);
222 let type_aliases =
223 primitive_generator.generate_all_primitive_type_aliases(primitive_structure_defs)?;
224
225 for type_alias in type_aliases {
226 let type_alias_tokens = self.token_generator.generate_type_alias(&type_alias);
227 all_tokens.extend(type_alias_tokens);
228 }
229
230 let syntax_tree = syn::parse2(all_tokens).map_err(|e| CodegenError::Generation {
232 message: format!("Failed to parse generated primitive tokens: {e}"),
233 })?;
234
235 let formatted_code = prettyplease::unparse(&syntax_tree);
236
237 fs::write(output_path, formatted_code)?;
239
240 Ok(())
241 }
242
243 pub fn generate_to_organized_directories<P: AsRef<Path>>(
245 &self,
246 structure_def: &StructureDefinition,
247 base_output_dir: P,
248 rust_struct: &RustStruct,
249 nested_structs: &[RustStruct],
250 ) -> CodegenResult<()> {
251 let base_dir = base_output_dir.as_ref();
252
253 let mut category = self.classify_fhir_structure_def(structure_def);
256 if category != FhirTypeCategory::Extension && Self::has_extension_base(rust_struct) {
257 category = FhirTypeCategory::Extension;
258 }
259
260 let target_dir = match category {
261 FhirTypeCategory::Resource => base_dir.join("src").join("resource"),
262 FhirTypeCategory::Profile => base_dir.join("src").join("profiles"),
263 FhirTypeCategory::DataType => base_dir.join("src").join("datatypes"),
264 FhirTypeCategory::Extension => base_dir.join("src").join("extensions"),
265 FhirTypeCategory::Primitive => base_dir.join("src").join("primitives"),
266 };
267
268 std::fs::create_dir_all(&target_dir)?;
270
271 let mut embedded_nested: Vec<RustStruct> = Vec::new();
276 let mut external_extensions: Vec<RustStruct> = Vec::new();
277
278 for nested in nested_structs {
279 if Self::has_extension_base(nested) {
280 external_extensions.push(nested.clone());
281 } else {
282 embedded_nested.push(nested.clone());
283 }
284 }
285
286 let filename = crate::naming::Naming::filename(structure_def);
289 let output_path = target_dir.join(filename);
290
291 let result =
292 self.generate_to_file(structure_def, output_path, rust_struct, &embedded_nested);
293
294 if !external_extensions.is_empty() {
298 let extensions_dir = base_dir.join("src").join("extensions");
299 std::fs::create_dir_all(&extensions_dir)?;
300
301 for ext in external_extensions {
302 self.write_struct_only_file(&ext, &extensions_dir)?;
305 }
306 }
307
308 result
309 }
310
311 pub fn generate_trait_to_organized_directory<P: AsRef<Path>>(
313 &self,
314 structure_def: &StructureDefinition,
315 base_output_dir: P,
316 rust_trait: &RustTrait,
317 ) -> CodegenResult<()> {
318 let traits_dir = base_output_dir.as_ref().join("src").join("traits");
319
320 std::fs::create_dir_all(&traits_dir)?;
322
323 let struct_name = crate::naming::Naming::struct_name(structure_def);
325 let snake_case_name = crate::naming::Naming::to_snake_case(&struct_name);
326 let filename = format!("{snake_case_name}.rs");
327 let output_path = traits_dir.join(filename);
328
329 self.generate_trait_to_file(structure_def, output_path, rust_trait)
330 }
331
332 fn has_extension_base(rust_struct: &RustStruct) -> bool {
334 rust_struct.fields.iter().any(|field| {
335 field.name == "base" && matches!(&field.field_type, crate::rust_types::RustType::Custom(type_name) if type_name == "Extension")
336 })
337 }
338
339 pub fn classify_fhir_structure_def(
341 &self,
342 structure_def: &StructureDefinition,
343 ) -> FhirTypeCategory {
344 if crate::generators::type_registry::TypeRegistry::is_profile(structure_def) {
346 return FhirTypeCategory::Profile;
347 }
348
349 if structure_def.kind == "primitive-type" {
351 return FhirTypeCategory::Primitive;
352 }
353
354 if GeneratorUtils::is_fhir_datatype(&structure_def.name)
356 || structure_def.base_type == "Element"
357 || structure_def.base_type == "BackboneElement"
358 || structure_def.base_type == "DataType"
359 || structure_def.name == "Extension"
360 {
361 return FhirTypeCategory::DataType;
362 }
363
364 if structure_def.base_type == "Extension" {
366 return FhirTypeCategory::Extension;
367 }
368
369 if structure_def.kind == "resource"
371 || structure_def.base_type == "Resource"
372 || structure_def.base_type == "DomainResource"
373 {
374 return FhirTypeCategory::Resource;
375 }
376
377 if structure_def.kind == "complex-type" {
379 return FhirTypeCategory::DataType;
380 }
381
382 FhirTypeCategory::Resource
384 }
385
386 pub fn generate_to_file<P: AsRef<Path>>(
388 &self,
389 structure_def: &StructureDefinition,
390 output_path: P,
391 rust_struct: &RustStruct,
392 nested_structs: &[RustStruct],
393 ) -> CodegenResult<()> {
394 let mut imports = HashSet::new();
396
397 if self.config.with_serde && structure_def.kind != "primitive-type" {
399 imports.insert("serde::{Deserialize, Serialize}".to_string());
400 }
401
402 let has_macro_calls = rust_struct
407 .fields
408 .iter()
409 .any(|field| field.macro_call.is_some())
410 || nested_structs
411 .iter()
412 .any(|s| s.fields.iter().any(|field| field.macro_call.is_some()));
413
414 if has_macro_calls {
415 imports.insert("crate::{primitive_string, primitive_boolean, primitive_integer, primitive_decimal, primitive_datetime, primitive_date, primitive_time, primitive_uri, primitive_canonical, primitive_base64binary, primitive_instant, primitive_positiveint, primitive_unsignedint, primitive_id, primitive_oid, primitive_uuid, primitive_code, primitive_markdown, primitive_url}".to_string());
417 }
418
419 let mut all_tokens = proc_macro2::TokenStream::new();
420
421 if structure_def.kind == "primitive-type" {
422 let mut type_cache = std::collections::HashMap::new();
424 let primitive_generator = PrimitiveGenerator::new(self.config, &mut type_cache);
425 let type_alias = primitive_generator.generate_primitive_type_alias(structure_def)?;
426 let type_alias_tokens = self.token_generator.generate_type_alias(&type_alias);
427 all_tokens.extend(type_alias_tokens);
428
429 } else {
432 let mut all_structs = vec![rust_struct.clone()];
434 all_structs.extend(nested_structs.iter().cloned());
435
436 let structs_in_file: HashSet<String> =
438 all_structs.iter().map(|s| s.name.clone()).collect();
439
440 for struct_def in &all_structs {
442 ImportManager::collect_custom_types_from_struct(
443 struct_def,
444 &mut imports,
445 &structs_in_file,
446 );
447 }
448
449 for struct_def in all_structs {
450 let struct_tokens = self.token_generator.generate_struct(&struct_def);
451 all_tokens.extend(struct_tokens);
452 }
453 }
454
455 let mut import_tokens = proc_macro2::TokenStream::new();
457 for import in &imports {
458 let import_token: proc_macro2::TokenStream = format!("use {import};")
459 .parse()
460 .expect("Invalid import statement");
461 import_tokens.extend(import_token);
462 }
463
464 let mut final_tokens = proc_macro2::TokenStream::new();
466 final_tokens.extend(import_tokens);
467 final_tokens.extend(all_tokens);
468
469 let syntax_tree = syn::parse2(final_tokens).map_err(|e| CodegenError::Generation {
471 message: format!("Failed to parse generated tokens: {e}"),
472 })?;
473
474 let mut formatted_code = prettyplease::unparse(&syntax_tree);
475
476 if structure_def.kind == "resource" || structure_def.kind == "complex-type" {
478 let default_impl = self.generate_default_implementation(structure_def, rust_struct);
479 if !default_impl.is_empty() {
480 formatted_code.push_str("\n\n");
481 formatted_code.push_str(&default_impl);
482 }
483
484 for nested in nested_structs {
486 let nested_default_impl =
487 self.generate_nested_struct_default_implementation(structure_def, nested);
488 if !nested_default_impl.is_empty() {
489 formatted_code.push_str("\n\n");
490 formatted_code.push_str(&nested_default_impl);
491 }
492 }
493 }
494
495 if structure_def.kind == "resource" || structure_def.kind == "complex-type" {
497 let invariants_const =
498 crate::generators::InvariantGenerator::generate_invariants_constant(structure_def);
499 if !invariants_const.is_empty() {
500 formatted_code.push_str("\n\n");
501 formatted_code.push_str(&invariants_const);
502 }
503 }
504
505 if structure_def.kind == "resource" || structure_def.kind == "complex-type" {
507 let bindings_const = BindingGenerator::generate_bindings_constant(structure_def);
508 if !bindings_const.is_empty() {
509 formatted_code.push_str("\n\n");
510 formatted_code.push_str(&bindings_const);
511 }
512 }
513
514 if structure_def.kind == "resource" || structure_def.kind == "complex-type" {
516 let cardinalities_const =
517 crate::generators::cardinality_generator::CardinalityGenerator::generate_cardinalities_constant(
518 structure_def,
519 );
520 if !cardinalities_const.is_empty() {
521 formatted_code.push_str("\n\n");
522 formatted_code.push_str(&cardinalities_const);
523 }
524 }
525
526 if structure_def.kind == "resource" {
528 formatted_code.push_str("\n\n");
529 formatted_code.push_str(&self.generate_trait_implementations(structure_def));
530 }
531
532 if structure_def.kind == "resource" || structure_def.kind == "complex-type" {
534 let validation_impl =
535 crate::generators::ValidationTraitGenerator::generate_trait_impl(structure_def);
536 if !validation_impl.is_empty() {
537 formatted_code.push_str("\n\n");
538 formatted_code.push_str(&validation_impl);
539 }
540 }
541
542 if structure_def.kind == "resource" {
544 formatted_code.push_str("\n\n");
545 formatted_code.push_str(&self.generate_trait_reexports(structure_def));
546 }
547
548 if structure_def.name == "Resource" {
550 formatted_code.push_str("\n\n");
551 }
554
555 if output_path.as_ref().exists() {
557 eprintln!(
558 "Warning: File '{}' already exists and will be overwritten. This may indicate a naming collision between FHIR StructureDefinitions.",
559 output_path.as_ref().display()
560 );
561 }
562
563 fs::write(output_path.as_ref(), formatted_code)?;
565
566 Ok(())
567 }
568
569 pub fn generate_trait_to_file<P: AsRef<Path>>(
571 &self,
572 _structure_def: &StructureDefinition,
573 output_path: P,
574 rust_trait: &RustTrait,
575 ) -> CodegenResult<()> {
576 let mut all_tokens = proc_macro2::TokenStream::new();
578
579 let mut imports = std::collections::HashSet::new();
581 ImportManager::collect_custom_types_from_trait(rust_trait, &mut imports);
582
583 for import_path in imports {
585 let import_stmt = format!("use {import_path};");
586 let import_tokens: proc_macro2::TokenStream =
587 import_stmt.parse().map_err(|e| CodegenError::Generation {
588 message: format!("Failed to parse import statement '{import_stmt}': {e}"),
589 })?;
590 all_tokens.extend(import_tokens);
591 }
592
593 let trait_tokens = self.token_generator.generate_trait(rust_trait);
595 all_tokens.extend(trait_tokens);
596
597 if std::env::var("DEBUG_TOKENS").is_ok() {
599 eprintln!(
600 "DEBUG: Generated tokens for trait '{}': {}",
601 rust_trait.name, all_tokens
602 );
603 }
604
605 let syntax_tree = syn::parse2(all_tokens).map_err(|e| CodegenError::Generation {
607 message: format!(
608 "Failed to parse generated trait tokens for '{}': {e}",
609 rust_trait.name
610 ),
611 })?;
612
613 let formatted_code = prettyplease::unparse(&syntax_tree);
614
615 if output_path.as_ref().exists() {
617 eprintln!(
618 "Warning: Trait file '{}' already exists and will be overwritten.",
619 output_path.as_ref().display()
620 );
621 }
622
623 fs::write(output_path.as_ref(), formatted_code)?;
625
626 Ok(())
627 }
628
629 pub fn generate_traits_to_file<P: AsRef<Path>>(
631 &self,
632 _structure_def: &StructureDefinition,
633 output_path: P,
634 rust_traits: &[&RustTrait],
635 ) -> CodegenResult<()> {
636 let mut all_tokens = proc_macro2::TokenStream::new();
638
639 let mut imports = std::collections::HashSet::new();
641 for rust_trait in rust_traits {
642 ImportManager::collect_custom_types_from_trait(rust_trait, &mut imports);
643 }
644
645 for import_path in imports {
647 let import_stmt = format!("use {import_path};");
648 let import_tokens: proc_macro2::TokenStream =
649 import_stmt.parse().map_err(|e| CodegenError::Generation {
650 message: format!("Failed to parse import statement '{import_stmt}': {e}"),
651 })?;
652 all_tokens.extend(import_tokens);
653 }
654
655 for rust_trait in rust_traits {
657 let trait_tokens = self.token_generator.generate_trait(rust_trait);
658 all_tokens.extend(trait_tokens);
659 }
660
661 if std::env::var("DEBUG_TOKENS").is_ok() {
663 let trait_names: Vec<&str> = rust_traits.iter().map(|t| t.name.as_str()).collect();
664 eprintln!(
665 "DEBUG: Generated tokens for traits [{}]: {}",
666 trait_names.join(", "),
667 all_tokens
668 );
669 }
670
671 let syntax_tree = syn::parse2(all_tokens).map_err(|e| CodegenError::Generation {
673 message: format!("Failed to parse generated trait tokens: {e}"),
674 })?;
675
676 let formatted_code = prettyplease::unparse(&syntax_tree);
677
678 if output_path.as_ref().exists() {
680 eprintln!(
681 "Warning: Trait file '{}' already exists and will be overwritten.",
682 output_path.as_ref().display()
683 );
684 }
685
686 fs::write(output_path.as_ref(), formatted_code)?;
688
689 Ok(())
690 }
691
692 pub fn generate_enum_files<P: AsRef<Path>>(
694 &self,
695 enums_dir: P,
696 enum_generator: &EnumGenerator,
697 ) -> CodegenResult<()> {
698 let enums_dir = enums_dir.as_ref();
699
700 if !enums_dir.exists() {
702 fs::create_dir_all(enums_dir)?;
703 }
704
705 for (enum_name, rust_enum) in enum_generator.get_cached_enums() {
707 let enum_filename = EnumGenerator::enum_name_to_filename(enum_name);
708 let enum_file_path = enums_dir.join(enum_filename);
709
710 let import_tokens = quote! {
712 use serde::{Deserialize, Serialize};
713 };
714 let enum_tokens = self.token_generator.generate_enum(rust_enum);
715 let combined_tokens = quote! {
716 #import_tokens
717
718 #enum_tokens
719 };
720
721 let syntax_tree =
723 syn::parse2(combined_tokens).map_err(|e| CodegenError::Generation {
724 message: format!("Failed to parse generated enum tokens for {enum_name}: {e}"),
725 })?;
726
727 let formatted_code = prettyplease::unparse(&syntax_tree);
728
729 if enum_file_path.exists() {
731 eprintln!(
732 "Warning: Enum file '{}' already exists and will be overwritten.",
733 enum_file_path.display()
734 );
735 }
736
737 fs::write(&enum_file_path, formatted_code)?;
739 }
740
741 Ok(())
742 }
743
744 pub fn generate_enums_mod_file<P: AsRef<Path>>(
746 &self,
747 enums_dir: P,
748 enum_generator: &EnumGenerator,
749 ) -> CodegenResult<()> {
750 let enums_dir = enums_dir.as_ref();
751 let mod_file_path = enums_dir.join("mod.rs");
752
753 let mut mod_content = vec![
754 "//! FHIR ValueSet enums".to_string(),
755 "//!".to_string(),
756 "//! This module contains all the generated enums from FHIR ValueSets.".to_string(),
757 "//! Each enum represents a specific ValueSet and its allowed codes.".to_string(),
758 "".to_string(),
759 ];
760
761 let mut enum_names: Vec<_> = enum_generator.get_cached_enums().keys().collect();
763 enum_names.sort();
764
765 for enum_name in enum_names {
767 let module_name = EnumGenerator::enum_name_to_module_name(enum_name);
768 mod_content.push(format!("pub mod {module_name};"));
769 }
770
771 let final_content = mod_content.join("\n") + "\n";
772
773 if mod_file_path.exists() {
775 eprintln!(
776 "Warning: Enum mod file '{}' already exists and will be overwritten.",
777 mod_file_path.display()
778 );
779 }
780
781 fs::write(&mod_file_path, final_content)?;
782
783 Ok(())
784 }
785
786 pub fn generate_trait_file_from_trait<P: AsRef<Path>>(
788 &self,
789 rust_trait: &RustTrait,
790 output_path: P,
791 ) -> CodegenResult<()> {
792 let mut all_tokens = proc_macro2::TokenStream::new();
794
795 let mut imports = std::collections::HashSet::new();
797 ImportManager::collect_custom_types_from_trait(rust_trait, &mut imports);
798
799 for import_path in imports {
801 let import_stmt = format!("use {import_path};");
802 let import_tokens: proc_macro2::TokenStream =
803 import_stmt.parse().map_err(|e| CodegenError::Generation {
804 message: format!("Failed to parse import statement '{import_stmt}': {e}"),
805 })?;
806 all_tokens.extend(import_tokens);
807 }
808
809 let trait_tokens = self.token_generator.generate_trait(rust_trait);
811 all_tokens.extend(trait_tokens);
812
813 let syntax_tree = syn::parse2(all_tokens).map_err(|e| CodegenError::Generation {
815 message: format!("Failed to parse generated trait tokens: {e}"),
816 })?;
817
818 let formatted_code = prettyplease::unparse(&syntax_tree);
819
820 if output_path.as_ref().exists() {
822 eprintln!(
823 "Warning: Trait file '{}' already exists and will be overwritten.",
824 output_path.as_ref().display()
825 );
826 }
827
828 fs::write(output_path.as_ref(), formatted_code)?;
830
831 Ok(())
832 }
833
834 fn write_struct_only_file<P: AsRef<Path>>(
838 &self,
839 rust_struct: &RustStruct,
840 dir: P,
841 ) -> CodegenResult<()> {
842 let dir = dir.as_ref();
843
844 let mut imports = HashSet::new();
846 if self.config.with_serde {
847 imports.insert("serde::{Deserialize, Serialize}".to_string());
848 }
849
850 let mut structs_in_file = HashSet::new();
852 structs_in_file.insert(rust_struct.name.clone());
853 ImportManager::collect_custom_types_from_struct(
854 rust_struct,
855 &mut imports,
856 &structs_in_file,
857 );
858
859 let mut all_tokens = proc_macro2::TokenStream::new();
861
862 for import in &imports {
864 let import_token: proc_macro2::TokenStream =
865 format!("use {import};").parse().expect("Invalid import");
866 all_tokens.extend(import_token);
867 }
868
869 all_tokens.extend(self.token_generator.generate_struct(rust_struct));
871
872 let syntax_tree = syn::parse2(all_tokens).map_err(|e| CodegenError::Generation {
874 message: format!(
875 "Failed to parse generated tokens for {}: {e}",
876 rust_struct.name
877 ),
878 })?;
879
880 let formatted_code = prettyplease::unparse(&syntax_tree);
881
882 let filename = format!(
884 "{}.rs",
885 crate::naming::Naming::to_snake_case(&rust_struct.name)
886 );
887 let output_path = dir.join(filename);
888
889 std::fs::write(output_path, formatted_code)?;
891
892 Ok(())
893 }
894
895 fn generate_trait_implementations(&self, structure_def: &StructureDefinition) -> String {
897 let trait_impl_generator = TraitImplGenerator::new();
898 let trait_impls = match trait_impl_generator.generate_trait_impls(structure_def) {
899 Ok(impls) => impls,
900 Err(e) => {
901 eprintln!(
902 "Warning: Failed to generate trait implementations for {}: {}",
903 structure_def.name, e
904 );
905 return String::new();
906 }
907 };
908
909 let mut implementations = Vec::new();
910
911 for trait_impl in trait_impls {
912 let impl_tokens = self.token_generator.generate_trait_impl(&trait_impl);
913
914 match syn::parse2(impl_tokens.clone()) {
916 Ok(syntax_tree) => {
917 let formatted_impl = prettyplease::unparse(&syntax_tree);
918 implementations.push(formatted_impl);
919 }
920 Err(e) => {
921 eprintln!(
922 "Warning: Failed to parse trait implementation for {}: {}",
923 trait_impl.struct_name, e
924 );
925 eprintln!("Generated tokens:\n{impl_tokens}");
926 }
927 }
928 }
929
930 if implementations.is_empty() {
931 String::new()
932 } else {
933 format!("// Trait implementations\n{}", implementations.join("\n\n"))
934 }
935 }
936
937 fn generate_trait_reexports(&self, structure_def: &StructureDefinition) -> String {
960 let is_profile = crate::generators::type_registry::TypeRegistry::is_profile(structure_def);
963
964 let (trait_module_name, trait_prefix) = if is_profile {
965 let struct_name = crate::naming::Naming::struct_name(structure_def);
967 let snake_module = crate::naming::Naming::to_rust_identifier(
968 &crate::naming::Naming::to_snake_case(&struct_name),
969 );
970 (snake_module, struct_name)
971 } else {
972 let resource_name = crate::naming::Naming::to_rust_identifier(&structure_def.name);
974 let snake_name = crate::naming::Naming::to_rust_identifier(
975 &crate::naming::Naming::to_snake_case(&resource_name),
976 );
977 (snake_name, resource_name)
978 };
979
980 format!(
981 r#"// Re-export traits for convenient importing
982// This allows users to just import the resource module and get all associated traits
983pub use crate::traits::{trait_module_name}::{{
984 {trait_prefix}Mutators,
985 {trait_prefix}Accessors,
986 {trait_prefix}Existence,
987}};"#
988 )
989 }
990
991 fn generate_default_implementation(
993 &self,
994 structure_def: &StructureDefinition,
995 rust_struct: &RustStruct,
996 ) -> String {
997 let is_profile = crate::generators::type_registry::TypeRegistry::is_profile(structure_def);
999 if is_profile {
1000 return String::new();
1001 }
1002
1003 let struct_name = &rust_struct.name;
1005
1006 if rust_struct.derives.iter().any(|d| d == "Default") {
1008 return String::new();
1009 }
1010
1011 let elements = if let Some(differential) = &structure_def.differential {
1013 &differential.element
1014 } else if let Some(snapshot) = &structure_def.snapshot {
1015 &snapshot.element
1016 } else {
1017 &Vec::new()
1019 };
1020
1021 let mut required_fields = Vec::new();
1023 for element in elements {
1024 let path_parts: Vec<&str> = element.path.split('.').collect();
1025 if path_parts.len() == 2 && path_parts[0] == structure_def.name {
1026 let field_name = path_parts[1];
1027 if let Some(min) = element.min {
1028 if min >= 1 && !field_name.ends_with("[x]") {
1029 required_fields.push((field_name, element.clone()));
1030 }
1031 }
1032 }
1033 }
1034
1035 let mut field_inits = Vec::new();
1038
1039 if let Some(base_def) = &rust_struct.base_definition {
1041 let base_type = base_def.split('/').next_back().unwrap_or(base_def);
1043 let base_type = crate::naming::Naming::to_rust_identifier(base_type);
1044 let proper_base_type = if base_type
1045 .chars()
1046 .next()
1047 .map(|c| c.is_lowercase())
1048 .unwrap_or(false)
1049 {
1050 crate::naming::Naming::capitalize_first(&base_type)
1051 } else {
1052 base_type
1053 };
1054 field_inits.push(format!("base: {proper_base_type}::default()"));
1055 }
1057
1058 for field in &rust_struct.fields {
1060 let field_name = &field.name;
1061
1062 let is_required = required_fields.iter().any(|(name, _)| {
1064 let snake_name = crate::naming::Naming::to_snake_case(name);
1065 snake_name == *field_name
1066 });
1067
1068 if is_required {
1069 let default_value = match field.field_type.to_string().as_str() {
1071 s if s.contains("::") && !s.contains("Option") && !s.contains("Vec") => {
1073 format!("{s}::default()")
1074 }
1075 "String" => "String::new()".to_string(),
1077 "i32" | "i64" | "u32" | "u64" => "0".to_string(),
1079 "f32" | "f64" => "0.0".to_string(),
1080 "bool" => "false".to_string(),
1081 s if s.starts_with("Vec<") => "Vec::new()".to_string(),
1083 _ => format!("{}::default()", field.field_type.to_string()),
1085 };
1086 field_inits.push(format!("{field_name}: {default_value}"));
1087 } else {
1088 field_inits.push(format!("{field_name}: Default::default()"));
1090 }
1091 }
1092
1093 let impl_block = format!(
1095 r#"impl Default for {} {{
1096 fn default() -> Self {{
1097 Self {{
1098 {}
1099 }}
1100 }}
1101}}"#,
1102 struct_name,
1103 field_inits.join(",\n ")
1104 );
1105
1106 impl_block
1107 }
1108
1109 fn generate_nested_struct_default_implementation(
1114 &self,
1115 parent_structure_def: &StructureDefinition,
1116 nested_struct: &RustStruct,
1117 ) -> String {
1118 let struct_name = &nested_struct.name;
1120
1121 if nested_struct.derives.iter().any(|d| d == "Default") {
1123 return String::new();
1124 }
1125
1126 let parent_name = &parent_structure_def.name;
1129 let nested_field_name = if struct_name.starts_with(parent_name) {
1130 let suffix = &struct_name[parent_name.len()..];
1131 crate::naming::Naming::to_snake_case(suffix)
1132 } else {
1133 return String::new();
1135 };
1136
1137 let base_path = format!("{parent_name}.{nested_field_name}");
1138
1139 let elements = if let Some(differential) = &parent_structure_def.differential {
1141 &differential.element
1142 } else if let Some(snapshot) = &parent_structure_def.snapshot {
1143 &snapshot.element
1144 } else {
1145 &Vec::new()
1146 };
1147
1148 let mut required_fields = Vec::new();
1150 for element in elements {
1151 if element.path.starts_with(&format!("{base_path}.")) {
1153 let field_path = element.path.strip_prefix(&format!("{base_path}.")).unwrap();
1154 if !field_path.contains('.') && !field_path.ends_with("[x]") {
1156 if let Some(min) = element.min {
1157 if min >= 1 {
1158 required_fields.push((field_path, element.clone()));
1159 }
1160 }
1161 }
1162 }
1163 }
1164
1165 let mut field_inits = Vec::new();
1167
1168 if let Some(base_def) = &nested_struct.base_definition {
1170 let base_type = base_def.split('/').next_back().unwrap_or(base_def);
1172 let base_type = crate::naming::Naming::to_rust_identifier(base_type);
1173 let proper_base_type = if base_type
1174 .chars()
1175 .next()
1176 .map(|c| c.is_lowercase())
1177 .unwrap_or(false)
1178 {
1179 crate::naming::Naming::capitalize_first(&base_type)
1180 } else {
1181 base_type
1182 };
1183 field_inits.push(format!("base: {proper_base_type}::default()"));
1184 }
1185
1186 for field in &nested_struct.fields {
1188 let field_name = &field.name;
1189
1190 let is_required = required_fields.iter().any(|(name, _)| {
1192 let snake_name = crate::naming::Naming::to_snake_case(name);
1193 snake_name == *field_name
1194 });
1195
1196 if is_required {
1197 let default_value = match field.field_type.to_string().as_str() {
1199 s if s.contains("::") && !s.contains("Option") && !s.contains("Vec") => {
1201 format!("{s}::default()")
1202 }
1203 "String" => "String::new()".to_string(),
1205 "i32" | "i64" | "u32" | "u64" => "0".to_string(),
1207 "f32" | "f64" => "0.0".to_string(),
1208 "bool" => "false".to_string(),
1209 s if s.starts_with("Vec<") => "Vec::new()".to_string(),
1211 _ => format!("{}::default()", field.field_type.to_string()),
1213 };
1214 field_inits.push(format!("{field_name}: {default_value}"));
1215 } else {
1216 field_inits.push(format!("{field_name}: Default::default()"));
1218 }
1219 }
1220
1221 let impl_block = format!(
1223 r#"impl Default for {} {{
1224 fn default() -> Self {{
1225 Self {{
1226 {}
1227 }}
1228 }}
1229}}"#,
1230 struct_name,
1231 field_inits.join(",\n ")
1232 );
1233
1234 impl_block
1235 }
1236
1237 pub fn generate_complete_crate<P: AsRef<Path>>(
1239 &self,
1240 output_dir: P,
1241 crate_name: &str,
1242 _structures: &[StructureDefinition],
1243 ) -> CodegenResult<()> {
1244 let output_dir = output_dir.as_ref();
1245
1246 let src_dir = output_dir.join("src");
1248 fs::create_dir_all(&src_dir)?;
1249
1250 let primitives_dir = src_dir.join("primitives");
1252 let datatypes_dir = src_dir.join("datatypes");
1253 let extensions_dir = src_dir.join("extensions");
1254 let resource_dir = src_dir.join("resource");
1255 let traits_dir = src_dir.join("traits");
1256
1257 fs::create_dir_all(&primitives_dir)?;
1258 fs::create_dir_all(&datatypes_dir)?;
1259 fs::create_dir_all(&extensions_dir)?;
1260 fs::create_dir_all(&resource_dir)?;
1261 fs::create_dir_all(&traits_dir)?;
1262
1263 self.generate_lib_file(src_dir.join("lib.rs"))?;
1265
1266 self.generate_macros_file(src_dir.join("macros.rs"))?;
1268
1269 self.generate_combined_primitives_file(&[], primitives_dir.join("mod.rs"))?;
1272
1273 let cargo_toml_path = output_dir.join("Cargo.toml");
1275 if !cargo_toml_path.exists() {
1276 self.generate_cargo_toml(&cargo_toml_path, crate_name)?;
1277 }
1278
1279 self.generate_module_file(&datatypes_dir, &[])?;
1282 self.generate_module_file(&extensions_dir, &[])?;
1283 self.generate_module_file(&resource_dir, &[])?;
1284 self.generate_module_file(&traits_dir, &[])?;
1285
1286 Ok(())
1287 }
1288
1289 fn generate_cargo_toml<P: AsRef<Path>>(
1291 &self,
1292 cargo_path: P,
1293 crate_name: &str,
1294 ) -> CodegenResult<()> {
1295 let cargo_content = format!(
1296 r#"[package]
1297name = "{crate_name}"
1298version = "0.1.0"
1299edition = "2021"
1300
1301[dependencies]
1302serde = {{ version = "1.0", features = ["derive"] }}
1303serde_json = "1.0"
1304"#
1305 );
1306
1307 fs::write(cargo_path, cargo_content)?;
1308 Ok(())
1309 }
1310}
1311
1312#[cfg(test)]
1313mod tests {
1314 use super::*;
1315 use crate::config::CodegenConfig;
1316 use crate::generators::token_generator::TokenGenerator;
1317 use std::fs;
1318 use tempfile::TempDir;
1319
1320 #[test]
1321 fn test_generate_macros_file() {
1322 let temp_dir = TempDir::new().unwrap();
1323 let macros_path = temp_dir.path().join("macros.rs");
1324
1325 let config = CodegenConfig::default();
1326 let token_generator = TokenGenerator::new();
1327 let file_generator = FileGenerator::new(&config, &token_generator);
1328
1329 file_generator.generate_macros_file(¯os_path).unwrap();
1330
1331 assert!(macros_path.exists());
1332 let content = fs::read_to_string(¯os_path).unwrap();
1333
1334 assert!(content.contains("macro_rules! primitive_string"));
1336 assert!(content.contains("macro_rules! primitive_boolean"));
1337 assert!(content.contains("macro_rules! primitive_id"));
1338 }
1339
1340 #[test]
1341 fn test_generate_lib_file() {
1342 let temp_dir = TempDir::new().unwrap();
1343 let lib_path = temp_dir.path().join("lib.rs");
1344
1345 let config = CodegenConfig::default();
1346 let token_generator = TokenGenerator::new();
1347 let file_generator = FileGenerator::new(&config, &token_generator);
1348
1349 file_generator.generate_lib_file(&lib_path).unwrap();
1350
1351 assert!(lib_path.exists());
1352 let content = fs::read_to_string(&lib_path).unwrap();
1353
1354 assert!(content.contains("pub mod macros;"));
1356 assert!(content.contains("pub mod primitives;"));
1357 assert!(content.contains("pub mod datatypes;"));
1358 assert!(content.contains("pub mod resources;"));
1359 assert!(content.contains("pub mod traits;"));
1360 assert!(content.contains("pub mod bindings;"));
1361
1362 assert!(content.contains("pub use macros::*;"));
1364 assert!(content.contains("pub use serde::{Deserialize, Serialize};"));
1365
1366 assert!(!content.contains("pub use primitives::*;"));
1368 assert!(!content.contains("pub use datatypes::*;"));
1369 assert!(!content.contains("pub use resource::*;"));
1370 assert!(!content.contains("pub use traits::*;"));
1371 assert!(!content.contains("pub use bindings::*;"));
1372 }
1373
1374 #[test]
1375 fn test_generate_complete_crate() {
1376 let temp_dir = TempDir::new().unwrap();
1377 let crate_path = temp_dir.path().join("test-crate");
1378
1379 let config = CodegenConfig::default();
1380 let token_generator = TokenGenerator::new();
1381 let file_generator = FileGenerator::new(&config, &token_generator);
1382
1383 file_generator
1384 .generate_complete_crate(
1385 &crate_path,
1386 "test-crate",
1387 &[], )
1389 .unwrap();
1390
1391 assert!(crate_path.join("Cargo.toml").exists());
1393 assert!(crate_path.join("src").is_dir());
1394 assert!(crate_path.join("src/lib.rs").exists());
1395 assert!(crate_path.join("src/macros.rs").exists());
1396 assert!(crate_path.join("src/primitives").is_dir());
1397 assert!(crate_path.join("src/primitives/mod.rs").exists());
1398 assert!(crate_path.join("src/datatypes").is_dir());
1399 assert!(crate_path.join("src/datatypes/mod.rs").exists());
1400 assert!(crate_path.join("src/resource").is_dir());
1401 assert!(crate_path.join("src/resource/mod.rs").exists());
1402 assert!(crate_path.join("src/traits").is_dir());
1403 assert!(crate_path.join("src/traits/mod.rs").exists());
1404
1405 let cargo_content = fs::read_to_string(crate_path.join("Cargo.toml")).unwrap();
1407 assert!(cargo_content.contains("name = \"test-crate\""));
1408 assert!(cargo_content.contains("edition = \"2021\""));
1409 assert!(cargo_content.contains("serde"));
1410 assert!(!cargo_content.contains("paste")); }
1412}