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}
207
208#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
217#[serde(rename_all = "snake_case")]
218pub enum EdgeKind {
219 Defines,
222
223 Contains,
225
226 Calls {
229 argument_count: u8,
231 is_async: bool,
235 },
236
237 References,
239
240 Imports {
242 alias: Option<StringId>,
244 is_wildcard: bool,
246 },
247
248 Exports {
250 kind: ExportKind,
252 alias: Option<StringId>,
254 },
255
256 TypeOf {
267 context: Option<TypeOfContext>,
269 index: Option<u16>,
271 name: Option<StringId>,
273 },
274
275 Inherits,
278
279 Implements,
281
282 LifetimeConstraint {
291 constraint_kind: LifetimeConstraintKind,
293 },
294
295 TraitMethodBinding {
304 trait_name: StringId,
306 impl_type: StringId,
308 is_ambiguous: bool,
310 },
311
312 MacroExpansion {
319 expansion_kind: MacroExpansionKind,
321 is_verified: bool,
323 },
324
325 FfiCall {
328 convention: FfiConvention,
330 },
331
332 HttpRequest {
334 method: HttpMethod,
336 url: Option<StringId>,
338 },
339
340 GrpcCall {
342 service: StringId,
344 method: StringId,
346 },
347
348 WebAssemblyCall,
350
351 DbQuery {
353 query_type: DbQueryType,
355 table: Option<StringId>,
357 },
358
359 TableRead {
361 table_name: StringId,
363 schema: Option<StringId>,
365 },
366
367 TableWrite {
369 table_name: StringId,
371 schema: Option<StringId>,
373 operation: TableWriteOp,
375 },
376
377 TriggeredBy {
379 trigger_name: StringId,
381 schema: Option<StringId>,
383 },
384
385 MessageQueue {
388 protocol: MqProtocol,
390 topic: Option<StringId>,
392 },
393
394 WebSocket {
396 event: Option<StringId>,
398 },
399
400 GraphQLOperation {
402 operation: StringId,
404 },
405
406 ProcessExec {
408 command: StringId,
410 },
411
412 FileIpc {
414 path_pattern: Option<StringId>,
416 },
417
418 ProtocolCall {
421 protocol: StringId,
423 metadata: Option<StringId>,
425 },
426}
427
428impl EdgeKind {
429 #[inline]
431 #[must_use]
432 pub const fn is_call(&self) -> bool {
433 matches!(
434 self,
435 Self::Calls { .. }
436 | Self::FfiCall { .. }
437 | Self::HttpRequest { .. }
438 | Self::GrpcCall { .. }
439 | Self::WebAssemblyCall
440 )
441 }
442
443 #[inline]
445 #[must_use]
446 pub const fn is_structural(&self) -> bool {
447 matches!(self, Self::Defines | Self::Contains)
448 }
449
450 #[inline]
452 #[must_use]
453 pub const fn is_type_relation(&self) -> bool {
454 matches!(
455 self,
456 Self::Inherits | Self::Implements | Self::TypeOf { .. }
457 )
458 }
459
460 #[inline]
462 #[must_use]
463 pub const fn is_cross_boundary(&self) -> bool {
464 matches!(
465 self,
466 Self::FfiCall { .. }
467 | Self::HttpRequest { .. }
468 | Self::GrpcCall { .. }
469 | Self::WebAssemblyCall
470 | Self::DbQuery { .. }
471 | Self::TableRead { .. }
472 | Self::TableWrite { .. }
473 | Self::TriggeredBy { .. }
474 | Self::MessageQueue { .. }
475 | Self::WebSocket { .. }
476 | Self::GraphQLOperation { .. }
477 | Self::ProcessExec { .. }
478 | Self::FileIpc { .. }
479 | Self::ProtocolCall { .. }
480 )
481 }
482
483 #[inline]
485 #[must_use]
486 pub const fn is_async(&self) -> bool {
487 matches!(
488 self,
489 Self::MessageQueue { .. } | Self::WebSocket { .. } | Self::GraphQLOperation { .. }
490 )
491 }
492
493 #[inline]
498 #[must_use]
499 pub const fn is_rust_specific(&self) -> bool {
500 matches!(
501 self,
502 Self::LifetimeConstraint { .. }
503 | Self::TraitMethodBinding { .. }
504 | Self::MacroExpansion { .. }
505 )
506 }
507
508 #[inline]
510 #[must_use]
511 pub const fn is_lifetime_constraint(&self) -> bool {
512 matches!(self, Self::LifetimeConstraint { .. })
513 }
514
515 #[inline]
517 #[must_use]
518 pub const fn is_trait_method_binding(&self) -> bool {
519 matches!(self, Self::TraitMethodBinding { .. })
520 }
521
522 #[inline]
524 #[must_use]
525 pub const fn is_macro_expansion(&self) -> bool {
526 matches!(self, Self::MacroExpansion { .. })
527 }
528
529 #[must_use]
531 pub const fn tag(&self) -> &'static str {
532 match self {
533 Self::Defines => "defines",
534 Self::Contains => "contains",
535 Self::Calls { .. } => "calls",
536 Self::References => "references",
537 Self::Imports { .. } => "imports",
538 Self::Exports { .. } => "exports",
539 Self::TypeOf { .. } => "type_of",
540 Self::Inherits => "inherits",
541 Self::Implements => "implements",
542 Self::LifetimeConstraint { .. } => "lifetime_constraint",
543 Self::TraitMethodBinding { .. } => "trait_method_binding",
544 Self::MacroExpansion { .. } => "macro_expansion",
545 Self::FfiCall { .. } => "ffi_call",
546 Self::HttpRequest { .. } => "http_request",
547 Self::GrpcCall { .. } => "grpc_call",
548 Self::WebAssemblyCall => "web_assembly_call",
549 Self::DbQuery { .. } => "db_query",
550 Self::TableRead { .. } => "table_read",
551 Self::TableWrite { .. } => "table_write",
552 Self::TriggeredBy { .. } => "triggered_by",
553 Self::MessageQueue { .. } => "message_queue",
554 Self::WebSocket { .. } => "web_socket",
555 Self::GraphQLOperation { .. } => "graphql_operation",
556 Self::ProcessExec { .. } => "process_exec",
557 Self::FileIpc { .. } => "file_ipc",
558 Self::ProtocolCall { .. } => "protocol_call",
559 }
560 }
561
562 #[must_use]
567 pub const fn estimated_size(&self) -> usize {
568 match self {
573 Self::Defines
575 | Self::Contains
576 | Self::References
577 | Self::Inherits
578 | Self::Implements
579 | Self::WebAssemblyCall => 1,
580
581 Self::Calls { .. } | Self::MacroExpansion { .. } => 3,
584
585 Self::Imports { .. } | Self::Exports { .. } | Self::DbQuery { .. } => 7,
588
589 Self::FfiCall { .. } | Self::LifetimeConstraint { .. } => 2,
592
593 Self::TraitMethodBinding { .. }
596 | Self::TableRead { .. }
597 | Self::TriggeredBy { .. }
598 | Self::ProtocolCall { .. } => 10,
599
600 Self::HttpRequest { .. } | Self::WebSocket { .. } | Self::FileIpc { .. } => 6,
603
604 Self::GrpcCall { .. } => 9,
606
607 Self::TableWrite { .. } | Self::MessageQueue { .. } | Self::TypeOf { .. } => 11,
610
611 Self::GraphQLOperation { .. } | Self::ProcessExec { .. } => 5,
613 }
614 }
615}
616
617impl fmt::Display for EdgeKind {
618 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
619 f.write_str(self.tag())
620 }
621}
622
623impl Default for EdgeKind {
624 fn default() -> Self {
626 Self::Calls {
627 argument_count: 0,
628 is_async: false,
629 }
630 }
631}
632
633#[cfg(test)]
634mod tests {
635 use super::*;
636
637 fn calls() -> EdgeKind {
639 EdgeKind::Calls {
640 argument_count: 0,
641 is_async: false,
642 }
643 }
644
645 fn imports() -> EdgeKind {
647 EdgeKind::Imports {
648 alias: None,
649 is_wildcard: false,
650 }
651 }
652
653 fn exports() -> EdgeKind {
655 EdgeKind::Exports {
656 kind: ExportKind::Direct,
657 alias: None,
658 }
659 }
660
661 #[test]
662 fn test_edge_kind_tag() {
663 assert_eq!(calls().tag(), "calls");
664 assert_eq!(imports().tag(), "imports");
665 assert_eq!(exports().tag(), "exports");
666 assert_eq!(EdgeKind::Defines.tag(), "defines");
667 assert_eq!(
668 EdgeKind::HttpRequest {
669 method: HttpMethod::Get,
670 url: None
671 }
672 .tag(),
673 "http_request"
674 );
675 }
676
677 #[test]
678 fn test_edge_kind_display() {
679 assert_eq!(format!("{}", calls()), "calls");
680 assert_eq!(format!("{}", imports()), "imports");
681 assert_eq!(format!("{}", exports()), "exports");
682 assert_eq!(format!("{}", EdgeKind::Inherits), "inherits");
683 }
684
685 #[test]
686 fn test_is_call() {
687 assert!(calls().is_call());
688 assert!(
689 EdgeKind::Calls {
690 argument_count: 5,
691 is_async: true
692 }
693 .is_call()
694 );
695 assert!(
696 EdgeKind::FfiCall {
697 convention: FfiConvention::C
698 }
699 .is_call()
700 );
701 assert!(
702 EdgeKind::HttpRequest {
703 method: HttpMethod::Post,
704 url: None
705 }
706 .is_call()
707 );
708 assert!(!EdgeKind::Defines.is_call());
709 assert!(!EdgeKind::Inherits.is_call());
710 assert!(!imports().is_call());
711 assert!(!exports().is_call());
712 }
713
714 #[test]
715 fn test_is_structural() {
716 assert!(EdgeKind::Defines.is_structural());
717 assert!(EdgeKind::Contains.is_structural());
718 assert!(!calls().is_structural());
719 assert!(!imports().is_structural());
720 assert!(!exports().is_structural());
721 }
722
723 #[test]
724 fn test_is_type_relation() {
725 assert!(EdgeKind::Inherits.is_type_relation());
726 assert!(EdgeKind::Implements.is_type_relation());
727 assert!(
728 EdgeKind::TypeOf {
729 context: None,
730 index: None,
731 name: None,
732 }
733 .is_type_relation()
734 );
735 assert!(!calls().is_type_relation());
736 }
737
738 #[test]
739 fn test_is_cross_boundary() {
740 assert!(
741 EdgeKind::FfiCall {
742 convention: FfiConvention::C
743 }
744 .is_cross_boundary()
745 );
746 assert!(
747 EdgeKind::HttpRequest {
748 method: HttpMethod::Get,
749 url: None
750 }
751 .is_cross_boundary()
752 );
753 assert!(
754 EdgeKind::GrpcCall {
755 service: StringId::INVALID,
756 method: StringId::INVALID
757 }
758 .is_cross_boundary()
759 );
760 assert!(!calls().is_cross_boundary());
761 assert!(!imports().is_cross_boundary());
762 assert!(!exports().is_cross_boundary());
763 }
764
765 #[test]
766 fn test_is_async() {
767 assert!(
768 EdgeKind::MessageQueue {
769 protocol: MqProtocol::Kafka,
770 topic: None
771 }
772 .is_async()
773 );
774 assert!(EdgeKind::WebSocket { event: None }.is_async());
775 assert!(!calls().is_async());
776 assert!(
779 !EdgeKind::Calls {
780 argument_count: 0,
781 is_async: true
782 }
783 .is_async()
784 );
785 }
786
787 #[test]
788 fn test_default() {
789 assert_eq!(EdgeKind::default(), calls());
790 assert_eq!(HttpMethod::default(), HttpMethod::Get);
791 assert_eq!(FfiConvention::default(), FfiConvention::C);
792 assert_eq!(DbQueryType::default(), DbQueryType::Select);
793 assert_eq!(ExportKind::default(), ExportKind::Direct);
794 }
795
796 #[test]
797 fn test_http_method_as_str() {
798 assert_eq!(HttpMethod::Get.as_str(), "GET");
799 assert_eq!(HttpMethod::Post.as_str(), "POST");
800 assert_eq!(HttpMethod::Delete.as_str(), "DELETE");
801 assert_eq!(HttpMethod::All.as_str(), "ALL");
802 }
803
804 #[test]
805 fn test_calls_with_metadata() {
806 let sync_call = EdgeKind::Calls {
807 argument_count: 3,
808 is_async: false,
809 };
810 let async_call = EdgeKind::Calls {
811 argument_count: 0,
812 is_async: true,
813 };
814 assert_eq!(sync_call.tag(), "calls");
815 assert_eq!(async_call.tag(), "calls");
816 assert!(sync_call.is_call());
817 assert!(async_call.is_call());
818 assert_ne!(sync_call, async_call);
819 }
820
821 #[test]
822 fn test_imports_with_metadata() {
823 let simple = imports();
824 let aliased = EdgeKind::Imports {
825 alias: Some(StringId::new(42)),
826 is_wildcard: false,
827 };
828 let wildcard = EdgeKind::Imports {
829 alias: None,
830 is_wildcard: true,
831 };
832
833 assert_eq!(simple.tag(), "imports");
834 assert_eq!(aliased.tag(), "imports");
835 assert_eq!(wildcard.tag(), "imports");
836 assert_ne!(simple, aliased);
837 assert_ne!(simple, wildcard);
838 }
839
840 #[test]
841 fn test_exports_with_metadata() {
842 let direct = exports();
843 let reexport = EdgeKind::Exports {
844 kind: ExportKind::Reexport,
845 alias: None,
846 };
847 let default_export = EdgeKind::Exports {
848 kind: ExportKind::Default,
849 alias: None,
850 };
851 let namespace = EdgeKind::Exports {
852 kind: ExportKind::Namespace,
853 alias: Some(StringId::new(1)),
854 };
855
856 assert_eq!(direct.tag(), "exports");
857 assert_eq!(reexport.tag(), "exports");
858 assert_eq!(default_export.tag(), "exports");
859 assert_eq!(namespace.tag(), "exports");
860 assert_ne!(direct, reexport);
861 assert_ne!(direct, default_export);
862 }
863
864 #[test]
865 fn test_serde_calls_imports_exports() {
866 let calls = EdgeKind::Calls {
868 argument_count: 5,
869 is_async: true,
870 };
871 let json = serde_json::to_string(&calls).unwrap();
872 let deserialized: EdgeKind = serde_json::from_str(&json).unwrap();
873 assert_eq!(calls, deserialized);
874 assert!(json.contains("\"calls\""));
875 assert!(json.contains("\"argument_count\":5"));
876 assert!(json.contains("\"is_async\":true"));
877
878 let imports = EdgeKind::Imports {
880 alias: Some(StringId::new(10)),
881 is_wildcard: false,
882 };
883 let json = serde_json::to_string(&imports).unwrap();
884 let deserialized: EdgeKind = serde_json::from_str(&json).unwrap();
885 assert_eq!(imports, deserialized);
886
887 let exports = EdgeKind::Exports {
889 kind: ExportKind::Reexport,
890 alias: None,
891 };
892 let json = serde_json::to_string(&exports).unwrap();
893 let deserialized: EdgeKind = serde_json::from_str(&json).unwrap();
894 assert_eq!(exports, deserialized);
895 }
896
897 #[test]
898 fn test_serde_complex_variants() {
899 let http = EdgeKind::HttpRequest {
901 method: HttpMethod::Post,
902 url: None,
903 };
904 let json = serde_json::to_string(&http).unwrap();
905 let deserialized: EdgeKind = serde_json::from_str(&json).unwrap();
906 assert_eq!(http, deserialized);
907
908 let grpc = EdgeKind::GrpcCall {
910 service: StringId::new(1),
911 method: StringId::new(2),
912 };
913 let json = serde_json::to_string(&grpc).unwrap();
914 let deserialized: EdgeKind = serde_json::from_str(&json).unwrap();
915 assert_eq!(grpc, deserialized);
916 }
917
918 #[test]
919 fn test_postcard_roundtrip_simple_enums() {
920 for conv in [
924 FfiConvention::C,
925 FfiConvention::Cdecl,
926 FfiConvention::Stdcall,
927 ] {
928 let bytes = postcard::to_allocvec(&conv).unwrap();
929 let deserialized: FfiConvention = postcard::from_bytes(&bytes).unwrap();
930 assert_eq!(conv, deserialized);
931 }
932
933 for method in [
935 HttpMethod::Get,
936 HttpMethod::Post,
937 HttpMethod::Delete,
938 HttpMethod::All,
939 ] {
940 let bytes = postcard::to_allocvec(&method).unwrap();
941 let deserialized: HttpMethod = postcard::from_bytes(&bytes).unwrap();
942 assert_eq!(method, deserialized);
943 }
944
945 for query in [
947 DbQueryType::Select,
948 DbQueryType::Insert,
949 DbQueryType::Update,
950 ] {
951 let bytes = postcard::to_allocvec(&query).unwrap();
952 let deserialized: DbQueryType = postcard::from_bytes(&bytes).unwrap();
953 assert_eq!(query, deserialized);
954 }
955
956 for kind in [
958 ExportKind::Direct,
959 ExportKind::Reexport,
960 ExportKind::Default,
961 ExportKind::Namespace,
962 ] {
963 let bytes = postcard::to_allocvec(&kind).unwrap();
964 let deserialized: ExportKind = postcard::from_bytes(&bytes).unwrap();
965 assert_eq!(kind, deserialized);
966 }
967 }
968
969 #[test]
970 fn test_edge_kind_json_compatibility() {
971 let kinds = [
974 calls(),
975 imports(),
976 exports(),
977 EdgeKind::Defines,
978 EdgeKind::HttpRequest {
979 method: HttpMethod::Get,
980 url: None,
981 },
982 EdgeKind::MessageQueue {
983 protocol: MqProtocol::Kafka,
984 topic: Some(StringId::new(1)),
985 },
986 ];
987
988 for kind in &kinds {
989 let json = serde_json::to_string(kind).unwrap();
991 let deserialized: EdgeKind = serde_json::from_str(&json).unwrap();
992 assert_eq!(*kind, deserialized);
993
994 let bytes = postcard::to_allocvec(kind).unwrap();
996 let from_postcard: EdgeKind = postcard::from_bytes(&bytes).unwrap();
997 assert_eq!(*kind, from_postcard);
998 }
999 }
1000
1001 #[test]
1002 fn test_hash() {
1003 use std::collections::HashSet;
1004
1005 let mut set = HashSet::new();
1006 set.insert(calls());
1007 set.insert(imports());
1008 set.insert(exports());
1009 set.insert(EdgeKind::Defines);
1010 set.insert(EdgeKind::HttpRequest {
1011 method: HttpMethod::Get,
1012 url: None,
1013 });
1014
1015 assert!(set.contains(&calls()));
1016 assert!(set.contains(&imports()));
1017 assert!(set.contains(&exports()));
1018 assert!(!set.contains(&EdgeKind::Inherits));
1019 assert_eq!(set.len(), 5);
1020 }
1021
1022 #[test]
1023 fn test_ffi_convention_variants() {
1024 let conventions = [
1025 FfiConvention::C,
1026 FfiConvention::Cdecl,
1027 FfiConvention::Stdcall,
1028 FfiConvention::Fastcall,
1029 FfiConvention::System,
1030 ];
1031
1032 for conv in conventions {
1033 let edge = EdgeKind::FfiCall { convention: conv };
1034 assert!(edge.is_call());
1035 assert!(edge.is_cross_boundary());
1036 }
1037 }
1038
1039 #[test]
1040 fn test_mq_protocol_variants() {
1041 let protocols = [
1042 MqProtocol::Kafka,
1043 MqProtocol::Sqs,
1044 MqProtocol::RabbitMq,
1045 MqProtocol::Nats,
1046 MqProtocol::Redis,
1047 MqProtocol::Other(StringId::new(1)),
1048 ];
1049
1050 for proto in protocols {
1051 let edge = EdgeKind::MessageQueue {
1052 protocol: proto.clone(),
1053 topic: None,
1054 };
1055 assert!(edge.is_async());
1056 assert!(edge.is_cross_boundary());
1057 }
1058 }
1059
1060 #[test]
1061 fn test_export_kind_variants() {
1062 let kinds = [
1063 ExportKind::Direct,
1064 ExportKind::Reexport,
1065 ExportKind::Default,
1066 ExportKind::Namespace,
1067 ];
1068
1069 for kind in kinds {
1070 let edge = EdgeKind::Exports { kind, alias: None };
1071 assert_eq!(edge.tag(), "exports");
1072 assert!(!edge.is_call());
1073 assert!(!edge.is_structural());
1074 assert!(!edge.is_cross_boundary());
1075 }
1076 }
1077
1078 #[test]
1079 fn test_estimated_size() {
1080 assert_eq!(EdgeKind::Defines.estimated_size(), 1);
1082 assert_eq!(EdgeKind::Contains.estimated_size(), 1);
1083 assert_eq!(EdgeKind::References.estimated_size(), 1);
1084
1085 assert_eq!(calls().estimated_size(), 3);
1087
1088 assert_eq!(imports().estimated_size(), 7);
1090
1091 assert_eq!(exports().estimated_size(), 7);
1093
1094 assert_eq!(
1096 EdgeKind::LifetimeConstraint {
1097 constraint_kind: LifetimeConstraintKind::Outlives
1098 }
1099 .estimated_size(),
1100 2
1101 );
1102 assert_eq!(
1103 EdgeKind::MacroExpansion {
1104 expansion_kind: MacroExpansionKind::Derive,
1105 is_verified: true
1106 }
1107 .estimated_size(),
1108 3
1109 );
1110 assert_eq!(
1111 EdgeKind::TraitMethodBinding {
1112 trait_name: StringId::INVALID,
1113 impl_type: StringId::INVALID,
1114 is_ambiguous: false
1115 }
1116 .estimated_size(),
1117 10
1118 );
1119 }
1120
1121 #[test]
1124 fn test_lifetime_constraint_kind_variants() {
1125 let kinds = [
1126 LifetimeConstraintKind::Outlives,
1127 LifetimeConstraintKind::TypeBound,
1128 LifetimeConstraintKind::Reference,
1129 LifetimeConstraintKind::Static,
1130 LifetimeConstraintKind::HigherRanked,
1131 LifetimeConstraintKind::TraitObject,
1132 LifetimeConstraintKind::ImplTrait,
1133 LifetimeConstraintKind::Elided,
1134 ];
1135
1136 for constraint_kind in kinds {
1137 let edge = EdgeKind::LifetimeConstraint { constraint_kind };
1138 assert!(edge.is_rust_specific());
1139 assert!(edge.is_lifetime_constraint());
1140 assert!(!edge.is_call());
1141 assert!(!edge.is_structural());
1142 assert_eq!(edge.tag(), "lifetime_constraint");
1143 }
1144 }
1145
1146 #[test]
1147 fn test_macro_expansion_kind_variants() {
1148 let kinds = [
1149 MacroExpansionKind::Derive,
1150 MacroExpansionKind::Attribute,
1151 MacroExpansionKind::Declarative,
1152 MacroExpansionKind::Function,
1153 ];
1154
1155 for expansion_kind in kinds {
1156 let edge = EdgeKind::MacroExpansion {
1157 expansion_kind,
1158 is_verified: true,
1159 };
1160 assert!(edge.is_rust_specific());
1161 assert!(edge.is_macro_expansion());
1162 assert!(!edge.is_call());
1163 assert_eq!(edge.tag(), "macro_expansion");
1164 }
1165 }
1166
1167 #[test]
1168 fn test_trait_method_binding() {
1169 let edge = EdgeKind::TraitMethodBinding {
1170 trait_name: StringId::new(1),
1171 impl_type: StringId::new(2),
1172 is_ambiguous: false,
1173 };
1174
1175 assert!(edge.is_rust_specific());
1176 assert!(edge.is_trait_method_binding());
1177 assert!(!edge.is_call());
1178 assert_eq!(edge.tag(), "trait_method_binding");
1179
1180 let ambiguous = EdgeKind::TraitMethodBinding {
1182 trait_name: StringId::new(1),
1183 impl_type: StringId::new(2),
1184 is_ambiguous: true,
1185 };
1186 assert!(ambiguous.is_trait_method_binding());
1187 }
1188
1189 #[test]
1190 fn test_rust_specific_edges_serde() {
1191 let lifetime = EdgeKind::LifetimeConstraint {
1193 constraint_kind: LifetimeConstraintKind::HigherRanked,
1194 };
1195 let json = serde_json::to_string(&lifetime).unwrap();
1196 let deserialized: EdgeKind = serde_json::from_str(&json).unwrap();
1197 assert_eq!(lifetime, deserialized);
1198
1199 let binding = EdgeKind::TraitMethodBinding {
1201 trait_name: StringId::new(10),
1202 impl_type: StringId::new(20),
1203 is_ambiguous: true,
1204 };
1205 let json = serde_json::to_string(&binding).unwrap();
1206 let deserialized: EdgeKind = serde_json::from_str(&json).unwrap();
1207 assert_eq!(binding, deserialized);
1208
1209 let expansion = EdgeKind::MacroExpansion {
1211 expansion_kind: MacroExpansionKind::Derive,
1212 is_verified: false,
1213 };
1214 let json = serde_json::to_string(&expansion).unwrap();
1215 let deserialized: EdgeKind = serde_json::from_str(&json).unwrap();
1216 assert_eq!(expansion, deserialized);
1217 }
1218
1219 #[test]
1220 fn test_rust_specific_edges_postcard() {
1221 let edges = [
1222 EdgeKind::LifetimeConstraint {
1223 constraint_kind: LifetimeConstraintKind::Outlives,
1224 },
1225 EdgeKind::TraitMethodBinding {
1226 trait_name: StringId::new(5),
1227 impl_type: StringId::new(6),
1228 is_ambiguous: false,
1229 },
1230 EdgeKind::MacroExpansion {
1231 expansion_kind: MacroExpansionKind::Attribute,
1232 is_verified: true,
1233 },
1234 ];
1235
1236 for edge in edges {
1237 let bytes = postcard::to_allocvec(&edge).unwrap();
1238 let deserialized: EdgeKind = postcard::from_bytes(&bytes).unwrap();
1239 assert_eq!(edge, deserialized);
1240 }
1241 }
1242
1243 #[test]
1244 fn test_lifetime_constraint_kind_defaults() {
1245 assert_eq!(
1246 LifetimeConstraintKind::default(),
1247 LifetimeConstraintKind::Outlives
1248 );
1249 assert_eq!(
1250 MacroExpansionKind::default(),
1251 MacroExpansionKind::Declarative
1252 );
1253 }
1254}