internal/openvex: refactor PURL

This changes the internal representation of a PURL to a struct that is
converted to a string. It will make other "purlFromX" functions less
redundant to write in the future.

Change-Id: I278f13ef175878c85b07341be510050f8d7f2c5d
Reviewed-on: https://go-review.googlesource.com/c/vuln/+/615795
Reviewed-by: Zvonimir Pavlinovic <zpavlinovic@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/internal/openvex/purl.go b/internal/openvex/purl.go
index f9c4c4f..fc1cea9 100644
--- a/internal/openvex/purl.go
+++ b/internal/openvex/purl.go
@@ -16,13 +16,31 @@
 // the module path. See https://github.com/package-url/purl-spec/issues/63
 // for further disucssion.
 
+const suffix = "pkg:golang/"
+
+type purl struct {
+	name    string
+	version string
+}
+
+func (p *purl) String() string {
+	var b strings.Builder
+	b.WriteString(suffix)
+	b.WriteString(url.PathEscape(p.name))
+	if p.version != "" {
+		b.WriteString("@")
+		b.WriteString(p.version)
+	}
+	return b.String()
+}
+
 // purlFromFinding takes a govulncheck finding and generates a purl to the
 // vulnerable dependency.
 func purlFromFinding(f *govulncheck.Finding) string {
-	var b strings.Builder
-	b.WriteString("pkg:golang/")
-	mod := f.Trace[0].Module
-	b.WriteString(url.PathEscape(mod))
-	b.WriteString("@" + f.Trace[0].Version)
-	return b.String()
+	purl := purl{
+		name:    f.Trace[0].Module,
+		version: f.Trace[0].Version,
+	}
+
+	return purl.String()
 }