///|
fn string_arrays_equal(left : Array[String], right : Array[String]) -> Bool {
if left.length() != right.length() {
return false
}
for i = 0; i < left.length(); i = i + 1 {
if left[i] != right[i] {
return false
}
}
true
}
///|
fn required_qualifier_matches(value : PackageUrl, expected : Qualifier) -> Bool {
match value.qualifier(expected.key) {
Some(actual) => actual == expected.value
None => false
}
}
///|
/// Match a parsed Package URL against explicit fields. Missing pattern fields
/// are wildcards; present fields are exact after profile normalization.
/// Qualifier requirements are a subset match and subpath is intentionally
/// ignored. The first mismatch is returned for stable CI diagnostics.
pub fn match_purl(value : PackageUrl, pattern : PurlPattern) -> MatchResult {
match pattern.package_type {
Some(expected) =>
if value.package_type != ascii_lower(expected) {
return TypeMismatch
}
None => ()
}
match pattern.namespace_parts {
Some(expected) =>
if !string_arrays_equal(value.namespace_parts, expected) {
return NamespaceMismatch
}
None => ()
}
match pattern.name {
Some(expected) => if value.name != expected { return NameMismatch }
None => ()
}
match pattern.version {
Some(expected) =>
match value.version {
Some(actual) => if actual != expected { return VersionMismatch }
None => return VersionMismatch
}
None => ()
}
for expected in pattern.qualifiers {
if !required_qualifier_matches(value, expected) {
return QualifierMismatch(expected.key)
}
}
Match
}
///|
pub fn matches(value : PackageUrl, pattern : PurlPattern) -> Bool {
match_purl(value, pattern) == Match
}
///|
/// Join two PURLs on type/namespace/name/version while deliberately ignoring
/// qualifiers and subpath. Callers must still compare artifact digests when
/// they need byte identity.
pub fn same_package_version(left : PackageUrl, right : PackageUrl) -> Bool {
left.identity() == right.identity()
}