1#[cfg(test)]
8use super::edge::kind::ResolvedVia;
9use super::edge::kind::{EdgeKind, ExportKind};
10use super::materialize::MaterializedNode;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
19pub enum EdgeClassification {
20 Call {
22 is_async: bool,
24 is_cross_boundary: bool,
26 },
27 Import {
29 is_wildcard: bool,
31 },
32 Export {
34 is_reexport: bool,
36 },
37 Reference,
39 Inherits,
41 Implements,
43 Contains,
45 Defines,
47 TypeOf,
49 DatabaseAccess,
51 ServiceInteraction,
53}
54
55impl From<&EdgeKind> for EdgeClassification {
56 #[allow(clippy::match_same_arms)]
59 fn from(kind: &EdgeKind) -> Self {
60 match kind {
61 EdgeKind::Calls { is_async, .. } => Self::Call {
63 is_async: *is_async,
64 is_cross_boundary: false,
65 },
66 EdgeKind::TraitMethodBinding { .. } => Self::Call {
67 is_async: false,
68 is_cross_boundary: false,
69 },
70 EdgeKind::FfiCall { .. }
71 | EdgeKind::HttpRequest { .. }
72 | EdgeKind::GrpcCall { .. }
73 | EdgeKind::WebAssemblyCall => Self::Call {
74 is_async: false,
75 is_cross_boundary: true,
76 },
77
78 EdgeKind::Imports { is_wildcard, .. } => Self::Import {
80 is_wildcard: *is_wildcard,
81 },
82 EdgeKind::Exports { kind, .. } => Self::Export {
83 is_reexport: matches!(kind, ExportKind::Reexport),
84 },
85
86 EdgeKind::References => Self::Reference,
88
89 EdgeKind::Inherits | EdgeKind::SealedPermit => Self::Inherits,
91 EdgeKind::Implements => Self::Implements,
92
93 EdgeKind::Contains | EdgeKind::CompanionOf => Self::Contains,
95 EdgeKind::Defines => Self::Defines,
96
97 EdgeKind::TypeOf { .. } => Self::TypeOf,
99
100 EdgeKind::DbQuery { .. }
102 | EdgeKind::TableRead { .. }
103 | EdgeKind::TableWrite { .. }
104 | EdgeKind::TriggeredBy { .. } => Self::DatabaseAccess,
105
106 EdgeKind::MessageQueue { .. }
108 | EdgeKind::WebSocket { .. }
109 | EdgeKind::GraphQLOperation { .. }
110 | EdgeKind::ProcessExec { .. }
111 | EdgeKind::FileIpc { .. }
112 | EdgeKind::ProtocolCall { .. } => Self::ServiceInteraction,
113
114 EdgeKind::GenericBound | EdgeKind::TypeArgument => Self::TypeOf,
116 EdgeKind::AnnotatedWith | EdgeKind::AnnotationParam => Self::Reference,
117 EdgeKind::LambdaCaptures | EdgeKind::ExtensionReceiver => Self::Reference,
118 EdgeKind::ModuleExports | EdgeKind::ModuleOpens => Self::Export { is_reexport: false },
119 EdgeKind::ModuleRequires | EdgeKind::ModuleProvides => {
120 Self::Import { is_wildcard: false }
121 }
122
123 EdgeKind::MacroExpansion { .. } => Self::Reference,
125 EdgeKind::LifetimeConstraint { .. } => Self::Reference,
126
127 EdgeKind::Wraps { .. } => Self::Reference,
136
137 EdgeKind::ChannelPeer { .. } | EdgeKind::Instantiates { .. } => Self::Reference,
145 }
146 }
147}
148
149#[derive(Debug, Clone, PartialEq, Eq)]
154pub struct MaterializedEdge {
155 pub source_idx: usize,
157 pub target_idx: usize,
159 pub classification: EdgeClassification,
161 pub raw_kind: EdgeKind,
163 pub depth: u32,
165}
166
167#[derive(Debug, Clone, Copy, PartialEq, Eq)]
169pub enum TruncationReason {
170 DepthLimit,
172 NodeLimit,
174 EdgeLimit,
176 PathLimit,
178}
179
180#[derive(Debug, Clone, PartialEq, Eq)]
182pub struct TraversalMetadata {
183 pub truncation: Option<TruncationReason>,
185 pub max_depth_reached: bool,
187 pub seed_count: usize,
189 pub nodes_visited: usize,
191 pub total_nodes: usize,
193 pub total_edges: usize,
195}
196
197#[derive(Debug, Clone, PartialEq, Eq)]
208pub struct TraversalResult {
209 pub nodes: Vec<MaterializedNode>,
211 pub edges: Vec<MaterializedEdge>,
213 pub paths: Option<Vec<Vec<usize>>>,
216 pub metadata: TraversalMetadata,
218}
219
220#[cfg(test)]
221mod tests {
222 use super::*;
223 use crate::graph::unified::edge::kind::{
224 DbQueryType, ExportKind, FfiConvention, LifetimeConstraintKind, MacroExpansionKind,
225 };
226 use crate::graph::unified::string::id::StringId;
227
228 fn test_string_id() -> StringId {
230 StringId::new(1)
231 }
232
233 #[test]
234 fn calls_async_classification() {
235 let edge = EdgeKind::Calls {
236 argument_count: 2,
237 is_async: true,
238 resolved_via: ResolvedVia::Direct,
239 };
240 let classified = EdgeClassification::from(&edge);
241 assert_eq!(
242 classified,
243 EdgeClassification::Call {
244 is_async: true,
245 is_cross_boundary: false,
246 }
247 );
248 }
249
250 #[test]
251 fn ffi_call_cross_boundary() {
252 let edge = EdgeKind::FfiCall {
253 convention: FfiConvention::C,
254 };
255 let classified = EdgeClassification::from(&edge);
256 assert_eq!(
257 classified,
258 EdgeClassification::Call {
259 is_async: false,
260 is_cross_boundary: true,
261 }
262 );
263 }
264
265 #[test]
266 fn trait_method_binding_classification() {
267 let edge = EdgeKind::TraitMethodBinding {
268 trait_name: test_string_id(),
269 impl_type: test_string_id(),
270 is_ambiguous: false,
271 };
272 let classified = EdgeClassification::from(&edge);
273 assert_eq!(
274 classified,
275 EdgeClassification::Call {
276 is_async: false,
277 is_cross_boundary: false,
278 }
279 );
280 }
281
282 #[test]
283 fn exports_reexport_classification() {
284 let edge = EdgeKind::Exports {
285 kind: ExportKind::Reexport,
286 alias: None,
287 };
288 let classified = EdgeClassification::from(&edge);
289 assert_eq!(classified, EdgeClassification::Export { is_reexport: true });
290 }
291
292 #[test]
293 fn exports_direct_classification() {
294 let edge = EdgeKind::Exports {
295 kind: ExportKind::Direct,
296 alias: None,
297 };
298 let classified = EdgeClassification::from(&edge);
299 assert_eq!(
300 classified,
301 EdgeClassification::Export { is_reexport: false }
302 );
303 }
304
305 #[test]
306 fn sealed_permit_inherits() {
307 let edge = EdgeKind::SealedPermit;
308 let classified = EdgeClassification::from(&edge);
309 assert_eq!(classified, EdgeClassification::Inherits);
310 }
311
312 #[test]
313 fn companion_of_contains() {
314 let edge = EdgeKind::CompanionOf;
315 let classified = EdgeClassification::from(&edge);
316 assert_eq!(classified, EdgeClassification::Contains);
317 }
318
319 #[test]
320 fn generic_bound_type_of() {
321 let edge = EdgeKind::GenericBound;
322 let classified = EdgeClassification::from(&edge);
323 assert_eq!(classified, EdgeClassification::TypeOf);
324 }
325
326 #[test]
327 fn module_exports_export() {
328 let edge = EdgeKind::ModuleExports;
329 let classified = EdgeClassification::from(&edge);
330 assert_eq!(
331 classified,
332 EdgeClassification::Export { is_reexport: false }
333 );
334 }
335
336 #[test]
337 fn http_request_cross_boundary() {
338 let edge = EdgeKind::HttpRequest {
339 method: crate::graph::unified::edge::kind::HttpMethod::Get,
340 url: None,
341 };
342 let classified = EdgeClassification::from(&edge);
343 assert_eq!(
344 classified,
345 EdgeClassification::Call {
346 is_async: false,
347 is_cross_boundary: true,
348 }
349 );
350 }
351
352 #[test]
353 fn db_query_database_access() {
354 let edge = EdgeKind::DbQuery {
355 query_type: DbQueryType::Select,
356 table: None,
357 };
358 let classified = EdgeClassification::from(&edge);
359 assert_eq!(classified, EdgeClassification::DatabaseAccess);
360 }
361
362 #[test]
363 fn macro_expansion_reference() {
364 let edge = EdgeKind::MacroExpansion {
365 expansion_kind: MacroExpansionKind::Derive,
366 is_verified: true,
367 };
368 let classified = EdgeClassification::from(&edge);
369 assert_eq!(classified, EdgeClassification::Reference);
370 }
371
372 #[test]
373 fn lifetime_constraint_reference() {
374 let edge = EdgeKind::LifetimeConstraint {
375 constraint_kind: LifetimeConstraintKind::Outlives,
376 };
377 let classified = EdgeClassification::from(&edge);
378 assert_eq!(classified, EdgeClassification::Reference);
379 }
380
381 #[test]
382 fn imports_wildcard() {
383 let edge = EdgeKind::Imports {
384 alias: None,
385 is_wildcard: true,
386 };
387 let classified = EdgeClassification::from(&edge);
388 assert_eq!(classified, EdgeClassification::Import { is_wildcard: true });
389 }
390
391 #[test]
392 fn inherits_classification() {
393 let edge = EdgeKind::Inherits;
394 let classified = EdgeClassification::from(&edge);
395 assert_eq!(classified, EdgeClassification::Inherits);
396 }
397
398 #[test]
399 fn implements_classification() {
400 let edge = EdgeKind::Implements;
401 let classified = EdgeClassification::from(&edge);
402 assert_eq!(classified, EdgeClassification::Implements);
403 }
404
405 #[test]
406 fn references_classification() {
407 let edge = EdgeKind::References;
408 let classified = EdgeClassification::from(&edge);
409 assert_eq!(classified, EdgeClassification::Reference);
410 }
411
412 #[test]
413 fn defines_classification() {
414 let edge = EdgeKind::Defines;
415 let classified = EdgeClassification::from(&edge);
416 assert_eq!(classified, EdgeClassification::Defines);
417 }
418
419 #[test]
420 fn contains_classification() {
421 let edge = EdgeKind::Contains;
422 let classified = EdgeClassification::from(&edge);
423 assert_eq!(classified, EdgeClassification::Contains);
424 }
425
426 #[test]
427 fn type_of_classification() {
428 let edge = EdgeKind::TypeOf {
429 context: None,
430 index: None,
431 name: None,
432 };
433 let classified = EdgeClassification::from(&edge);
434 assert_eq!(classified, EdgeClassification::TypeOf);
435 }
436
437 #[test]
438 fn message_queue_service_interaction() {
439 let edge = EdgeKind::MessageQueue {
440 protocol: crate::graph::unified::edge::kind::MqProtocol::Kafka,
441 topic: None,
442 };
443 let classified = EdgeClassification::from(&edge);
444 assert_eq!(classified, EdgeClassification::ServiceInteraction);
445 }
446
447 #[test]
448 fn websocket_service_interaction() {
449 let edge = EdgeKind::WebSocket { event: None };
450 let classified = EdgeClassification::from(&edge);
451 assert_eq!(classified, EdgeClassification::ServiceInteraction);
452 }
453
454 #[test]
455 fn grpc_call_cross_boundary() {
456 let edge = EdgeKind::GrpcCall {
457 service: test_string_id(),
458 method: test_string_id(),
459 };
460 let classified = EdgeClassification::from(&edge);
461 assert_eq!(
462 classified,
463 EdgeClassification::Call {
464 is_async: false,
465 is_cross_boundary: true,
466 }
467 );
468 }
469
470 #[test]
471 fn web_assembly_call_cross_boundary() {
472 let edge = EdgeKind::WebAssemblyCall;
473 let classified = EdgeClassification::from(&edge);
474 assert_eq!(
475 classified,
476 EdgeClassification::Call {
477 is_async: false,
478 is_cross_boundary: true,
479 }
480 );
481 }
482
483 #[test]
484 fn table_read_database_access() {
485 let edge = EdgeKind::TableRead {
486 table_name: test_string_id(),
487 schema: None,
488 };
489 let classified = EdgeClassification::from(&edge);
490 assert_eq!(classified, EdgeClassification::DatabaseAccess);
491 }
492
493 #[test]
494 fn table_write_database_access() {
495 let edge = EdgeKind::TableWrite {
496 table_name: test_string_id(),
497 schema: None,
498 operation: crate::graph::unified::edge::kind::TableWriteOp::Insert,
499 };
500 let classified = EdgeClassification::from(&edge);
501 assert_eq!(classified, EdgeClassification::DatabaseAccess);
502 }
503
504 #[test]
505 fn triggered_by_database_access() {
506 let edge = EdgeKind::TriggeredBy {
507 trigger_name: test_string_id(),
508 schema: None,
509 };
510 let classified = EdgeClassification::from(&edge);
511 assert_eq!(classified, EdgeClassification::DatabaseAccess);
512 }
513
514 #[test]
515 fn graphql_operation_service_interaction() {
516 let edge = EdgeKind::GraphQLOperation {
517 operation: test_string_id(),
518 };
519 let classified = EdgeClassification::from(&edge);
520 assert_eq!(classified, EdgeClassification::ServiceInteraction);
521 }
522
523 #[test]
524 fn process_exec_service_interaction() {
525 let edge = EdgeKind::ProcessExec {
526 command: test_string_id(),
527 };
528 let classified = EdgeClassification::from(&edge);
529 assert_eq!(classified, EdgeClassification::ServiceInteraction);
530 }
531
532 #[test]
533 fn file_ipc_service_interaction() {
534 let edge = EdgeKind::FileIpc { path_pattern: None };
535 let classified = EdgeClassification::from(&edge);
536 assert_eq!(classified, EdgeClassification::ServiceInteraction);
537 }
538
539 #[test]
540 fn protocol_call_service_interaction() {
541 let edge = EdgeKind::ProtocolCall {
542 protocol: test_string_id(),
543 metadata: None,
544 };
545 let classified = EdgeClassification::from(&edge);
546 assert_eq!(classified, EdgeClassification::ServiceInteraction);
547 }
548
549 #[test]
550 fn annotated_with_reference() {
551 let edge = EdgeKind::AnnotatedWith;
552 let classified = EdgeClassification::from(&edge);
553 assert_eq!(classified, EdgeClassification::Reference);
554 }
555
556 #[test]
557 fn annotation_param_reference() {
558 let edge = EdgeKind::AnnotationParam;
559 let classified = EdgeClassification::from(&edge);
560 assert_eq!(classified, EdgeClassification::Reference);
561 }
562
563 #[test]
564 fn lambda_captures_reference() {
565 let edge = EdgeKind::LambdaCaptures;
566 let classified = EdgeClassification::from(&edge);
567 assert_eq!(classified, EdgeClassification::Reference);
568 }
569
570 #[test]
571 fn extension_receiver_reference() {
572 let edge = EdgeKind::ExtensionReceiver;
573 let classified = EdgeClassification::from(&edge);
574 assert_eq!(classified, EdgeClassification::Reference);
575 }
576
577 #[test]
578 fn module_opens_export() {
579 let edge = EdgeKind::ModuleOpens;
580 let classified = EdgeClassification::from(&edge);
581 assert_eq!(
582 classified,
583 EdgeClassification::Export { is_reexport: false }
584 );
585 }
586
587 #[test]
588 fn module_requires_import() {
589 let edge = EdgeKind::ModuleRequires;
590 let classified = EdgeClassification::from(&edge);
591 assert_eq!(
592 classified,
593 EdgeClassification::Import { is_wildcard: false }
594 );
595 }
596
597 #[test]
598 fn module_provides_import() {
599 let edge = EdgeKind::ModuleProvides;
600 let classified = EdgeClassification::from(&edge);
601 assert_eq!(
602 classified,
603 EdgeClassification::Import { is_wildcard: false }
604 );
605 }
606
607 #[test]
608 fn type_argument_type_of() {
609 let edge = EdgeKind::TypeArgument;
610 let classified = EdgeClassification::from(&edge);
611 assert_eq!(classified, EdgeClassification::TypeOf);
612 }
613
614 #[test]
615 fn calls_sync_classification() {
616 let edge = EdgeKind::Calls {
617 argument_count: 0,
618 is_async: false,
619 resolved_via: ResolvedVia::Direct,
620 };
621 let classified = EdgeClassification::from(&edge);
622 assert_eq!(
623 classified,
624 EdgeClassification::Call {
625 is_async: false,
626 is_cross_boundary: false,
627 }
628 );
629 }
630}