113 lines
		
	
	
		
			2.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			113 lines
		
	
	
		
			2.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package id // import "go.unistack.org/micro/v3/util/id"
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"crypto/rand"
 | |
| 	"errors"
 | |
| 	"math"
 | |
| 
 | |
| 	"go.unistack.org/micro/v3/logger"
 | |
| )
 | |
| 
 | |
| // DefaultAlphabet is the alphabet used for ID characters by default
 | |
| var DefaultAlphabet = []rune("6789BCDFGHJKLMNPQRTWbcdfghjkmnpqrtwz")
 | |
| 
 | |
| // DefaultSize is the size used for ID by default
 | |
| // To get uuid like collision specify 21
 | |
| 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 fatals on error
 | |
| func Must(opts ...Option) string {
 | |
| 	id, err := New(opts...)
 | |
| 	if err != nil {
 | |
| 		logger.Fatal(context.TODO(), 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
 | |
| }
 |