160 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			160 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package id
 | |
| 
 | |
| import (
 | |
| 	"crypto/rand"
 | |
| 	"encoding/binary"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"time"
 | |
| 
 | |
| 	uuidv8 "github.com/ash3in/uuidv8"
 | |
| 	"github.com/google/uuid"
 | |
| 	nanoid "github.com/matoous/go-nanoid"
 | |
| )
 | |
| 
 | |
| var generatedNode [6]byte
 | |
| 
 | |
| 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
 | |
| var DefaultNanoidSize = 16
 | |
| 
 | |
| type Generator struct {
 | |
| 	opts Options
 | |
| }
 | |
| 
 | |
| func (g *Generator) MustNew() string {
 | |
| 	id, err := g.New()
 | |
| 	if err != nil {
 | |
| 		panic(err)
 | |
| 	}
 | |
| 	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
 | |
| func New(opts ...Option) (string, error) {
 | |
| 	options := NewOptions(opts...)
 | |
| 
 | |
| 	switch options.Type {
 | |
| 	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.NanoidSize <= 0 {
 | |
| 			return "", errors.New("invalid option, NanoidSize must be positive integer")
 | |
| 		}
 | |
| 
 | |
| 		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)
 | |
| 	}
 | |
| 
 | |
| 	return "", errors.New("invalid option, Type unspecified")
 | |
| }
 | |
| 
 | |
| func ToUUID(s string) uuid.UUID {
 | |
| 	return uuid.MustParse(s)
 | |
| }
 | |
| 
 | |
| // Must is the same as New but fatals on error
 | |
| func MustNew(opts ...Option) string {
 | |
| 	id, err := New(opts...)
 | |
| 	if err != nil {
 | |
| 		panic(err)
 | |
| 	}
 | |
| 	return id
 | |
| }
 | |
| 
 | |
| // Options contains id deneration options
 | |
| type Options struct {
 | |
| 	Type           Type
 | |
| 	NanoidAlphabet string
 | |
| 	NanoidSize     int
 | |
| 	UUIDNode       [6]byte
 | |
| }
 | |
| 
 | |
| // Option func signature
 | |
| type Option func(*Options)
 | |
| 
 | |
| // WithNanoidAlphabet specifies alphabet to use
 | |
| func WithNanoidAlphabet(alphabet string) Option {
 | |
| 	return func(o *Options) {
 | |
| 		o.NanoidAlphabet = alphabet
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // WithNanoidSize specifies generated id size
 | |
| func WithNanoidSize(size int) Option {
 | |
| 	return func(o *Options) {
 | |
| 		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
 | |
| func NewOptions(opts ...Option) Options {
 | |
| 	options := Options{
 | |
| 		Type:           TypeUUIDv8,
 | |
| 		NanoidAlphabet: DefaultNanoidAlphabet,
 | |
| 		NanoidSize:     DefaultNanoidSize,
 | |
| 		UUIDNode:       generatedNode,
 | |
| 	}
 | |
| 
 | |
| 	for _, o := range opts {
 | |
| 		o(&options)
 | |
| 	}
 | |
| 
 | |
| 	return options
 | |
| }
 |