From c69fdece4bece762f58bc33860c96b83c81cd27b Mon Sep 17 00:00:00 2001 From: Charlie Morrison Date: Sun, 8 Nov 2026 15:41:05 +0200 Subject: [PATCH 1/3] Normalize OSV ecosystem names to their ecosystems Fixes #60 --- ecosystem.go | 5 +++++ ecosystem_test.go | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/ecosystem.go b/ecosystem.go index f6733b1..a965618 100644 --- a/ecosystem.go +++ b/ecosystem.go @@ -39,6 +39,11 @@ var ecosystemAliases = map[string]string{ "go": ecosystemGolang, purlTypeGem: ecosystemRubyGems, ecosystemComposer: ecosystemPackagist, + // OSV names that do not lowercase to the ecosystem name. + "crates.io": "cargo", + "conancenter": "conan", + "swifturl": ecosystemSwift, + "github actions": ecosystemGitHubActions, } // osvEcosystemNames maps PURL types to OSV ecosystem names. diff --git a/ecosystem_test.go b/ecosystem_test.go index 9081ad8..a303b02 100644 --- a/ecosystem_test.go +++ b/ecosystem_test.go @@ -20,6 +20,7 @@ func TestNormalizeEcosystem(t *testing.T) { {"composer", "packagist"}, {"packagist", "packagist"}, {"cargo", "cargo"}, + {"crates.io", "cargo"}, {"unknown", "unknown"}, } @@ -32,6 +33,14 @@ func TestNormalizeEcosystem(t *testing.T) { } } +func TestNormalizeEcosystemAcceptsOSVNames(t *testing.T) { + for purlType, osv := range osvEcosystemNames { + if got, want := NormalizeEcosystem(osv), NormalizeEcosystem(PURLTypeToEcosystem(purlType)); got != want { + t.Errorf("NormalizeEcosystem(%q) = %q, want %q", osv, got, want) + } + } +} + func TestEcosystemToPURLType(t *testing.T) { tests := []struct { ecosystem string From c034785fa547a7497969fd77e8e943f888cdb629 Mon Sep 17 00:00:00 2001 From: Charlie Morrison Date: Mon, 9 Nov 2026 19:07:08 +0200 Subject: [PATCH 2/3] Normalize the githubactions PURL type too NormalizeEcosystem("githubactions") stayed githubactions while GitHub Actions maps to github-actions. The OSV-name test now checks each PURL type as well. --- ecosystem.go | 7 ++++--- ecosystem_test.go | 7 +++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/ecosystem.go b/ecosystem.go index a965618..6400cb4 100644 --- a/ecosystem.go +++ b/ecosystem.go @@ -36,9 +36,10 @@ var purlTypeForEcosystem = map[string]string{ // ecosystemAliases maps alternate names to canonical ecosystem names. var ecosystemAliases = map[string]string{ - "go": ecosystemGolang, - purlTypeGem: ecosystemRubyGems, - ecosystemComposer: ecosystemPackagist, + "go": ecosystemGolang, + purlTypeGem: ecosystemRubyGems, + ecosystemComposer: ecosystemPackagist, + purlTypeGitHubActions: ecosystemGitHubActions, // OSV names that do not lowercase to the ecosystem name. "crates.io": "cargo", "conancenter": "conan", diff --git a/ecosystem_test.go b/ecosystem_test.go index a303b02..6ab51fe 100644 --- a/ecosystem_test.go +++ b/ecosystem_test.go @@ -35,8 +35,11 @@ func TestNormalizeEcosystem(t *testing.T) { func TestNormalizeEcosystemAcceptsOSVNames(t *testing.T) { for purlType, osv := range osvEcosystemNames { - if got, want := NormalizeEcosystem(osv), NormalizeEcosystem(PURLTypeToEcosystem(purlType)); got != want { - t.Errorf("NormalizeEcosystem(%q) = %q, want %q", osv, got, want) + want := NormalizeEcosystem(PURLTypeToEcosystem(purlType)) + for _, name := range []string{osv, purlType} { + if got := NormalizeEcosystem(name); got != want { + t.Errorf("NormalizeEcosystem(%q) = %q, want %q", name, got, want) + } } } } From 107f3f9ce6e2f10ac1f8193d5e7c6b66ab57e1de Mon Sep 17 00:00:00 2001 From: Charlie Morrison Date: Tue, 10 Nov 2026 18:25:16 +0200 Subject: [PATCH 3/3] Test MakePURL output for the OSV ecosystem aliases The aliases also route these names through splitNamespace: githubactions and GitHub Actions split the action path, crates.io and ConanCenter build cargo and conan PURLs, and SwiftURL goes through splitSwift, so the swift identity rejections now run for SwiftURL too. --- ecosystem_test.go | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/ecosystem_test.go b/ecosystem_test.go index 6ab51fe..5947a4e 100644 --- a/ecosystem_test.go +++ b/ecosystem_test.go @@ -229,6 +229,11 @@ func TestMakePURLString(t *testing.T) { {"freebsd default namespace", "freebsd", "py311-requests", "2.32.3_1", "pkg:bsd/freebsd/py311-requests@2.32.3_1"}, {"github-actions", "github-actions", "actions/checkout", "v4", "pkg:githubactions/actions/checkout@v4"}, {"github-actions path", "github-actions", "actions/cache/restore", "v4", "pkg:githubactions/actions/cache@v4"}, + {"githubactions path", "githubactions", "actions/cache/restore", "v4", "pkg:githubactions/actions/cache@v4"}, + {"OSV GitHub Actions", "GitHub Actions", "actions/checkout", "v4", "pkg:githubactions/actions/checkout@v4"}, + {"OSV crates.io", "crates.io", "serde", "1.0.0", "pkg:cargo/serde@1.0.0"}, + {"OSV ConanCenter", "ConanCenter", "zlib", "1.3.1", "pkg:conan/zlib@1.3.1"}, + {"OSV SwiftURL", "SwiftURL", "github.com/apple/swift-nio", "2.65.0", "pkg:swift/github.com/apple/swift-nio@2.65.0"}, {"cargo", "cargo", "serde", "1.0.0", "pkg:cargo/serde@1.0.0"}, {"generic ecosystem escaping", "cargo", "serde+derive", "1.0.0", "pkg:cargo/serde%2Bderive@1.0.0"}, {"swift source coordinate", "swift", "github.com/apple/swift-argument-parser", "1.8.2", "pkg:swift/github.com/apple/swift-argument-parser@1.8.2"}, @@ -314,18 +319,20 @@ func TestMakePURLRejectsInvalidSwiftIdentity(t *testing.T) { "apple." + strings.Repeat("a", 101), } - for _, identity := range identities { - t.Run(identity, func(t *testing.T) { - if got := MakePURL("swift", identity, "1.8.2"); got != nil { - t.Errorf("MakePURL(%q) = %q, want nil", identity, got.String()) - } - if got := MakePURLString("swift", identity, "1.8.2"); got != "" { - t.Errorf("MakePURLString(%q) = %q, want empty string", identity, got) - } - if got := BuildPURLString("swift", identity, "1.8.2", ""); got != "" { - t.Errorf("BuildPURLString(%q) = %q, want empty string", identity, got) - } - }) + for _, ecosystem := range []string{"swift", "SwiftURL"} { + for _, identity := range identities { + t.Run(ecosystem+"/"+identity, func(t *testing.T) { + if got := MakePURL(ecosystem, identity, "1.8.2"); got != nil { + t.Errorf("MakePURL(%q) = %q, want nil", identity, got.String()) + } + if got := MakePURLString(ecosystem, identity, "1.8.2"); got != "" { + t.Errorf("MakePURLString(%q) = %q, want empty string", identity, got) + } + if got := BuildPURLString(ecosystem, identity, "1.8.2", ""); got != "" { + t.Errorf("BuildPURLString(%q) = %q, want empty string", identity, got) + } + }) + } } }