chore: refactor influxid.ID
, cleanup organization checking (#326)
This commit is contained in:
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
Reference in New Issue
Block a user