use super::edge::kind::{EdgeKind, ExportKind};
use super::materialize::MaterializedNode;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum EdgeClassification {
Call {
is_async: bool,
is_cross_boundary: bool,
},
Import {
is_wildcard: bool,
},
Export {
is_reexport: bool,
},
Reference,
Inherits,
Implements,
Contains,
Defines,
TypeOf,
DatabaseAccess,
ServiceInteraction,
}
impl From<&EdgeKind> for EdgeClassification {
#[allow(clippy::match_same_arms)]
fn from(kind: &EdgeKind) -> Self {
match kind {
EdgeKind::Calls { is_async, .. } => Self::Call {
is_async: *is_async,
is_cross_boundary: false,
},
EdgeKind::TraitMethodBinding { .. } => Self::Call {
is_async: false,
is_cross_boundary: false,
},
EdgeKind::FfiCall { .. }
| EdgeKind::HttpRequest { .. }
| EdgeKind::GrpcCall { .. }
| EdgeKind::WebAssemblyCall => Self::Call {
is_async: false,
is_cross_boundary: true,
},
EdgeKind::Imports { is_wildcard, .. } => Self::Import {
is_wildcard: *is_wildcard,
},
EdgeKind::Exports { kind, .. } => Self::Export {
is_reexport: matches!(kind, ExportKind::Reexport),
},
EdgeKind::References => Self::Reference,
EdgeKind::Inherits | EdgeKind::SealedPermit => Self::Inherits,
EdgeKind::Implements => Self::Implements,
EdgeKind::Contains | EdgeKind::CompanionOf => Self::Contains,
EdgeKind::Defines => Self::Defines,
EdgeKind::TypeOf { .. } => Self::TypeOf,
EdgeKind::DbQuery { .. }
| EdgeKind::TableRead { .. }
| EdgeKind::TableWrite { .. }
| EdgeKind::TriggeredBy { .. } => Self::DatabaseAccess,
EdgeKind::MessageQueue { .. }
| EdgeKind::WebSocket { .. }
| EdgeKind::GraphQLOperation { .. }
| EdgeKind::ProcessExec { .. }
| EdgeKind::FileIpc { .. }
| EdgeKind::ProtocolCall { .. } => Self::ServiceInteraction,
EdgeKind::GenericBound | EdgeKind::TypeArgument => Self::TypeOf,
EdgeKind::AnnotatedWith | EdgeKind::AnnotationParam => Self::Reference,
EdgeKind::LambdaCaptures | EdgeKind::ExtensionReceiver => Self::Reference,
EdgeKind::ModuleExports | EdgeKind::ModuleOpens => Self::Export { is_reexport: false },
EdgeKind::ModuleRequires | EdgeKind::ModuleProvides => {
Self::Import { is_wildcard: false }
}
EdgeKind::MacroExpansion { .. } => Self::Reference,
EdgeKind::LifetimeConstraint { .. } => Self::Reference,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MaterializedEdge {
pub source_idx: usize,
pub target_idx: usize,
pub classification: EdgeClassification,
pub raw_kind: EdgeKind,
pub depth: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TruncationReason {
DepthLimit,
NodeLimit,
EdgeLimit,
PathLimit,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TraversalMetadata {
pub truncation: Option<TruncationReason>,
pub max_depth_reached: bool,
pub seed_count: usize,
pub nodes_visited: usize,
pub total_nodes: usize,
pub total_edges: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TraversalResult {
pub nodes: Vec<MaterializedNode>,
pub edges: Vec<MaterializedEdge>,
pub paths: Option<Vec<Vec<usize>>>,
pub metadata: TraversalMetadata,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::graph::unified::edge::kind::{
DbQueryType, ExportKind, FfiConvention, LifetimeConstraintKind, MacroExpansionKind,
};
use crate::graph::unified::string::id::StringId;
fn test_string_id() -> StringId {
StringId::new(1)
}
#[test]
fn calls_async_classification() {
let edge = EdgeKind::Calls {
argument_count: 2,
is_async: true,
};
let classified = EdgeClassification::from(&edge);
assert_eq!(
classified,
EdgeClassification::Call {
is_async: true,
is_cross_boundary: false,
}
);
}
#[test]
fn ffi_call_cross_boundary() {
let edge = EdgeKind::FfiCall {
convention: FfiConvention::C,
};
let classified = EdgeClassification::from(&edge);
assert_eq!(
classified,
EdgeClassification::Call {
is_async: false,
is_cross_boundary: true,
}
);
}
#[test]
fn trait_method_binding_classification() {
let edge = EdgeKind::TraitMethodBinding {
trait_name: test_string_id(),
impl_type: test_string_id(),
is_ambiguous: false,
};
let classified = EdgeClassification::from(&edge);
assert_eq!(
classified,
EdgeClassification::Call {
is_async: false,
is_cross_boundary: false,
}
);
}
#[test]
fn exports_reexport_classification() {
let edge = EdgeKind::Exports {
kind: ExportKind::Reexport,
alias: None,
};
let classified = EdgeClassification::from(&edge);
assert_eq!(classified, EdgeClassification::Export { is_reexport: true });
}
#[test]
fn exports_direct_classification() {
let edge = EdgeKind::Exports {
kind: ExportKind::Direct,
alias: None,
};
let classified = EdgeClassification::from(&edge);
assert_eq!(
classified,
EdgeClassification::Export { is_reexport: false }
);
}
#[test]
fn sealed_permit_inherits() {
let edge = EdgeKind::SealedPermit;
let classified = EdgeClassification::from(&edge);
assert_eq!(classified, EdgeClassification::Inherits);
}
#[test]
fn companion_of_contains() {
let edge = EdgeKind::CompanionOf;
let classified = EdgeClassification::from(&edge);
assert_eq!(classified, EdgeClassification::Contains);
}
#[test]
fn generic_bound_type_of() {
let edge = EdgeKind::GenericBound;
let classified = EdgeClassification::from(&edge);
assert_eq!(classified, EdgeClassification::TypeOf);
}
#[test]
fn module_exports_export() {
let edge = EdgeKind::ModuleExports;
let classified = EdgeClassification::from(&edge);
assert_eq!(
classified,
EdgeClassification::Export { is_reexport: false }
);
}
#[test]
fn http_request_cross_boundary() {
let edge = EdgeKind::HttpRequest {
method: crate::graph::unified::edge::kind::HttpMethod::Get,
url: None,
};
let classified = EdgeClassification::from(&edge);
assert_eq!(
classified,
EdgeClassification::Call {
is_async: false,
is_cross_boundary: true,
}
);
}
#[test]
fn db_query_database_access() {
let edge = EdgeKind::DbQuery {
query_type: DbQueryType::Select,
table: None,
};
let classified = EdgeClassification::from(&edge);
assert_eq!(classified, EdgeClassification::DatabaseAccess);
}
#[test]
fn macro_expansion_reference() {
let edge = EdgeKind::MacroExpansion {
expansion_kind: MacroExpansionKind::Derive,
is_verified: true,
};
let classified = EdgeClassification::from(&edge);
assert_eq!(classified, EdgeClassification::Reference);
}
#[test]
fn lifetime_constraint_reference() {
let edge = EdgeKind::LifetimeConstraint {
constraint_kind: LifetimeConstraintKind::Outlives,
};
let classified = EdgeClassification::from(&edge);
assert_eq!(classified, EdgeClassification::Reference);
}
#[test]
fn imports_wildcard() {
let edge = EdgeKind::Imports {
alias: None,
is_wildcard: true,
};
let classified = EdgeClassification::from(&edge);
assert_eq!(classified, EdgeClassification::Import { is_wildcard: true });
}
#[test]
fn inherits_classification() {
let edge = EdgeKind::Inherits;
let classified = EdgeClassification::from(&edge);
assert_eq!(classified, EdgeClassification::Inherits);
}
#[test]
fn implements_classification() {
let edge = EdgeKind::Implements;
let classified = EdgeClassification::from(&edge);
assert_eq!(classified, EdgeClassification::Implements);
}
#[test]
fn references_classification() {
let edge = EdgeKind::References;
let classified = EdgeClassification::from(&edge);
assert_eq!(classified, EdgeClassification::Reference);
}
#[test]
fn defines_classification() {
let edge = EdgeKind::Defines;
let classified = EdgeClassification::from(&edge);
assert_eq!(classified, EdgeClassification::Defines);
}
#[test]
fn contains_classification() {
let edge = EdgeKind::Contains;
let classified = EdgeClassification::from(&edge);
assert_eq!(classified, EdgeClassification::Contains);
}
#[test]
fn type_of_classification() {
let edge = EdgeKind::TypeOf {
context: None,
index: None,
name: None,
};
let classified = EdgeClassification::from(&edge);
assert_eq!(classified, EdgeClassification::TypeOf);
}
#[test]
fn message_queue_service_interaction() {
let edge = EdgeKind::MessageQueue {
protocol: crate::graph::unified::edge::kind::MqProtocol::Kafka,
topic: None,
};
let classified = EdgeClassification::from(&edge);
assert_eq!(classified, EdgeClassification::ServiceInteraction);
}
#[test]
fn websocket_service_interaction() {
let edge = EdgeKind::WebSocket { event: None };
let classified = EdgeClassification::from(&edge);
assert_eq!(classified, EdgeClassification::ServiceInteraction);
}
#[test]
fn grpc_call_cross_boundary() {
let edge = EdgeKind::GrpcCall {
service: test_string_id(),
method: test_string_id(),
};
let classified = EdgeClassification::from(&edge);
assert_eq!(
classified,
EdgeClassification::Call {
is_async: false,
is_cross_boundary: true,
}
);
}
#[test]
fn web_assembly_call_cross_boundary() {
let edge = EdgeKind::WebAssemblyCall;
let classified = EdgeClassification::from(&edge);
assert_eq!(
classified,
EdgeClassification::Call {
is_async: false,
is_cross_boundary: true,
}
);
}
#[test]
fn table_read_database_access() {
let edge = EdgeKind::TableRead {
table_name: test_string_id(),
schema: None,
};
let classified = EdgeClassification::from(&edge);
assert_eq!(classified, EdgeClassification::DatabaseAccess);
}
#[test]
fn table_write_database_access() {
let edge = EdgeKind::TableWrite {
table_name: test_string_id(),
schema: None,
operation: crate::graph::unified::edge::kind::TableWriteOp::Insert,
};
let classified = EdgeClassification::from(&edge);
assert_eq!(classified, EdgeClassification::DatabaseAccess);
}
#[test]
fn triggered_by_database_access() {
let edge = EdgeKind::TriggeredBy {
trigger_name: test_string_id(),
schema: None,
};
let classified = EdgeClassification::from(&edge);
assert_eq!(classified, EdgeClassification::DatabaseAccess);
}
#[test]
fn graphql_operation_service_interaction() {
let edge = EdgeKind::GraphQLOperation {
operation: test_string_id(),
};
let classified = EdgeClassification::from(&edge);
assert_eq!(classified, EdgeClassification::ServiceInteraction);
}
#[test]
fn process_exec_service_interaction() {
let edge = EdgeKind::ProcessExec {
command: test_string_id(),
};
let classified = EdgeClassification::from(&edge);
assert_eq!(classified, EdgeClassification::ServiceInteraction);
}
#[test]
fn file_ipc_service_interaction() {
let edge = EdgeKind::FileIpc { path_pattern: None };
let classified = EdgeClassification::from(&edge);
assert_eq!(classified, EdgeClassification::ServiceInteraction);
}
#[test]
fn protocol_call_service_interaction() {
let edge = EdgeKind::ProtocolCall {
protocol: test_string_id(),
metadata: None,
};
let classified = EdgeClassification::from(&edge);
assert_eq!(classified, EdgeClassification::ServiceInteraction);
}
#[test]
fn annotated_with_reference() {
let edge = EdgeKind::AnnotatedWith;
let classified = EdgeClassification::from(&edge);
assert_eq!(classified, EdgeClassification::Reference);
}
#[test]
fn annotation_param_reference() {
let edge = EdgeKind::AnnotationParam;
let classified = EdgeClassification::from(&edge);
assert_eq!(classified, EdgeClassification::Reference);
}
#[test]
fn lambda_captures_reference() {
let edge = EdgeKind::LambdaCaptures;
let classified = EdgeClassification::from(&edge);
assert_eq!(classified, EdgeClassification::Reference);
}
#[test]
fn extension_receiver_reference() {
let edge = EdgeKind::ExtensionReceiver;
let classified = EdgeClassification::from(&edge);
assert_eq!(classified, EdgeClassification::Reference);
}
#[test]
fn module_opens_export() {
let edge = EdgeKind::ModuleOpens;
let classified = EdgeClassification::from(&edge);
assert_eq!(
classified,
EdgeClassification::Export { is_reexport: false }
);
}
#[test]
fn module_requires_import() {
let edge = EdgeKind::ModuleRequires;
let classified = EdgeClassification::from(&edge);
assert_eq!(
classified,
EdgeClassification::Import { is_wildcard: false }
);
}
#[test]
fn module_provides_import() {
let edge = EdgeKind::ModuleProvides;
let classified = EdgeClassification::from(&edge);
assert_eq!(
classified,
EdgeClassification::Import { is_wildcard: false }
);
}
#[test]
fn type_argument_type_of() {
let edge = EdgeKind::TypeArgument;
let classified = EdgeClassification::from(&edge);
assert_eq!(classified, EdgeClassification::TypeOf);
}
#[test]
fn calls_sync_classification() {
let edge = EdgeKind::Calls {
argument_count: 0,
is_async: false,
};
let classified = EdgeClassification::from(&edge);
assert_eq!(
classified,
EdgeClassification::Call {
is_async: false,
is_cross_boundary: false,
}
);
}
}