Skip to content

Commit bd6a65c

Browse files
authored
Add IsZero() and fix empty suffix bug (#352)
## Summary - Adds an `IsZero` method that returns true if the typeid has the zero suffix - Fixes bug where parsing allowed for the empty string as a suffix, and then generated a random suffix in it's place. ## How was it tested? Wrote unit tests and ran them.
1 parent f81dd70 commit bd6a65c

File tree

5 files changed

+65
-7
lines changed

5 files changed

+65
-7
lines changed

constructors.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func FromSuffix[T Subtype, PT SubtypePtr[T]](suffix string) (T, error) {
6060
}
6161

6262
prefix := defaultType[T]()
63-
return from[T, PT](prefix, suffix)
63+
return parse[T, PT](prefix, suffix)
6464
}
6565

6666
// FromString parses a TypeID from a string of the form <prefix>_<suffix>
@@ -83,7 +83,7 @@ func Parse[T Subtype, PT SubtypePtr[T]](s string) (T, error) {
8383
var id T
8484
return id, err
8585
}
86-
return from[T, PT](prefix, suffix)
86+
return parse[T, PT](prefix, suffix)
8787
}
8888

8989
func split(id string) (string, string, error) {
@@ -146,6 +146,14 @@ func fromUUID[T Subtype, PT SubtypePtr[T]](prefix, uidStr string) (T, error) {
146146
return nilID, err
147147
}
148148
suffix := base32.Encode(uid)
149+
return parse[T, PT](prefix, suffix)
150+
}
151+
152+
func parse[T Subtype, PT SubtypePtr[T]](prefix string, suffix string) (T, error) {
153+
if suffix == "" {
154+
var id T
155+
return id, errors.New("suffix can't be the empty string")
156+
}
149157
return from[T, PT](prefix, suffix)
150158
}
151159

subtype.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ func (tid *TypeID[P]) init(prefix string, suffix string) {
3232
tid.prefix = prefix
3333
}
3434

35-
// If we're dealing with the "nil" suffix, we don't need to store it.
36-
if suffix != nilSuffix {
35+
// If we're dealing with the "zero" suffix, we don't need to store it.
36+
if suffix != zeroSuffix {
3737
tid.suffix = suffix
3838
}
3939
}

testdata/invalid.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,13 @@
9797
- name: prefix-underscore-end
9898
typeid: "prefix__00000000000000000000000000"
9999
description: "The prefix can't end with an underscore"
100+
101+
# Tests specific to our golang implementation, consider promoting to the
102+
# spec tests
103+
- name: empty
104+
typeid: ""
105+
description: "The empty string is not a valid typeid"
106+
107+
- name: prefix-empty
108+
typeid: "prefix_"
109+
description: "The suffix can't be the empty string"

typeid.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@ func (tid TypeID[P]) Prefix() string {
1919
return defaultPrefix[P]()
2020
}
2121

22-
const nilSuffix = "00000000000000000000000000"
22+
const zeroSuffix = "00000000000000000000000000"
2323

2424
// Suffix returns the suffix of the TypeID in it's canonical base32 representation.
2525
func (tid TypeID[P]) Suffix() string {
26-
// We want to treat the "empty" TypeID as equivalent to the Nil typeid
26+
// We want to treat the "empty" TypeID as equivalent to the 'zero' typeid
2727
if tid.suffix == "" {
28-
return nilSuffix
28+
return zeroSuffix
2929
}
3030
return tid.suffix
3131
}
@@ -59,6 +59,18 @@ func (tid TypeID[P]) UUID() string {
5959
return uuid.FromBytesOrNil(tid.UUIDBytes()).String()
6060
}
6161

62+
// IsZero returns true if the suffix of the TypeID is the zero suffix:
63+
// "00000000000000000000000000"
64+
//
65+
// Note that IsZero() returns true regardless of the prefix value. All
66+
// of these ids would return `IsZero == true`:
67+
// + "prefix_00000000000000000000000000"
68+
// + "test_00000000000000000000000000"
69+
// + "00000000000000000000000000"
70+
func (tid TypeID[P]) IsZero() bool {
71+
return tid.suffix == "" || tid.suffix == zeroSuffix
72+
}
73+
6274
// Must returns a TypeID if the error is nil, otherwise panics.
6375
// Often used with New() to create a TypeID in a single line as follows:
6476
// tid := Must(New("prefix"))

typeid_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,34 @@ func TestNilIsEmpty(t *testing.T) {
3939
assert.Equal(t, nilID.UUIDBytes(), emptyID.UUIDBytes())
4040
}
4141

42+
func TestIsZero(t *testing.T) {
43+
testdata := []struct {
44+
input string
45+
output bool
46+
}{
47+
// IsZero == true values
48+
{"00000000000000000000000000", true},
49+
{"prefix_00000000000000000000000000", true},
50+
{"other_00000000000000000000000000", true},
51+
// IsZero == false values
52+
{"00000000000000000000000001", false},
53+
{"prefix_00000000000000000000000001", false},
54+
{"other_00000000000000000000000001", false},
55+
}
56+
57+
for _, td := range testdata {
58+
t.Run(td.input, func(t *testing.T) {
59+
tid, err := typeid.FromString(td.input)
60+
if err != nil {
61+
t.Error(err)
62+
}
63+
if tid.IsZero() != td.output {
64+
t.Errorf("TypeId.IsZero should be %v for id %s", td.output, td.input)
65+
}
66+
})
67+
}
68+
}
69+
4270
func TestInvalidPrefix(t *testing.T) {
4371
testdata := []struct {
4472
name string

0 commit comments

Comments
 (0)