chore: refactor influxid.ID, cleanup organization checking (#326)

This commit is contained in:
Dane Strandboge
2021-11-10 15:12:59 -06:00
committed by GitHub
parent 99791bafd3
commit adc58b8441
63 changed files with 699 additions and 1259 deletions

View File

@ -18,115 +18,34 @@ var (
ErrInvalidIDLength = errors.New("id must have a length of 16 bytes")
)
// ID is a unique identifier.
//
// Its zero value is not a valid ID.
type ID uint64
// IDFromString creates an ID from a given string.
//
// It errors if the input string does not match a valid ID.
func IDFromString(str string) (ID, error) {
var id ID
err := id.DecodeFromString(str)
if err != nil {
return 0, err
}
return id, nil
// Validate ensures that a passed string has a valid ID syntax.
// Checks that the string is of length 16, and is a valid hex-encoded uint.
func Validate(id string) error {
_, err := Decode(id)
return err
}
// MustIDFromString is like IDFromString but panics if
// s is not a valid base-16 identifier.
func MustIDFromString(s string) ID {
id, err := IDFromString(s)
if err != nil {
panic(err)
}
return id
}
// Decode parses b as a hex-encoded byte-slice-string.
//
// It errors if the input byte slice does not have the correct length
// or if it contains all zeros.
func (i *ID) Decode(b []byte) error {
if len(b) != IDLength {
return ErrInvalidIDLength
}
res, err := strconv.ParseUint(string(b), 16, 64)
if err != nil {
return ErrInvalidID
}
if *i = ID(res); !i.Valid() {
return ErrInvalidID
}
return nil
}
// DecodeFromString parses s as a hex-encoded string.
func (i *ID) DecodeFromString(s string) error {
return i.Decode([]byte(s))
}
// Encode converts ID to a hex-encoded byte-slice-string.
//
// It errors if the receiving ID holds its zero value.
func (i ID) Encode() ([]byte, error) {
if !i.Valid() {
return nil, ErrInvalidID
}
// Encode converts a uint64 to a hex-encoded byte-slice-string.
func Encode(id uint64) string {
b := make([]byte, hex.DecodedLen(IDLength))
binary.BigEndian.PutUint64(b, uint64(i))
binary.BigEndian.PutUint64(b, id)
dst := make([]byte, hex.EncodedLen(len(b)))
hex.Encode(dst, b)
return dst, nil
return string(dst)
}
// Valid checks whether the receiving ID is a valid one or not.
func (i ID) Valid() bool {
return i != 0
}
// String returns the ID as a hex encoded string.
// Decode parses id as a hex-encoded byte-slice-string.
//
// Returns an empty string in the case the ID is invalid.
func (i ID) String() string {
enc, _ := i.Encode()
return string(enc)
}
// GoString formats the ID the same as the String method.
// Without this, when using the %#v verb, an ID would be printed as a uint64,
// so you would see e.g. 0x2def021097c6000 instead of 02def021097c6000
// (note the leading 0x, which means the former doesn't show up in searches for the latter).
func (i ID) GoString() string {
return `"` + i.String() + `"`
}
// MarshalText encodes i as text.
// Providing this method is a fallback for json.Marshal,
// with the added benefit that IDs encoded as map keys will be the expected string encoding,
// rather than the effective fmt.Sprintf("%d", i) that json.Marshal uses by default for integer types.
func (i ID) MarshalText() ([]byte, error) {
return i.Encode()
}
// UnmarshalText decodes i from a byte slice.
// Providing this method is also a fallback for json.Unmarshal,
// also relevant when IDs are used as map keys.
func (i *ID) UnmarshalText(b []byte) error {
return i.Decode(b)
}
func (i *ID) Set(s string) error {
id, err := IDFromString(s)
if err != nil {
return err
// It errors if the input byte slice does not have the correct length
// or if it contains all zeros.
func Decode(id string) (uint64, error) {
if len([]byte(id)) != 16 {
return 0, ErrInvalidIDLength
}
*i = id
return nil
res, err := strconv.ParseUint(id, 16, 64)
if err != nil || res == 0 {
return 0, ErrInvalidID
}
return res, nil
}

View File

@ -2,222 +2,46 @@ package influxid_test
import (
"bytes"
"encoding/json"
"fmt"
"reflect"
"testing"
"github.com/influxdata/influx-cli/v2/pkg/influxid"
)
func TestIDFromString(t *testing.T) {
tests := []struct {
name string
id string
want influxid.ID
wantErr bool
err string
}{
{
name: "Should be able to decode an all zeros ID",
id: "0000000000000000",
wantErr: true,
err: influxid.ErrInvalidID.Error(),
},
{
name: "Should be able to decode an all f ID",
id: "ffffffffffffffff",
want: influxid.MustIDFromString("ffffffffffffffff"),
},
{
name: "Should be able to decode an ID",
id: "020f755c3c082000",
want: influxid.MustIDFromString("020f755c3c082000"),
},
{
name: "Should not be able to decode a non hex ID",
id: "gggggggggggggggg",
wantErr: true,
err: influxid.ErrInvalidID.Error(),
},
{
name: "Should not be able to decode inputs with length less than 16 bytes",
id: "abc",
wantErr: true,
err: influxid.ErrInvalidIDLength.Error(),
},
{
name: "Should not be able to decode inputs with length greater than 16 bytes",
id: "abcdabcdabcdabcd0",
wantErr: true,
err: influxid.ErrInvalidIDLength.Error(),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := influxid.IDFromString(tt.id)
// Check negative test cases
if (err != nil) && tt.wantErr {
if tt.err != err.Error() {
t.Errorf("IDFromString() errors out \"%s\", want \"%s\"", err, tt.err)
}
return
}
// Check positive test cases
if !reflect.DeepEqual(got, tt.want) && !tt.wantErr {
t.Errorf("IDFromString() outputs %v, want %v", got, tt.want)
}
})
}
}
func TestDecodeFromString(t *testing.T) {
var id influxid.ID
err := id.DecodeFromString("020f755c3c082000")
if err != nil {
func TestDecode(t *testing.T) {
if _, err := influxid.Decode("020f755c3c082000"); err != nil {
t.Errorf(err.Error())
}
want := []byte{48, 50, 48, 102, 55, 53, 53, 99, 51, 99, 48, 56, 50, 48, 48, 48}
got, _ := id.Encode()
if !bytes.Equal(want, got) {
t.Errorf("got %s not equal to wanted %s", string(got), string(want))
}
if id.String() != "020f755c3c082000" {
t.Errorf("expecting string representation to contain the right value")
}
if !id.Valid() {
t.Errorf("expecting ID to be a valid one")
}
}
func TestEncode(t *testing.T) {
var id influxid.ID
if _, err := id.Encode(); err == nil {
t.Errorf("encoding an invalid ID should not be possible")
}
id.DecodeFromString("5ca1ab1eba5eba11")
res, _ := influxid.Decode("5ca1ab1eba5eba11")
want := []byte{53, 99, 97, 49, 97, 98, 49, 101, 98, 97, 53, 101, 98, 97, 49, 49}
got, _ := id.Encode()
got := []byte(influxid.Encode(res))
if !bytes.Equal(want, got) {
t.Errorf("encoding error")
}
if id.String() != "5ca1ab1eba5eba11" {
t.Errorf("expecting string representation to contain the right value")
}
if !id.Valid() {
t.Errorf("expecting ID to be a valid one")
}
}
func TestDecodeFromAllZeros(t *testing.T) {
var id influxid.ID
err := id.Decode(make([]byte, influxid.IDLength))
if err == nil {
if _, err := influxid.Decode(string(make([]byte, influxid.IDLength))); err == nil {
t.Errorf("expecting all zeros ID to not be a valid ID")
}
}
func TestDecodeFromShorterString(t *testing.T) {
var id influxid.ID
err := id.DecodeFromString("020f75")
if err == nil {
if _, err := influxid.Decode("020f75"); err == nil {
t.Errorf("expecting shorter inputs to error")
}
if id.String() != "" {
t.Errorf("expecting invalid ID to be serialized into empty string")
}
}
func TestDecodeFromLongerString(t *testing.T) {
var id influxid.ID
err := id.DecodeFromString("020f755c3c082000aaa")
if err == nil {
if _, err := influxid.Decode("020f755c3c082000aaa"); err == nil {
t.Errorf("expecting shorter inputs to error")
}
if id.String() != "" {
t.Errorf("expecting invalid ID to be serialized into empty string")
}
}
func TestDecodeFromEmptyString(t *testing.T) {
var id influxid.ID
err := id.DecodeFromString("")
if err == nil {
if _, err := influxid.Decode(""); err == nil {
t.Errorf("expecting empty inputs to error")
}
if id.String() != "" {
t.Errorf("expecting invalid ID to be serialized into empty string")
}
}
func TestMarshalling(t *testing.T) {
var id0 influxid.ID
_, err := json.Marshal(id0)
if err == nil {
t.Errorf("expecting empty ID to not be a valid one")
}
init := "ca55e77eca55e77e"
id1, err := influxid.IDFromString(init)
if err != nil {
t.Errorf(err.Error())
}
serialized, err := json.Marshal(id1)
if err != nil {
t.Errorf(err.Error())
}
var id2 influxid.ID
json.Unmarshal(serialized, &id2)
bytes1, _ := id1.Encode()
bytes2, _ := id2.Encode()
if !bytes.Equal(bytes1, bytes2) {
t.Errorf("error marshalling/unmarshalling ID")
}
// When used as a map key, IDs must use their string encoding.
// If you only implement json.Marshaller, they will be encoded with Go's default integer encoding.
b, err := json.Marshal(map[influxid.ID]int{0x1234: 5678})
if err != nil {
t.Error(err)
}
const exp = `{"0000000000001234":5678}`
if string(b) != exp {
t.Errorf("expected map to json.Marshal as %s; got %s", exp, string(b))
}
var idMap map[influxid.ID]int
if err := json.Unmarshal(b, &idMap); err != nil {
t.Error(err)
}
if len(idMap) != 1 {
t.Errorf("expected length 1, got %d", len(idMap))
}
if idMap[0x1234] != 5678 {
t.Errorf("unmarshalled incorrectly; exp 0x1234:5678, got %v", idMap)
}
}
func TestID_GoString(t *testing.T) {
type idGoStringTester struct {
ID influxid.ID
}
var x idGoStringTester
const idString = "02def021097c6000"
if err := x.ID.DecodeFromString(idString); err != nil {
t.Fatal(err)
}
sharpV := fmt.Sprintf("%#v", x)
want := `influxid_test.idGoStringTester{ID:"` + idString + `"}`
if sharpV != want {
t.Fatalf("bad GoString: got %q, want %q", sharpV, want)
}
}

View File

@ -25,7 +25,7 @@ func PrintSummary(summary api.TemplateSummaryResources, out io.Writer, useColor
if labels := summary.Labels; len(labels) > 0 {
printer := newPrinter("LABELS", []string{"Description", "Color"})
for _, l := range labels {
id := influxid.ID(l.Id).String()
id := influxid.Encode(l.Id)
var desc string
if l.Properties.Description != nil {
desc = *l.Properties.Description
@ -39,7 +39,7 @@ func PrintSummary(summary api.TemplateSummaryResources, out io.Writer, useColor
if buckets := summary.Buckets; len(buckets) > 0 {
printer := newPrinter("BUCKETS", []string{"Retention", "Description", "Schema Type"})
for _, b := range buckets {
id := influxid.ID(b.Id).String()
id := influxid.Encode(b.Id)
var desc string
if b.Description != nil {
desc = *b.Description
@ -61,7 +61,7 @@ func PrintSummary(summary api.TemplateSummaryResources, out io.Writer, useColor
if checks := summary.Checks; len(checks) > 0 {
printer := newPrinter("CHECKS", []string{"Description"})
for _, c := range checks {
id := influxid.ID(c.Id).String()
id := influxid.Encode(c.Id)
var desc string
if c.Description != nil {
desc = *c.Description
@ -75,7 +75,7 @@ func PrintSummary(summary api.TemplateSummaryResources, out io.Writer, useColor
if dashboards := summary.Dashboards; len(dashboards) > 0 {
printer := newPrinter("DASHBOARDS", []string{"Description"})
for _, d := range dashboards {
id := influxid.ID(d.Id).String()
id := influxid.Encode(d.Id)
var desc string
if d.Description != nil {
desc = *d.Description
@ -89,7 +89,7 @@ func PrintSummary(summary api.TemplateSummaryResources, out io.Writer, useColor
if endpoints := summary.NotificationEndpoints; len(endpoints) > 0 {
printer := newPrinter("NOTIFICATION ENDPOINTS", []string{"Description", "Status"})
for _, e := range endpoints {
id := influxid.ID(e.Id).String()
id := influxid.Encode(e.Id)
var desc string
if e.Description != nil {
desc = *e.Description
@ -103,8 +103,8 @@ func PrintSummary(summary api.TemplateSummaryResources, out io.Writer, useColor
if rules := summary.NotificationRules; len(rules) > 0 {
printer := newPrinter("NOTIFICATION RULES", []string{"Description", "Every", "Offset", "Endpoint Name", "Endpoint ID", "Endpoint Type"})
for _, r := range rules {
id := influxid.ID(r.Id).String()
eid := influxid.ID(r.EndpointID).String()
id := influxid.Encode(r.Id)
eid := influxid.Encode(r.EndpointID)
var desc string
if r.Description != nil {
desc = *r.Description
@ -118,7 +118,7 @@ func PrintSummary(summary api.TemplateSummaryResources, out io.Writer, useColor
if tasks := summary.Tasks; len(tasks) > 0 {
printer := newPrinter("TASKS", []string{"Description", "Cycle"})
for _, t := range tasks {
id := influxid.ID(t.Id).String()
id := influxid.Encode(t.Id)
var desc string
if t.Description != nil {
desc = *t.Description
@ -156,7 +156,7 @@ func PrintSummary(summary api.TemplateSummaryResources, out io.Writer, useColor
if vars := summary.Variables; len(vars) > 0 {
printer := newPrinter("VARIABLES", []string{"Description", "Arg Type", "Arg Values"})
for _, v := range vars {
id := influxid.ID(v.Id).String()
id := influxid.Encode(v.Id)
var desc string
if v.Description != nil {
desc = *v.Description
@ -176,8 +176,8 @@ func PrintSummary(summary api.TemplateSummaryResources, out io.Writer, useColor
Title("LABEL ASSOCIATIONS").
SetHeaders("Resource Type", "Resource Name", "Resource ID", "Label Name", "Label ID")
for _, m := range mappings {
rid := influxid.ID(m.ResourceID).String()
lid := influxid.ID(m.LabelID).String()
rid := influxid.Encode(m.ResourceID)
lid := influxid.Encode(m.LabelID)
printer.Append([]string{m.ResourceType, m.ResourceName, rid, m.LabelName, lid})
}
printer.Render()