diff --git a/.gitignore b/.gitignore index 4b7a3f3..e39ad0d 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,6 @@ _testmain.go # govendor /vendor/*/ + +# ide +.idea diff --git a/ksuid.go b/ksuid.go index 79bbe56..d2a4909 100644 --- a/ksuid.go +++ b/ksuid.go @@ -5,6 +5,7 @@ import ( "crypto/rand" "database/sql/driver" "encoding/binary" + "encoding/json" "fmt" "io" "math" @@ -38,8 +39,9 @@ const ( ) // KSUIDs are 20 bytes: -// 00-03 byte: uint32 BE UTC timestamp with custom epoch -// 04-19 byte: random "payload" +// +// 00-03 byte: uint32 BE UTC timestamp with custom epoch +// 04-19 byte: random "payload" type KSUID [byteLength]byte var ( @@ -102,6 +104,11 @@ func (i KSUID) Get() interface{} { return i } +// Equal compares two KSUIDs for equality. It returns true if the two KSUIDs +func (i KSUID) Equal(k2 KSUID) bool { + return bytes.Equal(i[:], k2[:]) +} + // Set satisfies the flag.Value interface, making it possible to use KSUIDs as // part of of the command line options of a program. func (i *KSUID) Set(s string) error { @@ -116,6 +123,10 @@ func (i KSUID) MarshalBinary() ([]byte, error) { return i.Bytes(), nil } +func (i KSUID) MarshalJSON() ([]byte, error) { + return json.Marshal(i.String()) +} + func (i *KSUID) UnmarshalText(b []byte) error { id, err := Parse(string(b)) if err != nil { @@ -134,6 +145,21 @@ func (i *KSUID) UnmarshalBinary(b []byte) error { return nil } +func (i *KSUID) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + + id, err := Parse(s) + if err != nil { + return fmt.Errorf("invalid KSUID: %w", err) + } + + *i = id + return nil +} + // Value converts the KSUID into a SQL driver value which can be used to // directly use the KSUID as parameter to a SQL query. func (i KSUID) Value() (driver.Value, error) { diff --git a/ksuid_test.go b/ksuid_test.go index 3620187..bbf448f 100644 --- a/ksuid_test.go +++ b/ksuid_test.go @@ -2,7 +2,6 @@ package ksuid import ( "bytes" - "encoding/json" "flag" "fmt" "sort" @@ -122,6 +121,20 @@ func TestEncodeAndDecode(t *testing.T) { } } +func TestEqual(t *testing.T) { + x := New() + y := x + + if !x.Equal(y) { + t.Error(x, "!=", y) + } + + z := New() + if x.Equal(z) { + t.Error(x, "==", z) + } +} + func TestMarshalText(t *testing.T) { var id1 = New() var id2 KSUID @@ -164,12 +177,13 @@ func TestMashalJSON(t *testing.T) { var id1 = New() var id2 KSUID - if b, err := json.Marshal(id1); err != nil { - t.Fatal(err) - } else if err := json.Unmarshal(b, &id2); err != nil { - t.Fatal(err) - } else if id1 != id2 { - t.Error(id1, "!=", id2) + b, err := id1.MarshalJSON() + if err != nil { + t.Error("unexpected error on marshal:", err) + } + + if err := id2.UnmarshalJSON(b); err != nil { + t.Error("failed to unmarshal JSON:", err) } } @@ -309,7 +323,7 @@ func TestGetTimestamp(t *testing.T) { x, _ := NewRandomWithTime(nowTime) xTime := int64(x.Timestamp()) unix := nowTime.Unix() - if xTime != unix - epochStamp { + if xTime != unix-epochStamp { t.Fatal(xTime, "!=", unix) } }