Skip to main content

tauri_typegen/
models.rs

1use serde::{Deserialize, Serialize};
2use serde_rename_rule::RenameRule;
3
4/// Represents the structure of a type for code generation
5/// This allows generators to work with parsed type information instead of string parsing
6#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
7#[serde(rename_all = "camelCase")]
8pub enum TypeStructure {
9    /// Primitive types: "string", "number", "boolean", "void"
10    Primitive(String),
11
12    /// Array/Vec types: `Vec<T>` -> `Array(T)`
13    Array(Box<TypeStructure>),
14
15    /// Map types: `HashMap<K, V>`, `BTreeMap<K, V>` -> `Map { key: K, value: V }`
16    Map {
17        key: Box<TypeStructure>,
18        value: Box<TypeStructure>,
19    },
20
21    /// Set types: `HashSet<T>`, `BTreeSet<T>` -> `Set(T)`
22    Set(Box<TypeStructure>),
23
24    /// Tuple types: `(T, U, V)` -> `Tuple([T, U, V])`
25    Tuple(Vec<TypeStructure>),
26
27    /// Optional types: `Option<T>` -> `Optional(T)`
28    Optional(Box<TypeStructure>),
29
30    /// Result types: `Result<T, E>` -> `Result(T)` (error type ignored for TS)
31    Result(Box<TypeStructure>),
32
33    /// Custom/User-defined types
34    Custom(String),
35}
36
37impl Default for TypeStructure {
38    fn default() -> Self {
39        // Default to string for test compatibility
40        TypeStructure::Primitive("string".to_string())
41    }
42}
43
44/// Represents the kind of an enum variant for discriminated union generation
45#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
46#[serde(rename_all = "camelCase")]
47pub enum EnumVariantKind {
48    /// Unit variant: `Quit`
49    Unit,
50    /// Tuple variant with unnamed fields: `Move(i32, i32)` or `Write(String)`
51    Tuple(Vec<TypeStructure>),
52    /// Struct variant with named fields: `ChangeColor { r: u8, g: u8, b: u8 }`
53    Struct(Vec<FieldInfo>),
54}
55
56/// Information about an enum variant for discriminated union generation
57#[derive(Debug, Clone)]
58pub struct EnumVariantInfo {
59    /// The variant name (e.g., "Quit", "Move", "ChangeColor")
60    pub name: String,
61    /// The kind of variant and its associated data
62    pub kind: EnumVariantKind,
63    /// Serde rename attribute: #[serde(rename = "...")]
64    pub serde_rename: Option<String>,
65}
66
67impl EnumVariantInfo {
68    /// Returns true if this is a unit variant (no associated data)
69    pub fn is_unit(&self) -> bool {
70        matches!(self.kind, EnumVariantKind::Unit)
71    }
72
73    /// Returns true if this is a tuple variant (unnamed fields)
74    pub fn is_tuple(&self) -> bool {
75        matches!(self.kind, EnumVariantKind::Tuple(_))
76    }
77
78    /// Returns true if this is a struct variant (named fields)
79    pub fn is_struct(&self) -> bool {
80        matches!(self.kind, EnumVariantKind::Struct(_))
81    }
82
83    /// Returns the tuple fields if this is a tuple variant
84    pub fn tuple_fields(&self) -> Option<&Vec<TypeStructure>> {
85        match &self.kind {
86            EnumVariantKind::Tuple(fields) => Some(fields),
87            _ => None,
88        }
89    }
90
91    /// Returns the struct fields if this is a struct variant
92    pub fn struct_fields(&self) -> Option<&Vec<FieldInfo>> {
93        match &self.kind {
94            EnumVariantKind::Struct(fields) => Some(fields),
95            _ => None,
96        }
97    }
98}
99
100pub struct CommandInfo {
101    pub name: String,
102    pub file_path: String,
103    pub line_number: usize,
104    pub parameters: Vec<ParameterInfo>,
105    pub return_type: String, // Rust return type (e.g., "Vec<Banana>")
106    /// Structured representation of the return type for generators
107    pub return_type_structure: TypeStructure,
108    pub is_async: bool,
109    pub channels: Vec<ChannelInfo>,
110    /// Serde rename_all attribute: #[serde(rename_all = "...")]
111    /// Applied to command function, affects parameter/channel serialization
112    pub serde_rename_all: Option<RenameRule>,
113}
114
115impl CommandInfo {
116    /// Helper for tests: Create a CommandInfo
117    #[doc(hidden)]
118    pub fn new_for_test(
119        name: impl Into<String>,
120        file_path: impl Into<String>,
121        line_number: usize,
122        parameters: Vec<ParameterInfo>,
123        return_type: impl Into<String>,
124        is_async: bool,
125        channels: Vec<ChannelInfo>,
126    ) -> Self {
127        use crate::analysis::type_resolver::TypeResolver;
128        let return_type_str = return_type.into();
129        let type_resolver = TypeResolver::new();
130        let return_type_structure = type_resolver.parse_type_structure(&return_type_str);
131
132        Self {
133            name: name.into(),
134            file_path: file_path.into(),
135            line_number,
136            parameters,
137            return_type: return_type_str,
138            return_type_structure,
139            is_async,
140            channels,
141            serde_rename_all: None,
142        }
143    }
144}
145
146pub struct ParameterInfo {
147    pub name: String,
148    pub rust_type: String,
149    pub is_optional: bool,
150    /// Structured representation of the type for generators
151    pub type_structure: TypeStructure,
152    /// Serde rename attribute (optional, for future extensibility)
153    /// Parameters are serialized following Tauri/JS conventions (camelCase)
154    pub serde_rename: Option<String>,
155}
156
157#[derive(Clone, Debug)]
158pub struct StructInfo {
159    pub name: String,
160    pub fields: Vec<FieldInfo>,
161    pub file_path: String,
162    pub is_enum: bool,
163    /// Serde rename_all attribute: #[serde(rename_all = "...")]
164    pub serde_rename_all: Option<RenameRule>,
165    /// Serde tag attribute for enums: #[serde(tag = "...")]
166    /// Used for internally-tagged enum representation
167    pub serde_tag: Option<String>,
168    /// Enum variants with full type information (only populated for enums)
169    /// When populated, provides richer variant data than the `fields` vector
170    pub enum_variants: Option<Vec<EnumVariantInfo>>,
171}
172
173impl StructInfo {
174    /// Returns true if this is a simple enum (all unit variants)
175    /// Simple enums can be represented as TypeScript string literal unions
176    pub fn is_simple_enum(&self) -> bool {
177        if !self.is_enum {
178            return false;
179        }
180
181        match &self.enum_variants {
182            Some(variants) => variants.iter().all(|v| v.is_unit()),
183            // Fallback to checking fields for backward compatibility
184            None => self.fields.iter().all(|f| f.rust_type == "enum_variant"),
185        }
186    }
187
188    /// Returns true if this is a complex enum (has tuple or struct variants)
189    /// Complex enums need discriminated union representation in TypeScript
190    pub fn is_complex_enum(&self) -> bool {
191        self.is_enum && !self.is_simple_enum()
192    }
193
194    /// Returns the discriminator tag name for this enum
195    /// Defaults to "type" if not specified via #[serde(tag = "...")]
196    pub fn discriminator_tag(&self) -> &str {
197        self.serde_tag.as_deref().unwrap_or("type")
198    }
199}
200
201#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
202#[serde(rename_all = "camelCase")]
203pub struct FieldInfo {
204    pub name: String,
205    pub rust_type: String,
206    pub is_optional: bool,
207    pub is_public: bool,
208    pub validator_attributes: Option<ValidatorAttributes>,
209    /// Serde rename attribute: #[serde(rename = "...")]
210    pub serde_rename: Option<String>,
211    /// Structured representation of the type for generators
212    pub type_structure: TypeStructure,
213}
214
215#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
216#[serde(rename_all = "camelCase")]
217pub struct ValidatorAttributes {
218    pub length: Option<LengthConstraint>,
219    pub range: Option<RangeConstraint>,
220    pub email: bool,
221    pub url: bool,
222    pub custom_message: Option<String>,
223}
224
225#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
226#[serde(rename_all = "camelCase")]
227pub struct LengthConstraint {
228    pub min: Option<u64>,
229    pub max: Option<u64>,
230    pub message: Option<String>,
231}
232
233#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
234#[serde(rename_all = "camelCase")]
235pub struct RangeConstraint {
236    pub min: Option<f64>,
237    pub max: Option<f64>,
238    pub message: Option<String>,
239}
240
241// Event information for frontend event listeners
242pub struct EventInfo {
243    pub event_name: String,
244    pub payload_type: String,
245    /// Structured representation of the payload type for generators
246    pub payload_type_structure: TypeStructure,
247    pub file_path: String,
248    pub line_number: usize,
249}
250
251// Channel information for streaming data from Rust to frontend
252#[derive(Clone)]
253pub struct ChannelInfo {
254    pub parameter_name: String,
255    pub message_type: String,
256    pub command_name: String,
257    pub file_path: String,
258    pub line_number: usize,
259    /// Serde rename attribute (optional, for future extensibility)
260    /// Channel parameters are serialized following Tauri/JS conventions (camelCase)
261    pub serde_rename: Option<String>,
262    /// Structured representation of the message type for generators
263    pub message_type_structure: TypeStructure,
264}
265
266impl ChannelInfo {
267    /// Helper for tests: Create a ChannelInfo
268    #[doc(hidden)]
269    pub fn new_for_test(
270        parameter_name: impl Into<String>,
271        message_type: impl Into<String>,
272        command_name: impl Into<String>,
273        file_path: impl Into<String>,
274        line_number: usize,
275    ) -> Self {
276        let message_type_str = message_type.into();
277        Self {
278            parameter_name: parameter_name.into(),
279            message_type: message_type_str.clone(),
280            command_name: command_name.into(),
281            file_path: file_path.into(),
282            line_number,
283            serde_rename: None,
284            // Parse message_type into TypeStructure for tests
285            message_type_structure: crate::analysis::type_resolver::TypeResolver::new()
286                .parse_type_structure(&message_type_str),
287        }
288    }
289}
290
291#[cfg(test)]
292mod tests {
293    use super::*;
294
295    // TypeStructure tests
296    mod type_structure {
297        use super::*;
298
299        #[test]
300        fn test_default_is_string_primitive() {
301            let default_type = TypeStructure::default();
302            match default_type {
303                TypeStructure::Primitive(name) => assert_eq!(name, "string"),
304                _ => panic!("Default should be Primitive(\"string\")"),
305            }
306        }
307
308        #[test]
309        fn test_primitive_variants() {
310            let types = vec!["string", "number", "boolean", "void"];
311            for type_name in types {
312                let primitive = TypeStructure::Primitive(type_name.to_string());
313                match primitive {
314                    TypeStructure::Primitive(name) => assert_eq!(name, type_name),
315                    _ => panic!("Should be Primitive variant"),
316                }
317            }
318        }
319
320        #[test]
321        fn test_array_wraps_inner_type() {
322            let inner = TypeStructure::Primitive("number".to_string());
323            let array = TypeStructure::Array(Box::new(inner));
324
325            match array {
326                TypeStructure::Array(boxed) => match *boxed {
327                    TypeStructure::Primitive(name) => assert_eq!(name, "number"),
328                    _ => panic!("Inner should be Primitive"),
329                },
330                _ => panic!("Should be Array variant"),
331            }
332        }
333
334        #[test]
335        fn test_map_has_key_and_value() {
336            let key = TypeStructure::Primitive("string".to_string());
337            let value = TypeStructure::Primitive("number".to_string());
338            let map = TypeStructure::Map {
339                key: Box::new(key),
340                value: Box::new(value),
341            };
342
343            match map {
344                TypeStructure::Map { key, value } => match (*key, *value) {
345                    (TypeStructure::Primitive(k), TypeStructure::Primitive(v)) => {
346                        assert_eq!(k, "string");
347                        assert_eq!(v, "number");
348                    }
349                    _ => panic!("Key and value should be Primitives"),
350                },
351                _ => panic!("Should be Map variant"),
352            }
353        }
354
355        #[test]
356        fn test_set_wraps_inner_type() {
357            let inner = TypeStructure::Primitive("string".to_string());
358            let set = TypeStructure::Set(Box::new(inner));
359
360            match set {
361                TypeStructure::Set(boxed) => match *boxed {
362                    TypeStructure::Primitive(name) => assert_eq!(name, "string"),
363                    _ => panic!("Inner should be Primitive"),
364                },
365                _ => panic!("Should be Set variant"),
366            }
367        }
368
369        #[test]
370        fn test_tuple_with_multiple_types() {
371            let types = vec![
372                TypeStructure::Primitive("string".to_string()),
373                TypeStructure::Primitive("number".to_string()),
374                TypeStructure::Primitive("boolean".to_string()),
375            ];
376            let tuple = TypeStructure::Tuple(types);
377
378            match tuple {
379                TypeStructure::Tuple(types) => {
380                    assert_eq!(types.len(), 3);
381                    match &types[0] {
382                        TypeStructure::Primitive(name) => assert_eq!(name, "string"),
383                        _ => panic!("First type should be string"),
384                    }
385                }
386                _ => panic!("Should be Tuple variant"),
387            }
388        }
389
390        #[test]
391        fn test_empty_tuple() {
392            let tuple = TypeStructure::Tuple(vec![]);
393            match tuple {
394                TypeStructure::Tuple(types) => assert_eq!(types.len(), 0),
395                _ => panic!("Should be Tuple variant"),
396            }
397        }
398
399        #[test]
400        fn test_optional_wraps_inner_type() {
401            let inner = TypeStructure::Custom("User".to_string());
402            let optional = TypeStructure::Optional(Box::new(inner));
403
404            match optional {
405                TypeStructure::Optional(boxed) => match *boxed {
406                    TypeStructure::Custom(name) => assert_eq!(name, "User"),
407                    _ => panic!("Inner should be Custom"),
408                },
409                _ => panic!("Should be Optional variant"),
410            }
411        }
412
413        #[test]
414        fn test_result_wraps_success_type() {
415            let success = TypeStructure::Primitive("string".to_string());
416            let result = TypeStructure::Result(Box::new(success));
417
418            match result {
419                TypeStructure::Result(boxed) => match *boxed {
420                    TypeStructure::Primitive(name) => assert_eq!(name, "string"),
421                    _ => panic!("Inner should be Primitive"),
422                },
423                _ => panic!("Should be Result variant"),
424            }
425        }
426
427        #[test]
428        fn test_custom_type() {
429            let custom = TypeStructure::Custom("UserProfile".to_string());
430            match custom {
431                TypeStructure::Custom(name) => assert_eq!(name, "UserProfile"),
432                _ => panic!("Should be Custom variant"),
433            }
434        }
435
436        #[test]
437        fn test_nested_structures() {
438            // Vec<Option<HashMap<String, User>>>
439            let user = TypeStructure::Custom("User".to_string());
440            let map = TypeStructure::Map {
441                key: Box::new(TypeStructure::Primitive("string".to_string())),
442                value: Box::new(user),
443            };
444            let optional = TypeStructure::Optional(Box::new(map));
445            let array = TypeStructure::Array(Box::new(optional));
446
447            match array {
448                TypeStructure::Array(arr_inner) => match *arr_inner {
449                    TypeStructure::Optional(opt_inner) => match *opt_inner {
450                        TypeStructure::Map { key, value } => match (*key, *value) {
451                            (TypeStructure::Primitive(k), TypeStructure::Custom(v)) => {
452                                assert_eq!(k, "string");
453                                assert_eq!(v, "User");
454                            }
455                            _ => panic!("Map types incorrect"),
456                        },
457                        _ => panic!("Should be Map"),
458                    },
459                    _ => panic!("Should be Optional"),
460                },
461                _ => panic!("Should be Array"),
462            }
463        }
464
465        #[test]
466        fn test_clone_type_structure() {
467            let original = TypeStructure::Primitive("string".to_string());
468            let cloned = original.clone();
469
470            match (original, cloned) {
471                (TypeStructure::Primitive(o), TypeStructure::Primitive(c)) => {
472                    assert_eq!(o, c);
473                }
474                _ => panic!("Clone should maintain variant"),
475            }
476        }
477
478        #[test]
479        fn test_serialize_deserialize_primitive() {
480            let primitive = TypeStructure::Primitive("number".to_string());
481            let json = serde_json::to_string(&primitive).unwrap();
482            let deserialized: TypeStructure = serde_json::from_str(&json).unwrap();
483
484            match deserialized {
485                TypeStructure::Primitive(name) => assert_eq!(name, "number"),
486                _ => panic!("Should deserialize to Primitive"),
487            }
488        }
489
490        #[test]
491        fn test_serialize_deserialize_complex() {
492            let complex = TypeStructure::Array(Box::new(TypeStructure::Optional(Box::new(
493                TypeStructure::Custom("User".to_string()),
494            ))));
495
496            let json = serde_json::to_string(&complex).unwrap();
497            let deserialized: TypeStructure = serde_json::from_str(&json).unwrap();
498
499            match deserialized {
500                TypeStructure::Array(arr) => match *arr {
501                    TypeStructure::Optional(opt) => match *opt {
502                        TypeStructure::Custom(name) => assert_eq!(name, "User"),
503                        _ => panic!("Should be Custom"),
504                    },
505                    _ => panic!("Should be Optional"),
506                },
507                _ => panic!("Should be Array"),
508            }
509        }
510    }
511
512    // ValidatorAttributes tests
513    mod validator_attributes {
514        use super::*;
515
516        #[test]
517        fn test_length_constraint() {
518            let length = LengthConstraint {
519                min: Some(5),
520                max: Some(100),
521                message: Some("Invalid length".to_string()),
522            };
523
524            assert_eq!(length.min, Some(5));
525            assert_eq!(length.max, Some(100));
526            assert_eq!(length.message, Some("Invalid length".to_string()));
527        }
528
529        #[test]
530        fn test_range_constraint() {
531            let range = RangeConstraint {
532                min: Some(0.0),
533                max: Some(10.5),
534                message: Some("Out of range".to_string()),
535            };
536
537            assert_eq!(range.min, Some(0.0));
538            assert_eq!(range.max, Some(10.5));
539            assert_eq!(range.message, Some("Out of range".to_string()));
540        }
541
542        #[test]
543        fn test_validator_attributes_email() {
544            let validator = ValidatorAttributes {
545                length: None,
546                range: None,
547                email: true,
548                url: false,
549                custom_message: None,
550            };
551
552            assert!(validator.email);
553            assert!(!validator.url);
554        }
555
556        #[test]
557        fn test_validator_attributes_with_length() {
558            let validator = ValidatorAttributes {
559                length: Some(LengthConstraint {
560                    min: Some(1),
561                    max: Some(50),
562                    message: None,
563                }),
564                range: None,
565                email: false,
566                url: false,
567                custom_message: None,
568            };
569
570            assert!(validator.length.is_some());
571            let length = validator.length.unwrap();
572            assert_eq!(length.min, Some(1));
573            assert_eq!(length.max, Some(50));
574        }
575
576        #[test]
577        fn test_serialize_validator_attributes() {
578            let validator = ValidatorAttributes {
579                length: Some(LengthConstraint {
580                    min: Some(5),
581                    max: Some(100),
582                    message: None,
583                }),
584                range: None,
585                email: true,
586                url: false,
587                custom_message: Some("Custom error".to_string()),
588            };
589
590            let json = serde_json::to_string(&validator).unwrap();
591            let deserialized: ValidatorAttributes = serde_json::from_str(&json).unwrap();
592
593            assert!(deserialized.email);
594            assert_eq!(
595                deserialized.custom_message,
596                Some("Custom error".to_string())
597            );
598            assert!(deserialized.length.is_some());
599        }
600
601        #[test]
602        fn test_validator_attributes_clone() {
603            let original = ValidatorAttributes {
604                length: None,
605                range: Some(RangeConstraint {
606                    min: Some(0.0),
607                    max: Some(1.0),
608                    message: None,
609                }),
610                email: false,
611                url: true,
612                custom_message: None,
613            };
614
615            let cloned = original.clone();
616            assert!(cloned.url);
617            assert!(cloned.range.is_some());
618        }
619    }
620
621    // CommandInfo tests
622    mod command_info {
623        use super::*;
624
625        #[test]
626        fn test_new_for_test_creates_valid_command() {
627            let params = vec![];
628            let channels = vec![];
629
630            let cmd = CommandInfo::new_for_test(
631                "greet",
632                "src/main.rs",
633                10,
634                params,
635                "String",
636                false,
637                channels,
638            );
639
640            assert_eq!(cmd.name, "greet");
641            assert_eq!(cmd.file_path, "src/main.rs");
642            assert_eq!(cmd.line_number, 10);
643            assert_eq!(cmd.return_type, "String");
644            assert!(!cmd.is_async);
645            assert!(cmd.serde_rename_all.is_none());
646        }
647
648        #[test]
649        fn test_new_for_test_parses_return_type_structure() {
650            let cmd = CommandInfo::new_for_test(
651                "get_users",
652                "src/api.rs",
653                20,
654                vec![],
655                "Vec<String>",
656                true,
657                vec![],
658            );
659
660            // Should parse Vec<String> into Array(Primitive("string"))
661            match cmd.return_type_structure {
662                TypeStructure::Array(inner) => match *inner {
663                    TypeStructure::Primitive(name) => assert_eq!(name, "string"),
664                    _ => panic!("Should be string primitive"),
665                },
666                _ => panic!("Should be Array"),
667            }
668            assert!(cmd.is_async);
669        }
670
671        #[test]
672        fn test_command_with_parameters() {
673            let param = ParameterInfo {
674                name: "user_id".to_string(),
675                rust_type: "String".to_string(),
676                is_optional: false,
677                type_structure: TypeStructure::Primitive("string".to_string()),
678                serde_rename: None,
679            };
680
681            let cmd = CommandInfo::new_for_test(
682                "get_user",
683                "src/api.rs",
684                30,
685                vec![param],
686                "User",
687                false,
688                vec![],
689            );
690
691            assert_eq!(cmd.parameters.len(), 1);
692            assert_eq!(cmd.parameters[0].name, "user_id");
693            assert_eq!(cmd.parameters[0].rust_type, "String");
694        }
695
696        #[test]
697        fn test_command_with_channels() {
698            let channel = ChannelInfo::new_for_test(
699                "progress",
700                "u32",
701                "download_file",
702                "src/download.rs",
703                40,
704            );
705
706            let cmd = CommandInfo::new_for_test(
707                "download_file",
708                "src/download.rs",
709                40,
710                vec![],
711                "Result<(), String>",
712                true,
713                vec![channel],
714            );
715
716            assert_eq!(cmd.channels.len(), 1);
717            assert_eq!(cmd.channels[0].parameter_name, "progress");
718            assert_eq!(cmd.channels[0].message_type, "u32");
719        }
720    }
721
722    // ChannelInfo tests
723    mod channel_info {
724        use super::*;
725
726        #[test]
727        fn test_new_for_test_creates_valid_channel() {
728            let channel =
729                ChannelInfo::new_for_test("updates", "String", "subscribe", "src/events.rs", 50);
730
731            assert_eq!(channel.parameter_name, "updates");
732            assert_eq!(channel.message_type, "String");
733            assert_eq!(channel.command_name, "subscribe");
734            assert_eq!(channel.file_path, "src/events.rs");
735            assert_eq!(channel.line_number, 50);
736            assert!(channel.serde_rename.is_none());
737        }
738
739        #[test]
740        fn test_channel_parses_message_type_structure() {
741            let channel =
742                ChannelInfo::new_for_test("data", "Vec<u32>", "stream_data", "src/stream.rs", 60);
743
744            // Should parse Vec<u32> into Array(Primitive("number"))
745            match channel.message_type_structure {
746                TypeStructure::Array(inner) => match *inner {
747                    TypeStructure::Primitive(name) => assert_eq!(name, "number"),
748                    _ => panic!("Should be number primitive"),
749                },
750                _ => panic!("Should be Array"),
751            }
752        }
753
754        #[test]
755        fn test_channel_clone() {
756            let original =
757                ChannelInfo::new_for_test("status", "bool", "monitor", "src/monitor.rs", 70);
758
759            let cloned = original.clone();
760            assert_eq!(cloned.parameter_name, "status");
761            assert_eq!(cloned.message_type, "bool");
762            assert_eq!(cloned.command_name, "monitor");
763        }
764    }
765
766    // ParameterInfo tests
767    mod parameter_info {
768        use super::*;
769
770        #[test]
771        fn test_parameter_with_optional_type() {
772            let param = ParameterInfo {
773                name: "email".to_string(),
774                rust_type: "Option<String>".to_string(),
775                is_optional: true,
776                type_structure: TypeStructure::Optional(Box::new(TypeStructure::Primitive(
777                    "string".to_string(),
778                ))),
779                serde_rename: None,
780            };
781
782            assert!(param.is_optional);
783            match param.type_structure {
784                TypeStructure::Optional(_) => (),
785                _ => panic!("Should be Optional"),
786            }
787        }
788
789        #[test]
790        fn test_parameter_with_serde_rename() {
791            let param = ParameterInfo {
792                name: "user_id".to_string(),
793                rust_type: "String".to_string(),
794                is_optional: false,
795                type_structure: TypeStructure::Primitive("string".to_string()),
796                serde_rename: Some("userId".to_string()),
797            };
798
799            assert_eq!(param.serde_rename, Some("userId".to_string()));
800        }
801    }
802
803    // StructInfo tests
804    mod struct_info {
805        use super::*;
806
807        #[test]
808        fn test_struct_with_fields() {
809            let field = FieldInfo {
810                name: "name".to_string(),
811                rust_type: "String".to_string(),
812                is_optional: false,
813                is_public: true,
814                validator_attributes: None,
815                serde_rename: None,
816                type_structure: TypeStructure::Primitive("string".to_string()),
817            };
818
819            let struct_info = StructInfo {
820                name: "User".to_string(),
821                fields: vec![field],
822                file_path: "src/models.rs".to_string(),
823                is_enum: false,
824                serde_rename_all: None,
825                serde_tag: None,
826                enum_variants: None,
827            };
828
829            assert_eq!(struct_info.name, "User");
830            assert!(!struct_info.is_enum);
831            assert_eq!(struct_info.fields.len(), 1);
832        }
833
834        #[test]
835        fn test_enum_struct() {
836            let struct_info = StructInfo {
837                name: "Status".to_string(),
838                fields: vec![],
839                file_path: "src/types.rs".to_string(),
840                is_enum: true,
841                serde_rename_all: Some(RenameRule::CamelCase),
842                serde_tag: None,
843                enum_variants: None,
844            };
845
846            assert!(struct_info.is_enum);
847            assert!(struct_info.serde_rename_all.is_some());
848        }
849
850        #[test]
851        fn test_struct_clone() {
852            let original = StructInfo {
853                name: "Product".to_string(),
854                fields: vec![],
855                file_path: "src/product.rs".to_string(),
856                is_enum: false,
857                serde_rename_all: None,
858                serde_tag: None,
859                enum_variants: None,
860            };
861
862            let cloned = original.clone();
863            assert_eq!(cloned.name, "Product");
864            assert!(!cloned.is_enum);
865        }
866
867        #[test]
868        fn test_simple_enum_detection() {
869            // Simple enum with unit variants only
870            let simple_enum = StructInfo {
871                name: "Status".to_string(),
872                fields: vec![
873                    FieldInfo {
874                        name: "Active".to_string(),
875                        rust_type: "enum_variant".to_string(),
876                        is_optional: false,
877                        is_public: true,
878                        validator_attributes: None,
879                        serde_rename: None,
880                        type_structure: TypeStructure::Custom("enum_variant".to_string()),
881                    },
882                    FieldInfo {
883                        name: "Inactive".to_string(),
884                        rust_type: "enum_variant".to_string(),
885                        is_optional: false,
886                        is_public: true,
887                        validator_attributes: None,
888                        serde_rename: None,
889                        type_structure: TypeStructure::Custom("enum_variant".to_string()),
890                    },
891                ],
892                file_path: "src/types.rs".to_string(),
893                is_enum: true,
894                serde_rename_all: None,
895                serde_tag: None,
896                enum_variants: None,
897            };
898
899            assert!(simple_enum.is_simple_enum());
900            assert!(!simple_enum.is_complex_enum());
901        }
902
903        #[test]
904        fn test_complex_enum_detection_via_fields() {
905            // Complex enum detected via rust_type field (backward compatibility)
906            let complex_enum = StructInfo {
907                name: "Message".to_string(),
908                fields: vec![
909                    FieldInfo {
910                        name: "Quit".to_string(),
911                        rust_type: "enum_variant".to_string(),
912                        is_optional: false,
913                        is_public: true,
914                        validator_attributes: None,
915                        serde_rename: None,
916                        type_structure: TypeStructure::Custom("enum_variant".to_string()),
917                    },
918                    FieldInfo {
919                        name: "Move".to_string(),
920                        rust_type: "enum_variant_tuple".to_string(),
921                        is_optional: false,
922                        is_public: true,
923                        validator_attributes: None,
924                        serde_rename: None,
925                        type_structure: TypeStructure::Custom("enum_variant".to_string()),
926                    },
927                ],
928                file_path: "src/types.rs".to_string(),
929                is_enum: true,
930                serde_rename_all: None,
931                serde_tag: None,
932                enum_variants: None,
933            };
934
935            assert!(!complex_enum.is_simple_enum());
936            assert!(complex_enum.is_complex_enum());
937        }
938
939        #[test]
940        fn test_complex_enum_detection_via_enum_variants() {
941            // Complex enum with EnumVariantInfo populated
942            let complex_enum = StructInfo {
943                name: "Message".to_string(),
944                fields: vec![],
945                file_path: "src/types.rs".to_string(),
946                is_enum: true,
947                serde_rename_all: None,
948                serde_tag: Some("type".to_string()),
949                enum_variants: Some(vec![
950                    EnumVariantInfo {
951                        name: "Quit".to_string(),
952                        kind: EnumVariantKind::Unit,
953                        serde_rename: None,
954                    },
955                    EnumVariantInfo {
956                        name: "Move".to_string(),
957                        kind: EnumVariantKind::Tuple(vec![
958                            TypeStructure::Primitive("number".to_string()),
959                            TypeStructure::Primitive("number".to_string()),
960                        ]),
961                        serde_rename: None,
962                    },
963                ]),
964            };
965
966            assert!(!complex_enum.is_simple_enum());
967            assert!(complex_enum.is_complex_enum());
968        }
969
970        #[test]
971        fn test_discriminator_tag_default() {
972            let enum_info = StructInfo {
973                name: "Status".to_string(),
974                fields: vec![],
975                file_path: "src/types.rs".to_string(),
976                is_enum: true,
977                serde_rename_all: None,
978                serde_tag: None,
979                enum_variants: None,
980            };
981
982            assert_eq!(enum_info.discriminator_tag(), "type");
983        }
984
985        #[test]
986        fn test_discriminator_tag_custom() {
987            let enum_info = StructInfo {
988                name: "Status".to_string(),
989                fields: vec![],
990                file_path: "src/types.rs".to_string(),
991                is_enum: true,
992                serde_rename_all: None,
993                serde_tag: Some("kind".to_string()),
994                enum_variants: None,
995            };
996
997            assert_eq!(enum_info.discriminator_tag(), "kind");
998        }
999    }
1000
1001    // EnumVariantKind tests
1002    mod enum_variant_kind {
1003        use super::*;
1004
1005        #[test]
1006        fn test_unit_variant() {
1007            let kind = EnumVariantKind::Unit;
1008            assert_eq!(kind, EnumVariantKind::Unit);
1009        }
1010
1011        #[test]
1012        fn test_tuple_variant_single_field() {
1013            let kind = EnumVariantKind::Tuple(vec![TypeStructure::Primitive("string".to_string())]);
1014
1015            match kind {
1016                EnumVariantKind::Tuple(fields) => {
1017                    assert_eq!(fields.len(), 1);
1018                    assert_eq!(fields[0], TypeStructure::Primitive("string".to_string()));
1019                }
1020                _ => panic!("Should be Tuple variant"),
1021            }
1022        }
1023
1024        #[test]
1025        fn test_tuple_variant_multiple_fields() {
1026            let kind = EnumVariantKind::Tuple(vec![
1027                TypeStructure::Primitive("number".to_string()),
1028                TypeStructure::Primitive("number".to_string()),
1029            ]);
1030
1031            match kind {
1032                EnumVariantKind::Tuple(fields) => {
1033                    assert_eq!(fields.len(), 2);
1034                }
1035                _ => panic!("Should be Tuple variant"),
1036            }
1037        }
1038
1039        #[test]
1040        fn test_struct_variant() {
1041            let fields = vec![
1042                FieldInfo {
1043                    name: "r".to_string(),
1044                    rust_type: "u8".to_string(),
1045                    is_optional: false,
1046                    is_public: true,
1047                    validator_attributes: None,
1048                    serde_rename: None,
1049                    type_structure: TypeStructure::Primitive("number".to_string()),
1050                },
1051                FieldInfo {
1052                    name: "g".to_string(),
1053                    rust_type: "u8".to_string(),
1054                    is_optional: false,
1055                    is_public: true,
1056                    validator_attributes: None,
1057                    serde_rename: None,
1058                    type_structure: TypeStructure::Primitive("number".to_string()),
1059                },
1060            ];
1061            let kind = EnumVariantKind::Struct(fields);
1062
1063            match kind {
1064                EnumVariantKind::Struct(f) => {
1065                    assert_eq!(f.len(), 2);
1066                    assert_eq!(f[0].name, "r");
1067                    assert_eq!(f[1].name, "g");
1068                }
1069                _ => panic!("Should be Struct variant"),
1070            }
1071        }
1072
1073        #[test]
1074        fn test_serialize_deserialize() {
1075            let unit = EnumVariantKind::Unit;
1076            let json = serde_json::to_string(&unit).unwrap();
1077            let deserialized: EnumVariantKind = serde_json::from_str(&json).unwrap();
1078            assert_eq!(deserialized, EnumVariantKind::Unit);
1079
1080            let tuple =
1081                EnumVariantKind::Tuple(vec![TypeStructure::Primitive("string".to_string())]);
1082            let json = serde_json::to_string(&tuple).unwrap();
1083            let deserialized: EnumVariantKind = serde_json::from_str(&json).unwrap();
1084            match deserialized {
1085                EnumVariantKind::Tuple(fields) => assert_eq!(fields.len(), 1),
1086                _ => panic!("Should deserialize to Tuple"),
1087            }
1088        }
1089    }
1090
1091    // EnumVariantInfo tests
1092    mod enum_variant_info {
1093        use super::*;
1094
1095        #[test]
1096        fn test_unit_variant_helpers() {
1097            let variant = EnumVariantInfo {
1098                name: "Quit".to_string(),
1099                kind: EnumVariantKind::Unit,
1100                serde_rename: None,
1101            };
1102
1103            assert!(variant.is_unit());
1104            assert!(!variant.is_tuple());
1105            assert!(!variant.is_struct());
1106            assert!(variant.tuple_fields().is_none());
1107            assert!(variant.struct_fields().is_none());
1108        }
1109
1110        #[test]
1111        fn test_tuple_variant_helpers() {
1112            let variant = EnumVariantInfo {
1113                name: "Move".to_string(),
1114                kind: EnumVariantKind::Tuple(vec![
1115                    TypeStructure::Primitive("number".to_string()),
1116                    TypeStructure::Primitive("number".to_string()),
1117                ]),
1118                serde_rename: None,
1119            };
1120
1121            assert!(!variant.is_unit());
1122            assert!(variant.is_tuple());
1123            assert!(!variant.is_struct());
1124
1125            let fields = variant.tuple_fields().unwrap();
1126            assert_eq!(fields.len(), 2);
1127            assert!(variant.struct_fields().is_none());
1128        }
1129
1130        #[test]
1131        fn test_struct_variant_helpers() {
1132            let variant = EnumVariantInfo {
1133                name: "ChangeColor".to_string(),
1134                kind: EnumVariantKind::Struct(vec![FieldInfo {
1135                    name: "r".to_string(),
1136                    rust_type: "u8".to_string(),
1137                    is_optional: false,
1138                    is_public: true,
1139                    validator_attributes: None,
1140                    serde_rename: None,
1141                    type_structure: TypeStructure::Primitive("number".to_string()),
1142                }]),
1143                serde_rename: None,
1144            };
1145
1146            assert!(!variant.is_unit());
1147            assert!(!variant.is_tuple());
1148            assert!(variant.is_struct());
1149
1150            assert!(variant.tuple_fields().is_none());
1151            let fields = variant.struct_fields().unwrap();
1152            assert_eq!(fields.len(), 1);
1153            assert_eq!(fields[0].name, "r");
1154        }
1155
1156        #[test]
1157        fn test_variant_with_serde_rename() {
1158            let variant = EnumVariantInfo {
1159                name: "Quit".to_string(),
1160                kind: EnumVariantKind::Unit,
1161                serde_rename: Some("quit".to_string()),
1162            };
1163
1164            assert_eq!(variant.serde_rename, Some("quit".to_string()));
1165        }
1166
1167        #[test]
1168        fn test_clone() {
1169            let original = EnumVariantInfo {
1170                name: "Write".to_string(),
1171                kind: EnumVariantKind::Tuple(vec![TypeStructure::Primitive("string".to_string())]),
1172                serde_rename: None,
1173            };
1174
1175            let cloned = original.clone();
1176            assert_eq!(cloned.name, "Write");
1177            assert!(cloned.is_tuple());
1178        }
1179    }
1180
1181    // FieldInfo tests
1182    mod field_info {
1183        use super::*;
1184
1185        #[test]
1186        fn test_field_with_validator() {
1187            let validator = ValidatorAttributes {
1188                length: Some(LengthConstraint {
1189                    min: Some(1),
1190                    max: Some(100),
1191                    message: None,
1192                }),
1193                range: None,
1194                email: false,
1195                url: false,
1196                custom_message: None,
1197            };
1198
1199            let field = FieldInfo {
1200                name: "username".to_string(),
1201                rust_type: "String".to_string(),
1202                is_optional: false,
1203                is_public: true,
1204                validator_attributes: Some(validator),
1205                serde_rename: None,
1206                type_structure: TypeStructure::Primitive("string".to_string()),
1207            };
1208
1209            assert!(field.validator_attributes.is_some());
1210            let attrs = field.validator_attributes.unwrap();
1211            assert!(attrs.length.is_some());
1212        }
1213
1214        #[test]
1215        fn test_private_field() {
1216            let field = FieldInfo {
1217                name: "internal_id".to_string(),
1218                rust_type: "u64".to_string(),
1219                is_optional: false,
1220                is_public: false,
1221                validator_attributes: None,
1222                serde_rename: None,
1223                type_structure: TypeStructure::Primitive("number".to_string()),
1224            };
1225
1226            assert!(!field.is_public);
1227        }
1228
1229        #[test]
1230        fn test_field_with_serde_rename() {
1231            let field = FieldInfo {
1232                name: "created_at".to_string(),
1233                rust_type: "String".to_string(),
1234                is_optional: true,
1235                is_public: true,
1236                validator_attributes: None,
1237                serde_rename: Some("createdAt".to_string()),
1238                type_structure: TypeStructure::Optional(Box::new(TypeStructure::Primitive(
1239                    "string".to_string(),
1240                ))),
1241            };
1242
1243            assert_eq!(field.serde_rename, Some("createdAt".to_string()));
1244            assert!(field.is_optional);
1245        }
1246
1247        #[test]
1248        fn test_field_clone() {
1249            let original = FieldInfo {
1250                name: "count".to_string(),
1251                rust_type: "i32".to_string(),
1252                is_optional: false,
1253                is_public: true,
1254                validator_attributes: None,
1255                serde_rename: None,
1256                type_structure: TypeStructure::Primitive("number".to_string()),
1257            };
1258
1259            let cloned = original.clone();
1260            assert_eq!(cloned.name, "count");
1261            assert_eq!(cloned.rust_type, "i32");
1262        }
1263    }
1264
1265    // EventInfo tests
1266    mod event_info {
1267        use super::*;
1268
1269        #[test]
1270        fn test_event_info_creation() {
1271            let event = EventInfo {
1272                event_name: "user-updated".to_string(),
1273                payload_type: "User".to_string(),
1274                payload_type_structure: TypeStructure::Custom("User".to_string()),
1275                file_path: "src/events.rs".to_string(),
1276                line_number: 100,
1277            };
1278
1279            assert_eq!(event.event_name, "user-updated");
1280            assert_eq!(event.payload_type, "User");
1281            match event.payload_type_structure {
1282                TypeStructure::Custom(name) => assert_eq!(name, "User"),
1283                _ => panic!("Should be Custom type"),
1284            }
1285        }
1286
1287        #[test]
1288        fn test_event_with_primitive_payload() {
1289            let event = EventInfo {
1290                event_name: "progress".to_string(),
1291                payload_type: "u32".to_string(),
1292                payload_type_structure: TypeStructure::Primitive("number".to_string()),
1293                file_path: "src/progress.rs".to_string(),
1294                line_number: 50,
1295            };
1296
1297            match event.payload_type_structure {
1298                TypeStructure::Primitive(name) => assert_eq!(name, "number"),
1299                _ => panic!("Should be Primitive type"),
1300            }
1301        }
1302    }
1303}