1use std::fmt;
16
17use serde::{Deserialize, Serialize};
18
19use super::super::string::StringId;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
25#[serde(rename_all = "snake_case")]
26pub enum TypeOfContext {
27 Parameter,
29 Return,
31 Field,
33 Variable,
35 TypeParameter,
37 Constraint,
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
43#[serde(rename_all = "snake_case")]
44#[derive(Default)]
45pub enum FfiConvention {
46 #[default]
48 C,
49 Cdecl,
51 Stdcall,
53 Fastcall,
55 System,
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
61#[serde(rename_all = "UPPERCASE")]
62#[derive(Default)]
63pub enum HttpMethod {
64 #[default]
66 Get,
67 Post,
69 Put,
71 Delete,
73 Patch,
75 Head,
77 Options,
79 All,
81}
82
83impl HttpMethod {
84 #[must_use]
86 pub const fn as_str(self) -> &'static str {
87 match self {
88 Self::Get => "GET",
89 Self::Post => "POST",
90 Self::Put => "PUT",
91 Self::Delete => "DELETE",
92 Self::Patch => "PATCH",
93 Self::Head => "HEAD",
94 Self::Options => "OPTIONS",
95 Self::All => "ALL",
96 }
97 }
98}
99
100#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
102#[serde(rename_all = "snake_case")]
103#[derive(Default)]
104pub enum DbQueryType {
105 #[default]
107 Select,
108 Insert,
110 Update,
112 Delete,
114 Execute,
116}
117
118#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
120#[serde(rename_all = "snake_case")]
121pub enum TableWriteOp {
122 Insert,
124 Update,
126 Delete,
128}
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
132#[serde(rename_all = "snake_case")]
133#[derive(Default)]
134pub enum ExportKind {
135 #[default]
137 Direct,
138 Reexport,
140 Default,
142 Namespace,
144}
145
146#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
148#[serde(rename_all = "snake_case")]
149#[derive(Default)]
150pub enum MqProtocol {
151 #[default]
153 Kafka,
154 Sqs,
156 RabbitMq,
158 Nats,
160 Redis,
162 Other(StringId),
164}
165
166#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
170#[serde(rename_all = "snake_case")]
171#[derive(Default)]
172pub enum LifetimeConstraintKind {
173 #[default]
175 Outlives,
176 TypeBound,
178 Reference,
180 Static,
182 HigherRanked,
184 TraitObject,
186 ImplTrait,
188 Elided,
190}
191
192#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
194#[serde(rename_all = "snake_case")]
195#[derive(Default)]
196pub enum MacroExpansionKind {
197 Derive,
199 Attribute,
201 #[default]
203 Declarative,
204 Function,
206 CfgGate,
208}
209
210#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
219#[serde(rename_all = "snake_case")]
220pub enum EdgeKind {
221 Defines,
224
225 Contains,
227
228 Calls {
231 argument_count: u8,
233 is_async: bool,
237 },
238
239 References,
241
242 Imports {
244 alias: Option<StringId>,
246 is_wildcard: bool,
248 },
249
250 Exports {
252 kind: ExportKind,
254 alias: Option<StringId>,
256 },
257
258 TypeOf {
269 context: Option<TypeOfContext>,
271 index: Option<u16>,
273 name: Option<StringId>,
275 },
276
277 Inherits,
280
281 Implements,
283
284 LifetimeConstraint {
293 constraint_kind: LifetimeConstraintKind,
295 },
296
297 TraitMethodBinding {
306 trait_name: StringId,
308 impl_type: StringId,
310 is_ambiguous: bool,
312 },
313
314 MacroExpansion {
321 expansion_kind: MacroExpansionKind,
323 is_verified: bool,
325 },
326
327 FfiCall {
330 convention: FfiConvention,
332 },
333
334 HttpRequest {
336 method: HttpMethod,
338 url: Option<StringId>,
340 },
341
342 GrpcCall {
344 service: StringId,
346 method: StringId,
348 },
349
350 WebAssemblyCall,
352
353 DbQuery {
355 query_type: DbQueryType,
357 table: Option<StringId>,
359 },
360
361 TableRead {
363 table_name: StringId,
365 schema: Option<StringId>,
367 },
368
369 TableWrite {
371 table_name: StringId,
373 schema: Option<StringId>,
375 operation: TableWriteOp,
377 },
378
379 TriggeredBy {
381 trigger_name: StringId,
383 schema: Option<StringId>,
385 },
386
387 MessageQueue {
390 protocol: MqProtocol,
392 topic: Option<StringId>,
394 },
395
396 WebSocket {
398 event: Option<StringId>,
400 },
401
402 GraphQLOperation {
404 operation: StringId,
406 },
407
408 ProcessExec {
410 command: StringId,
412 },
413
414 FileIpc {
416 path_pattern: Option<StringId>,
418 },
419
420 ProtocolCall {
423 protocol: StringId,
425 metadata: Option<StringId>,
427 },
428
429 GenericBound,
432
433 AnnotatedWith,
435
436 AnnotationParam,
438
439 LambdaCaptures,
441
442 ModuleExports,
444
445 ModuleRequires,
447
448 ModuleOpens,
450
451 ModuleProvides,
453
454 TypeArgument,
456
457 ExtensionReceiver,
459
460 CompanionOf,
462
463 SealedPermit,
465}
466
467impl EdgeKind {
468 #[inline]
470 #[must_use]
471 pub const fn is_call(&self) -> bool {
472 matches!(
473 self,
474 Self::Calls { .. }
475 | Self::FfiCall { .. }
476 | Self::HttpRequest { .. }
477 | Self::GrpcCall { .. }
478 | Self::WebAssemblyCall
479 )
480 }
481
482 #[inline]
484 #[must_use]
485 pub const fn is_structural(&self) -> bool {
486 matches!(self, Self::Defines | Self::Contains)
487 }
488
489 #[inline]
491 #[must_use]
492 pub const fn is_type_relation(&self) -> bool {
493 matches!(
494 self,
495 Self::Inherits
496 | Self::Implements
497 | Self::TypeOf { .. }
498 | Self::GenericBound
499 | Self::TypeArgument
500 | Self::ExtensionReceiver
501 | Self::SealedPermit
502 )
503 }
504
505 #[inline]
507 #[must_use]
508 pub const fn is_cross_boundary(&self) -> bool {
509 matches!(
510 self,
511 Self::FfiCall { .. }
512 | Self::HttpRequest { .. }
513 | Self::GrpcCall { .. }
514 | Self::WebAssemblyCall
515 | Self::DbQuery { .. }
516 | Self::TableRead { .. }
517 | Self::TableWrite { .. }
518 | Self::TriggeredBy { .. }
519 | Self::MessageQueue { .. }
520 | Self::WebSocket { .. }
521 | Self::GraphQLOperation { .. }
522 | Self::ProcessExec { .. }
523 | Self::FileIpc { .. }
524 | Self::ProtocolCall { .. }
525 )
526 }
527
528 #[inline]
530 #[must_use]
531 pub const fn is_async(&self) -> bool {
532 matches!(
533 self,
534 Self::MessageQueue { .. } | Self::WebSocket { .. } | Self::GraphQLOperation { .. }
535 )
536 }
537
538 #[inline]
543 #[must_use]
544 pub const fn is_rust_specific(&self) -> bool {
545 matches!(
546 self,
547 Self::LifetimeConstraint { .. }
548 | Self::TraitMethodBinding { .. }
549 | Self::MacroExpansion { .. }
550 )
551 }
552
553 #[inline]
555 #[must_use]
556 pub const fn is_lifetime_constraint(&self) -> bool {
557 matches!(self, Self::LifetimeConstraint { .. })
558 }
559
560 #[inline]
562 #[must_use]
563 pub const fn is_trait_method_binding(&self) -> bool {
564 matches!(self, Self::TraitMethodBinding { .. })
565 }
566
567 #[inline]
569 #[must_use]
570 pub const fn is_macro_expansion(&self) -> bool {
571 matches!(self, Self::MacroExpansion { .. })
572 }
573
574 #[must_use]
576 pub const fn tag(&self) -> &'static str {
577 match self {
578 Self::Defines => "defines",
579 Self::Contains => "contains",
580 Self::Calls { .. } => "calls",
581 Self::References => "references",
582 Self::Imports { .. } => "imports",
583 Self::Exports { .. } => "exports",
584 Self::TypeOf { .. } => "type_of",
585 Self::Inherits => "inherits",
586 Self::Implements => "implements",
587 Self::LifetimeConstraint { .. } => "lifetime_constraint",
588 Self::TraitMethodBinding { .. } => "trait_method_binding",
589 Self::MacroExpansion { .. } => "macro_expansion",
590 Self::FfiCall { .. } => "ffi_call",
591 Self::HttpRequest { .. } => "http_request",
592 Self::GrpcCall { .. } => "grpc_call",
593 Self::WebAssemblyCall => "web_assembly_call",
594 Self::DbQuery { .. } => "db_query",
595 Self::TableRead { .. } => "table_read",
596 Self::TableWrite { .. } => "table_write",
597 Self::TriggeredBy { .. } => "triggered_by",
598 Self::MessageQueue { .. } => "message_queue",
599 Self::WebSocket { .. } => "web_socket",
600 Self::GraphQLOperation { .. } => "graphql_operation",
601 Self::ProcessExec { .. } => "process_exec",
602 Self::FileIpc { .. } => "file_ipc",
603 Self::ProtocolCall { .. } => "protocol_call",
604 Self::GenericBound => "generic_bound",
605 Self::AnnotatedWith => "annotated_with",
606 Self::AnnotationParam => "annotation_param",
607 Self::LambdaCaptures => "lambda_captures",
608 Self::ModuleExports => "module_exports",
609 Self::ModuleRequires => "module_requires",
610 Self::ModuleOpens => "module_opens",
611 Self::ModuleProvides => "module_provides",
612 Self::TypeArgument => "type_argument",
613 Self::ExtensionReceiver => "extension_receiver",
614 Self::CompanionOf => "companion_of",
615 Self::SealedPermit => "sealed_permit",
616 }
617 }
618
619 #[must_use]
624 pub const fn estimated_size(&self) -> usize {
625 match self {
630 Self::Defines
632 | Self::Contains
633 | Self::References
634 | Self::Inherits
635 | Self::Implements
636 | Self::WebAssemblyCall
637 | Self::GenericBound
638 | Self::AnnotatedWith
639 | Self::AnnotationParam
640 | Self::LambdaCaptures
641 | Self::ModuleExports
642 | Self::ModuleRequires
643 | Self::ModuleOpens
644 | Self::ModuleProvides
645 | Self::TypeArgument
646 | Self::ExtensionReceiver
647 | Self::CompanionOf
648 | Self::SealedPermit => 1,
649
650 Self::Calls { .. } | Self::MacroExpansion { .. } => 3,
653
654 Self::Imports { .. } | Self::Exports { .. } | Self::DbQuery { .. } => 7,
657
658 Self::FfiCall { .. } | Self::LifetimeConstraint { .. } => 2,
661
662 Self::TraitMethodBinding { .. }
665 | Self::TableRead { .. }
666 | Self::TriggeredBy { .. }
667 | Self::ProtocolCall { .. } => 10,
668
669 Self::HttpRequest { .. } | Self::WebSocket { .. } | Self::FileIpc { .. } => 6,
672
673 Self::GrpcCall { .. } => 9,
675
676 Self::TableWrite { .. } | Self::MessageQueue { .. } | Self::TypeOf { .. } => 11,
679
680 Self::GraphQLOperation { .. } | Self::ProcessExec { .. } => 5,
682 }
683 }
684}
685
686impl fmt::Display for EdgeKind {
687 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
688 f.write_str(self.tag())
689 }
690}
691
692impl Default for EdgeKind {
693 fn default() -> Self {
695 Self::Calls {
696 argument_count: 0,
697 is_async: false,
698 }
699 }
700}
701
702#[cfg(test)]
703mod tests {
704 use super::*;
705
706 fn calls() -> EdgeKind {
708 EdgeKind::Calls {
709 argument_count: 0,
710 is_async: false,
711 }
712 }
713
714 fn imports() -> EdgeKind {
716 EdgeKind::Imports {
717 alias: None,
718 is_wildcard: false,
719 }
720 }
721
722 fn exports() -> EdgeKind {
724 EdgeKind::Exports {
725 kind: ExportKind::Direct,
726 alias: None,
727 }
728 }
729
730 #[test]
731 fn test_edge_kind_tag() {
732 assert_eq!(calls().tag(), "calls");
733 assert_eq!(imports().tag(), "imports");
734 assert_eq!(exports().tag(), "exports");
735 assert_eq!(EdgeKind::Defines.tag(), "defines");
736 assert_eq!(
737 EdgeKind::HttpRequest {
738 method: HttpMethod::Get,
739 url: None
740 }
741 .tag(),
742 "http_request"
743 );
744 }
745
746 #[test]
747 fn test_edge_kind_display() {
748 assert_eq!(format!("{}", calls()), "calls");
749 assert_eq!(format!("{}", imports()), "imports");
750 assert_eq!(format!("{}", exports()), "exports");
751 assert_eq!(format!("{}", EdgeKind::Inherits), "inherits");
752 }
753
754 #[test]
755 fn test_is_call() {
756 assert!(calls().is_call());
757 assert!(
758 EdgeKind::Calls {
759 argument_count: 5,
760 is_async: true
761 }
762 .is_call()
763 );
764 assert!(
765 EdgeKind::FfiCall {
766 convention: FfiConvention::C
767 }
768 .is_call()
769 );
770 assert!(
771 EdgeKind::HttpRequest {
772 method: HttpMethod::Post,
773 url: None
774 }
775 .is_call()
776 );
777 assert!(!EdgeKind::Defines.is_call());
778 assert!(!EdgeKind::Inherits.is_call());
779 assert!(!imports().is_call());
780 assert!(!exports().is_call());
781 }
782
783 #[test]
784 fn test_is_structural() {
785 assert!(EdgeKind::Defines.is_structural());
786 assert!(EdgeKind::Contains.is_structural());
787 assert!(!calls().is_structural());
788 assert!(!imports().is_structural());
789 assert!(!exports().is_structural());
790 }
791
792 #[test]
793 fn test_is_type_relation() {
794 assert!(EdgeKind::Inherits.is_type_relation());
795 assert!(EdgeKind::Implements.is_type_relation());
796 assert!(
797 EdgeKind::TypeOf {
798 context: None,
799 index: None,
800 name: None,
801 }
802 .is_type_relation()
803 );
804 assert!(!calls().is_type_relation());
805 }
806
807 #[test]
808 fn test_is_cross_boundary() {
809 assert!(
810 EdgeKind::FfiCall {
811 convention: FfiConvention::C
812 }
813 .is_cross_boundary()
814 );
815 assert!(
816 EdgeKind::HttpRequest {
817 method: HttpMethod::Get,
818 url: None
819 }
820 .is_cross_boundary()
821 );
822 assert!(
823 EdgeKind::GrpcCall {
824 service: StringId::INVALID,
825 method: StringId::INVALID
826 }
827 .is_cross_boundary()
828 );
829 assert!(!calls().is_cross_boundary());
830 assert!(!imports().is_cross_boundary());
831 assert!(!exports().is_cross_boundary());
832 }
833
834 #[test]
835 fn test_is_async() {
836 assert!(
837 EdgeKind::MessageQueue {
838 protocol: MqProtocol::Kafka,
839 topic: None
840 }
841 .is_async()
842 );
843 assert!(EdgeKind::WebSocket { event: None }.is_async());
844 assert!(!calls().is_async());
845 assert!(
848 !EdgeKind::Calls {
849 argument_count: 0,
850 is_async: true
851 }
852 .is_async()
853 );
854 }
855
856 #[test]
857 fn test_default() {
858 assert_eq!(EdgeKind::default(), calls());
859 assert_eq!(HttpMethod::default(), HttpMethod::Get);
860 assert_eq!(FfiConvention::default(), FfiConvention::C);
861 assert_eq!(DbQueryType::default(), DbQueryType::Select);
862 assert_eq!(ExportKind::default(), ExportKind::Direct);
863 }
864
865 #[test]
866 fn test_http_method_as_str() {
867 assert_eq!(HttpMethod::Get.as_str(), "GET");
868 assert_eq!(HttpMethod::Post.as_str(), "POST");
869 assert_eq!(HttpMethod::Delete.as_str(), "DELETE");
870 assert_eq!(HttpMethod::All.as_str(), "ALL");
871 }
872
873 #[test]
874 fn test_calls_with_metadata() {
875 let sync_call = EdgeKind::Calls {
876 argument_count: 3,
877 is_async: false,
878 };
879 let async_call = EdgeKind::Calls {
880 argument_count: 0,
881 is_async: true,
882 };
883 assert_eq!(sync_call.tag(), "calls");
884 assert_eq!(async_call.tag(), "calls");
885 assert!(sync_call.is_call());
886 assert!(async_call.is_call());
887 assert_ne!(sync_call, async_call);
888 }
889
890 #[test]
891 fn test_imports_with_metadata() {
892 let simple = imports();
893 let aliased = EdgeKind::Imports {
894 alias: Some(StringId::new(42)),
895 is_wildcard: false,
896 };
897 let wildcard = EdgeKind::Imports {
898 alias: None,
899 is_wildcard: true,
900 };
901
902 assert_eq!(simple.tag(), "imports");
903 assert_eq!(aliased.tag(), "imports");
904 assert_eq!(wildcard.tag(), "imports");
905 assert_ne!(simple, aliased);
906 assert_ne!(simple, wildcard);
907 }
908
909 #[test]
910 fn test_exports_with_metadata() {
911 let direct = exports();
912 let reexport = EdgeKind::Exports {
913 kind: ExportKind::Reexport,
914 alias: None,
915 };
916 let default_export = EdgeKind::Exports {
917 kind: ExportKind::Default,
918 alias: None,
919 };
920 let namespace = EdgeKind::Exports {
921 kind: ExportKind::Namespace,
922 alias: Some(StringId::new(1)),
923 };
924
925 assert_eq!(direct.tag(), "exports");
926 assert_eq!(reexport.tag(), "exports");
927 assert_eq!(default_export.tag(), "exports");
928 assert_eq!(namespace.tag(), "exports");
929 assert_ne!(direct, reexport);
930 assert_ne!(direct, default_export);
931 }
932
933 #[test]
934 fn test_serde_calls_imports_exports() {
935 let calls = EdgeKind::Calls {
937 argument_count: 5,
938 is_async: true,
939 };
940 let json = serde_json::to_string(&calls).unwrap();
941 let deserialized: EdgeKind = serde_json::from_str(&json).unwrap();
942 assert_eq!(calls, deserialized);
943 assert!(json.contains("\"calls\""));
944 assert!(json.contains("\"argument_count\":5"));
945 assert!(json.contains("\"is_async\":true"));
946
947 let imports = EdgeKind::Imports {
949 alias: Some(StringId::new(10)),
950 is_wildcard: false,
951 };
952 let json = serde_json::to_string(&imports).unwrap();
953 let deserialized: EdgeKind = serde_json::from_str(&json).unwrap();
954 assert_eq!(imports, deserialized);
955
956 let exports = EdgeKind::Exports {
958 kind: ExportKind::Reexport,
959 alias: None,
960 };
961 let json = serde_json::to_string(&exports).unwrap();
962 let deserialized: EdgeKind = serde_json::from_str(&json).unwrap();
963 assert_eq!(exports, deserialized);
964 }
965
966 #[test]
967 fn test_serde_complex_variants() {
968 let http = EdgeKind::HttpRequest {
970 method: HttpMethod::Post,
971 url: None,
972 };
973 let json = serde_json::to_string(&http).unwrap();
974 let deserialized: EdgeKind = serde_json::from_str(&json).unwrap();
975 assert_eq!(http, deserialized);
976
977 let grpc = EdgeKind::GrpcCall {
979 service: StringId::new(1),
980 method: StringId::new(2),
981 };
982 let json = serde_json::to_string(&grpc).unwrap();
983 let deserialized: EdgeKind = serde_json::from_str(&json).unwrap();
984 assert_eq!(grpc, deserialized);
985 }
986
987 #[test]
988 fn test_postcard_roundtrip_simple_enums() {
989 for conv in [
993 FfiConvention::C,
994 FfiConvention::Cdecl,
995 FfiConvention::Stdcall,
996 ] {
997 let bytes = postcard::to_allocvec(&conv).unwrap();
998 let deserialized: FfiConvention = postcard::from_bytes(&bytes).unwrap();
999 assert_eq!(conv, deserialized);
1000 }
1001
1002 for method in [
1004 HttpMethod::Get,
1005 HttpMethod::Post,
1006 HttpMethod::Delete,
1007 HttpMethod::All,
1008 ] {
1009 let bytes = postcard::to_allocvec(&method).unwrap();
1010 let deserialized: HttpMethod = postcard::from_bytes(&bytes).unwrap();
1011 assert_eq!(method, deserialized);
1012 }
1013
1014 for query in [
1016 DbQueryType::Select,
1017 DbQueryType::Insert,
1018 DbQueryType::Update,
1019 ] {
1020 let bytes = postcard::to_allocvec(&query).unwrap();
1021 let deserialized: DbQueryType = postcard::from_bytes(&bytes).unwrap();
1022 assert_eq!(query, deserialized);
1023 }
1024
1025 for kind in [
1027 ExportKind::Direct,
1028 ExportKind::Reexport,
1029 ExportKind::Default,
1030 ExportKind::Namespace,
1031 ] {
1032 let bytes = postcard::to_allocvec(&kind).unwrap();
1033 let deserialized: ExportKind = postcard::from_bytes(&bytes).unwrap();
1034 assert_eq!(kind, deserialized);
1035 }
1036 }
1037
1038 #[test]
1039 fn test_edge_kind_json_compatibility() {
1040 let kinds = [
1043 calls(),
1044 imports(),
1045 exports(),
1046 EdgeKind::Defines,
1047 EdgeKind::HttpRequest {
1048 method: HttpMethod::Get,
1049 url: None,
1050 },
1051 EdgeKind::MessageQueue {
1052 protocol: MqProtocol::Kafka,
1053 topic: Some(StringId::new(1)),
1054 },
1055 ];
1056
1057 for kind in &kinds {
1058 let json = serde_json::to_string(kind).unwrap();
1060 let deserialized: EdgeKind = serde_json::from_str(&json).unwrap();
1061 assert_eq!(*kind, deserialized);
1062
1063 let bytes = postcard::to_allocvec(kind).unwrap();
1065 let from_postcard: EdgeKind = postcard::from_bytes(&bytes).unwrap();
1066 assert_eq!(*kind, from_postcard);
1067 }
1068 }
1069
1070 #[test]
1071 fn test_hash() {
1072 use std::collections::HashSet;
1073
1074 let mut set = HashSet::new();
1075 set.insert(calls());
1076 set.insert(imports());
1077 set.insert(exports());
1078 set.insert(EdgeKind::Defines);
1079 set.insert(EdgeKind::HttpRequest {
1080 method: HttpMethod::Get,
1081 url: None,
1082 });
1083
1084 assert!(set.contains(&calls()));
1085 assert!(set.contains(&imports()));
1086 assert!(set.contains(&exports()));
1087 assert!(!set.contains(&EdgeKind::Inherits));
1088 assert_eq!(set.len(), 5);
1089 }
1090
1091 #[test]
1092 fn test_ffi_convention_variants() {
1093 let conventions = [
1094 FfiConvention::C,
1095 FfiConvention::Cdecl,
1096 FfiConvention::Stdcall,
1097 FfiConvention::Fastcall,
1098 FfiConvention::System,
1099 ];
1100
1101 for conv in conventions {
1102 let edge = EdgeKind::FfiCall { convention: conv };
1103 assert!(edge.is_call());
1104 assert!(edge.is_cross_boundary());
1105 }
1106 }
1107
1108 #[test]
1109 fn test_mq_protocol_variants() {
1110 let protocols = [
1111 MqProtocol::Kafka,
1112 MqProtocol::Sqs,
1113 MqProtocol::RabbitMq,
1114 MqProtocol::Nats,
1115 MqProtocol::Redis,
1116 MqProtocol::Other(StringId::new(1)),
1117 ];
1118
1119 for proto in protocols {
1120 let edge = EdgeKind::MessageQueue {
1121 protocol: proto.clone(),
1122 topic: None,
1123 };
1124 assert!(edge.is_async());
1125 assert!(edge.is_cross_boundary());
1126 }
1127 }
1128
1129 #[test]
1130 fn test_export_kind_variants() {
1131 let kinds = [
1132 ExportKind::Direct,
1133 ExportKind::Reexport,
1134 ExportKind::Default,
1135 ExportKind::Namespace,
1136 ];
1137
1138 for kind in kinds {
1139 let edge = EdgeKind::Exports { kind, alias: None };
1140 assert_eq!(edge.tag(), "exports");
1141 assert!(!edge.is_call());
1142 assert!(!edge.is_structural());
1143 assert!(!edge.is_cross_boundary());
1144 }
1145 }
1146
1147 #[test]
1148 fn test_estimated_size() {
1149 assert_eq!(EdgeKind::Defines.estimated_size(), 1);
1151 assert_eq!(EdgeKind::Contains.estimated_size(), 1);
1152 assert_eq!(EdgeKind::References.estimated_size(), 1);
1153
1154 assert_eq!(calls().estimated_size(), 3);
1156
1157 assert_eq!(imports().estimated_size(), 7);
1159
1160 assert_eq!(exports().estimated_size(), 7);
1162
1163 assert_eq!(
1165 EdgeKind::LifetimeConstraint {
1166 constraint_kind: LifetimeConstraintKind::Outlives
1167 }
1168 .estimated_size(),
1169 2
1170 );
1171 assert_eq!(
1172 EdgeKind::MacroExpansion {
1173 expansion_kind: MacroExpansionKind::Derive,
1174 is_verified: true
1175 }
1176 .estimated_size(),
1177 3
1178 );
1179 assert_eq!(
1180 EdgeKind::TraitMethodBinding {
1181 trait_name: StringId::INVALID,
1182 impl_type: StringId::INVALID,
1183 is_ambiguous: false
1184 }
1185 .estimated_size(),
1186 10
1187 );
1188 }
1189
1190 #[test]
1193 fn test_lifetime_constraint_kind_variants() {
1194 let kinds = [
1195 LifetimeConstraintKind::Outlives,
1196 LifetimeConstraintKind::TypeBound,
1197 LifetimeConstraintKind::Reference,
1198 LifetimeConstraintKind::Static,
1199 LifetimeConstraintKind::HigherRanked,
1200 LifetimeConstraintKind::TraitObject,
1201 LifetimeConstraintKind::ImplTrait,
1202 LifetimeConstraintKind::Elided,
1203 ];
1204
1205 for constraint_kind in kinds {
1206 let edge = EdgeKind::LifetimeConstraint { constraint_kind };
1207 assert!(edge.is_rust_specific());
1208 assert!(edge.is_lifetime_constraint());
1209 assert!(!edge.is_call());
1210 assert!(!edge.is_structural());
1211 assert_eq!(edge.tag(), "lifetime_constraint");
1212 }
1213 }
1214
1215 #[test]
1216 fn test_macro_expansion_kind_variants() {
1217 let kinds = [
1218 MacroExpansionKind::Derive,
1219 MacroExpansionKind::Attribute,
1220 MacroExpansionKind::Declarative,
1221 MacroExpansionKind::Function,
1222 MacroExpansionKind::CfgGate,
1223 ];
1224
1225 for expansion_kind in kinds {
1226 let edge = EdgeKind::MacroExpansion {
1227 expansion_kind,
1228 is_verified: true,
1229 };
1230 assert!(edge.is_rust_specific());
1231 assert!(edge.is_macro_expansion());
1232 assert!(!edge.is_call());
1233 assert_eq!(edge.tag(), "macro_expansion");
1234 }
1235 }
1236
1237 #[test]
1238 fn test_trait_method_binding() {
1239 let edge = EdgeKind::TraitMethodBinding {
1240 trait_name: StringId::new(1),
1241 impl_type: StringId::new(2),
1242 is_ambiguous: false,
1243 };
1244
1245 assert!(edge.is_rust_specific());
1246 assert!(edge.is_trait_method_binding());
1247 assert!(!edge.is_call());
1248 assert_eq!(edge.tag(), "trait_method_binding");
1249
1250 let ambiguous = EdgeKind::TraitMethodBinding {
1252 trait_name: StringId::new(1),
1253 impl_type: StringId::new(2),
1254 is_ambiguous: true,
1255 };
1256 assert!(ambiguous.is_trait_method_binding());
1257 }
1258
1259 #[test]
1260 fn test_rust_specific_edges_serde() {
1261 let lifetime = EdgeKind::LifetimeConstraint {
1263 constraint_kind: LifetimeConstraintKind::HigherRanked,
1264 };
1265 let json = serde_json::to_string(&lifetime).unwrap();
1266 let deserialized: EdgeKind = serde_json::from_str(&json).unwrap();
1267 assert_eq!(lifetime, deserialized);
1268
1269 let binding = EdgeKind::TraitMethodBinding {
1271 trait_name: StringId::new(10),
1272 impl_type: StringId::new(20),
1273 is_ambiguous: true,
1274 };
1275 let json = serde_json::to_string(&binding).unwrap();
1276 let deserialized: EdgeKind = serde_json::from_str(&json).unwrap();
1277 assert_eq!(binding, deserialized);
1278
1279 let expansion = EdgeKind::MacroExpansion {
1281 expansion_kind: MacroExpansionKind::Derive,
1282 is_verified: false,
1283 };
1284 let json = serde_json::to_string(&expansion).unwrap();
1285 let deserialized: EdgeKind = serde_json::from_str(&json).unwrap();
1286 assert_eq!(expansion, deserialized);
1287 }
1288
1289 #[test]
1290 fn test_rust_specific_edges_postcard() {
1291 let edges = [
1292 EdgeKind::LifetimeConstraint {
1293 constraint_kind: LifetimeConstraintKind::Outlives,
1294 },
1295 EdgeKind::TraitMethodBinding {
1296 trait_name: StringId::new(5),
1297 impl_type: StringId::new(6),
1298 is_ambiguous: false,
1299 },
1300 EdgeKind::MacroExpansion {
1301 expansion_kind: MacroExpansionKind::Attribute,
1302 is_verified: true,
1303 },
1304 ];
1305
1306 for edge in edges {
1307 let bytes = postcard::to_allocvec(&edge).unwrap();
1308 let deserialized: EdgeKind = postcard::from_bytes(&bytes).unwrap();
1309 assert_eq!(edge, deserialized);
1310 }
1311 }
1312
1313 #[test]
1314 fn test_lifetime_constraint_kind_defaults() {
1315 assert_eq!(
1316 LifetimeConstraintKind::default(),
1317 LifetimeConstraintKind::Outlives
1318 );
1319 assert_eq!(
1320 MacroExpansionKind::default(),
1321 MacroExpansionKind::Declarative
1322 );
1323 }
1324}