internal/report: add Read function

Factor out common code.

Change-Id: Ie645ea9cf312fcfa3462be77541c7a22741df855
Reviewed-on: https://go-review.googlesource.com/c/vulndb/+/379774
Trust: Jonathan Amsterdam <jba@google.com>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Damien Neil <dneil@google.com>
diff --git a/cmd/vulnreport/main.go b/cmd/vulnreport/main.go
index 5e0813d..400f5d5 100644
--- a/cmd/vulnreport/main.go
+++ b/cmd/vulnreport/main.go
@@ -11,7 +11,6 @@
 	"encoding/json"
 	"flag"
 	"fmt"
-	"io/ioutil"
 	"log"
 	"strconv"
 	"strings"
@@ -160,7 +159,7 @@
 
 func lint(filename string) (err error) {
 	defer derrors.Wrap(&err, "lint(%q)", filename)
-	r, err := readReport(filename)
+	r, err := report.Read(filename)
 	if err != nil {
 		return err
 	}
@@ -173,7 +172,7 @@
 
 func fix(filename string) (err error) {
 	defer derrors.Wrap(&err, "fix(%q)", filename)
-	r, err := readReport(filename)
+	r, err := report.Read(filename)
 	if err != nil {
 		return err
 	}
@@ -189,20 +188,6 @@
 	return nil
 }
 
-func readReport(filename string) (_ *report.Report, err error) {
-	defer derrors.Wrap(&err, "readReport(%q)", filename)
-	content, err := ioutil.ReadFile(filename)
-	if err != nil {
-		return nil, fmt.Errorf("ioutil.ReadFile: %v", err)
-	}
-
-	var r report.Report
-	if err := yaml.UnmarshalStrict(content, &r); err != nil {
-		return nil, fmt.Errorf("yaml.UnmarshalStrict: %v", err)
-	}
-	return &r, nil
-}
-
 func newCVE(filename string) (err error) {
 	defer derrors.Wrap(&err, "newCVE(%q)", filename)
 	cve, err := report.ToCVE(filename)
diff --git a/internal/database/generate.go b/internal/database/generate.go
index 0d0ce47..b9f1d00 100644
--- a/internal/database/generate.go
+++ b/internal/database/generate.go
@@ -22,7 +22,6 @@
 	"golang.org/x/vulndb/internal/gitrepo"
 	"golang.org/x/vulndb/internal/report"
 	"golang.org/x/vulndb/internal/stdlib"
-	"gopkg.in/yaml.v2"
 )
 
 const (
@@ -55,13 +54,9 @@
 		if !strings.HasSuffix(f.Name(), ".yaml") {
 			continue
 		}
-		content, err := ioutil.ReadFile(filepath.Join(repoDir, yamlDir, f.Name()))
+		r, err := report.Read(filepath.Join(repoDir, yamlDir, f.Name()))
 		if err != nil {
-			return fmt.Errorf("can't read %q: %s", f.Name(), err)
-		}
-		var r report.Report
-		if err := yaml.UnmarshalStrict(content, &r); err != nil {
-			return fmt.Errorf("unable to unmarshal %q: %s", f.Name(), err)
+			return err
 		}
 		if r.Published.IsZero() {
 			yamlPath := filepath.Join(yamlDir, f.Name())
@@ -81,7 +76,7 @@
 
 		name := strings.TrimSuffix(filepath.Base(f.Name()), filepath.Ext(f.Name()))
 		linkName := fmt.Sprintf("%s%s", dbURL, name)
-		entry, paths := generateOSVEntry(name, linkName, r)
+		entry, paths := generateOSVEntry(name, linkName, *r)
 		for _, path := range paths {
 			jsonVulns[path] = append(jsonVulns[path], entry)
 		}
diff --git a/internal/report/cve.go b/internal/report/cve.go
index 62a2f83..c48c846 100644
--- a/internal/report/cve.go
+++ b/internal/report/cve.go
@@ -6,31 +6,22 @@
 
 import (
 	"errors"
-	"fmt"
-	"io/ioutil"
 	"sort"
 	"strings"
 
 	"golang.org/x/vulndb/internal/cveschema"
 	"golang.org/x/vulndb/internal/derrors"
 	"golang.org/x/vulndb/internal/stdlib"
-	"gopkg.in/yaml.v2"
 )
 
 // ToCVE creates a CVE from a reports/GO-YYYY-NNNN.yaml file.
 func ToCVE(reportPath string) (_ *cveschema.CVE, err error) {
 	defer derrors.Wrap(&err, "report.ToCVE(%q)", reportPath)
 
-	b, err := ioutil.ReadFile(reportPath)
+	r, err := Read(reportPath)
 	if err != nil {
-		return nil, fmt.Errorf("ioutil.ReadFile(%q): %v", reportPath, err)
+		return nil, err
 	}
-
-	var r Report
-	if err = yaml.UnmarshalStrict(b, &r); err != nil {
-		return nil, fmt.Errorf("yaml.Unmarshal:: %v", err)
-	}
-
 	if len(r.CVEs) > 0 {
 		return nil, errors.New("report has CVE ID is wrong section (should be in cve_metadata for self-issued CVEs)")
 	}
diff --git a/internal/report/lint.go b/internal/report/lint.go
index e2ea822..02e1384 100644
--- a/internal/report/lint.go
+++ b/internal/report/lint.go
@@ -18,7 +18,6 @@
 	"golang.org/x/mod/semver"
 	"golang.org/x/vulndb/internal/derrors"
 	"golang.org/x/vulndb/internal/stdlib"
-	"gopkg.in/yaml.v2"
 )
 
 // TODO: getting things from the proxy should all be cached so we
@@ -155,14 +154,10 @@
 // the YAML reports.
 func LintFile(filename string) (_ []string, err error) {
 	defer derrors.Wrap(&err, "LintFile(%q)", filename)
-	b, err := os.ReadFile(filename)
+	r, err := Read(filename)
 	if err != nil {
 		return nil, err
 	}
-	var r Report
-	if err := yaml.UnmarshalStrict(b, &r); err != nil {
-		return nil, fmt.Errorf("yaml.UnmarshalStrict(b, &r): %v (%q)", err, filename)
-	}
 	return r.Lint(), nil
 }
 
diff --git a/internal/report/report.go b/internal/report/report.go
index 1ea448d..3e5d1ae 100644
--- a/internal/report/report.go
+++ b/internal/report/report.go
@@ -6,7 +6,14 @@
 // in reports/.
 package report
 
-import "time"
+import (
+	"fmt"
+	"os"
+	"time"
+
+	"golang.org/x/vulndb/internal/derrors"
+	"gopkg.in/yaml.v2"
+)
 
 type VersionRange struct {
 	Introduced string `yaml:"introduced,omitempty"`
@@ -70,3 +77,18 @@
 	// to fill in the ID string.
 	CVEMetadata *CVEMeta `yaml:"cve_metadata,omitempty"`
 }
+
+// Read reads a Report from filename.
+func Read(filename string) (_ *Report, err error) {
+	defer derrors.Wrap(&err, "report.Read(%q)", filename)
+
+	b, err := os.ReadFile(filename)
+	if err != nil {
+		return nil, err
+	}
+	var r Report
+	if err := yaml.UnmarshalStrict(b, &r); err != nil {
+		return nil, fmt.Errorf("yaml.UnmarshalStrict(b, &r): %v (%q)", err, filename)
+	}
+	return &r, nil
+}