nanoid

A tiny, secure, URL-friendly, unique string ID generator for Go
git clone git://git.lair.cx/nanoid
Log | Files | Refs | README

commit 301eb851a2f00812b00d470e98cf014e5ce4d4e2
Author: Yongbin Kim <iam@yongbin.kim>
Date:   Mon,  9 Jan 2023 15:30:32 +0000

First commit

Diffstat:
AREADME | 28++++++++++++++++++++++++++++
Ago.mod | 3+++
Ananoid.go | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ananoid_test.go | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 165 insertions(+), 0 deletions(-)

diff --git a/README b/README @@ -0,0 +1,28 @@ +go.lair.cx/nanoid +================= + +This module is a Go port of nanoid. + + +Installation +------------ + + go get -u go.lair.cx/nanoid + + +Usage +----- + +See nanoid_test.go for examples. + + +License +------- + +Public domain. See unlicense.org + + +Report a bug +------------ + +Send me a email to <iam@yongbin.kim>. Thanks! diff --git a/go.mod b/go.mod @@ -0,0 +1,3 @@ +module go.lair.cx/nanoid + +go 1.19 diff --git a/nanoid.go b/nanoid.go @@ -0,0 +1,83 @@ +package nanoid + +import ( + "io" + "sync" + + cryptoRand "crypto/rand" +) + +const ( + characters = "_-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + idSize = 21 +) + +type NanoID [idSize]byte + +type Generator interface { + Generate() NanoID +} + +type generator struct { + mu sync.Mutex + source io.Reader + buf []byte + bufSize int + offset int +} + +func New(n int) Generator { + return newWithReader(cryptoRand.Reader, n) +} + +func NewWithReader(r io.Reader, n int) Generator { + return newWithReader(r, n) +} + +func newWithReader(source io.Reader, length int) Generator { + return &generator{ + source: source, + buf: make([]byte, length*idSize), + bufSize: length * idSize, + offset: length * idSize, + } +} + +func (g *generator) Generate() NanoID { + g.mu.Lock() + defer g.mu.Unlock() + + if g.offset == g.bufSize { + g.source.Read(g.buf) + g.offset = 0 + } + + var id NanoID + + for i := 0; i < idSize; i++ { + id[i] = characters[g.buf[g.offset+i]&0b00111111] + } + g.offset += idSize + + return id +} + +func Generate() NanoID { + return New(1).Generate() +} + +func GenerateN(n int) []NanoID { + gen := New(n) + ids := make([]NanoID, n) + + for i := range ids { + ids[i] = gen.Generate() + } + + return ids +} + +// String returns the string representation of the id. +func (id NanoID) String() string { + return string(id[:]) +} diff --git a/nanoid_test.go b/nanoid_test.go @@ -0,0 +1,51 @@ +package nanoid + +import ( + "math/rand" + "testing" + "time" +) + +const benchBufLength = 21 * 21 * 7 / 21 + +var expectedIDs = []NanoID{ + {0x2d, 0x69, 0x58, 0x30, 0x55, 0x4a, 0x57, 0x5f, 0x2d, 0x68, 0x5a, 0x67, 0x32, 0x70, 0x4e, 0x36, 0x49, 0x64, 0x54, 0x74, 0x51}, + {0x77, 0x7a, 0x49, 0x43, 0x45, 0x58, 0x4f, 0x38, 0x48, 0x39, 0x50, 0x56, 0x6d, 0x62, 0x4a, 0x36, 0x42, 0x73, 0x75, 0x4c, 0x62}, + {0x54, 0x34, 0x66, 0x33, 0x64, 0x69, 0x76, 0x53, 0x37, 0x49, 0x77, 0x33, 0x47, 0x76, 0x66, 0x69, 0x6a, 0x53, 0x36, 0x50, 0x6b}, + {0x63, 0x77, 0x68, 0x65, 0x65, 0x73, 0x46, 0x53, 0x54, 0x4f, 0x61, 0x43, 0x74, 0x4b, 0x32, 0x34, 0x4c, 0x53, 0x56, 0x63, 0x36}, + {0x56, 0x5a, 0x33, 0x55, 0x46, 0x6e, 0x33, 0x4d, 0x67, 0x35, 0x30, 0x71, 0x4d, 0x4e, 0x43, 0x49, 0x52, 0x73, 0x53, 0x6b, 0x51}, + {0x6e, 0x2d, 0x64, 0x62, 0x63, 0x61, 0x52, 0x35, 0x73, 0x55, 0x2d, 0x5a, 0x73, 0x42, 0x74, 0x42, 0x64, 0x77, 0x30, 0x64, 0x57}, + {0x31, 0x69, 0x4e, 0x48, 0x77, 0x6b, 0x59, 0x61, 0x63, 0x78, 0x49, 0x73, 0x36, 0x67, 0x45, 0x74, 0x38, 0x6c, 0x39, 0x39, 0x73}, + {0x4d, 0x69, 0x41, 0x68, 0x69, 0x51, 0x62, 0x48, 0x44, 0x6c, 0x76, 0x45, 0x73, 0x64, 0x6f, 0x41, 0x49, 0x31, 0x57, 0x53, 0x34}, + {0x45, 0x75, 0x2d, 0x47, 0x2d, 0x75, 0x35, 0x75, 0x4a, 0x62, 0x47, 0x45, 0x32, 0x51, 0x67, 0x73, 0x75, 0x56, 0x75, 0x78, 0x4c}, + {0x4a, 0x6b, 0x72, 0x62, 0x65, 0x72, 0x50, 0x32, 0x71, 0x5a, 0x5f, 0x64, 0x38, 0x54, 0x46, 0x63, 0x47, 0x66, 0x47, 0x53, 0x2d}, +} + +func TestNanoID(t *testing.T) { + randomSource := rand.New(rand.NewSource(0)) + gen := NewWithReader(randomSource, 1) + + for _, expectedID := range expectedIDs { + id := gen.Generate() + if id != expectedID { + t.Errorf("Expected %s, got %s", expectedID, id) + } + } +} + +func BenchmarkMathNanoID(b *testing.B) { + gen := NewWithReader(rand.New(rand.NewSource(time.Now().UnixNano())), benchBufLength) + + for i := 0; i < b.N; i++ { + gen.Generate() + } +} + +// BenchmarkNanoID benchmarks the NanoID generation. +func BenchmarkNanoID(b *testing.B) { + gen := New(benchBufLength) + + for i := 0; i < b.N; i++ { + gen.Generate() + } +}