///|
fn blocked_type(policy : InventoryPolicy, package_type : String) -> Bool {
for value in policy.blocked_types {
if ascii_lower(value) == package_type {
return true
}
}
false
}
///|
fn missing_required_qualifier(
value : PackageUrl,
policy : InventoryPolicy,
) -> String? {
for required in policy.required_qualifiers {
if !required_qualifier_matches(value, required) {
return Some(ascii_lower(required.key))
}
}
None
}
///|
fn inventory_finding(
entry : InventoryEntry,
canonical : String,
decision : InventoryDecision,
code : String,
offset : Int,
) -> InventoryFinding {
{
record_id: entry.record_id,
input: entry.purl,
canonical,
decision,
code,
offset,
}
}
///|
/// Audit a structured PURL inventory without network access. Every input row
/// produces one finding. Duplicate means the same type/namespace/name/version
/// appeared earlier; qualifiers and subpath are excluded from that key, so the
/// result is a join warning rather than proof of byte-identical artifacts.
pub fn audit_inventory(
entries : Array[InventoryEntry],
policy? : InventoryPolicy = InventoryPolicy::default(),
) -> InventoryReport {
let findings : Array[InventoryFinding] = []
let identities : @hashset.HashSet[String] = @hashset.HashSet([])
let mut ready = 0
let mut invalid = 0
let mut unversioned = 0
let mut duplicate = 0
let mut policy_blocked = 0
for entry in entries {
let value = parse(entry.purl) catch {
Syntax(code, offset) => {
findings.push(inventory_finding(entry, "", Invalid, code, offset))
invalid += 1
continue
}
Unsupported(code, offset) => {
findings.push(inventory_finding(entry, "", Invalid, code, offset))
invalid += 1
continue
}
}
let canonical = value.to_string()
let identity = value.identity()
let finding = if policy.require_version && value.version is None {
unversioned += 1
inventory_finding(entry, canonical, Unversioned, "VERSION_REQUIRED", -1)
} else if blocked_type(policy, value.package_type) {
policy_blocked += 1
inventory_finding(entry, canonical, PolicyBlocked, "TYPE_BLOCKED", -1)
} else {
match missing_required_qualifier(value, policy) {
Some(key) => {
policy_blocked += 1
inventory_finding(
entry,
canonical,
PolicyBlocked,
"QUALIFIER_REQUIRED:" + key,
-1,
)
}
None =>
if identities.contains(identity) {
duplicate += 1
inventory_finding(
entry,
canonical,
Duplicate,
"DUPLICATE_IDENTITY",
-1,
)
} else {
ready += 1
inventory_finding(entry, canonical, Ready, "", -1)
}
}
}
identities.add(identity)
findings.push(finding)
}
{ findings, ready, invalid, unversioned, duplicate, policy_blocked, }
}