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