util/id: add uuid v8 #382
@ -8,7 +8,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.unistack.org/micro/v3/config"
|
"go.unistack.org/micro/v3/config"
|
||||||
mid "go.unistack.org/micro/v3/util/id"
|
|
||||||
mtime "go.unistack.org/micro/v3/util/time"
|
mtime "go.unistack.org/micro/v3/util/time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -115,8 +114,6 @@ func TestDefault(t *testing.T) {
|
|||||||
|
|
||||||
if conf.IDValue == "" {
|
if conf.IDValue == "" {
|
||||||
t.Fatalf("id value empty")
|
t.Fatalf("id value empty")
|
||||||
} else if len(conf.IDValue) != mid.DefaultSize {
|
|
||||||
t.Fatalf("id value invalid: %s", conf.IDValue)
|
|
||||||
}
|
}
|
||||||
_ = conf
|
_ = conf
|
||||||
// t.Logf("%#+v\n", conf)
|
// t.Logf("%#+v\n", conf)
|
||||||
|
6
go.mod
6
go.mod
@ -1,14 +1,14 @@
|
|||||||
module go.unistack.org/micro/v3
|
module go.unistack.org/micro/v3
|
||||||
|
|
||||||
go 1.22.0
|
go 1.23.4
|
||||||
|
|
||||||
toolchain go1.23.4
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
dario.cat/mergo v1.0.1
|
dario.cat/mergo v1.0.1
|
||||||
github.com/DATA-DOG/go-sqlmock v1.5.0
|
github.com/DATA-DOG/go-sqlmock v1.5.0
|
||||||
github.com/KimMachineGun/automemlimit v0.6.1
|
github.com/KimMachineGun/automemlimit v0.6.1
|
||||||
|
github.com/ash3in/uuidv8 v1.0.1
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
|
github.com/matoous/go-nanoid v1.5.1
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
github.com/silas/dag v0.0.0-20220518035006-a7e85ada93c5
|
github.com/silas/dag v0.0.0-20220518035006-a7e85ada93c5
|
||||||
go.uber.org/automaxprocs v1.6.0
|
go.uber.org/automaxprocs v1.6.0
|
||||||
|
5
go.sum
5
go.sum
@ -4,6 +4,8 @@ github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20O
|
|||||||
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||||
github.com/KimMachineGun/automemlimit v0.6.1 h1:ILa9j1onAAMadBsyyUJv5cack8Y1WT26yLj/V+ulKp8=
|
github.com/KimMachineGun/automemlimit v0.6.1 h1:ILa9j1onAAMadBsyyUJv5cack8Y1WT26yLj/V+ulKp8=
|
||||||
github.com/KimMachineGun/automemlimit v0.6.1/go.mod h1:T7xYht7B8r6AG/AqFcUdc7fzd2bIdBKmepfP2S1svPY=
|
github.com/KimMachineGun/automemlimit v0.6.1/go.mod h1:T7xYht7B8r6AG/AqFcUdc7fzd2bIdBKmepfP2S1svPY=
|
||||||
|
github.com/ash3in/uuidv8 v1.0.1 h1:dIq1XRkWT8lGA7N5s7WRTB4V3k49WTBLvILz7aCLp80=
|
||||||
|
github.com/ash3in/uuidv8 v1.0.1/go.mod h1:EoyUgCtxNBnrnpc9efw5rVN1cQ+LFGCoJiFuD6maOMw=
|
||||||
github.com/cilium/ebpf v0.16.0 h1:+BiEnHL6Z7lXnlGUsXQPPAE7+kenAd4ES8MQ5min0Ok=
|
github.com/cilium/ebpf v0.16.0 h1:+BiEnHL6Z7lXnlGUsXQPPAE7+kenAd4ES8MQ5min0Ok=
|
||||||
github.com/cilium/ebpf v0.16.0/go.mod h1:L7u2Blt2jMM/vLAVgjxluxtBKlz3/GWjB0dMOEngfwE=
|
github.com/cilium/ebpf v0.16.0/go.mod h1:L7u2Blt2jMM/vLAVgjxluxtBKlz3/GWjB0dMOEngfwE=
|
||||||
github.com/containerd/cgroups/v3 v3.0.4 h1:2fs7l3P0Qxb1nKWuJNFiwhp2CqiKzho71DQkDrHJIo4=
|
github.com/containerd/cgroups/v3 v3.0.4 h1:2fs7l3P0Qxb1nKWuJNFiwhp2CqiKzho71DQkDrHJIo4=
|
||||||
@ -40,6 +42,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/matoous/go-nanoid v1.5.1 h1:aCjdvTyO9LLnTIi0fgdXhOPPvOHjpXN6Ik9DaNjIct4=
|
||||||
|
github.com/matoous/go-nanoid v1.5.1/go.mod h1:zyD2a71IubI24efhpvkJz+ZwfwagzgSO6UNiFsZKN7U=
|
||||||
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
|
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
|
||||||
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
|
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
|
||||||
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
|
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
|
||||||
@ -58,6 +62,7 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH
|
|||||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||||
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
|
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||||
github.com/silas/dag v0.0.0-20220518035006-a7e85ada93c5 h1:G/FZtUu7a6NTWl3KUHMV9jkLAh/Rvtf03NWMHaEDl+E=
|
github.com/silas/dag v0.0.0-20220518035006-a7e85ada93c5 h1:G/FZtUu7a6NTWl3KUHMV9jkLAh/Rvtf03NWMHaEDl+E=
|
||||||
github.com/silas/dag v0.0.0-20220518035006-a7e85ada93c5/go.mod h1:7RTUFBdIRC9nZ7/3RyRNH1bdqIShrDejd1YbLwgPS+I=
|
github.com/silas/dag v0.0.0-20220518035006-a7e85ada93c5/go.mod h1:7RTUFBdIRC9nZ7/3RyRNH1bdqIShrDejd1YbLwgPS+I=
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
|
@ -30,7 +30,6 @@ type Options struct {
|
|||||||
StacktraceKey string
|
StacktraceKey string
|
||||||
// Name holds the logger name
|
// Name holds the logger name
|
||||||
Name string
|
Name string
|
||||||
|
|
||||||
// Out holds the output writer
|
// Out holds the output writer
|
||||||
Out io.Writer
|
Out io.Writer
|
||||||
// Context holds exernal options
|
// Context holds exernal options
|
||||||
@ -39,12 +38,10 @@ type Options struct {
|
|||||||
Meter meter.Meter
|
Meter meter.Meter
|
||||||
// TimeFunc used to obtain current time
|
// TimeFunc used to obtain current time
|
||||||
TimeFunc func() time.Time
|
TimeFunc func() time.Time
|
||||||
|
|
||||||
// Fields holds additional metadata
|
// Fields holds additional metadata
|
||||||
Fields []interface{}
|
Fields []interface{}
|
||||||
// ContextAttrFuncs contains funcs that executed before log func on context
|
// ContextAttrFuncs contains funcs that executed before log func on context
|
||||||
ContextAttrFuncs []ContextAttrFunc
|
ContextAttrFuncs []ContextAttrFunc
|
||||||
|
|
||||||
// callerSkipCount number of frmaes to skip
|
// callerSkipCount number of frmaes to skip
|
||||||
CallerSkipCount int
|
CallerSkipCount int
|
||||||
// The logging level the logger should log
|
// The logging level the logger should log
|
||||||
|
@ -2,6 +2,7 @@ package slog
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
@ -10,6 +11,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
"go.unistack.org/micro/v3/logger"
|
"go.unistack.org/micro/v3/logger"
|
||||||
"go.unistack.org/micro/v3/semconv"
|
"go.unistack.org/micro/v3/semconv"
|
||||||
@ -224,6 +226,10 @@ func (s *slogLogger) Error(ctx context.Context, msg string, attrs ...interface{}
|
|||||||
|
|
||||||
func (s *slogLogger) Fatal(ctx context.Context, msg string, attrs ...interface{}) {
|
func (s *slogLogger) Fatal(ctx context.Context, msg string, attrs ...interface{}) {
|
||||||
s.printLog(ctx, logger.FatalLevel, msg, attrs...)
|
s.printLog(ctx, logger.FatalLevel, msg, attrs...)
|
||||||
|
if closer, ok := s.opts.Out.(io.Closer); ok {
|
||||||
|
closer.Close()
|
||||||
|
}
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,7 +276,7 @@ func (s *slogLogger) printLog(ctx context.Context, lvl logger.Level, msg string,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.opts.AddStacktrace && lvl == logger.ErrorLevel {
|
if (s.opts.AddStacktrace || lvl == logger.FatalLevel) || (s.opts.AddStacktrace && lvl == logger.ErrorLevel) {
|
||||||
stackInfo := make([]byte, 1024*1024)
|
stackInfo := make([]byte, 1024*1024)
|
||||||
if stackSize := runtime.Stack(stackInfo, false); stackSize > 0 {
|
if stackSize := runtime.Stack(stackInfo, false); stackSize > 0 {
|
||||||
traceLines := reTrace.Split(string(stackInfo[:stackSize]), -1)
|
traceLines := reTrace.Split(string(stackInfo[:stackSize]), -1)
|
||||||
|
@ -15,6 +15,24 @@ import (
|
|||||||
"go.unistack.org/micro/v3/metadata"
|
"go.unistack.org/micro/v3/metadata"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestStacktrace(t *testing.T) {
|
||||||
|
ctx := context.TODO()
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
l := NewLogger(logger.WithLevel(logger.ErrorLevel), logger.WithOutput(buf),
|
||||||
|
WithHandlerFunc(slog.NewTextHandler),
|
||||||
|
logger.WithAddStacktrace(true),
|
||||||
|
)
|
||||||
|
if err := l.Init(logger.WithFields("key1", "val1")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Error(ctx, "msg1", errors.New("err"))
|
||||||
|
|
||||||
|
if !bytes.Contains(buf.Bytes(), []byte(`slog_test.go:29`)) {
|
||||||
|
t.Fatalf("logger error not works, buf contains: %s", buf.Bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestWithFields(t *testing.T) {
|
func TestWithFields(t *testing.T) {
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
buf := bytes.NewBuffer(nil)
|
buf := bytes.NewBuffer(nil)
|
||||||
|
@ -119,7 +119,7 @@ func Tracer(t tracer.Tracer) Option {
|
|||||||
// NewOptions returns network default options
|
// NewOptions returns network default options
|
||||||
func NewOptions(opts ...Option) Options {
|
func NewOptions(opts ...Option) Options {
|
||||||
options := Options{
|
options := Options{
|
||||||
ID: id.Must(),
|
ID: id.MustNew(),
|
||||||
Name: "go.micro",
|
Name: "go.micro",
|
||||||
Address: ":0",
|
Address: ":0",
|
||||||
Logger: logger.DefaultLogger,
|
Logger: logger.DefaultLogger,
|
||||||
|
@ -164,7 +164,7 @@ func DialWait(b bool) DialOption {
|
|||||||
// NewOptions returns router default options with filled values
|
// NewOptions returns router default options with filled values
|
||||||
func NewOptions(opts ...Option) Options {
|
func NewOptions(opts ...Option) Options {
|
||||||
options := Options{
|
options := Options{
|
||||||
ID: id.Must(),
|
ID: id.MustNew(),
|
||||||
Address: DefaultAddress,
|
Address: DefaultAddress,
|
||||||
Token: DefaultToken,
|
Token: DefaultToken,
|
||||||
Logger: logger.DefaultLogger,
|
Logger: logger.DefaultLogger,
|
||||||
|
@ -80,7 +80,7 @@ func Name(n string) Option {
|
|||||||
// NewOptions returns router default options
|
// NewOptions returns router default options
|
||||||
func NewOptions(opts ...Option) Options {
|
func NewOptions(opts ...Option) Options {
|
||||||
options := Options{
|
options := Options{
|
||||||
ID: id.Must(),
|
ID: id.MustNew(),
|
||||||
Network: DefaultNetwork,
|
Network: DefaultNetwork,
|
||||||
Register: register.DefaultRegister,
|
Register: register.DefaultRegister,
|
||||||
Logger: logger.DefaultLogger,
|
Logger: logger.DefaultLogger,
|
||||||
|
@ -100,7 +100,7 @@ func NewOptions(opts ...Option) Options {
|
|||||||
Address: DefaultAddress,
|
Address: DefaultAddress,
|
||||||
Name: DefaultName,
|
Name: DefaultName,
|
||||||
Version: DefaultVersion,
|
Version: DefaultVersion,
|
||||||
ID: id.Must(),
|
ID: id.MustNew(),
|
||||||
Namespace: DefaultNamespace,
|
Namespace: DefaultNamespace,
|
||||||
GracefulTimeout: DefaultGracefulTimeout,
|
GracefulTimeout: DefaultGracefulTimeout,
|
||||||
}
|
}
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2018-2021 Matous Dzivjak <matousdzivjak@gmail.com>
|
|
||||||
Copyright (c) 2021 Unistack LLC <v.tolstov@unistack.org>
|
|
||||||
|
|
||||||
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.
|
|
154
util/id/id.go
154
util/id/id.go
@ -1,112 +1,154 @@
|
|||||||
package id
|
package id
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"math"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"go.unistack.org/micro/v3/logger"
|
uuidv8 "github.com/ash3in/uuidv8"
|
||||||
|
nanoid "github.com/matoous/go-nanoid"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultAlphabet is the alphabet used for ID characters by default
|
var generatedNode [6]byte
|
||||||
var DefaultAlphabet = []rune("6789BCDFGHJKLMNPQRTWbcdfghjkmnpqrtwz")
|
|
||||||
|
|
||||||
// DefaultSize is the size used for ID by default
|
func init() {
|
||||||
|
if _, err := rand.Read(generatedNode[:]); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Type int
|
||||||
|
|
||||||
|
const (
|
||||||
|
TypeUnspecified Type = iota
|
||||||
|
TypeNanoid
|
||||||
|
TypeUUIDv8
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultNanoidAlphabet is the alphabet used for ID characters by default
|
||||||
|
var DefaultNanoidAlphabet = "6789BCDFGHJKLMNPQRTWbcdfghjkmnpqrtwz"
|
||||||
|
|
||||||
|
// DefaultNanoidSize is the size used for ID by default
|
||||||
// To get uuid like collision specify 21
|
// To get uuid like collision specify 21
|
||||||
var DefaultSize = 16
|
var DefaultNanoidSize = 16
|
||||||
|
|
||||||
// getMask generates bit mask used to obtain bits from the random bytes that are used to get index of random character
|
type Generator struct {
|
||||||
// from the alphabet. Example: if the alphabet has 6 = (110)_2 characters it is sufficient to use mask 7 = (111)_2
|
opts Options
|
||||||
func getMask(alphabetSize int) int {
|
|
||||||
for i := 1; i <= 8; i++ {
|
|
||||||
mask := (2 << uint(i)) - 1
|
|
||||||
if mask >= alphabetSize-1 {
|
|
||||||
return mask
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *Generator) MustNew() string {
|
||||||
|
id, err := g.New()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
}
|
}
|
||||||
return 0
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Generator) New() (string, error) {
|
||||||
|
switch g.opts.Type {
|
||||||
|
case TypeNanoid:
|
||||||
|
if len(g.opts.NanoidAlphabet) == 0 || len(g.opts.NanoidAlphabet) > 255 {
|
||||||
|
return "", errors.New("invalid option, NanoidAlphabet must not be empty and contain no more than 255 chars")
|
||||||
|
}
|
||||||
|
if g.opts.NanoidSize <= 0 {
|
||||||
|
return "", errors.New("invalid option, NanoidSize must be positive integer")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nanoid.Generate(g.opts.NanoidAlphabet, g.opts.NanoidSize)
|
||||||
|
case TypeUUIDv8:
|
||||||
|
timestamp := uint64(time.Now().UnixNano())
|
||||||
|
clockSeq := make([]byte, 2)
|
||||||
|
if _, err := rand.Read(clockSeq); err != nil {
|
||||||
|
return "", fmt.Errorf("failed to generate random clock sequence: %w", err)
|
||||||
|
}
|
||||||
|
clockSeqValue := binary.BigEndian.Uint16(clockSeq) & 0x0FFF // Mask to 12 bits
|
||||||
|
return uuidv8.NewWithParams(timestamp, clockSeqValue, g.opts.UUIDNode[:], uuidv8.TimestampBits48)
|
||||||
|
}
|
||||||
|
return "", errors.New("invalid option, Type unspecified")
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns new id or error
|
// New returns new id or error
|
||||||
func New(opts ...Option) (string, error) {
|
func New(opts ...Option) (string, error) {
|
||||||
options := NewOptions(opts...)
|
options := NewOptions(opts...)
|
||||||
|
|
||||||
if len(options.Alphabet) == 0 || len(options.Alphabet) > 255 {
|
switch options.Type {
|
||||||
return "", errors.New("alphabet must not be empty and contain no more than 255 chars")
|
case TypeNanoid:
|
||||||
|
if len(options.NanoidAlphabet) == 0 || len(options.NanoidAlphabet) > 255 {
|
||||||
|
return "", errors.New("invalid option, NanoidAlphabet must not be empty and contain no more than 255 chars")
|
||||||
}
|
}
|
||||||
if options.Size <= 0 {
|
if options.NanoidSize <= 0 {
|
||||||
return "", errors.New("size must be positive integer")
|
return "", errors.New("invalid option, NanoidSize must be positive integer")
|
||||||
}
|
}
|
||||||
|
|
||||||
chars := options.Alphabet
|
return nanoid.Generate(options.NanoidAlphabet, options.NanoidSize)
|
||||||
|
case TypeUUIDv8:
|
||||||
|
timestamp := uint64(time.Now().UnixNano())
|
||||||
|
clockSeq := make([]byte, 2)
|
||||||
|
if _, err := rand.Read(clockSeq); err != nil {
|
||||||
|
return "", fmt.Errorf("failed to generate random clock sequence: %w", err)
|
||||||
|
}
|
||||||
|
clockSeqValue := binary.BigEndian.Uint16(clockSeq) & 0x0FFF // Mask to 12 bits
|
||||||
|
return uuidv8.NewWithParams(timestamp, clockSeqValue, options.UUIDNode[:], uuidv8.TimestampBits48)
|
||||||
|
}
|
||||||
|
|
||||||
mask := getMask(len(chars))
|
return "", errors.New("invalid option, Type unspecified")
|
||||||
// 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 fatals on error
|
// Must is the same as New but fatals on error
|
||||||
func Must(opts ...Option) string {
|
func MustNew(opts ...Option) string {
|
||||||
id, err := New(opts...)
|
id, err := New(opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.DefaultLogger.Fatal(context.TODO(), "Must call is failed", err)
|
panic(err)
|
||||||
}
|
}
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
// Options contains id deneration options
|
// Options contains id deneration options
|
||||||
type Options struct {
|
type Options struct {
|
||||||
Alphabet []rune
|
Type Type
|
||||||
Size int
|
NanoidAlphabet string
|
||||||
|
NanoidSize int
|
||||||
|
UUIDNode [6]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// Option func signature
|
// Option func signature
|
||||||
type Option func(*Options)
|
type Option func(*Options)
|
||||||
|
|
||||||
// Alphabet specifies alphabet to use
|
// WithNanoidAlphabet specifies alphabet to use
|
||||||
func Alphabet(alphabet string) Option {
|
func WithNanoidAlphabet(alphabet string) Option {
|
||||||
return func(o *Options) {
|
return func(o *Options) {
|
||||||
o.Alphabet = []rune(alphabet)
|
o.NanoidAlphabet = alphabet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Size specifies id size
|
// WithNanoidSize specifies generated id size
|
||||||
func Size(size int) Option {
|
func WithNanoidSize(size int) Option {
|
||||||
return func(o *Options) {
|
return func(o *Options) {
|
||||||
o.Size = size
|
o.NanoidSize = size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithUUIDNode specifies node component for UUIDv8
|
||||||
|
func WithUUIDNode(node [6]byte) Option {
|
||||||
|
return func(o *Options) {
|
||||||
|
o.UUIDNode = node
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewOptions returns new Options struct filled by opts
|
// NewOptions returns new Options struct filled by opts
|
||||||
func NewOptions(opts ...Option) Options {
|
func NewOptions(opts ...Option) Options {
|
||||||
options := Options{
|
options := Options{
|
||||||
Alphabet: DefaultAlphabet,
|
Type: TypeUUIDv8,
|
||||||
Size: DefaultSize,
|
NanoidAlphabet: DefaultNanoidAlphabet,
|
||||||
|
NanoidSize: DefaultNanoidSize,
|
||||||
|
UUIDNode: generatedNode,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, o := range opts {
|
for _, o := range opts {
|
||||||
o(&options)
|
o(&options)
|
||||||
}
|
}
|
||||||
|
|
||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
11
util/id/id_test.go
Normal file
11
util/id/id_test.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package id
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestUUIDv8(t *testing.T) {
|
||||||
|
id, err := New()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Logf("xxx %s\n", id)
|
||||||
|
}
|
@ -113,7 +113,7 @@ func (b *Buffer) Stream() (<-chan *Entry, chan bool) {
|
|||||||
defer b.Unlock()
|
defer b.Unlock()
|
||||||
|
|
||||||
entries := make(chan *Entry, 128)
|
entries := make(chan *Entry, 128)
|
||||||
id := id.Must()
|
id := id.MustNew()
|
||||||
stop := make(chan bool)
|
stop := make(chan bool)
|
||||||
|
|
||||||
b.streams[id] = &Stream{
|
b.streams[id] = &Stream{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user