internal/osv: add reviewed field

Change-Id: I3b55fc54fbe6e5067b68f7d71a4580e0d0b226c7
Reviewed-on: https://go-review.googlesource.com/c/pkgsite-metrics/+/587516
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
Run-TryBot: Zvonimir Pavlinovic <zpavlinovic@google.com>
diff --git a/internal/osv/osv.go b/internal/osv/osv.go
index ebedbc2..bb3f779 100644
--- a/internal/osv/osv.go
+++ b/internal/osv/osv.go
@@ -231,4 +231,6 @@
 	// The URL of the Go advisory for this vulnerability, of the form
 	// "https://pkg.go.dev/GO-YYYY-XXXX".
 	URL string `json:"url,omitempty"`
+	// The review status of this report (UNREVIEWED or REVIEWED).
+	ReviewStatus ReviewStatus `json:"review_status,omitempty"`
 }
diff --git a/internal/osv/review_status.go b/internal/osv/review_status.go
new file mode 100644
index 0000000..adf70fe
--- /dev/null
+++ b/internal/osv/review_status.go
@@ -0,0 +1,69 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package osv
+
+import (
+	"encoding/json"
+	"fmt"
+)
+
+// ReviewStatus encodes the review status of a
+// report: unknown, reviewed, and unreviewed.
+type ReviewStatus int
+
+const (
+	ReviewStatusUnknown ReviewStatus = iota
+	ReviewStatusUnreviewed
+	ReviewStatusReviewed
+)
+
+var statusStrs = []string{
+	ReviewStatusUnknown:    "",
+	ReviewStatusUnreviewed: "UNREVIEWED",
+	ReviewStatusReviewed:   "REVIEWED",
+}
+
+func (r ReviewStatus) String() string {
+	if !r.IsValid() {
+		return fmt.Sprintf("INVALID(%d)", r)
+	}
+	return statusStrs[r]
+}
+
+func ReviewStatusValues() []string {
+	return statusStrs[1:]
+}
+
+func (r ReviewStatus) IsValid() bool {
+	return int(r) >= 0 && int(r) < len(statusStrs)
+}
+
+func ToReviewStatus(s string) (ReviewStatus, bool) {
+	for stat, str := range statusStrs {
+		if s == str {
+			return ReviewStatus(stat), true
+		}
+	}
+	return 0, false
+}
+
+func (r ReviewStatus) MarshalJSON() ([]byte, error) {
+	if !r.IsValid() {
+		return nil, fmt.Errorf("MarshalJSON: unrecognized review status: %d", r)
+	}
+	return json.Marshal(r.String())
+}
+
+func (r *ReviewStatus) UnmarshalJSON(b []byte) error {
+	var s string
+	if err := json.Unmarshal(b, &s); err != nil {
+		return err
+	}
+	if rs, ok := ToReviewStatus(s); ok {
+		*r = rs
+		return nil
+	}
+	return fmt.Errorf("UnmarshalJSON: unrecognized review status: %s", s)
+}