1use serde::{Deserialize, Serialize};
2use serde_rename_rule::RenameRule;
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
7#[serde(rename_all = "camelCase")]
8pub enum TypeStructure {
9 Primitive(String),
11
12 Array(Box<TypeStructure>),
14
15 Map {
17 key: Box<TypeStructure>,
18 value: Box<TypeStructure>,
19 },
20
21 Set(Box<TypeStructure>),
23
24 Tuple(Vec<TypeStructure>),
26
27 Optional(Box<TypeStructure>),
29
30 Result(Box<TypeStructure>),
32
33 Custom(String),
35}
36
37impl Default for TypeStructure {
38 fn default() -> Self {
39 TypeStructure::Primitive("string".to_string())
41 }
42}
43
44pub struct CommandInfo {
45 pub name: String,
46 pub file_path: String,
47 pub line_number: usize,
48 pub parameters: Vec<ParameterInfo>,
49 pub return_type: String, pub return_type_structure: TypeStructure,
52 pub is_async: bool,
53 pub channels: Vec<ChannelInfo>,
54 pub serde_rename_all: Option<RenameRule>,
57}
58
59impl CommandInfo {
60 #[doc(hidden)]
62 pub fn new_for_test(
63 name: impl Into<String>,
64 file_path: impl Into<String>,
65 line_number: usize,
66 parameters: Vec<ParameterInfo>,
67 return_type: impl Into<String>,
68 is_async: bool,
69 channels: Vec<ChannelInfo>,
70 ) -> Self {
71 use crate::analysis::type_resolver::TypeResolver;
72 let return_type_str = return_type.into();
73 let type_resolver = TypeResolver::new();
74 let return_type_structure = type_resolver.parse_type_structure(&return_type_str);
75
76 Self {
77 name: name.into(),
78 file_path: file_path.into(),
79 line_number,
80 parameters,
81 return_type: return_type_str,
82 return_type_structure,
83 is_async,
84 channels,
85 serde_rename_all: None,
86 }
87 }
88}
89
90pub struct ParameterInfo {
91 pub name: String,
92 pub rust_type: String,
93 pub is_optional: bool,
94 pub type_structure: TypeStructure,
96 pub serde_rename: Option<String>,
99}
100
101#[derive(Clone, Debug)]
102pub struct StructInfo {
103 pub name: String,
104 pub fields: Vec<FieldInfo>,
105 pub file_path: String,
106 pub is_enum: bool,
107 pub serde_rename_all: Option<RenameRule>,
109}
110
111#[derive(Clone, Debug)]
112pub struct FieldInfo {
113 pub name: String,
114 pub rust_type: String,
115 pub is_optional: bool,
116 pub is_public: bool,
117 pub validator_attributes: Option<ValidatorAttributes>,
118 pub serde_rename: Option<String>,
120 pub type_structure: TypeStructure,
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
125#[serde(rename_all = "camelCase")]
126pub struct ValidatorAttributes {
127 pub length: Option<LengthConstraint>,
128 pub range: Option<RangeConstraint>,
129 pub email: bool,
130 pub url: bool,
131 pub custom_message: Option<String>,
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize)]
135#[serde(rename_all = "camelCase")]
136pub struct LengthConstraint {
137 pub min: Option<u64>,
138 pub max: Option<u64>,
139 pub message: Option<String>,
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize)]
143#[serde(rename_all = "camelCase")]
144pub struct RangeConstraint {
145 pub min: Option<f64>,
146 pub max: Option<f64>,
147 pub message: Option<String>,
148}
149
150pub struct EventInfo {
152 pub event_name: String,
153 pub payload_type: String,
154 pub payload_type_structure: TypeStructure,
156 pub file_path: String,
157 pub line_number: usize,
158}
159
160#[derive(Clone)]
162pub struct ChannelInfo {
163 pub parameter_name: String,
164 pub message_type: String,
165 pub command_name: String,
166 pub file_path: String,
167 pub line_number: usize,
168 pub serde_rename: Option<String>,
171 pub message_type_structure: TypeStructure,
173}
174
175impl ChannelInfo {
176 #[doc(hidden)]
178 pub fn new_for_test(
179 parameter_name: impl Into<String>,
180 message_type: impl Into<String>,
181 command_name: impl Into<String>,
182 file_path: impl Into<String>,
183 line_number: usize,
184 ) -> Self {
185 let message_type_str = message_type.into();
186 Self {
187 parameter_name: parameter_name.into(),
188 message_type: message_type_str.clone(),
189 command_name: command_name.into(),
190 file_path: file_path.into(),
191 line_number,
192 serde_rename: None,
193 message_type_structure: crate::analysis::type_resolver::TypeResolver::new()
195 .parse_type_structure(&message_type_str),
196 }
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203
204 mod type_structure {
206 use super::*;
207
208 #[test]
209 fn test_default_is_string_primitive() {
210 let default_type = TypeStructure::default();
211 match default_type {
212 TypeStructure::Primitive(name) => assert_eq!(name, "string"),
213 _ => panic!("Default should be Primitive(\"string\")"),
214 }
215 }
216
217 #[test]
218 fn test_primitive_variants() {
219 let types = vec!["string", "number", "boolean", "void"];
220 for type_name in types {
221 let primitive = TypeStructure::Primitive(type_name.to_string());
222 match primitive {
223 TypeStructure::Primitive(name) => assert_eq!(name, type_name),
224 _ => panic!("Should be Primitive variant"),
225 }
226 }
227 }
228
229 #[test]
230 fn test_array_wraps_inner_type() {
231 let inner = TypeStructure::Primitive("number".to_string());
232 let array = TypeStructure::Array(Box::new(inner));
233
234 match array {
235 TypeStructure::Array(boxed) => match *boxed {
236 TypeStructure::Primitive(name) => assert_eq!(name, "number"),
237 _ => panic!("Inner should be Primitive"),
238 },
239 _ => panic!("Should be Array variant"),
240 }
241 }
242
243 #[test]
244 fn test_map_has_key_and_value() {
245 let key = TypeStructure::Primitive("string".to_string());
246 let value = TypeStructure::Primitive("number".to_string());
247 let map = TypeStructure::Map {
248 key: Box::new(key),
249 value: Box::new(value),
250 };
251
252 match map {
253 TypeStructure::Map { key, value } => match (*key, *value) {
254 (TypeStructure::Primitive(k), TypeStructure::Primitive(v)) => {
255 assert_eq!(k, "string");
256 assert_eq!(v, "number");
257 }
258 _ => panic!("Key and value should be Primitives"),
259 },
260 _ => panic!("Should be Map variant"),
261 }
262 }
263
264 #[test]
265 fn test_set_wraps_inner_type() {
266 let inner = TypeStructure::Primitive("string".to_string());
267 let set = TypeStructure::Set(Box::new(inner));
268
269 match set {
270 TypeStructure::Set(boxed) => match *boxed {
271 TypeStructure::Primitive(name) => assert_eq!(name, "string"),
272 _ => panic!("Inner should be Primitive"),
273 },
274 _ => panic!("Should be Set variant"),
275 }
276 }
277
278 #[test]
279 fn test_tuple_with_multiple_types() {
280 let types = vec![
281 TypeStructure::Primitive("string".to_string()),
282 TypeStructure::Primitive("number".to_string()),
283 TypeStructure::Primitive("boolean".to_string()),
284 ];
285 let tuple = TypeStructure::Tuple(types);
286
287 match tuple {
288 TypeStructure::Tuple(types) => {
289 assert_eq!(types.len(), 3);
290 match &types[0] {
291 TypeStructure::Primitive(name) => assert_eq!(name, "string"),
292 _ => panic!("First type should be string"),
293 }
294 }
295 _ => panic!("Should be Tuple variant"),
296 }
297 }
298
299 #[test]
300 fn test_empty_tuple() {
301 let tuple = TypeStructure::Tuple(vec![]);
302 match tuple {
303 TypeStructure::Tuple(types) => assert_eq!(types.len(), 0),
304 _ => panic!("Should be Tuple variant"),
305 }
306 }
307
308 #[test]
309 fn test_optional_wraps_inner_type() {
310 let inner = TypeStructure::Custom("User".to_string());
311 let optional = TypeStructure::Optional(Box::new(inner));
312
313 match optional {
314 TypeStructure::Optional(boxed) => match *boxed {
315 TypeStructure::Custom(name) => assert_eq!(name, "User"),
316 _ => panic!("Inner should be Custom"),
317 },
318 _ => panic!("Should be Optional variant"),
319 }
320 }
321
322 #[test]
323 fn test_result_wraps_success_type() {
324 let success = TypeStructure::Primitive("string".to_string());
325 let result = TypeStructure::Result(Box::new(success));
326
327 match result {
328 TypeStructure::Result(boxed) => match *boxed {
329 TypeStructure::Primitive(name) => assert_eq!(name, "string"),
330 _ => panic!("Inner should be Primitive"),
331 },
332 _ => panic!("Should be Result variant"),
333 }
334 }
335
336 #[test]
337 fn test_custom_type() {
338 let custom = TypeStructure::Custom("UserProfile".to_string());
339 match custom {
340 TypeStructure::Custom(name) => assert_eq!(name, "UserProfile"),
341 _ => panic!("Should be Custom variant"),
342 }
343 }
344
345 #[test]
346 fn test_nested_structures() {
347 let user = TypeStructure::Custom("User".to_string());
349 let map = TypeStructure::Map {
350 key: Box::new(TypeStructure::Primitive("string".to_string())),
351 value: Box::new(user),
352 };
353 let optional = TypeStructure::Optional(Box::new(map));
354 let array = TypeStructure::Array(Box::new(optional));
355
356 match array {
357 TypeStructure::Array(arr_inner) => match *arr_inner {
358 TypeStructure::Optional(opt_inner) => match *opt_inner {
359 TypeStructure::Map { key, value } => match (*key, *value) {
360 (TypeStructure::Primitive(k), TypeStructure::Custom(v)) => {
361 assert_eq!(k, "string");
362 assert_eq!(v, "User");
363 }
364 _ => panic!("Map types incorrect"),
365 },
366 _ => panic!("Should be Map"),
367 },
368 _ => panic!("Should be Optional"),
369 },
370 _ => panic!("Should be Array"),
371 }
372 }
373
374 #[test]
375 fn test_clone_type_structure() {
376 let original = TypeStructure::Primitive("string".to_string());
377 let cloned = original.clone();
378
379 match (original, cloned) {
380 (TypeStructure::Primitive(o), TypeStructure::Primitive(c)) => {
381 assert_eq!(o, c);
382 }
383 _ => panic!("Clone should maintain variant"),
384 }
385 }
386
387 #[test]
388 fn test_serialize_deserialize_primitive() {
389 let primitive = TypeStructure::Primitive("number".to_string());
390 let json = serde_json::to_string(&primitive).unwrap();
391 let deserialized: TypeStructure = serde_json::from_str(&json).unwrap();
392
393 match deserialized {
394 TypeStructure::Primitive(name) => assert_eq!(name, "number"),
395 _ => panic!("Should deserialize to Primitive"),
396 }
397 }
398
399 #[test]
400 fn test_serialize_deserialize_complex() {
401 let complex = TypeStructure::Array(Box::new(TypeStructure::Optional(Box::new(
402 TypeStructure::Custom("User".to_string()),
403 ))));
404
405 let json = serde_json::to_string(&complex).unwrap();
406 let deserialized: TypeStructure = serde_json::from_str(&json).unwrap();
407
408 match deserialized {
409 TypeStructure::Array(arr) => match *arr {
410 TypeStructure::Optional(opt) => match *opt {
411 TypeStructure::Custom(name) => assert_eq!(name, "User"),
412 _ => panic!("Should be Custom"),
413 },
414 _ => panic!("Should be Optional"),
415 },
416 _ => panic!("Should be Array"),
417 }
418 }
419 }
420
421 mod validator_attributes {
423 use super::*;
424
425 #[test]
426 fn test_length_constraint() {
427 let length = LengthConstraint {
428 min: Some(5),
429 max: Some(100),
430 message: Some("Invalid length".to_string()),
431 };
432
433 assert_eq!(length.min, Some(5));
434 assert_eq!(length.max, Some(100));
435 assert_eq!(length.message, Some("Invalid length".to_string()));
436 }
437
438 #[test]
439 fn test_range_constraint() {
440 let range = RangeConstraint {
441 min: Some(0.0),
442 max: Some(10.5),
443 message: Some("Out of range".to_string()),
444 };
445
446 assert_eq!(range.min, Some(0.0));
447 assert_eq!(range.max, Some(10.5));
448 assert_eq!(range.message, Some("Out of range".to_string()));
449 }
450
451 #[test]
452 fn test_validator_attributes_email() {
453 let validator = ValidatorAttributes {
454 length: None,
455 range: None,
456 email: true,
457 url: false,
458 custom_message: None,
459 };
460
461 assert!(validator.email);
462 assert!(!validator.url);
463 }
464
465 #[test]
466 fn test_validator_attributes_with_length() {
467 let validator = ValidatorAttributes {
468 length: Some(LengthConstraint {
469 min: Some(1),
470 max: Some(50),
471 message: None,
472 }),
473 range: None,
474 email: false,
475 url: false,
476 custom_message: None,
477 };
478
479 assert!(validator.length.is_some());
480 let length = validator.length.unwrap();
481 assert_eq!(length.min, Some(1));
482 assert_eq!(length.max, Some(50));
483 }
484
485 #[test]
486 fn test_serialize_validator_attributes() {
487 let validator = ValidatorAttributes {
488 length: Some(LengthConstraint {
489 min: Some(5),
490 max: Some(100),
491 message: None,
492 }),
493 range: None,
494 email: true,
495 url: false,
496 custom_message: Some("Custom error".to_string()),
497 };
498
499 let json = serde_json::to_string(&validator).unwrap();
500 let deserialized: ValidatorAttributes = serde_json::from_str(&json).unwrap();
501
502 assert!(deserialized.email);
503 assert_eq!(
504 deserialized.custom_message,
505 Some("Custom error".to_string())
506 );
507 assert!(deserialized.length.is_some());
508 }
509
510 #[test]
511 fn test_validator_attributes_clone() {
512 let original = ValidatorAttributes {
513 length: None,
514 range: Some(RangeConstraint {
515 min: Some(0.0),
516 max: Some(1.0),
517 message: None,
518 }),
519 email: false,
520 url: true,
521 custom_message: None,
522 };
523
524 let cloned = original.clone();
525 assert!(cloned.url);
526 assert!(cloned.range.is_some());
527 }
528 }
529
530 mod command_info {
532 use super::*;
533
534 #[test]
535 fn test_new_for_test_creates_valid_command() {
536 let params = vec![];
537 let channels = vec![];
538
539 let cmd = CommandInfo::new_for_test(
540 "greet",
541 "src/main.rs",
542 10,
543 params,
544 "String",
545 false,
546 channels,
547 );
548
549 assert_eq!(cmd.name, "greet");
550 assert_eq!(cmd.file_path, "src/main.rs");
551 assert_eq!(cmd.line_number, 10);
552 assert_eq!(cmd.return_type, "String");
553 assert!(!cmd.is_async);
554 assert!(cmd.serde_rename_all.is_none());
555 }
556
557 #[test]
558 fn test_new_for_test_parses_return_type_structure() {
559 let cmd = CommandInfo::new_for_test(
560 "get_users",
561 "src/api.rs",
562 20,
563 vec![],
564 "Vec<String>",
565 true,
566 vec![],
567 );
568
569 match cmd.return_type_structure {
571 TypeStructure::Array(inner) => match *inner {
572 TypeStructure::Primitive(name) => assert_eq!(name, "string"),
573 _ => panic!("Should be string primitive"),
574 },
575 _ => panic!("Should be Array"),
576 }
577 assert!(cmd.is_async);
578 }
579
580 #[test]
581 fn test_command_with_parameters() {
582 let param = ParameterInfo {
583 name: "user_id".to_string(),
584 rust_type: "String".to_string(),
585 is_optional: false,
586 type_structure: TypeStructure::Primitive("string".to_string()),
587 serde_rename: None,
588 };
589
590 let cmd = CommandInfo::new_for_test(
591 "get_user",
592 "src/api.rs",
593 30,
594 vec![param],
595 "User",
596 false,
597 vec![],
598 );
599
600 assert_eq!(cmd.parameters.len(), 1);
601 assert_eq!(cmd.parameters[0].name, "user_id");
602 assert_eq!(cmd.parameters[0].rust_type, "String");
603 }
604
605 #[test]
606 fn test_command_with_channels() {
607 let channel = ChannelInfo::new_for_test(
608 "progress",
609 "u32",
610 "download_file",
611 "src/download.rs",
612 40,
613 );
614
615 let cmd = CommandInfo::new_for_test(
616 "download_file",
617 "src/download.rs",
618 40,
619 vec![],
620 "Result<(), String>",
621 true,
622 vec![channel],
623 );
624
625 assert_eq!(cmd.channels.len(), 1);
626 assert_eq!(cmd.channels[0].parameter_name, "progress");
627 assert_eq!(cmd.channels[0].message_type, "u32");
628 }
629 }
630
631 mod channel_info {
633 use super::*;
634
635 #[test]
636 fn test_new_for_test_creates_valid_channel() {
637 let channel =
638 ChannelInfo::new_for_test("updates", "String", "subscribe", "src/events.rs", 50);
639
640 assert_eq!(channel.parameter_name, "updates");
641 assert_eq!(channel.message_type, "String");
642 assert_eq!(channel.command_name, "subscribe");
643 assert_eq!(channel.file_path, "src/events.rs");
644 assert_eq!(channel.line_number, 50);
645 assert!(channel.serde_rename.is_none());
646 }
647
648 #[test]
649 fn test_channel_parses_message_type_structure() {
650 let channel =
651 ChannelInfo::new_for_test("data", "Vec<u32>", "stream_data", "src/stream.rs", 60);
652
653 match channel.message_type_structure {
655 TypeStructure::Array(inner) => match *inner {
656 TypeStructure::Primitive(name) => assert_eq!(name, "number"),
657 _ => panic!("Should be number primitive"),
658 },
659 _ => panic!("Should be Array"),
660 }
661 }
662
663 #[test]
664 fn test_channel_clone() {
665 let original =
666 ChannelInfo::new_for_test("status", "bool", "monitor", "src/monitor.rs", 70);
667
668 let cloned = original.clone();
669 assert_eq!(cloned.parameter_name, "status");
670 assert_eq!(cloned.message_type, "bool");
671 assert_eq!(cloned.command_name, "monitor");
672 }
673 }
674
675 mod parameter_info {
677 use super::*;
678
679 #[test]
680 fn test_parameter_with_optional_type() {
681 let param = ParameterInfo {
682 name: "email".to_string(),
683 rust_type: "Option<String>".to_string(),
684 is_optional: true,
685 type_structure: TypeStructure::Optional(Box::new(TypeStructure::Primitive(
686 "string".to_string(),
687 ))),
688 serde_rename: None,
689 };
690
691 assert!(param.is_optional);
692 match param.type_structure {
693 TypeStructure::Optional(_) => (),
694 _ => panic!("Should be Optional"),
695 }
696 }
697
698 #[test]
699 fn test_parameter_with_serde_rename() {
700 let param = ParameterInfo {
701 name: "user_id".to_string(),
702 rust_type: "String".to_string(),
703 is_optional: false,
704 type_structure: TypeStructure::Primitive("string".to_string()),
705 serde_rename: Some("userId".to_string()),
706 };
707
708 assert_eq!(param.serde_rename, Some("userId".to_string()));
709 }
710 }
711
712 mod struct_info {
714 use super::*;
715
716 #[test]
717 fn test_struct_with_fields() {
718 let field = FieldInfo {
719 name: "name".to_string(),
720 rust_type: "String".to_string(),
721 is_optional: false,
722 is_public: true,
723 validator_attributes: None,
724 serde_rename: None,
725 type_structure: TypeStructure::Primitive("string".to_string()),
726 };
727
728 let struct_info = StructInfo {
729 name: "User".to_string(),
730 fields: vec![field],
731 file_path: "src/models.rs".to_string(),
732 is_enum: false,
733 serde_rename_all: None,
734 };
735
736 assert_eq!(struct_info.name, "User");
737 assert!(!struct_info.is_enum);
738 assert_eq!(struct_info.fields.len(), 1);
739 }
740
741 #[test]
742 fn test_enum_struct() {
743 let struct_info = StructInfo {
744 name: "Status".to_string(),
745 fields: vec![],
746 file_path: "src/types.rs".to_string(),
747 is_enum: true,
748 serde_rename_all: Some(RenameRule::CamelCase),
749 };
750
751 assert!(struct_info.is_enum);
752 assert!(struct_info.serde_rename_all.is_some());
753 }
754
755 #[test]
756 fn test_struct_clone() {
757 let original = StructInfo {
758 name: "Product".to_string(),
759 fields: vec![],
760 file_path: "src/product.rs".to_string(),
761 is_enum: false,
762 serde_rename_all: None,
763 };
764
765 let cloned = original.clone();
766 assert_eq!(cloned.name, "Product");
767 assert!(!cloned.is_enum);
768 }
769 }
770
771 mod field_info {
773 use super::*;
774
775 #[test]
776 fn test_field_with_validator() {
777 let validator = ValidatorAttributes {
778 length: Some(LengthConstraint {
779 min: Some(1),
780 max: Some(100),
781 message: None,
782 }),
783 range: None,
784 email: false,
785 url: false,
786 custom_message: None,
787 };
788
789 let field = FieldInfo {
790 name: "username".to_string(),
791 rust_type: "String".to_string(),
792 is_optional: false,
793 is_public: true,
794 validator_attributes: Some(validator),
795 serde_rename: None,
796 type_structure: TypeStructure::Primitive("string".to_string()),
797 };
798
799 assert!(field.validator_attributes.is_some());
800 let attrs = field.validator_attributes.unwrap();
801 assert!(attrs.length.is_some());
802 }
803
804 #[test]
805 fn test_private_field() {
806 let field = FieldInfo {
807 name: "internal_id".to_string(),
808 rust_type: "u64".to_string(),
809 is_optional: false,
810 is_public: false,
811 validator_attributes: None,
812 serde_rename: None,
813 type_structure: TypeStructure::Primitive("number".to_string()),
814 };
815
816 assert!(!field.is_public);
817 }
818
819 #[test]
820 fn test_field_with_serde_rename() {
821 let field = FieldInfo {
822 name: "created_at".to_string(),
823 rust_type: "String".to_string(),
824 is_optional: true,
825 is_public: true,
826 validator_attributes: None,
827 serde_rename: Some("createdAt".to_string()),
828 type_structure: TypeStructure::Optional(Box::new(TypeStructure::Primitive(
829 "string".to_string(),
830 ))),
831 };
832
833 assert_eq!(field.serde_rename, Some("createdAt".to_string()));
834 assert!(field.is_optional);
835 }
836
837 #[test]
838 fn test_field_clone() {
839 let original = FieldInfo {
840 name: "count".to_string(),
841 rust_type: "i32".to_string(),
842 is_optional: false,
843 is_public: true,
844 validator_attributes: None,
845 serde_rename: None,
846 type_structure: TypeStructure::Primitive("number".to_string()),
847 };
848
849 let cloned = original.clone();
850 assert_eq!(cloned.name, "count");
851 assert_eq!(cloned.rust_type, "i32");
852 }
853 }
854
855 mod event_info {
857 use super::*;
858
859 #[test]
860 fn test_event_info_creation() {
861 let event = EventInfo {
862 event_name: "user-updated".to_string(),
863 payload_type: "User".to_string(),
864 payload_type_structure: TypeStructure::Custom("User".to_string()),
865 file_path: "src/events.rs".to_string(),
866 line_number: 100,
867 };
868
869 assert_eq!(event.event_name, "user-updated");
870 assert_eq!(event.payload_type, "User");
871 match event.payload_type_structure {
872 TypeStructure::Custom(name) => assert_eq!(name, "User"),
873 _ => panic!("Should be Custom type"),
874 }
875 }
876
877 #[test]
878 fn test_event_with_primitive_payload() {
879 let event = EventInfo {
880 event_name: "progress".to_string(),
881 payload_type: "u32".to_string(),
882 payload_type_structure: TypeStructure::Primitive("number".to_string()),
883 file_path: "src/progress.rs".to_string(),
884 line_number: 50,
885 };
886
887 match event.payload_type_structure {
888 TypeStructure::Primitive(name) => assert_eq!(name, "number"),
889 _ => panic!("Should be Primitive type"),
890 }
891 }
892 }
893}