Skip to main content

rh_codegen/generators/
token_generator.rs

1//! Token generation utilities for Rust code emission
2//!
3//! This module handles the generation of Rust code tokens from the internal
4//! representation, including structs, enums, and modules.
5
6use crate::rust_types::{
7    RustEnum, RustEnumVariant, RustField, RustModule, RustStruct, RustType, RustTypeAlias,
8};
9use proc_macro2::TokenStream;
10use quote::{format_ident, quote};
11
12/// Generates Rust code tokens from internal representations
13#[derive(Debug)]
14pub struct TokenGenerator {
15    /// Whether to include serde derives by default
16    include_serde: bool,
17}
18
19impl TokenGenerator {
20    pub fn new() -> Self {
21        Self {
22            include_serde: true,
23        }
24    }
25
26    pub fn with_serde(mut self, include_serde: bool) -> Self {
27        self.include_serde = include_serde;
28        self
29    }
30
31    /// Generate tokens for a complete module
32    pub fn generate_module(&self, module: &RustModule) -> TokenStream {
33        let mut tokens = TokenStream::new();
34
35        // Add module documentation if present
36        if let Some(doc) = &module.doc_comment {
37            let doc_lines: Vec<_> = doc.lines().collect();
38            for line in doc_lines {
39                tokens.extend(quote! {
40                    #[doc = #line]
41                });
42            }
43        }
44
45        // Add imports
46        for import in &module.imports {
47            let import_tokens: TokenStream = import
48                .parse()
49                .expect("codegen bug: invalid import statement in module imports");
50            tokens.extend(quote! {
51                use #import_tokens;
52            });
53        }
54
55        // Add structs
56        for rust_struct in &module.structs {
57            tokens.extend(self.generate_struct(rust_struct));
58        }
59
60        // Add enums
61        for rust_enum in &module.enums {
62            tokens.extend(self.generate_enum(rust_enum));
63        }
64
65        tokens
66    }
67
68    /// Generate tokens for a Rust struct
69    pub fn generate_struct(&self, rust_struct: &RustStruct) -> TokenStream {
70        let name = format_ident!("{}", rust_struct.name);
71
72        // Generate documentation
73        let doc_attrs = if let Some(doc) = &rust_struct.doc_comment {
74            let doc_lines: Vec<_> = doc.lines().collect();
75            let attrs: Vec<TokenStream> = doc_lines
76                .iter()
77                .map(|line| {
78                    let formatted_line = if line.trim().is_empty() {
79                        "".to_string()
80                    } else {
81                        format!(" {line}")
82                    };
83                    quote! { #[doc = #formatted_line] }
84                })
85                .collect();
86            quote! { #(#attrs)* }
87        } else {
88            quote! {}
89        };
90
91        // Generate derive attributes
92        let mut derives = rust_struct.derives.clone();
93        if self.include_serde {
94            if !derives.contains(&"Serialize".to_string()) {
95                derives.push("Serialize".to_string());
96            }
97            if !derives.contains(&"Deserialize".to_string()) {
98                derives.push("Deserialize".to_string());
99            }
100        }
101
102        let derive_idents: Vec<_> = derives.iter().map(|d| format_ident!("{}", d)).collect();
103
104        // Generate fields
105        let mut fields: Vec<TokenStream> = Vec::new();
106
107        // Add base definition as flattened field if present
108        if let Some(base_def) = &rust_struct.base_definition {
109            // Extract the base type name from the URL (e.g., "http://hl7.org/fhir/StructureDefinition/Element" -> "Element")
110            let base_type = base_def.split('/').next_back().unwrap_or(base_def);
111            // Convert base type to a valid Rust identifier to handle cases with hyphens
112            let base_type = crate::naming::Naming::to_rust_identifier(base_type);
113
114            // For base definitions, ensure proper struct name casing but only for
115            // names that are clearly in all lowercase (like "vitalsigns")
116            let proper_base_type = if base_type
117                .chars()
118                .all(|c| c.is_lowercase() || c.is_numeric())
119            {
120                // Convert all-lowercase names to PascalCase (e.g., "vitalsigns" -> "Vitalsigns")
121                crate::naming::Naming::capitalize_first(&base_type)
122            } else {
123                // Keep names that already have proper casing (e.g., "BackboneElement")
124                base_type
125            };
126            let base_field_name = format_ident!("base");
127            let base_type_ident = format_ident!("{}", proper_base_type);
128
129            fields.push(quote! {
130                #[doc = " Base definition inherited from FHIR specification"]
131                #[serde(flatten)]
132                pub #base_field_name: #base_type_ident
133            });
134        }
135
136        // Add regular fields
137        let regular_fields: Vec<_> = rust_struct
138            .fields
139            .iter()
140            .map(|field| self.generate_field(field))
141            .collect();
142        fields.extend(regular_fields);
143
144        // Generate visibility
145        let vis = if rust_struct.is_public {
146            quote! { pub }
147        } else {
148            quote! {}
149        };
150
151        quote! {
152            #doc_attrs
153            #[derive(#(#derive_idents),*)]
154            #vis struct #name {
155                #(#fields),*
156            }
157        }
158    }
159
160    /// Generate tokens for a struct field
161    fn generate_field(&self, field: &RustField) -> TokenStream {
162        // Check if this is a macro call field - emit it directly as a macro call
163        if let Some(macro_call) = &field.macro_call {
164            return self.emit_macro_call(macro_call);
165        }
166
167        let name = format_ident!("{}", field.name);
168
169        // For 0..* cardinality fields, emit Vec<T> with serde(default, skip_serializing_if)
170        // instead of Option<Vec<T>>, because absent ⇔ empty in FHIR JSON.
171        let field_type = if field.is_repeating {
172            // The field_type is already RustType::Vec(inner) for regular array fields,
173            // or RustType::Custom for companion extension fields. Extract the inner
174            // type so we emit Vec<inner> instead of Option<Vec<inner>> or Vec<Vec<inner>>.
175            let inner = match &field.field_type {
176                RustType::Vec(inner) => self.generate_type(inner, false),
177                other => self.generate_type(other, false),
178            };
179            quote! { Vec<#inner> }
180        } else {
181            self.generate_type(&field.field_type, field.is_optional)
182        };
183
184        // Generate documentation
185        let doc_attrs = if let Some(doc) = &field.doc_comment {
186            let doc_lines: Vec<_> = doc.lines().collect();
187            let attrs: Vec<TokenStream> = doc_lines
188                .iter()
189                .map(|line| {
190                    let formatted_line = if line.trim().is_empty() {
191                        "".to_string()
192                    } else {
193                        format!(" {line}")
194                    };
195                    quote! { #[doc = #formatted_line] }
196                })
197                .collect();
198            quote! { #(#attrs)* }
199        } else {
200            quote! {}
201        };
202
203        // Generate serde attributes
204        // For repeating optional fields (0..*), add default + skip_serializing_if
205        let mut serde_attrs: Vec<TokenStream> = field
206            .serde_attributes
207            .iter()
208            .map(|attr| {
209                let attr_tokens: TokenStream = format!("serde({attr})")
210                    .parse()
211                    .expect("codegen bug: invalid serde attribute");
212                quote! { #[#attr_tokens] }
213            })
214            .collect();
215
216        if field.is_repeating && field.is_optional {
217            // 0..* cardinality: Vec<T> with serde(default, skip_serializing_if = "Vec::is_empty")
218            let default_attr: TokenStream =
219                "serde(default, skip_serializing_if = \"Vec::is_empty\")"
220                    .parse()
221                    .expect("codegen bug: invalid serde attribute for repeating field");
222            serde_attrs.push(quote! { #[#default_attr] });
223        }
224
225        // Generate visibility
226        let vis = if field.is_public {
227            quote! { pub }
228        } else {
229            quote! {}
230        };
231
232        quote! {
233            #doc_attrs
234            #(#serde_attrs)*
235            #vis #name: #field_type
236        }
237    }
238
239    /// Generate tokens for a Rust enum
240    pub fn generate_enum(&self, rust_enum: &RustEnum) -> TokenStream {
241        let name = format_ident!("{}", rust_enum.name);
242
243        // Generate documentation
244        let doc_attrs = if let Some(doc) = &rust_enum.doc_comment {
245            let doc_lines: Vec<_> = doc.lines().collect();
246            let attrs: Vec<TokenStream> = doc_lines
247                .iter()
248                .map(|line| quote! { #[doc = #line] })
249                .collect();
250            quote! { #(#attrs)* }
251        } else {
252            quote! {}
253        };
254
255        // Generate derive attributes
256        let mut derives = rust_enum.derives.clone();
257        if self.include_serde {
258            if !derives.contains(&"Serialize".to_string()) {
259                derives.push("Serialize".to_string());
260            }
261            if !derives.contains(&"Deserialize".to_string()) {
262                derives.push("Deserialize".to_string());
263            }
264        }
265
266        let derive_idents: Vec<_> = derives.iter().map(|d| format_ident!("{}", d)).collect();
267
268        // Generate variants
269        let variants: Vec<_> = rust_enum
270            .variants
271            .iter()
272            .map(|variant| self.generate_enum_variant(variant))
273            .collect();
274
275        // Generate visibility
276        let vis = if rust_enum.is_public {
277            quote! { pub }
278        } else {
279            quote! {}
280        };
281
282        // Generate Default implementation using the first variant
283        let default_impl = if !rust_enum.variants.is_empty() {
284            let first_variant = &rust_enum.variants[0];
285            let first_variant_name = format_ident!("{}", first_variant.name);
286
287            // Check if the first variant has data (tuple variant)
288            if first_variant.data.is_some() {
289                // For variants with data, we can't easily generate a Default
290                // Skip Default implementation for enums with data variants
291                quote! {}
292            } else {
293                // For unit variants, generate Default implementation
294                quote! {
295                    impl Default for #name {
296                        fn default() -> Self {
297                            Self::#first_variant_name
298                        }
299                    }
300                }
301            }
302        } else {
303            quote! {}
304        };
305
306        quote! {
307            #doc_attrs
308            #[derive(#(#derive_idents),*)]
309            #vis enum #name {
310                #(#variants),*
311            }
312
313            #default_impl
314        }
315    }
316
317    /// Generate tokens for an enum variant
318    fn generate_enum_variant(&self, variant: &RustEnumVariant) -> TokenStream {
319        let name = format_ident!("{}", variant.name);
320
321        // Generate documentation
322        let doc_attrs = if let Some(doc) = &variant.doc_comment {
323            let doc_lines: Vec<_> = doc.lines().collect();
324            let attrs: Vec<TokenStream> = doc_lines
325                .iter()
326                .map(|line| quote! { #[doc = #line] })
327                .collect();
328            quote! { #(#attrs)* }
329        } else {
330            quote! {}
331        };
332
333        // Generate serde rename attribute if needed
334        let serde_attrs = if let Some(rename) = &variant.serde_rename {
335            quote! { #[serde(rename = #rename)] }
336        } else {
337            quote! {}
338        };
339
340        // Generate variant with optional data
341        if let Some(data_type) = &variant.data {
342            let data_tokens = self.generate_type(data_type, false);
343            quote! {
344                #doc_attrs
345                #serde_attrs
346                #name(#data_tokens)
347            }
348        } else {
349            quote! {
350                #doc_attrs
351                #serde_attrs
352                #name
353            }
354        }
355    }
356
357    /// Generate tokens for a Rust type
358    #[allow(clippy::only_used_in_recursion)]
359    fn generate_type(&self, rust_type: &RustType, wrap_optional: bool) -> TokenStream {
360        let base_type = match rust_type {
361            RustType::String => quote! { String },
362            RustType::Integer => quote! { i32 },
363            RustType::Boolean => quote! { bool },
364            RustType::Float => quote! { f64 },
365            RustType::Option(inner) => {
366                let inner_tokens = self.generate_type(inner, false);
367                quote! { Option<#inner_tokens> }
368            }
369            RustType::Vec(inner) => {
370                let inner_tokens = self.generate_type(inner, false);
371                quote! { Vec<#inner_tokens> }
372            }
373            RustType::Box(inner) => {
374                let inner_tokens = self.generate_type(inner, false);
375                quote! { Box<#inner_tokens> }
376            }
377            RustType::Slice(inner) => {
378                let inner_tokens = self.generate_type(inner, false);
379                quote! { &[#inner_tokens] }
380            }
381            RustType::Custom(name) => {
382                // Check if this is a complex type that shouldn't be treated as an identifier
383                if name.contains('&')
384                    || name.contains('<')
385                    || name.contains('>')
386                    || name.contains('[')
387                    || name.contains(']')
388                    || name.contains('\'')
389                {
390                    // Parse as a type expression
391                    let type_tokens: TokenStream = name
392                        .parse()
393                        .expect("codegen bug: invalid type expression in Custom RustType");
394                    quote! { #type_tokens }
395                } else {
396                    let ident = format_ident!("{}", name);
397                    quote! { #ident }
398                }
399            }
400            RustType::Reference(name) => {
401                let ident = format_ident!("{}", name);
402                quote! { &#ident }
403            }
404        };
405
406        if wrap_optional && !matches!(rust_type, RustType::Option(_)) {
407            quote! { Option<#base_type> }
408        } else {
409            base_type
410        }
411    }
412
413    /// Generate tokens for a Rust type alias
414    pub fn generate_type_alias(&self, type_alias: &RustTypeAlias) -> TokenStream {
415        let name = format_ident!("{}", type_alias.name);
416        let target_type = self.generate_type(&type_alias.target_type, false);
417
418        // Generate documentation
419        let doc_attrs = if let Some(doc) = &type_alias.doc_comment {
420            let doc_lines: Vec<_> = doc.lines().collect();
421            let attrs: Vec<TokenStream> = doc_lines
422                .iter()
423                .map(|line| quote! { #[doc = #line] })
424                .collect();
425            quote! { #(#attrs)* }
426        } else {
427            quote! {}
428        };
429
430        // Generate visibility
431        let vis = if type_alias.is_public {
432            quote! { pub }
433        } else {
434            quote! {}
435        };
436
437        quote! {
438            #doc_attrs
439            #vis type #name = #target_type;
440        }
441    }
442
443    /// Generate tokens for a Rust trait
444    pub fn generate_trait(&self, rust_trait: &crate::rust_types::RustTrait) -> TokenStream {
445        let trait_name = format_ident!("{}", rust_trait.name);
446
447        // Generate documentation
448        let doc = if let Some(doc_comment) = &rust_trait.doc_comment {
449            let doc_lines: Vec<_> = doc_comment.lines().collect();
450            let attrs: Vec<TokenStream> = doc_lines
451                .iter()
452                .map(|line| {
453                    let formatted_line = if line.trim().is_empty() {
454                        "".to_string()
455                    } else {
456                        format!(" {line}")
457                    };
458                    quote! { #[doc = #formatted_line] }
459                })
460                .collect();
461            quote! { #(#attrs)* }
462        } else {
463            quote! {}
464        };
465
466        // Generate super traits
467        let super_traits = if !rust_trait.super_traits.is_empty() {
468            let super_trait_idents: Vec<_> = rust_trait
469                .super_traits
470                .iter()
471                .map(|s| format_ident!("{}", s))
472                .collect();
473            quote! { : #(#super_trait_idents)+* }
474        } else {
475            quote! {}
476        };
477
478        // Generate trait methods
479        let methods: Vec<TokenStream> = rust_trait
480            .methods
481            .iter()
482            .map(|method| self.generate_trait_method(method))
483            .collect();
484
485        quote! {
486            #doc
487            pub trait #trait_name #super_traits {
488                #(#methods)*
489            }
490        }
491    }
492
493    /// Generate tokens for a trait method
494    fn generate_trait_method(&self, method: &crate::rust_types::RustTraitMethod) -> TokenStream {
495        let method_name = format_ident!("{}", method.name);
496
497        // Generate documentation
498        let doc = if let Some(doc_comment) = &method.doc_comment {
499            let doc_lines: Vec<_> = doc_comment.lines().collect();
500            let attrs: Vec<TokenStream> = doc_lines
501                .iter()
502                .map(|line| {
503                    let formatted_line = if line.trim().is_empty() {
504                        "".to_string()
505                    } else {
506                        format!(" {line}")
507                    };
508                    quote! { #[doc = #formatted_line] }
509                })
510                .collect();
511            quote! { #(#attrs)* }
512        } else {
513            quote! {}
514        };
515
516        // Generate parameters
517        let params: Vec<TokenStream> = method
518            .params
519            .iter()
520            .map(|param| {
521                let param_name = format_ident!("{}", param.name);
522                let param_type = self.generate_type(&param.param_type, false);
523
524                match (param.is_ref, param.is_mut) {
525                    (true, true) => quote! { #param_name: &mut #param_type },
526                    (true, false) => quote! { #param_name: &#param_type },
527                    (false, _) => quote! { #param_name: #param_type },
528                }
529            })
530            .collect();
531
532        // Add self parameter if specified
533        let all_params = if let Some(self_param_str) = &method.self_param {
534            let self_param: TokenStream = match self_param_str.as_str() {
535                "self" => quote! { self },
536                "&self" => quote! { &self },
537                "&mut self" => quote! { &mut self },
538                "mut self" => quote! { mut self },
539                _ => self_param_str.parse().unwrap_or_else(|_| quote! { &self }),
540            };
541            if params.is_empty() {
542                vec![self_param]
543            } else {
544                let mut all = vec![self_param];
545                all.extend(params);
546                all
547            }
548        } else {
549            // No self parameter (e.g., for associated functions like new())
550            params
551        };
552
553        // Generate return type
554        let return_type = if let Some(ret_type) = &method.return_type {
555            let return_tokens = self.generate_type(ret_type, false);
556            quote! { -> #return_tokens }
557        } else {
558            quote! {}
559        };
560
561        // Generate method body for default implementations
562        if method.is_default {
563            let body = if let Some(body_code) = &method.default_body {
564                let body_tokens: TokenStream = body_code
565                    .parse()
566                    .unwrap_or_else(|_| quote! { unimplemented!() });
567                quote! { { #body_tokens } }
568            } else {
569                quote! { { unimplemented!() } }
570            };
571
572            quote! {
573                #doc
574                fn #method_name(#(#all_params),*) #return_type #body
575            }
576        } else {
577            quote! {
578                #doc
579                fn #method_name(#(#all_params),*) #return_type;
580            }
581        }
582    }
583
584    /// Expand a primitive macro call into actual struct fields
585    #[allow(dead_code)]
586    fn expand_primitive_macro(&self, macro_call: &str) -> TokenStream {
587        // Parse the macro call to extract parameters
588        // Expected format: "primitive_type!(field_name, is_optional)"
589
590        // Simple parsing without regex
591        if let Some(start) = macro_call.find('!') {
592            let macro_name = &macro_call[..start];
593
594            // Extract the content between parentheses
595            if let (Some(paren_start), Some(paren_end)) =
596                (macro_call.find('('), macro_call.rfind(')'))
597            {
598                let content = &macro_call[paren_start + 1..paren_end];
599                let parts: Vec<&str> = content.split(',').map(|s| s.trim()).collect();
600
601                if parts.len() == 2 {
602                    let field_name = parts[0].trim_matches('"');
603                    let is_optional = parts[1] == "true";
604
605                    // Map macro names to Rust types
606                    let rust_type = match macro_name {
607                        "primitive_string" => "String",
608                        "primitive_boolean" => "bool",
609                        "primitive_integer" => "i32",
610                        "primitive_decimal" => "f64",
611                        "primitive_datetime" => "String", // Placeholder
612                        "primitive_date" => "String",
613                        "primitive_time" => "String",
614                        "primitive_uri" => "String",
615                        "primitive_canonical" => "String",
616                        "primitive_base64binary" => "String",
617                        "primitive_instant" => "String",
618                        "primitive_positiveint" => "u32",
619                        "primitive_unsignedint" => "u32",
620                        "primitive_id" => "String",
621                        "primitive_oid" => "String",
622                        "primitive_uuid" => "String",
623                        "primitive_code" => "String",
624                        "primitive_markdown" => "String",
625                        "primitive_url" => "String",
626                        _ => "String", // Default fallback
627                    };
628
629                    let field_ident = format_ident!("{}", field_name);
630                    let companion_field_ident = format_ident!("_{}", field_name);
631                    let type_ident = format_ident!("{}", rust_type);
632                    let companion_rename = format!("_{field_name}");
633
634                    // Generate both the main field and companion field
635                    if is_optional {
636                        quote! {
637                            pub #field_ident: Option<#type_ident>,
638                            #[serde(rename = #companion_rename)]
639                            pub #companion_field_ident: Option<serde_json::Value>
640                        }
641                    } else {
642                        quote! {
643                            pub #field_ident: #type_ident,
644                            #[serde(rename = #companion_rename)]
645                            pub #companion_field_ident: Option<serde_json::Value>
646                        }
647                    }
648                } else {
649                    // Fallback: return empty
650                    quote! {}
651                }
652            } else {
653                // Fallback: return empty
654                quote! {}
655            }
656        } else {
657            // Fallback: return empty
658            quote! {}
659        }
660    }
661
662    /// Emit a macro call directly into the generated code
663    fn emit_macro_call(&self, macro_call: &str) -> TokenStream {
664        // The macro_call string should be parseable directly as a macro invocation
665        // For example: "primitive_string!(\"description\", true)"
666
667        match macro_call.parse::<TokenStream>() {
668            Ok(tokens) => tokens,
669            Err(_) => {
670                // If parsing fails, try to construct it manually
671                eprintln!("Warning: Failed to parse macro call: {macro_call}");
672                quote! { /* Invalid macro call: #macro_call */ }
673            }
674        }
675    }
676
677    /// Generate tokens for a trait implementation block
678    pub fn generate_trait_impl(
679        &self,
680        trait_impl: &crate::rust_types::RustTraitImpl,
681    ) -> TokenStream {
682        let trait_name: TokenStream = trait_impl.trait_name.parse().unwrap_or_else(|_| {
683            eprintln!(
684                "Warning: Failed to parse trait name: {}",
685                trait_impl.trait_name
686            );
687            quote! { InvalidTraitName }
688        });
689
690        let struct_name: TokenStream = trait_impl.struct_name.parse().unwrap_or_else(|_| {
691            eprintln!(
692                "Warning: Failed to parse struct name: {}",
693                trait_impl.struct_name
694            );
695            quote! { InvalidStructName }
696        });
697
698        let methods: Vec<TokenStream> = trait_impl
699            .methods
700            .iter()
701            .map(|method| {
702                let method_name: TokenStream = method.name.parse().unwrap_or_else(|_| {
703                    quote! { invalid_method_name }
704                });
705
706                let return_type: TokenStream = method.return_type.parse().unwrap_or_else(|_| {
707                    quote! { () }
708                });
709
710                let body: TokenStream = method.body.parse().unwrap_or_else(|_| {
711                    quote! { unimplemented!() }
712                });
713
714                // Generate parameters (excluding self which may or may not be present)
715                let params: Vec<TokenStream> = method
716                    .params
717                    .iter()
718                    .map(|param| {
719                        let param_name: TokenStream = param.name.parse().unwrap_or_else(|_| {
720                            quote! { invalid_param }
721                        });
722                        let param_type_str = param.param_type.to_string();
723                        let param_type: TokenStream = param_type_str.parse().unwrap_or_else(|_| {
724                            quote! { () }
725                        });
726                        quote! { #param_name: #param_type }
727                    })
728                    .collect();
729
730                // Build the complete parameter list with self if specified
731                let all_params = if let Some(self_param_str) = &method.self_param {
732                    let self_param: TokenStream = match self_param_str.as_str() {
733                        "self" => quote! { self },
734                        "&self" => quote! { &self },
735                        "&mut self" => quote! { &mut self },
736                        "mut self" => quote! { mut self },
737                        _ => self_param_str.parse().unwrap_or_else(|_| quote! { &self }),
738                    };
739                    if params.is_empty() {
740                        vec![self_param]
741                    } else {
742                        let mut all = vec![self_param];
743                        all.extend(params);
744                        all
745                    }
746                } else {
747                    // No self parameter (e.g., for associated functions like new())
748                    params
749                };
750
751                quote! {
752                    fn #method_name(#(#all_params),*) -> #return_type {
753                        #body
754                    }
755                }
756            })
757            .collect();
758
759        quote! {
760            impl #trait_name for #struct_name {
761                #(#methods)*
762            }
763        }
764    }
765}
766
767impl Default for TokenGenerator {
768    fn default() -> Self {
769        Self::new()
770    }
771}
772
773#[cfg(test)]
774mod tests {
775    use super::*;
776    use crate::rust_types::*;
777
778    #[test]
779    fn test_generate_simple_struct() {
780        let generator = TokenGenerator::new();
781
782        let mut rust_struct = RustStruct::new("TestStruct".to_string());
783        rust_struct.add_field(RustField::new("name".to_string(), RustType::String));
784        rust_struct.add_field(RustField::new("age".to_string(), RustType::Integer).optional());
785
786        let tokens = generator.generate_struct(&rust_struct);
787        let code = tokens.to_string();
788
789        assert!(code.contains("struct TestStruct"));
790        assert!(code.contains("pub name : String"));
791        assert!(code.contains("pub age : Option < i32 >"));
792    }
793
794    #[test]
795    fn test_generate_simple_enum() {
796        let generator = TokenGenerator::new();
797
798        let mut rust_enum = RustEnum::new("TestEnum".to_string());
799        rust_enum.add_variant(RustEnumVariant::new("Variant1".to_string()));
800        rust_enum
801            .add_variant(RustEnumVariant::new("Variant2".to_string()).with_data(RustType::String));
802
803        let tokens = generator.generate_enum(&rust_enum);
804        let code = tokens.to_string();
805
806        // Print the actual generated code for debugging
807        println!("Generated enum code: {code}");
808
809        assert!(code.contains("enum TestEnum"));
810        assert!(code.contains("Variant1"));
811        assert!(code.contains("Variant2"));
812        assert!(code.contains("String"));
813    }
814
815    #[test]
816    fn test_generate_struct_with_macro_calls() {
817        let generator = TokenGenerator::new();
818
819        let mut rust_struct = RustStruct::new("Patient".to_string());
820
821        // Add a regular field
822        rust_struct.add_field(RustField::new("id".to_string(), RustType::String));
823
824        // Add a field with a macro call
825        let macro_field =
826            RustField::new_macro_call("primitive_boolean!(\"active\", true)".to_string());
827        rust_struct.add_field(macro_field);
828
829        let tokens = generator.generate_struct(&rust_struct);
830        let code = tokens.to_string();
831
832        println!("Generated struct with macro code: {code}");
833
834        assert!(code.contains("struct Patient"));
835        assert!(code.contains("pub id : String"));
836        assert!(code.contains("primitive_boolean !"));
837        assert!(code.contains("\"active\""));
838        assert!(code.contains("true"));
839    }
840
841    #[test]
842    fn test_generate_type_alias() {
843        let generator = TokenGenerator::new();
844
845        let type_alias = RustTypeAlias::new("uri".to_string(), RustType::String)
846            .with_doc("FHIR URI primitive type".to_string());
847
848        let tokens = generator.generate_type_alias(&type_alias);
849        let code = tokens.to_string();
850
851        // Print the actual generated code for debugging
852        println!("Generated type alias code: {code}");
853
854        assert!(code.contains("type uri = String"));
855        assert!(code.contains("FHIR URI primitive type"));
856    }
857}