Skip to content

Commit

Permalink
add: support different basis types for id generation
Browse files Browse the repository at this point in the history
  • Loading branch information
dafanasiev committed Jun 7, 2024
1 parent 7799316 commit 0ffeb38
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 17 deletions.
37 changes: 20 additions & 17 deletions shortuuid.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package shortuuid

import (
"strings"

"github.com/google/uuid"
)

Expand All @@ -18,35 +16,40 @@ type Encoder interface {

// New returns a new UUIDv4, encoded with base57.
func New() string {
return DefaultEncoder.Encode(uuid.New())
rv, err := NewTyped(UUID_v4)
if err != nil {
panic(err)
}
return rv
}

// NewWithEncoder returns a new UUIDv4, encoded with enc.
func NewWithEncoder(enc Encoder) string {
return enc.Encode(uuid.New())
rv, err := NewTypedWithEncoder(UUID_v4, enc)
if err != nil {
panic(err)
}
return rv
}

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

switch {
case name == "":
u = uuid.New()
case strings.HasPrefix(strings.ToLower(name), "http://"):
u = uuid.NewSHA1(uuid.NameSpaceURL, []byte(name))
case strings.HasPrefix(strings.ToLower(name), "https://"):
u = uuid.NewSHA1(uuid.NameSpaceURL, []byte(name))
default:
u = uuid.NewSHA1(uuid.NameSpaceDNS, []byte(name))
rv, err := NewTypedWithNamespace(UUID_v4, UUID_v5, name)
if err != nil {
panic(err)
}

return DefaultEncoder.Encode(u)
return rv
}

// NewWithAlphabet returns a new UUIDv4, encoded with base57 using the
// alternative alphabet abc.
func NewWithAlphabet(abc string) string {
enc := base57{newAlphabet(abc)}
return enc.Encode(uuid.New())
rv, err := NewTypedWithAlphabet(UUID_v4, abc, enc)
if err != nil {
panic(err)
}

return rv
}
8 changes: 8 additions & 0 deletions shortuuid_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,16 @@ var testVector = []struct {
func TestGeneration(t *testing.T) {
tests := []string{
"",
"http",
"http:",
"http:/",
"http_some",
"http://www.example.com/",
"HTTP://www.example.com/",
"https://www.example.com/",
"HTTPS://www.example.com/",
"HttPS://www.example.com/",
"httpS://www.example.com/",
"example.com/",
}

Expand Down
154 changes: 154 additions & 0 deletions shortuuid_typed.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package shortuuid

import (
"fmt"
"github.com/google/uuid"
)

type UnderlyingType int

const (
UUID_v1 UnderlyingType = 0
UUID_v3 UnderlyingType = iota
UUID_v4 UnderlyingType = iota
UUID_v5 UnderlyingType = iota
UUID_v6 UnderlyingType = iota
UUID_v7 UnderlyingType = iota
)

func ut2uuid(ut UnderlyingType) (uuid.UUID, error) {
switch ut {
case UUID_v1:
return uuid.NewUUID()
case UUID_v4:
return uuid.New(), nil
case UUID_v6:
return uuid.NewV6()
case UUID_v7:
return uuid.NewV7()
default:
panic("unknown underlying type")
}
}

// NewTyped returns a new id (based on ut type), encoded with DefaultEncoder.
func NewTyped(ut UnderlyingType) (string, error) {
rv, err := ut2uuid(ut)
if err != nil {
return "", err
}

return DefaultEncoder.Encode(rv), nil
}

// NewTypedWithEncoder returns a new id (based on ut type), encoded with enc.
func NewTypedWithEncoder(ut UnderlyingType, enc Encoder) (string, error) {
rv, err := ut2uuid(ut)
if err != nil {
return "", err
}

return enc.Encode(rv), nil
}

// NewTypedWithNamespace returns a new id (based on ut type and name)
//
// when name is empty id will be based on emptyNameUt,
// otherwise id will be based on ut
func NewTypedWithNamespace(emptyNameUt UnderlyingType, ut UnderlyingType, name string) (string, error) {
nameLen := len(name)

if nameLen == 0 {
rv, err := ut2uuid(emptyNameUt)
if err != nil {
return "", err
}

return DefaultEncoder.Encode(rv), nil
}

ns := (func(name string, nameLen int) uuid.UUID {
//returns namespace by name prefix (case-insensitive compare)
var ch uint8
if nameLen >= len("http://") {
for {
idx := 0
ch = name[idx]
if ch != 'h' && ch != 'H' {
break
}

idx++
ch = name[idx]
if ch != 't' && ch != 'T' {
break
}

idx++
ch = name[idx]
if ch != 't' && ch != 'T' {
break
}

idx++
ch = name[idx]
if ch != 'p' && ch != 'P' {
break
}

idx++
ch = name[idx]
if ch != ':' {
//maybe httpS ?
if nameLen >= len("https://") && (ch == 's' || ch == 'S') {
idx++
ch = name[idx]
if ch != ':' {
break
}
} else {
break
}
}

idx++
ch = name[idx]
if ch != '/' {
break
}

idx++
ch = name[idx]
if ch != '/' {
break
}

// yeah! name starts with "http://" or "https://"
return uuid.NameSpaceURL

Check failure on line 127 in shortuuid_typed.go

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 1.16)

SA4004: the surrounding loop is unconditionally terminated (staticcheck)

Check failure on line 127 in shortuuid_typed.go

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 1.17)

SA4004: the surrounding loop is unconditionally terminated (staticcheck)
}
}

// default is DNS (backward compatibility)
return uuid.NameSpaceDNS
})(name, nameLen)

switch ut {
case UUID_v5:
return DefaultEncoder.Encode(uuid.NewSHA1(ns, []byte(name))), nil
case UUID_v3:
return DefaultEncoder.Encode(uuid.NewMD5(ns, []byte(name))), nil
default:
return "", fmt.Errorf("unsupported underlying type [%v] for non-empty name", ut)
}
}

// NewTypedWithAlphabet returns a new id, encoded with enc using the
// alternative alphabet abc.
func NewTypedWithAlphabet(ut UnderlyingType, abc string, enc Encoder) (string, error) {
rv, err := ut2uuid(ut)
if err != nil {
return "", err
}

return enc.Encode(rv), nil
}

0 comments on commit 0ffeb38

Please sign in to comment.