use crate::model::{Component, Ecosystem};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum ComponentKey {
Purl(String),
NameTuple(Ecosystem, String),
}
pub fn key(c: &Component) -> ComponentKey {
if let Some(p) = &c.purl {
ComponentKey::Purl(purl_without_version(p).to_string())
} else {
ComponentKey::NameTuple(c.ecosystem.clone(), c.name.clone())
}
}
pub fn purl_without_version(purl: &str) -> &str {
let head = purl.split(['?', '#']).next().unwrap_or(purl);
if let Some(at) = head.rfind('@') {
&head[..at]
} else {
head
}
}
#[cfg(test)]
mod tests {
#![allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::todo,
clippy::unimplemented
)]
use super::*;
use crate::model::Relationship;
#[test]
fn strips_simple_version() {
assert_eq!(
purl_without_version("pkg:npm/axios@1.14.0"),
"pkg:npm/axios"
);
assert_eq!(
purl_without_version("pkg:cargo/serde@1.0.228"),
"pkg:cargo/serde"
);
}
#[test]
fn strips_version_with_qualifiers() {
assert_eq!(
purl_without_version("pkg:npm/axios@1.14.0?vcs_url=https://github.com/axios/axios"),
"pkg:npm/axios"
);
}
#[test]
fn strips_subpath() {
assert_eq!(
purl_without_version("pkg:golang/github.com/foo/bar@v1.0.0#subpath"),
"pkg:golang/github.com/foo/bar"
);
}
#[test]
fn handles_npm_scoped_with_at_in_namespace() {
assert_eq!(
purl_without_version("pkg:npm/@scope/name@1.0.0"),
"pkg:npm/@scope/name"
);
assert_eq!(
purl_without_version("pkg:npm/%40scope/name@1.0.0"),
"pkg:npm/%40scope/name"
);
}
#[test]
fn passes_through_when_no_version() {
assert_eq!(purl_without_version("pkg:npm/axios"), "pkg:npm/axios");
}
fn make(name: &str, eco: Ecosystem, purl: Option<&str>) -> Component {
Component {
name: name.to_string(),
version: "0".to_string(),
ecosystem: eco,
purl: purl.map(str::to_string),
licenses: Vec::new(),
supplier: None,
hashes: Vec::new(),
relationship: Relationship::Unknown,
source_url: None,
bom_ref: None,
}
}
#[test]
fn key_uses_purl_when_present() {
let c = make("axios", Ecosystem::Npm, Some("pkg:npm/axios@1.14.0"));
assert_eq!(key(&c), ComponentKey::Purl("pkg:npm/axios".to_string()));
}
#[test]
fn key_falls_back_to_name_tuple() {
let c = make("custom", Ecosystem::Cargo, None);
assert_eq!(
key(&c),
ComponentKey::NameTuple(Ecosystem::Cargo, "custom".to_string())
);
}
}