pub mod graphml;
pub mod json;
pub mod memory;
pub mod sqlite;
use crate::{schema::EdgeType, Edge, Node};
pub(super) fn parse_node_type(s: &str) -> Option<crate::schema::NodeType> {
use crate::schema::NodeType;
match s {
"domain" => Some(NodeType::Domain),
"subdomain" => Some(NodeType::Subdomain),
"ip" => Some(NodeType::Ip),
"port" => Some(NodeType::Port),
"service" => Some(NodeType::Service),
"tech" => Some(NodeType::Tech),
"endpoint" => Some(NodeType::Endpoint),
"secret" => Some(NodeType::Secret),
"cloud" => Some(NodeType::Cloud),
"finding" => Some(NodeType::Finding),
_ => None,
}
}
pub(super) fn parse_edge_type(s: &str) -> Option<crate::schema::EdgeType> {
use crate::schema::EdgeType;
match s {
"RESOLVES_TO" => Some(EdgeType::ResolvesTo),
"HOSTS" => Some(EdgeType::Hosts),
"RUNS" => Some(EdgeType::Runs),
"EXPOSES" => Some(EdgeType::Exposes),
"LEAKS" => Some(EdgeType::Leaks),
"MISCONFIGURED" => Some(EdgeType::Misconfigured),
"HAS_FINDING" => Some(EdgeType::HasFinding),
"HAS_SERVICE" => Some(EdgeType::HasService),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::schema::{EdgeType, NodeType};
#[test]
fn parse_node_type_roundtrip_all_variants() {
let cases = [
(NodeType::Domain, "domain"),
(NodeType::Subdomain, "subdomain"),
(NodeType::Ip, "ip"),
(NodeType::Port, "port"),
(NodeType::Service, "service"),
(NodeType::Tech, "tech"),
(NodeType::Endpoint, "endpoint"),
(NodeType::Secret, "secret"),
(NodeType::Cloud, "cloud"),
(NodeType::Finding, "finding"),
];
for (variant, tag) in &cases {
let parsed = parse_node_type(tag)
.unwrap_or_else(|| panic!("parse_node_type({tag:?}) returned None"));
assert_eq!(
parsed, *variant,
"parse_node_type({tag:?}) must return {variant:?}"
);
assert_eq!(
variant.to_string(), *tag,
"NodeType::{variant:?}.to_string() must equal {tag:?}"
);
}
}
#[test]
fn parse_node_type_unknown_returns_none() {
assert!(parse_node_type("").is_none());
assert!(parse_node_type("DOMAIN").is_none()); assert!(parse_node_type("unknown_xyz").is_none());
}
#[test]
fn parse_edge_type_roundtrip_all_variants() {
let cases = [
(EdgeType::ResolvesTo, "RESOLVES_TO"),
(EdgeType::Hosts, "HOSTS"),
(EdgeType::Runs, "RUNS"),
(EdgeType::Exposes, "EXPOSES"),
(EdgeType::Leaks, "LEAKS"),
(EdgeType::Misconfigured, "MISCONFIGURED"),
(EdgeType::HasFinding, "HAS_FINDING"),
(EdgeType::HasService, "HAS_SERVICE"),
];
for (variant, tag) in &cases {
let parsed = parse_edge_type(tag)
.unwrap_or_else(|| panic!("parse_edge_type({tag:?}) returned None"));
assert_eq!(
parsed, *variant,
"parse_edge_type({tag:?}) must return {variant:?}"
);
assert_eq!(
variant.to_string(), *tag,
"EdgeType::{variant:?}.to_string() must equal {tag:?}"
);
}
}
#[test]
fn parse_edge_type_unknown_returns_none() {
assert!(parse_edge_type("").is_none());
assert!(parse_edge_type("resolves_to").is_none()); assert!(parse_edge_type("UNKNOWN_EDGE").is_none());
}
}
pub trait GraphBackend {
type Error: std::error::Error + Send + Sync + 'static;
fn init(&mut self) -> Result<(), Self::Error>;
fn write_nodes(&mut self, nodes: &[Node]) -> Result<(), Self::Error>;
fn write_edges(&mut self, edges: &[Edge]) -> Result<(), Self::Error>;
fn read_nodes(&self) -> Result<Vec<Node>, Self::Error>;
fn read_edges(&self) -> Result<Vec<Edge>, Self::Error>;
fn find_nodes_by_type(&self, kind: crate::schema::NodeType) -> Result<Vec<Node>, Self::Error>;
fn neighbors(
&self,
node_id: &str,
edge_type: Option<EdgeType>,
) -> Result<Vec<Edge>, Self::Error>;
fn clear(&mut self) -> Result<(), Self::Error>;
}