diff --git a/auth/auth.go b/auth/auth.go index 50bb6c02..c66e080c 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -55,7 +55,7 @@ type Auth interface { type Account struct { // Metadata any other associated metadata Metadata metadata.Metadata `json:"metadata"` - // ID of the account e.g. email or uuid + // ID of the account e.g. email or id ID string `json:"id"` // Type of the account, e.g. service Type string `json:"type"` diff --git a/auth/noop.go b/auth/noop.go index 8c86fc73..386ff5b5 100644 --- a/auth/noop.go +++ b/auth/noop.go @@ -1,7 +1,7 @@ package auth import ( - "github.com/google/uuid" + "github.com/unistack-org/micro/v3/util/id" ) type noopAuth struct { @@ -61,11 +61,11 @@ func (n *noopAuth) Verify(acc *Account, res *Resource, opts ...VerifyOption) err // Inspect a token func (n *noopAuth) Inspect(token string) (*Account, error) { - uid, err := uuid.NewRandom() + id, err := id.New() if err != nil { return nil, err } - return &Account{ID: uid.String(), Issuer: n.Options().Issuer}, nil + return &Account{ID: id, Issuer: n.Options().Issuer}, nil } // Token generation using an account id and secret diff --git a/broker/memory.go b/broker/memory.go index 39eaaa1c..d1adc72c 100644 --- a/broker/memory.go +++ b/broker/memory.go @@ -4,10 +4,10 @@ import ( "context" "sync" - "github.com/google/uuid" "github.com/unistack-org/micro/v3/logger" "github.com/unistack-org/micro/v3/metadata" maddr "github.com/unistack-org/micro/v3/util/addr" + "github.com/unistack-org/micro/v3/util/id" mnet "github.com/unistack-org/micro/v3/util/net" "github.com/unistack-org/micro/v3/util/rand" ) @@ -224,7 +224,7 @@ func (m *memoryBroker) BatchSubscribe(ctx context.Context, topic string, handler } m.RUnlock() - id, err := uuid.NewRandom() + sid, err := id.New() if err != nil { return nil, err } @@ -233,7 +233,7 @@ func (m *memoryBroker) BatchSubscribe(ctx context.Context, topic string, handler sub := &memorySubscriber{ exit: make(chan bool, 1), - id: id.String(), + id: sid, topic: topic, batchhandler: handler, opts: options, @@ -269,7 +269,7 @@ func (m *memoryBroker) Subscribe(ctx context.Context, topic string, handler Hand } m.RUnlock() - id, err := uuid.NewRandom() + sid, err := id.New() if err != nil { return nil, err } @@ -278,7 +278,7 @@ func (m *memoryBroker) Subscribe(ctx context.Context, topic string, handler Hand sub := &memorySubscriber{ exit: make(chan bool, 1), - id: id.String(), + id: sid, topic: topic, handler: handler, opts: options, diff --git a/errors/errors.go b/errors/errors.go index 7cba5424..924296e0 100644 --- a/errors/errors.go +++ b/errors/errors.go @@ -37,7 +37,7 @@ var ( // Error type type Error struct { - // Id holds error id or service, usually someting like my_service or uuid + // Id holds error id or service, usually someting like my_service or id Id string // Detail holds some useful details about error Detail string diff --git a/flow/default.go b/flow/default.go index 5b20b2df..3cfc1c84 100644 --- a/flow/default.go +++ b/flow/default.go @@ -6,13 +6,13 @@ import ( "path/filepath" "sync" - "github.com/google/uuid" "github.com/silas/dag" "github.com/unistack-org/micro/v3/client" "github.com/unistack-org/micro/v3/codec" "github.com/unistack-org/micro/v3/logger" "github.com/unistack-org/micro/v3/metadata" "github.com/unistack-org/micro/v3/store" + "github.com/unistack-org/micro/v3/util/id" ) type microFlow struct { @@ -149,18 +149,18 @@ func (w *microWorkflow) getSteps(start string, reverse bool) ([][]Step, error) { return steps, nil } -func (w *microWorkflow) Abort(ctx context.Context, eid string) error { - workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", eid)) +func (w *microWorkflow) Abort(ctx context.Context, id string) error { + workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", id)) return workflowStore.Write(ctx, "status", &codec.Frame{Data: []byte(StatusAborted.String())}) } -func (w *microWorkflow) Suspend(ctx context.Context, eid string) error { - workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", eid)) +func (w *microWorkflow) Suspend(ctx context.Context, id string) error { + workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", id)) return workflowStore.Write(ctx, "status", &codec.Frame{Data: []byte(StatusSuspend.String())}) } -func (w *microWorkflow) Resume(ctx context.Context, eid string) error { - workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", eid)) +func (w *microWorkflow) Resume(ctx context.Context, id string) error { + workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", id)) return workflowStore.Write(ctx, "status", &codec.Frame{Data: []byte(StatusRunning.String())}) } @@ -176,14 +176,13 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...Execu } w.Unlock() - uid, err := uuid.NewRandom() + id, err := id.New() if err != nil { return "", err } - eid := uid.String() - stepStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("steps", eid)) - workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", eid)) + stepStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("steps", id)) + workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", id)) options := NewExecuteOptions(opts...) @@ -215,13 +214,13 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...Execu if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusRunning.String())}); werr != nil { w.opts.Logger.Errorf(w.opts.Context, "store error: %v", werr) - return eid, werr + return id, werr } for idx := range steps { for nidx := range steps[idx] { cstep := steps[idx][nidx] if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "status"), &codec.Frame{Data: []byte(StatusPending.String())}); werr != nil { - return eid, werr + return id, werr } } } @@ -317,7 +316,7 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...Execu }() if options.Async { - return eid, nil + return id, nil } logger.Tracef(ctx, "wait for finish or error") diff --git a/flow/flow.go b/flow/flow.go index 1dee697e..369a0f8a 100644 --- a/flow/flow.go +++ b/flow/flow.go @@ -116,11 +116,11 @@ type Workflow interface { // Steps returns steps slice where parallel steps returned on the same level Steps() ([][]Step, error) // Suspend suspends execution - Suspend(ctx context.Context, eid string) error + Suspend(ctx context.Context, id string) error // Resume resumes execution - Resume(ctx context.Context, eid string) error + Resume(ctx context.Context, id string) error // Abort abort execution - Abort(ctx context.Context, eid string) error + Abort(ctx context.Context, id string) error } // Flow the base interface to interact with workflows diff --git a/go.mod b/go.mod index 98deba5c..e550f876 100644 --- a/go.mod +++ b/go.mod @@ -5,10 +5,10 @@ go 1.16 require ( github.com/ef-ds/deque v1.0.4 github.com/golang-jwt/jwt/v4 v4.0.0 - github.com/google/uuid v1.3.0 github.com/imdario/mergo v0.3.12 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/silas/dag v0.0.0-20210121180416-41cf55125c34 + github.com/stretchr/testify v1.7.0 github.com/unistack-org/micro-proto v0.0.5 golang.org/x/net v0.0.0-20210510120150-4163338589ed ) diff --git a/go.sum b/go.sum index 0af524ae..0dcf34d5 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/ef-ds/deque v1.0.4 h1:iFAZNmveMT9WERAkqLJ+oaABF9AcVQ5AjXem/hroniI= github.com/ef-ds/deque v1.0.4/go.mod h1:gXDnTC3yqvBcHbq2lcExjtAcVrOnJCbMcZXmuj8Z4tg= github.com/golang-jwt/jwt/v4 v4.0.0 h1:RAqyYixv1p7uEnocuy8P1nru5wprCh/MH2BIlW5z5/o= @@ -5,14 +7,17 @@ github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/silas/dag v0.0.0-20210121180416-41cf55125c34 h1:vBfVmA5mZhsQa2jr1FOL9nfA37N/jnbBmi5XUfviVTI= github.com/silas/dag v0.0.0-20210121180416-41cf55125c34/go.mod h1:7RTUFBdIRC9nZ7/3RyRNH1bdqIShrDejd1YbLwgPS+I= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/unistack-org/micro-proto v0.0.5 h1:DIC97Hufa2nGjuvTsfToD9laEOKddWMRTzeCfBwJ1j8= github.com/unistack-org/micro-proto v0.0.5/go.mod h1:EuI7UlfGXmT1hy6WacULib9LbNgRnDYQvTCFoLgKM2I= golang.org/x/net v0.0.0-20210510120150-4163338589ed h1:p9UgmWI9wKpfYmgaV/IZKGdXc5qEK45tDwwwDyjS26I= @@ -27,6 +32,9 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/network/options.go b/network/options.go index c508e5b9..91d337cb 100644 --- a/network/options.go +++ b/network/options.go @@ -1,13 +1,13 @@ package network import ( - "github.com/google/uuid" "github.com/unistack-org/micro/v3/logger" "github.com/unistack-org/micro/v3/meter" "github.com/unistack-org/micro/v3/network/tunnel" "github.com/unistack-org/micro/v3/proxy" "github.com/unistack-org/micro/v3/router" "github.com/unistack-org/micro/v3/tracer" + "github.com/unistack-org/micro/v3/util/id" ) // Option func @@ -119,7 +119,7 @@ func Tracer(t tracer.Tracer) Option { // NewOptions returns network default options func NewOptions(opts ...Option) Options { options := Options{ - Id: uuid.New().String(), + Id: id.Must(), Name: "go.micro", Address: ":0", Logger: logger.DefaultLogger, diff --git a/network/tunnel/options.go b/network/tunnel/options.go index 552cb62e..86cc1faa 100644 --- a/network/tunnel/options.go +++ b/network/tunnel/options.go @@ -3,11 +3,11 @@ package tunnel import ( "time" - "github.com/google/uuid" "github.com/unistack-org/micro/v3/logger" "github.com/unistack-org/micro/v3/meter" "github.com/unistack-org/micro/v3/network/transport" "github.com/unistack-org/micro/v3/tracer" + "github.com/unistack-org/micro/v3/util/id" ) var ( @@ -164,7 +164,7 @@ func DialWait(b bool) DialOption { // NewOptions returns router default options with filled values func NewOptions(opts ...Option) Options { options := Options{ - ID: uuid.New().String(), + ID: id.Must(), Address: DefaultAddress, Token: DefaultToken, Logger: logger.DefaultLogger, diff --git a/register/memory.go b/register/memory.go index 3e6bfb23..774c3b8a 100644 --- a/register/memory.go +++ b/register/memory.go @@ -6,8 +6,8 @@ import ( "sync" "time" - "github.com/google/uuid" "github.com/unistack-org/micro/v3/logger" + "github.com/unistack-org/micro/v3/util/id" ) var ( @@ -378,13 +378,16 @@ func (m *memory) ListServices(ctx context.Context, opts ...ListOption) ([]*Servi } func (m *memory) Watch(ctx context.Context, opts ...WatchOption) (Watcher, error) { + id, err := id.New() + if err != nil { + return nil, err + } wo := NewWatchOptions(opts...) - // construct the watcher w := &watcher{ exit: make(chan bool), res: make(chan *Result), - id: uuid.New().String(), + id: id, wo: wo, } diff --git a/router/options.go b/router/options.go index 4a57b930..42b2b45f 100644 --- a/router/options.go +++ b/router/options.go @@ -3,9 +3,9 @@ package router import ( "context" - "github.com/google/uuid" "github.com/unistack-org/micro/v3/logger" "github.com/unistack-org/micro/v3/register" + "github.com/unistack-org/micro/v3/util/id" ) // Options are router options @@ -80,7 +80,7 @@ func Name(n string) Option { // NewOptions returns router default options func NewOptions(opts ...Option) Options { options := Options{ - Id: uuid.New().String(), + Id: id.Must(), Network: DefaultNetwork, Register: register.DefaultRegister, Logger: logger.DefaultLogger, diff --git a/server/server.go b/server/server.go index 5e2815f7..ff97c0ee 100644 --- a/server/server.go +++ b/server/server.go @@ -5,10 +5,10 @@ import ( "context" "time" - "github.com/google/uuid" "github.com/unistack-org/micro/v3/codec" "github.com/unistack-org/micro/v3/metadata" "github.com/unistack-org/micro/v3/register" + "github.com/unistack-org/micro/v3/util/id" ) // DefaultServer default server @@ -22,7 +22,7 @@ var ( // DefaultVersion will be used if no version passed DefaultVersion = "latest" // DefaultID will be used if no id passed - DefaultID = uuid.New().String() + DefaultID = id.Must() // DefaultRegisterCheck holds func that run before register server DefaultRegisterCheck = func(context.Context) error { return nil } // DefaultRegisterInterval holds interval for register diff --git a/util/auth/auth.go b/util/auth/auth.go index b185de4e..b3889403 100644 --- a/util/auth/auth.go +++ b/util/auth/auth.go @@ -4,9 +4,9 @@ import ( "context" "time" - "github.com/google/uuid" "github.com/unistack-org/micro/v3/auth" "github.com/unistack-org/micro/v3/logger" + "github.com/unistack-org/micro/v3/util/id" ) // Verify the auth credentials and refresh the auth token periodically @@ -22,7 +22,11 @@ func Verify(a auth.Auth) error { auth.WithScopes("service"), } - acc, err := a.Generate(uuid.New().String(), opts...) + id, err := id.New() + if err != nil { + return err + } + acc, err := a.Generate(id, opts...) if err != nil { return err } diff --git a/util/id/LICENSE b/util/id/LICENSE new file mode 100644 index 00000000..fec90ede --- /dev/null +++ b/util/id/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2018-2021 Matous Dzivjak +Copyright (c) 2021 Unistack LLC + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/util/id/id.go b/util/id/id.go new file mode 100644 index 00000000..3e64769e --- /dev/null +++ b/util/id/id.go @@ -0,0 +1,108 @@ +package id + +import ( + "crypto/rand" + "errors" + "math" +) + +// DefaultAlphabet is the alphabet used for ID characters by default +var DefaultAlphabet = []rune("6789BCDFGHJKLMNPQRTWbcdfghjkmnpqrtwz") + +// DefaultSize is the size used for ID by default +var DefaultSize = 16 + +// getMask generates bit mask used to obtain bits from the random bytes that are used to get index of random character +// from the alphabet. Example: if the alphabet has 6 = (110)_2 characters it is sufficient to use mask 7 = (111)_2 +func getMask(alphabetSize int) int { + for i := 1; i <= 8; i++ { + mask := (2 << uint(i)) - 1 + if mask >= alphabetSize-1 { + return mask + } + } + return 0 +} + +// New returns new id or error +func New(opts ...Option) (string, error) { + options := NewOptions(opts...) + + if len(options.Alphabet) == 0 || len(options.Alphabet) > 255 { + return "", errors.New("alphabet must not be empty and contain no more than 255 chars") + } + if options.Size <= 0 { + return "", errors.New("size must be positive integer") + } + + chars := options.Alphabet + + mask := getMask(len(chars)) + // estimate how many random bytes we will need for the ID, we might actually need more but this is tradeoff + // between average case and worst case + ceilArg := 1.6 * float64(mask*options.Size) / float64(len(options.Alphabet)) + step := int(math.Ceil(ceilArg)) + + id := make([]rune, options.Size) + bytes := make([]byte, step) + for j := 0; ; { + _, err := rand.Read(bytes) + if err != nil { + return "", err + } + for i := 0; i < step; i++ { + currByte := bytes[i] & byte(mask) + if currByte < byte(len(chars)) { + id[j] = chars[currByte] + j++ + if j == options.Size { + return string(id[:options.Size]), nil + } + } + } + } +} + +// Must is the same as New but panics on error +func Must(opts ...Option) string { + id, err := New(opts...) + if err != nil { + panic(err) + } + return id +} + +// Options contains id deneration options +type Options struct { + Alphabet []rune + Size int +} + +// Option func signature +type Option func(*Options) + +// Alphabet specifies alphabet to use +func Alphabet(alphabet string) Option { + return func(o *Options) { + o.Alphabet = []rune(alphabet) + } +} + +// Size specifies id size +func Size(size int) Option { + return func(o *Options) { + o.Size = size + } +} + +// NewOptions returns new Options struct filled by opts +func NewOptions(opts ...Option) Options { + options := Options{ + Alphabet: DefaultAlphabet, + Size: DefaultSize, + } + for _, o := range opts { + o(&options) + } + return options +} diff --git a/util/id/id_internal_test.go b/util/id/id_internal_test.go new file mode 100644 index 00000000..88091a95 --- /dev/null +++ b/util/id/id_internal_test.go @@ -0,0 +1,41 @@ +package id + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestHasNoCollisions(t *testing.T) { + tries := 100_000 + used := make(map[string]bool, tries) + for i := 0; i < tries; i++ { + id := Must() + require.False(t, used[id], "shouldn't return colliding IDs") + used[id] = true + } +} + +func TestFlatDistribution(t *testing.T) { + tries := 100_000 + alphabet := "abcdefghij" + size := 10 + chars := make(map[rune]int) + for i := 0; i < tries; i++ { + id := Must(Alphabet(alphabet), Size(size)) + for _, r := range id { + chars[r]++ + } + } + + for _, count := range chars { + require.InEpsilon(t, size*tries/len(alphabet), count, .01, "should have flat distribution") + } +} + +// Benchmark id generator +func BenchmarkNanoid(b *testing.B) { + for n := 0; n < b.N; n++ { + _, _ = New() + } +} diff --git a/util/id/id_test.go b/util/id/id_test.go new file mode 100644 index 00000000..e2f0c7f1 --- /dev/null +++ b/util/id/id_test.go @@ -0,0 +1,68 @@ +package id_test + +import ( + "strings" + "testing" + "unicode/utf8" + + "github.com/stretchr/testify/assert" + id "github.com/unistack-org/micro/v3/util/id" +) + +func TestGenerate(t *testing.T) { + t.Run("short alphabet", func(t *testing.T) { + alphabet := "" + _, err := id.New(id.Alphabet(alphabet), id.Size(32)) + assert.Error(t, err, "should return error if the alphabet is too small") + }) + + t.Run("long alphabet", func(t *testing.T) { + alphabet := strings.Repeat("a", 256) + _, err := id.New(id.Alphabet(alphabet), id.Size(32)) + assert.Error(t, err, "should return error if the alphabet is too long") + }) + + t.Run("negative ID length", func(t *testing.T) { + _, err := id.New(id.Alphabet("abcdef"), id.Size(-1)) + assert.Error(t, err, "should return error if the requested ID length is invalid") + }) + + t.Run("happy path", func(t *testing.T) { + alphabet := "abcdef" + id, err := id.New(id.Alphabet(alphabet), id.Size(6)) + assert.NoError(t, err, "shouldn't return error") + assert.Len(t, id, 6, "should return ID of requested length") + for _, r := range id { + assert.True(t, strings.ContainsRune(alphabet, r), "should use given alphabet") + } + }) + + t.Run("works with unicode", func(t *testing.T) { + alphabet := "🚀💩🦄🤖" + id, err := id.New(id.Alphabet(alphabet), id.Size(6)) + assert.NoError(t, err, "shouldn't return error") + assert.Equal(t, utf8.RuneCountInString(id), 6, "should return ID of requested length") + for _, r := range id { + assert.True(t, strings.ContainsRune(alphabet, r), "should use given alphabet") + } + }) +} + +func TestNew(t *testing.T) { + t.Run("negative ID length", func(t *testing.T) { + _, err := id.New(id.Size(-1)) + assert.Error(t, err, "should return error if the requested ID length is invalid") + }) + + t.Run("happy path", func(t *testing.T) { + nid, err := id.New() + assert.NoError(t, err, "shouldn't return error") + assert.Len(t, nid, id.DefaultSize, "should return ID of default length") + }) + + t.Run("custom length", func(t *testing.T) { + id, err := id.New(id.Size(6)) + assert.NoError(t, err, "shouldn't return error") + assert.Len(t, id, 6, "should return ID of requested length") + }) +} diff --git a/util/pool/default.go b/util/pool/default.go index 20878127..f8ff2ae4 100644 --- a/util/pool/default.go +++ b/util/pool/default.go @@ -5,8 +5,8 @@ import ( "sync" "time" - "github.com/google/uuid" "github.com/unistack-org/micro/v3/network/transport" + "github.com/unistack-org/micro/v3/util/id" ) type pool struct { @@ -87,9 +87,13 @@ func (p *pool) Get(ctx context.Context, addr string, opts ...transport.DialOptio if err != nil { return nil, err } + id, err := id.New() + if err != nil { + return nil, err + } return &poolConn{ Client: c, - id: uuid.New().String(), + id: id, created: time.Now(), }, nil } diff --git a/util/ring/buffer.go b/util/ring/buffer.go index ed18d20a..c3cbd07c 100644 --- a/util/ring/buffer.go +++ b/util/ring/buffer.go @@ -5,7 +5,7 @@ import ( "sync" "time" - "github.com/google/uuid" + "github.com/unistack-org/micro/v3/util/id" ) // Buffer is ring buffer @@ -112,7 +112,7 @@ func (b *Buffer) Stream() (<-chan *Entry, chan bool) { defer b.Unlock() entries := make(chan *Entry, 128) - id := uuid.New().String() + id := id.Must() stop := make(chan bool) b.streams[id] = &Stream{ diff --git a/util/token/basic/basic.go b/util/token/basic/basic.go index 8c1e0baf..b0c002fd 100644 --- a/util/token/basic/basic.go +++ b/util/token/basic/basic.go @@ -6,9 +6,9 @@ import ( "fmt" "time" - "github.com/google/uuid" "github.com/unistack-org/micro/v3/auth" "github.com/unistack-org/micro/v3/store" + "github.com/unistack-org/micro/v3/util/id" "github.com/unistack-org/micro/v3/util/token" ) @@ -44,7 +44,11 @@ func (b *Basic) Generate(acc *auth.Account, opts ...token.GenerateOption) (*toke } // write to the store - key := uuid.New().String() + key, err := id.New() + if err !=nil { + return nil, err + } + err = b.store.Write(context.Background(), fmt.Sprintf("%v%v", StorePrefix, key), bytes, store.WriteTTL(options.Expiry)) if err != nil { return nil, err