diff --git a/internal/core/client.go b/internal/core/client.go index b0f4dc5..440384d 100644 --- a/internal/core/client.go +++ b/internal/core/client.go @@ -245,3 +245,22 @@ func (b *BaseURLs) PURL(name, version string) string { } return fmt.Sprintf("pkg:%s/%s", "generic", name) } + +// BuildURLs returns a map of all non-empty URLs for a package. +// Keys are "registry", "download", "docs", and "purl". +func BuildURLs(urls URLBuilder, name, version string) map[string]string { + result := make(map[string]string) + if v := urls.Registry(name, version); v != "" { + result["registry"] = v + } + if v := urls.Download(name, version); v != "" { + result["download"] = v + } + if v := urls.Documentation(name, version); v != "" { + result["docs"] = v + } + if v := urls.PURL(name, version); v != "" { + result["purl"] = v + } + return result +} diff --git a/internal/core/client_test.go b/internal/core/client_test.go index 8e202a8..ca0629b 100644 --- a/internal/core/client_test.go +++ b/internal/core/client_test.go @@ -7,6 +7,57 @@ import ( "testing" ) +func TestBuildURLs(t *testing.T) { + urls := &BaseURLs{ + RegistryFn: func(name, version string) string { return "https://example.com/" + name }, + DownloadFn: func(name, version string) string { return "https://example.com/" + name + "/download" }, + DocumentationFn: func(name, version string) string { return "https://docs.example.com/" + name }, + PURLFn: func(name, version string) string { return "pkg:test/" + name + "@" + version }, + } + + got := BuildURLs(urls, "foo", "1.0.0") + + expected := map[string]string{ + "registry": "https://example.com/foo", + "download": "https://example.com/foo/download", + "docs": "https://docs.example.com/foo", + "purl": "pkg:test/foo@1.0.0", + } + + if len(got) != len(expected) { + t.Fatalf("BuildURLs returned %d entries, want %d", len(got), len(expected)) + } + + for k, want := range expected { + if got[k] != want { + t.Errorf("BuildURLs[%q] = %q, want %q", k, got[k], want) + } + } +} + +func TestBuildURLs_OmitsEmpty(t *testing.T) { + urls := &BaseURLs{ + RegistryFn: func(name, version string) string { return "https://example.com/" + name }, + // Documentation and Download are nil, so they return "" + } + + got := BuildURLs(urls, "foo", "1.0.0") + + if _, ok := got["docs"]; ok { + t.Error("BuildURLs should omit empty docs URL") + } + if _, ok := got["download"]; ok { + t.Error("BuildURLs should omit empty download URL") + } + if _, ok := got["registry"]; !ok { + t.Error("BuildURLs should include non-empty registry URL") + } + // PURL falls back to generic format in BaseURLs + if _, ok := got["purl"]; !ok { + t.Error("BuildURLs should include PURL fallback") + } +} + func TestDefaultClient_UserAgent(t *testing.T) { var gotUA string server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/registries.go b/registries.go index fb4a3ea..c49f24a 100644 --- a/registries.go +++ b/registries.go @@ -135,6 +135,12 @@ func SupportedEcosystems() []string { return core.SupportedEcosystems() } +// BuildURLs returns a map of all non-empty URLs for a package. +// Keys are "registry", "download", "docs", and "purl". +func BuildURLs(urls URLBuilder, name, version string) map[string]string { + return core.BuildURLs(urls, name, version) +} + // DefaultURL returns the default registry URL for an ecosystem. func DefaultURL(ecosystem string) string { return core.DefaultURL(ecosystem) diff --git a/registries_test.go b/registries_test.go index 6c9b8b4..bcc0a1e 100644 --- a/registries_test.go +++ b/registries_test.go @@ -174,6 +174,25 @@ func TestIntegration(t *testing.T) { } } +func TestBuildURLs(t *testing.T) { + reg, err := registries.New("cargo", "", nil) + if err != nil { + t.Fatalf("New failed: %v", err) + } + + got := registries.BuildURLs(reg.URLs(), "serde", "1.0.0") + + if got["registry"] == "" { + t.Error("expected non-empty registry URL") + } + if got["docs"] == "" { + t.Error("expected non-empty docs URL") + } + if got["purl"] != "pkg:cargo/serde@1.0.0" { + t.Errorf("purl = %q, want %q", got["purl"], "pkg:cargo/serde@1.0.0") + } +} + func TestConstants(t *testing.T) { // Verify constants are exported correctly if registries.Runtime != "runtime" {