cmd/diff,internal/database: refactor

Logic for srv/cmd/diff is is moved into internal/database.

Change-Id: I015c6cd8d98818f325b2e9b8e1acd3a3f7801127
Reviewed-on: https://go-review.googlesource.com/c/vuln/+/372997
Trust: Julie Qiu <julie@golang.org>
Run-TryBot: Julie Qiu <julie@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
diff --git a/srv/cmd/dbdiff/main.go b/srv/cmd/dbdiff/main.go
index 21c19d4..244811d 100644
--- a/srv/cmd/dbdiff/main.go
+++ b/srv/cmd/dbdiff/main.go
@@ -7,98 +7,19 @@
 package main
 
 import (
-	"encoding/json"
 	"fmt"
-	"io/ioutil"
+	"log"
 	"os"
-	"path/filepath"
-	"strings"
 
-	"github.com/google/go-cmp/cmp"
-	"golang.org/x/vuln/client"
-	"golang.org/x/vuln/osv"
-	"golang.org/x/vuln/srv/internal"
-	"golang.org/x/vuln/srv/internal/derrors"
+	"golang.org/x/vuln/srv/internal/database"
 )
 
-func loadDB(dbPath string) (_ client.DBIndex, _ map[string][]osv.Entry, err error) {
-	defer derrors.Wrap(&err, "loadDB(%q)", dbPath)
-	index := client.DBIndex{}
-	dbMap := map[string][]osv.Entry{}
-
-	var loadDir func(string) error
-	loadDir = func(path string) error {
-		dir, err := ioutil.ReadDir(path)
-		if err != nil {
-			return err
-		}
-		for _, f := range dir {
-			fpath := filepath.Join(path, f.Name())
-			if f.IsDir() {
-				if err := loadDir(fpath); err != nil {
-					return err
-				}
-				continue
-			}
-			content, err := ioutil.ReadFile(fpath)
-			if err != nil {
-				return err
-			}
-			if path == dbPath && f.Name() == "index.json" {
-				if err := json.Unmarshal(content, &index); err != nil {
-					return fmt.Errorf("unable to parse %q: %s", fpath, err)
-				}
-			} else if path == filepath.Join(dbPath, internal.IDDirectory) {
-				if f.Name() == "index.json" {
-					// The ID index is just a list of the entries' IDs; we'll
-					// catch any diffs in the entries themselves.
-					continue
-				}
-				var entry osv.Entry
-				if err := json.Unmarshal(content, &entry); err != nil {
-					return fmt.Errorf("unable to parse %q: %s", fpath, err)
-				}
-				fname := strings.TrimPrefix(fpath, dbPath)
-				dbMap[fname] = []osv.Entry{entry}
-			} else {
-				var entries []osv.Entry
-				if err := json.Unmarshal(content, &entries); err != nil {
-					return fmt.Errorf("unable to parse %q: %s", fpath, err)
-				}
-				module := strings.TrimPrefix(fpath, dbPath)
-				dbMap[module] = entries
-			}
-		}
-		return nil
-	}
-	if err := loadDir(dbPath); err != nil {
-		return nil, nil, err
-	}
-	return index, dbMap, nil
-}
-
 func main() {
 	if len(os.Args) != 3 {
 		fmt.Fprintln(os.Stderr, "usage: dbdiff db-a db-b")
 		os.Exit(1)
 	}
-	indexA, dbA, err := loadDB(os.Args[1])
-	if err != nil {
-		fmt.Fprintf(os.Stderr, "unable to load %q: %s\n", os.Args[1], err)
-		os.Exit(1)
+	if err := database.Diff(os.Args[1], os.Args[2]); err != nil {
+		log.Fatal(err)
 	}
-	indexB, dbB, err := loadDB(os.Args[2])
-	if err != nil {
-		fmt.Fprintf(os.Stderr, "unable to load %q: %s\n", os.Args[2], err)
-		os.Exit(1)
-	}
-	indexDiff := cmp.Diff(indexA, indexB)
-	if indexDiff == "" {
-		indexDiff = "(no change)"
-	}
-	dbDiff := cmp.Diff(dbA, dbB)
-	if dbDiff == "" {
-		dbDiff = "(no change)"
-	}
-	fmt.Printf("# index\n%s\n\n# db\n%s\n", indexDiff, dbDiff)
 }
diff --git a/srv/internal/database/load.go b/srv/internal/database/load.go
new file mode 100644
index 0000000..fc1dece
--- /dev/null
+++ b/srv/internal/database/load.go
@@ -0,0 +1,97 @@
+// Copyright 2021 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 database
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"path/filepath"
+	"strings"
+
+	"github.com/google/go-cmp/cmp"
+	"golang.org/x/vuln/client"
+	"golang.org/x/vuln/osv"
+	"golang.org/x/vuln/srv/internal"
+	"golang.org/x/vuln/srv/internal/derrors"
+)
+
+func Diff(dbname1, dbname2 string) (err error) {
+	defer derrors.Wrap(&err, "Diff(%q, %q)", dbname1, dbname2)
+	indexA, dbA, err := loadDB(dbname1)
+	if err != nil {
+		return fmt.Errorf("unable to load %q: %s", dbname1, err)
+	}
+	indexB, dbB, err := loadDB(dbname2)
+	if err != nil {
+		return fmt.Errorf("unable to load %q: %s", dbname2, err)
+	}
+	indexDiff := cmp.Diff(indexA, indexB)
+	if indexDiff == "" {
+		indexDiff = "(no change)"
+	}
+	dbDiff := cmp.Diff(dbA, dbB)
+	if dbDiff == "" {
+		dbDiff = "(no change)"
+	}
+	fmt.Printf("# index\n%s\n\n# db\n%s\n", indexDiff, dbDiff)
+	return nil
+}
+
+func loadDB(dbPath string) (_ client.DBIndex, _ map[string][]osv.Entry, err error) {
+	defer derrors.Wrap(&err, "loadDB(%q)", dbPath)
+	index := client.DBIndex{}
+	dbMap := map[string][]osv.Entry{}
+
+	var loadDir func(string) error
+	loadDir = func(path string) error {
+		dir, err := ioutil.ReadDir(path)
+		if err != nil {
+			return err
+		}
+		for _, f := range dir {
+			fpath := filepath.Join(path, f.Name())
+			if f.IsDir() {
+				if err := loadDir(fpath); err != nil {
+					return err
+				}
+				continue
+			}
+			content, err := ioutil.ReadFile(fpath)
+			if err != nil {
+				return err
+			}
+			if path == dbPath && f.Name() == "index.json" {
+				if err := json.Unmarshal(content, &index); err != nil {
+					return fmt.Errorf("unable to parse %q: %s", fpath, err)
+				}
+			} else if path == filepath.Join(dbPath, internal.IDDirectory) {
+				if f.Name() == "index.json" {
+					// The ID index is just a list of the entries' IDs; we'll
+					// catch any diffs in the entries themselves.
+					continue
+				}
+				var entry osv.Entry
+				if err := json.Unmarshal(content, &entry); err != nil {
+					return fmt.Errorf("unable to parse %q: %s", fpath, err)
+				}
+				fname := strings.TrimPrefix(fpath, dbPath)
+				dbMap[fname] = []osv.Entry{entry}
+			} else {
+				var entries []osv.Entry
+				if err := json.Unmarshal(content, &entries); err != nil {
+					return fmt.Errorf("unable to parse %q: %s", fpath, err)
+				}
+				module := strings.TrimPrefix(fpath, dbPath)
+				dbMap[module] = entries
+			}
+		}
+		return nil
+	}
+	if err := loadDir(dbPath); err != nil {
+		return nil, nil, err
+	}
+	return index, dbMap, nil
+}