Skip to content

Commit

Permalink
chore: work towards new API
Browse files Browse the repository at this point in the history
  • Loading branch information
ctron committed Jan 29, 2025
1 parent 498d54a commit ae7f455
Show file tree
Hide file tree
Showing 5 changed files with 222 additions and 120 deletions.
92 changes: 86 additions & 6 deletions modules/analysis/src/model/roots.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::model::Node;
use crate::model::{BaseSummary, Node};
use std::collections::HashMap;
use trustify_common::model::PaginatedResults;
use trustify_entity::relationship::Relationship;

pub trait Roots {
/// Collect all top level ancestors.
Expand All @@ -20,12 +21,12 @@ impl Roots for PaginatedResults<Node> {

impl Roots for Vec<Node> {
fn roots(self) -> Vec<Node> {
fn root_into(
fn roots_into(
nodes: impl IntoIterator<Item = Node>,
result: &mut HashMap<(String, String), Node>,
) {
for node in nodes.into_iter() {
root_into(node.ancestor.clone().into_iter().flatten(), result);
roots_into(node.ancestor.clone().into_iter().flatten(), result);

if let Some(true) = node.ancestor.as_ref().map(|a| a.is_empty()) {
result.insert((node.base.sbom_id.clone(), node.base.node_id.clone()), node);
Expand All @@ -34,12 +35,65 @@ impl Roots for Vec<Node> {
}

let mut result = HashMap::new();
root_into(self, &mut result);
roots_into(self, &mut result);

result.into_values().collect()
}
}

pub trait RootTraces {
type Result;

/// Collect all traces to the root nodes
fn root_traces(self) -> Self::Result;
}

impl<'a> RootTraces for &'a PaginatedResults<Node> {
type Result = PaginatedResults<Vec<(&'a BaseSummary, Relationship)>>;

fn root_traces(self) -> Self::Result {
let items = self.items.root_traces();
let total = items.len();
Self::Result {
items,
total: total as _,
}
}
}

impl<'a> RootTraces for &'a Vec<Node> {
type Result = Vec<Vec<(&'a BaseSummary, Relationship)>>;

fn root_traces(self) -> Self::Result {
fn roots_into<'a>(
nodes: impl IntoIterator<Item = &'a Node>,
parents: &Vec<(&'a BaseSummary, Relationship)>,
result: &mut Vec<Vec<(&'a BaseSummary, Relationship)>>,
) {
for node in nodes.into_iter() {
let mut next = parents.clone();

// if we don't have a relationship to the parent node, we are the initial node
// and will be skipped
if let Some(relationship) = node.relationship {
next.push((&node.base, relationship));
};

if let Some(true) = node.ancestor.as_ref().map(|a| a.is_empty()) {
result.push(next);
} else {
roots_into(node.ancestor.iter().flatten(), &next, result);
}
}
}

let mut result = Vec::new();
roots_into(self, &Vec::new(), &mut result);

result
}
}

#[cfg(test)]
mod test {
use super::*;
Expand Down Expand Up @@ -71,7 +125,7 @@ mod test {
}

#[test]
fn simple() {
fn simple_roots() {
let result = vec![Node {
base: base("AA"),
relationship: None,
Expand All @@ -96,7 +150,7 @@ mod test {
}

#[test]
fn nested() {
fn nested_roots() {
let result = vec![Node {
ancestor: Some(vec![Node {
base: base("AA"),
Expand All @@ -122,4 +176,30 @@ mod test {
}]
);
}

#[test]
fn nested_root_traces() {
let result = vec![Node {
ancestor: Some(vec![Node {
base: base("AA"),
relationship: Some(Relationship::DependencyOf),
ancestor: Some(vec![Node {
ancestor: Some(vec![]),
relationship: Some(Relationship::DependencyOf),
..node("A")
}]),
descendent: None,
}]),
..node("AAA")
}];
let result = result.root_traces();

assert_eq!(
result,
vec![vec![
(&base("AA"), Relationship::DependencyOf),
(&base("A"), Relationship::DependencyOf),
]]
);
}
}
57 changes: 33 additions & 24 deletions modules/analysis/src/service/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pub use walk::*;
mod test;

use crate::{
model::{AnalysisStatus, BaseSummary, GraphMap, Node, PackageNode, Roots},
model::{AnalysisStatus, BaseSummary, GraphMap, Node, PackageNode},
Error,
};
use fixedbitset::FixedBitSet;
Expand Down Expand Up @@ -53,7 +53,7 @@ fn collect(
depth: u64,
discovered: &mut FixedBitSet,
) -> Option<Vec<Node>> {
log::debug!("Direction: {direction:?}");
tracing::debug!(direction = ?direction, "collecting for {node:?}");

if depth == 0 {
log::debug!("depth is zero");
Expand All @@ -70,18 +70,25 @@ fn collect(
let mut result = Vec::new();

for edge in graph.edges_directed(node, direction) {
let relationship = edge.weight();
let neighbor = edge.target();
log::debug!("edge {edge:?}");

let Some(package_node) = graph.node_weight(neighbor) else {
continue;
// we only recurse in one direction
let (ancestor, descendent, package_node) = match direction {
Direction::Incoming => (
collect(graph, edge.source(), direction, depth - 1, discovered),
None,
graph.node_weight(edge.source()),
),
Direction::Outgoing => (
None,
collect(graph, edge.target(), direction, depth - 1, discovered),
graph.node_weight(edge.target()),
),
};

let related = collect(graph, neighbor, direction, depth - 1, discovered);
// we only recurse in one direction
let (ancestor, descendent) = match direction {
Direction::Incoming => (related, None),
Direction::Outgoing => (None, related),
let relationship = edge.weight();
let Some(package_node) = package_node else {
continue;
};

result.push(Node {
Expand Down Expand Up @@ -210,11 +217,13 @@ impl AnalysisService {
F: FnMut(&Graph<PackageNode, Relationship>, NodeIndex, &PackageNode, &mut FixedBitSet),
{
let Some(graph) = graph.get(sbom_id) else {
// FIXME: we need a better strategy handling such errors
log::warn!("Unable to find SBOM: {sbom_id}");
return;
};

if is_cyclic_directed(graph) {
// FIXME: we need a better strategy handling such errors
log::warn!(
"analysis graph of sbom {} has circular references!",
sbom_id
Expand All @@ -230,7 +239,6 @@ impl AnalysisService {
.node_indices()
.filter(|&i| Self::filter(graph, &query, i))
.for_each(|node_index| {
log::debug!("matched!");
if visited.insert(node_index) {
if let Some(find_match_package_node) = graph.node_weight(node_index) {
f(graph, node_index, find_match_package_node, &mut discovered);
Expand Down Expand Up @@ -278,24 +286,25 @@ impl AnalysisService {
)
}

/// This function searches for a component(s) by name in a specific sbom, then returns that
/// component's root components.
pub async fn retrieve_all_sbom_roots_by_name<C: ConnectionTrait>(
/// locate components, retrieve dependency information, from a single SBOM
#[instrument(skip(self, connection), err)]
pub async fn retrieve_single<C: ConnectionTrait>(
&self,
sbom_id: Uuid,
component_name: String,
query: impl Into<GraphQuery<'_>> + Debug,
options: impl Into<QueryOptions> + Debug,
paginated: Paginated,
connection: &C,
) -> Result<Vec<Node>, Error> {
) -> Result<PaginatedResults<Node>, Error> {
let distinct_sbom_ids = vec![sbom_id.to_string()];
self.load_graphs(connection, &distinct_sbom_ids).await?;

let components = self.run_graph_query(
GraphQuery::Component(ComponentReference::Name(&component_name)),
QueryOptions::ancestors(),
distinct_sbom_ids,
);
let query = query.into();
let options = options.into();

Ok(components.roots())
self.load_graphs(connection, &distinct_sbom_ids).await?;
let components = self.run_graph_query(query, options, distinct_sbom_ids);

Ok(paginated.paginate_array(&components))
}

/// locate components, retrieve dependency information
Expand Down
2 changes: 1 addition & 1 deletion modules/analysis/src/service/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ impl AnalysisService {
) {
let _ = writeln!(
self.data,
r#""{source}" -> "{target}"maybe [label="{label}"]"#,
r#""{source}" -> "{target}" [label="{label}"]"#,
source = escape(&source.node_id),
target = escape(&target.node_id),
label = escape(&relationship.to_string())
Expand Down
Loading

0 comments on commit ae7f455

Please sign in to comment.