Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add UUIDv7 support with encoding options #65

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions shortuuid.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,42 @@ func NewWithAlphabet(abc string) string {
return enc.Encode(uuid.New())
}

// NewV7 returns a new UUIDv7, encoded with base57.
// UUIDv7 is time-based and provides monotonicity, making it ideal for certain database indexing scenarios.
func NewV7() string {
return DefaultEncoder.Encode(uuid.Must(uuid.NewV7()))
}

// NewV7WithEncoder returns a new UUIDv7, encoded with enc.
func NewV7WithEncoder(enc Encoder) string {
return enc.Encode(uuid.Must(uuid.NewV7()))
}

// NewV7WithNamespace returns a new UUIDv5 (or v7 if name is empty), encoded with base57.
func NewV7WithNamespace(name string) string {
var u uuid.UUID

switch {
case name == "":
u = uuid.Must(uuid.NewV7())
case hasPrefixCaseInsensitive(name, "https://"):
u = hashedUUID(uuid.NameSpaceURL, name)
case hasPrefixCaseInsensitive(name, "http://"):
u = hashedUUID(uuid.NameSpaceURL, name)
ashwingopalsamy marked this conversation as resolved.
Show resolved Hide resolved
default:
u = hashedUUID(uuid.NameSpaceDNS, name)
}

return DefaultEncoder.Encode(u)
}

// NewV7WithAlphabet returns a new UUIDv7, encoded with base57 using the
// alternative alphabet abc.
func NewV7WithAlphabet(abc string) string {
enc := encoder{newAlphabet(abc)}
return enc.Encode(uuid.Must(uuid.NewV7()))
}

func hasPrefixCaseInsensitive(s, prefix string) bool {
return len(s) >= len(prefix) && strings.EqualFold(s[:len(prefix)], prefix)
}
Expand Down
91 changes: 91 additions & 0 deletions shortuuid_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,79 @@ func TestAlphabet_MB(t *testing.T) {
}
}

func TestNewV7(t *testing.T) {
shortUUID := NewV7()

decodedUUID, err := DefaultEncoder.Decode(shortUUID)
if err != nil {
t.Fatalf("failed to decode short UUIDv7: %v", err)
}

if decodedUUID.Version() != 7 {
t.Errorf("expected UUID version 7, got %d", decodedUUID.Version())
}

if decodedUUID.Variant() != uuid.RFC4122 {
t.Errorf("expected RFC4122 variant, got %d", decodedUUID.Variant())
}
}

func TestNewV7WithEncoder(t *testing.T) {
customAlphabet := "abcdef0123456789"
enc := encoder{newAlphabet(customAlphabet)}

shortUUID := NewV7WithEncoder(enc)

decodedUUID, err := enc.Decode(shortUUID)
if err != nil {
t.Fatalf("failed to decode short UUIDv7 with custom encoder: %v", err)
}

if decodedUUID.Version() != 7 {
t.Errorf("expected UUID version 7, got %d", decodedUUID.Version())
}
}

func TestNewV7WithNamespace(t *testing.T) {
var tests = []struct {
name string
}{
{"http://www.example.com/"},
{"example.com/"},
{"https://shortuuid.example/"},
{""}, // Empty name for UUIDv7
}
for _, test := range tests {
shortUUID := NewV7WithNamespace(test.name)

decodedUUID, err := DefaultEncoder.Decode(shortUUID)
if err != nil {
t.Fatalf("failed to decode short UUIDv7 with namespace %q: %v", test.name, err)
}

if test.name == "" && decodedUUID.Version() != 7 {
t.Errorf("expected UUID version 7, got %d for empty namespace", decodedUUID.Version())
} else if test.name != "" && decodedUUID.Version() != 5 {
t.Errorf("expected UUID version 5, got %d for namespace %q", decodedUUID.Version(), test.name)
}
}
}

func TestNewV7WithAlphabet(t *testing.T) {
customAlphabet := "abcdefghij12345"
shortUUID := NewV7WithAlphabet(customAlphabet)

enc := encoder{newAlphabet(customAlphabet)}
decodedUUID, err := enc.Decode(shortUUID)
if err != nil {
t.Fatalf("failed to decode short UUIDv7 with custom alphabet: %v", err)
}

if decodedUUID.Version() != 7 {
t.Errorf("expected UUID version 7, got %d", decodedUUID.Version())
}
}

func BenchmarkUUID(b *testing.B) {
for i := 0; i < b.N; i++ {
New()
Expand Down Expand Up @@ -375,3 +448,21 @@ func BenchmarkNewWithNamespaceHttps(b *testing.B) {
_ = NewWithNamespace("https://someaveragelengthurl.test")
}
}

func BenchmarkNewV7(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = NewV7()
}
}

func BenchmarkNewV7WithAlphabet(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = NewV7WithAlphabet("abcdefghij12345")
}
}

func BenchmarkNewV7WithNamespace(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = NewV7WithNamespace("http://example.com/")
}
}