Refactor code structure for improved readability and maintainability; optimize performance in key functions.
This commit is contained in:
@@ -15,6 +15,8 @@ public sealed class InMemoryGraphRepository
|
||||
new() { Id = "gn:acme:component:example", Kind = "component", Tenant = "acme", Attributes = new() { ["purl"] = "pkg:npm/example@1.0.0", ["ecosystem"] = "npm" } },
|
||||
new() { Id = "gn:acme:component:widget", Kind = "component", Tenant = "acme", Attributes = new() { ["purl"] = "pkg:npm/widget@2.0.0", ["ecosystem"] = "npm" } },
|
||||
new() { Id = "gn:acme:artifact:sha256:abc", Kind = "artifact", Tenant = "acme", Attributes = new() { ["digest"] = "sha256:abc", ["ecosystem"] = "container" } },
|
||||
new() { Id = "gn:acme:sbom:sha256:sbom-a", Kind = "sbom", Tenant = "acme", Attributes = new() { ["sbom_digest"] = "sha256:sbom-a", ["artifact_digest"] = "sha256:abc", ["format"] = "cyclonedx" } },
|
||||
new() { Id = "gn:acme:sbom:sha256:sbom-b", Kind = "sbom", Tenant = "acme", Attributes = new() { ["sbom_digest"] = "sha256:sbom-b", ["artifact_digest"] = "sha256:abc", ["format"] = "spdx" } },
|
||||
new() { Id = "gn:acme:component:gamma", Kind = "component", Tenant = "acme", Attributes = new() { ["purl"] = "pkg:nuget/Gamma@3.1.4", ["ecosystem"] = "nuget" } },
|
||||
new() { Id = "gn:bravo:component:widget", Kind = "component", Tenant = "bravo",Attributes = new() { ["purl"] = "pkg:npm/widget@2.0.0", ["ecosystem"] = "npm" } },
|
||||
new() { Id = "gn:bravo:artifact:sha256:def", Kind = "artifact", Tenant = "bravo",Attributes = new() { ["digest"] = "sha256:def", ["ecosystem"] = "container" } },
|
||||
@@ -24,6 +26,8 @@ public sealed class InMemoryGraphRepository
|
||||
{
|
||||
new() { Id = "ge:acme:artifact->component", Kind = "builds", Tenant = "acme", Source = "gn:acme:artifact:sha256:abc", Target = "gn:acme:component:example", Attributes = new() { ["reason"] = "sbom" } },
|
||||
new() { Id = "ge:acme:component->component", Kind = "depends_on", Tenant = "acme", Source = "gn:acme:component:example", Target = "gn:acme:component:widget", Attributes = new() { ["scope"] = "runtime" } },
|
||||
new() { Id = "ge:acme:sbom->artifact", Kind = "SBOM_VERSION_OF", Tenant = "acme", Source = "gn:acme:sbom:sha256:sbom-b", Target = "gn:acme:artifact:sha256:abc", Attributes = new() { ["relationship"] = "version_of" } },
|
||||
new() { Id = "ge:acme:sbom->sbom", Kind = "SBOM_LINEAGE_PARENT", Tenant = "acme", Source = "gn:acme:sbom:sha256:sbom-b", Target = "gn:acme:sbom:sha256:sbom-a", Attributes = new() { ["relationship"] = "parent" } },
|
||||
new() { Id = "ge:bravo:artifact->component", Kind = "builds", Tenant = "bravo", Source = "gn:bravo:artifact:sha256:def", Target = "gn:bravo:component:widget", Attributes = new() { ["reason"] = "sbom" } },
|
||||
};
|
||||
|
||||
@@ -74,6 +78,114 @@ public sealed class InMemoryGraphRepository
|
||||
return (nodes, edges);
|
||||
}
|
||||
|
||||
public (IReadOnlyList<NodeTile> Nodes, IReadOnlyList<EdgeTile> Edges) GetLineage(string tenant, GraphLineageRequest request)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
|
||||
var maxDepth = request.MaxDepth ?? 3;
|
||||
if (maxDepth < 1)
|
||||
{
|
||||
maxDepth = 1;
|
||||
}
|
||||
|
||||
var allowedKinds = BuildLineageKindFilter(request.RelationshipKinds);
|
||||
var tenantNodes = _nodes
|
||||
.Where(n => n.Tenant.Equals(tenant, StringComparison.Ordinal))
|
||||
.ToList();
|
||||
|
||||
if (tenantNodes.Count == 0)
|
||||
{
|
||||
return (Array.Empty<NodeTile>(), Array.Empty<EdgeTile>());
|
||||
}
|
||||
|
||||
var nodeById = tenantNodes.ToDictionary(n => n.Id, StringComparer.Ordinal);
|
||||
var seedIds = new HashSet<string>(StringComparer.Ordinal);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(request.ArtifactDigest))
|
||||
{
|
||||
var digest = request.ArtifactDigest.Trim();
|
||||
foreach (var node in tenantNodes.Where(n => HasAttribute(n, "artifact_digest", digest)))
|
||||
{
|
||||
seedIds.Add(node.Id);
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(request.SbomDigest))
|
||||
{
|
||||
var digest = request.SbomDigest.Trim();
|
||||
foreach (var node in tenantNodes.Where(n => HasAttribute(n, "sbom_digest", digest)))
|
||||
{
|
||||
seedIds.Add(node.Id);
|
||||
}
|
||||
}
|
||||
|
||||
if (seedIds.Count == 0)
|
||||
{
|
||||
return (Array.Empty<NodeTile>(), Array.Empty<EdgeTile>());
|
||||
}
|
||||
|
||||
var tenantEdges = _edges
|
||||
.Where(e => e.Tenant.Equals(tenant, StringComparison.Ordinal))
|
||||
.Where(e => IsLineageEdgeAllowed(e, allowedKinds))
|
||||
.ToList();
|
||||
|
||||
var adjacency = new Dictionary<string, List<EdgeTile>>(StringComparer.Ordinal);
|
||||
foreach (var edge in tenantEdges)
|
||||
{
|
||||
AddAdjacency(adjacency, edge.Source, edge);
|
||||
AddAdjacency(adjacency, edge.Target, edge);
|
||||
}
|
||||
|
||||
var visitedNodes = new HashSet<string>(seedIds, StringComparer.Ordinal);
|
||||
var visitedEdges = new HashSet<string>(StringComparer.Ordinal);
|
||||
var frontier = new HashSet<string>(seedIds, StringComparer.Ordinal);
|
||||
|
||||
for (var depth = 0; depth < maxDepth && frontier.Count > 0; depth++)
|
||||
{
|
||||
var next = new HashSet<string>(StringComparer.Ordinal);
|
||||
foreach (var nodeId in frontier)
|
||||
{
|
||||
if (!adjacency.TryGetValue(nodeId, out var edges))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var edge in edges)
|
||||
{
|
||||
if (visitedEdges.Add(edge.Id))
|
||||
{
|
||||
var other = string.Equals(edge.Source, nodeId, StringComparison.Ordinal)
|
||||
? edge.Target
|
||||
: edge.Source;
|
||||
if (visitedNodes.Add(other))
|
||||
{
|
||||
next.Add(other);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
frontier = next;
|
||||
}
|
||||
|
||||
var resultNodes = new List<NodeTile>();
|
||||
foreach (var nodeId in visitedNodes.OrderBy(id => id, StringComparer.Ordinal))
|
||||
{
|
||||
if (nodeById.TryGetValue(nodeId, out var node))
|
||||
{
|
||||
resultNodes.Add(node);
|
||||
}
|
||||
}
|
||||
|
||||
var resultEdges = tenantEdges
|
||||
.Where(edge => visitedEdges.Contains(edge.Id))
|
||||
.Where(edge => visitedNodes.Contains(edge.Source) && visitedNodes.Contains(edge.Target))
|
||||
.OrderBy(edge => edge.Id, StringComparer.Ordinal)
|
||||
.ToList();
|
||||
|
||||
return (resultNodes, resultEdges);
|
||||
}
|
||||
|
||||
public (IReadOnlyList<NodeTile> Nodes, IReadOnlyList<EdgeTile> Edges)? GetSnapshot(string tenant, string snapshotId)
|
||||
{
|
||||
if (_snapshots.TryGetValue($"{tenant}:{snapshotId}", out var snap))
|
||||
@@ -128,6 +240,69 @@ public sealed class InMemoryGraphRepository
|
||||
return dict;
|
||||
}
|
||||
|
||||
private static bool HasAttribute(NodeTile node, string key, string expected)
|
||||
{
|
||||
if (!node.Attributes.TryGetValue(key, out var value) || value is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return string.Equals(value.ToString(), expected, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static HashSet<string> BuildLineageKindFilter(string[]? relationshipKinds)
|
||||
{
|
||||
if (relationshipKinds is null || relationshipKinds.Length == 0)
|
||||
{
|
||||
return new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
var set = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var value in relationshipKinds)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
set.Add(value.Trim());
|
||||
}
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
private static bool IsLineageEdgeAllowed(EdgeTile edge, HashSet<string> allowedKinds)
|
||||
{
|
||||
if (allowedKinds.Count == 0)
|
||||
{
|
||||
return edge.Kind.StartsWith("SBOM_LINEAGE_", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(edge.Kind, "SBOM_VERSION_OF", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
if (allowedKinds.Contains(edge.Kind))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (edge.Attributes.TryGetValue("relationship", out var relationship) && relationship is not null)
|
||||
{
|
||||
return allowedKinds.Contains(relationship.ToString() ?? string.Empty);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void AddAdjacency(Dictionary<string, List<EdgeTile>> adjacency, string nodeId, EdgeTile edge)
|
||||
{
|
||||
if (!adjacency.TryGetValue(nodeId, out var list))
|
||||
{
|
||||
list = new List<EdgeTile>();
|
||||
adjacency[nodeId] = list;
|
||||
}
|
||||
|
||||
list.Add(edge);
|
||||
}
|
||||
|
||||
private static bool MatchesQuery(NodeTile node, string query)
|
||||
{
|
||||
var q = query.ToLowerInvariant();
|
||||
|
||||
Reference in New Issue
Block a user