109 lines
2.4 KiB
Go
109 lines
2.4 KiB
Go
|
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
|
||
|
}
|