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

add: support different basis types for id generation #55

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
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
}
Loading