Skip to content

Commit

Permalink
Add some functionality to xid (#34)
Browse files Browse the repository at this point in the history
* Add `NilID` function for getting a zero value for `xid.ID`
* Add `ID.Bytes` and `FromBytes` functions for converting between `ID` and `[]byte`
* Add `ID.Compare` method
`ID.Compare` method behaves just like `bytes.Compare`.
* Add `Sort` function for sorting `ID` array

Under the hood we implement `sort.Interface` on wrapper type `sorter` of `[]ID`, then
use `sort.Sort` to do the heavy lifting.
  • Loading branch information
QuantumGhost authored and rs committed May 18, 2018
1 parent 1ed2fb9 commit 12be76b
Show file tree
Hide file tree
Showing 2 changed files with 154 additions and 0 deletions.
50 changes: 50 additions & 0 deletions id.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ import (
"os"
"sync/atomic"
"time"
"bytes"
"sort"
)

// Code inspired from mgo/bson ObjectId
Expand Down Expand Up @@ -309,3 +311,51 @@ func (id *ID) Scan(value interface{}) (err error) {
func (id ID) IsNil() bool {
return id == nilID
}

// NilID returns a zero value for `xid.ID`.
func NilID() ID {
return nilID
}

// Bytes returns the byte array representation of `ID`
func (id ID) Bytes() []byte {
return id[:]
}

// FromBytes convert the byte array representation of `ID` back to `ID`
func FromBytes(b []byte) (ID, error) {
var id ID
if len(b) != rawLen {
return id, ErrInvalidID
}
copy(id[:], b)
return id, nil
}

// Compare returns an integer comparing two IDs. It behaves just like `bytes.Compare`.
// The result will be 0 if two IDs are identical, -1 if current id is less than the other one,
// and 1 if current id is greater than the other.
func (id ID) Compare(other ID) int {
return bytes.Compare(id[:], other[:])
}


type sorter []ID

func (s sorter) Len() int {
return len(s)
}

func (s sorter) Less(i, j int) bool {
return s[i].Compare(s[j]) < 0
}

func (s sorter) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}

// Sort sorts an array of IDs inplace.
// It works by wrapping `[]ID` and use `sort.Sort`.
func Sort(ids []ID) {
sort.Sort(sorter(ids))
}
104 changes: 104 additions & 0 deletions id_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,3 +218,107 @@ func TestID_IsNil(t *testing.T) {
assert.Equal(t, tt.id.IsNil(), tt.want)
}
}

func TestNilID(t *testing.T) {
var id ID
nilid := NilID()
assert.Equal(t, id, nilid)
}

func TestNilID_IsNil(t *testing.T) {
assert.True(t, NilID().IsNil())
}

func TestID_Bytes(t *testing.T) {
id := New()
underlying := [rawLen]byte(id)
b := id.Bytes()
for i := range underlying {
assert.Equal(t, underlying[i], b[i])
}
}

func TestFromBytes_Invariant(t *testing.T) {
id := New()
b, err := FromBytes(id.Bytes())
assert.NoError(t, err)
assert.Equal(t, b, id)
}

func TestFromBytes_InvalidBytes(t *testing.T) {
cases := []struct{
length int; shouldFail bool} {
{11, true},
{12, false},
{13, true},
}
for _, c := range cases {
b := make([]byte, c.length, c.length)
_, err := FromBytes(b)
if c.shouldFail {
assert.Error(t, err, "Length %d should fail.", c.length)
} else {
assert.NoError(t, err, "Length %d should not fail.", c.length)
}
}
}

func TestID_Compare(t *testing.T) {
pairs := []struct{
left ID
right ID
expected int
} {
{IDs[1].id, IDs[0].id, -1},
{ID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, IDs[2].id, -1},
{IDs[0].id, IDs[0].id, 0},
}
for _, p := range pairs {
assert.Equal(
t, p.expected, p.left.Compare(p.right),
"%s Compare to %s should return %d", p.left, p.right, p.expected,
)
assert.Equal(
t, -1 * p.expected, p.right.Compare(p.left),
"%s Compare to %s should return %d", p.right, p.left, - 1 * p.expected,
)
}
}

var IDList = []ID{IDs[0].id, IDs[1].id, IDs[2].id}


func TestSorter_Len(t *testing.T) {
assert.Equal(t, 0, sorter([]ID{}).Len())
assert.Equal(t, 3, sorter(IDList).Len())
}


func TestSorter_Less(t *testing.T) {
sorter := sorter(IDList)
assert.True(t, sorter.Less(1, 0))
assert.False(t, sorter.Less(2, 1))
assert.False(t, sorter.Less(0, 0))
}

func TestSorter_Swap(t *testing.T) {
ids := make([]ID, 0)
for _, id := range IDList {
ids = append(ids, id)
}
sorter := sorter(ids)
sorter.Swap(0, 1)
assert.Equal(t, ids[0], IDList[1])
assert.Equal(t, ids[1], IDList[0])
sorter.Swap(2, 2)
assert.Equal(t, ids[2], IDList[2])
}

func TestSort(t *testing.T) {
ids := make([]ID, 0)
for _, id := range IDList {
ids = append(ids, id)
}
Sort(ids)
assert.Equal(t, ids, []ID{IDList[1], IDList[2], IDList[0]})
}

0 comments on commit 12be76b

Please sign in to comment.