internal/complete: add a package for shared completion types

We're going to store completion entries in redis sorted sets, which
means they need to be string-encodable and string-decodable, in such a
way that they preserve their lexical sorting. This CL adds a 'complete'
package with a 'Completion' type that implements such an encoding, so
that it can be shared by both ETL and frontend.

There are also some constants added that will be used in later CLs.

Updates b/143370178

Change-Id: I94700d8f4e5deab4d74e9ef47cc8b124087f7275
Reviewed-on: https://team-review.git.corp.google.com/c/golang/discovery/+/602061
Reviewed-by: Jonathan Amsterdam <jba@google.com>
This commit is contained in:
Rob Findley 2019-11-19 13:34:51 -05:00 коммит произвёл Julie Qiu
Родитель 857e664937
Коммит 707980d183
2 изменённых файлов: 143 добавлений и 0 удалений

Просмотреть файл

@ -0,0 +1,97 @@
// Copyright 2019 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 complete defines a Completion type that is used in auto-completion,
// along with Encode and Decode methods that can be used for storing this type
// in redis.
package complete
import (
"fmt"
"strconv"
"strings"
"golang.org/x/discovery/internal/derrors"
)
const keySep = "|"
// Redis keys for completion sorted sets ("indexes"). They are in this package
// so that they can be accessed by both ETL and frontend.
const (
KeyPrefix = "completions"
PopularKey = KeyPrefix + "Popular"
RemainingKey = KeyPrefix + "Rest"
)
// Completion holds package data from an auto-completion match.
type Completion struct {
// Suffix is the path suffix that matched the compltion input, e.g. a query
// for "error" would match the suffix "errors" of "github.com/pkg/errors".
Suffix string
// ModulePath is the module path of the completion match. We may support
// matches of the same path in different modules.
ModulePath string
// Version is the module version of the completion entry.
Version string
// PackagePath is the full import path.
PackagePath string
// Importers is the number of importers of this package. It is used for
// sorting completion results.
Importers int
}
// Encode string-encodes a completion for storing in the completion index.
func (c Completion) Encode() string {
return strings.Join(c.keyData(), keySep)
}
func (c Completion) keyData() []string {
var suffix string
if strings.HasPrefix(c.PackagePath, c.ModulePath) {
suffix = strings.TrimPrefix(c.PackagePath, c.ModulePath)
suffix = "/" + strings.Trim(suffix, "/")
} else {
// In the case of the standard library, ModulePath will not be a prefix of
// PackagePath.
suffix = c.PackagePath
}
return []string{
c.Suffix,
strings.TrimRight(c.ModulePath, "/"),
c.Version,
suffix,
// It's important that importers is last in this key, since it is the only
// datum that changes. By having it last, we reserve the ability to
// selectively update this entry by deleting the prefix corresponding to
// the values above.
strconv.Itoa(c.Importers),
}
}
// Decode parses a completion entry from the completions index.
func Decode(entry string) (_ *Completion, err error) {
defer derrors.Wrap(&err, "complete.Decode(%q)", entry)
parts := strings.Split(entry, "|")
if len(parts) != 5 {
return nil, fmt.Errorf("got %d parts, want 5", len(parts))
}
c := &Completion{
Suffix: parts[0],
ModulePath: parts[1],
Version: parts[2],
}
suffix := parts[3]
if strings.HasPrefix(suffix, "/") {
c.PackagePath = strings.Trim(c.ModulePath+suffix, "/")
} else {
c.PackagePath = suffix
}
importers, err := strconv.Atoi(parts[4])
if err != nil {
return nil, fmt.Errorf("error parsing importers: %v", err)
}
c.Importers = importers
return c, nil
}

Просмотреть файл

@ -0,0 +1,46 @@
// Copyright 2019 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 complete
import (
"testing"
"github.com/google/go-cmp/cmp"
)
func TestEncodeDecode(t *testing.T) {
completions := []Completion{
{},
{Version: "foo"},
{Importers: 42},
{Suffix: "foo"},
{ModulePath: "foo"},
{PackagePath: "github.com/foo/bar/baz"},
{
Suffix: "github.com/foo",
ModulePath: "github.com/foo/bar",
Version: "v1.2.3",
PackagePath: "github.com/foo/bar/baz",
Importers: 101,
},
{
Suffix: "fmt",
ModulePath: "std",
Version: "go1.13",
PackagePath: "fmt",
Importers: 1234,
},
}
for _, c := range completions {
encoded := c.Encode()
decoded, err := Decode(encoded)
if err != nil {
t.Fatal(err)
}
if diff := cmp.Diff(c, *decoded); diff != "" {
t.Errorf("[%#v] decoded mismatch (-initial +decoded):\n%s\nencoded: %q", c, diff, encoded)
}
}
}