├── go.mod ├── SECURITY.md ├── nanoid_example_test.go ├── LICENSE ├── README.md ├── nanoid.go ├── .gitignore ├── .github └── workflows │ └── codeql-analysis.yml └── nanoid_test.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/aidarkhanov/nanoid/v2 2 | 3 | go 1.14 4 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | | Version | Supported | 6 | | ------- | ------------------ | 7 | | 2.0.x | :white_check_mark: | 8 | | 1.0.x | :white_check_mark: | 9 | 10 | ## Reporting a Vulnerability 11 | 12 | In case of a vulnerability, open a security advisory on GitHub for this repository. 13 | -------------------------------------------------------------------------------- /nanoid_example_test.go: -------------------------------------------------------------------------------- 1 | package nanoid_test 2 | 3 | import ( 4 | "crypto/rand" 5 | "fmt" 6 | "log" 7 | 8 | "github.com/aidarkhanov/nanoid/v2" 9 | ) 10 | 11 | func ExampleFormatString() { 12 | alphabet := nanoid.DefaultAlphabet 13 | size := nanoid.DefaultSize 14 | 15 | // generateBytesBuffer returns random bytes buffer 16 | generateBytesBuffer := func(step int) ([]byte, error) { 17 | buffer := make([]byte, step) 18 | if _, err := rand.Read(buffer); err != nil { 19 | return nil, err 20 | } 21 | 22 | return buffer, nil 23 | } 24 | 25 | id, err := nanoid.FormatString(generateBytesBuffer, alphabet, size) 26 | if err != nil { 27 | log.Fatalln(err) 28 | } 29 | 30 | fmt.Println(id) 31 | } 32 | 33 | func ExampleGenerateString() { 34 | alphabet := nanoid.DefaultAlphabet 35 | size := nanoid.DefaultSize 36 | 37 | id, err := nanoid.GenerateString(alphabet, size) 38 | if err != nil { 39 | log.Fatalln(err) 40 | } 41 | 42 | fmt.Println(id) 43 | } 44 | 45 | func ExampleNew() { 46 | id, err := nanoid.New() 47 | if err != nil { 48 | log.Fatalln(err) 49 | } 50 | 51 | fmt.Println(id) 52 | } 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Dair Aidarkhanov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nano ID [![GoDoc](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-round)](https://pkg.go.dev/github.com/aidarkhanov/nanoid/v2) 2 | 3 | ### A tiny and fast _Go_ unique string generator 4 | 5 | * __Safe__. It uses cryptographically strong random APIs and tests distribution of symbols. 6 | * __Compact__. It uses a larger alphabet than UUID `(A-Za-z0-9_-)`. So ID size was reduced from 36 to 21 symbols. 7 | 8 | ```go 9 | id, err := nanoid.New() //> "i25_rX9zwDdDn7Sg-ZoaH" 10 | if err != nil { 11 | log.Fatalln(err) 12 | } 13 | ``` 14 | 15 | ### Installation 16 | 17 | Once Go is installed, run the following command to get Nano ID. 18 | 19 | ```sh 20 | go get github.com/aidarkhanov/nanoid/v2 21 | ``` 22 | 23 | ### Documentation 24 | 25 | The package reference is located at [pkg.go.dev/github.com/aidarkhanov/nanoid/v2](https://pkg.go.dev/github.com/aidarkhanov/nanoid/v2). 26 | 27 | ### Roadmap 28 | 29 | * The API of this package is frozen. 30 | * Release patches if necessary. 31 | 32 | ### License 33 | 34 | This package is provided under MIT/Expat license. See [LICENSE.md](https://raw.githubusercontent.com/aidarkhanov/nanoid/master/LICENSE) file for details. 35 | 36 | ### Thanks to 37 | 38 | * [Andrey Sitnik](https://github.com/ai) for [the original JavaScript algorithm](https://github.com/ai/nanoid) implementation. 39 | * [Paul Yuan](https://github.com/puyuan) for letting me improve [the Python version](https://github.com/puyuan/py-nanoid) of the library. Without the previous work, I would not be able to fully understand the algorithm. 40 | -------------------------------------------------------------------------------- /nanoid.go: -------------------------------------------------------------------------------- 1 | // Package nanoid provides fast and convenient unique string generator. 2 | package nanoid 3 | 4 | import ( 5 | "crypto/rand" 6 | "math" 7 | "math/bits" 8 | "strings" 9 | ) 10 | 11 | const ( 12 | // DefaultAlphabet is the default alphabet for Nano ID. 13 | DefaultAlphabet = "-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz" 14 | // DefaultSize is the default size for Nano ID. 15 | DefaultSize = 21 16 | ) 17 | 18 | // BytesGenerator represents random bytes buffer. 19 | type BytesGenerator func(step int) ([]byte, error) 20 | 21 | func generateRandomBuffer(step int) ([]byte, error) { 22 | buffer := make([]byte, step) 23 | if _, err := rand.Read(buffer); err != nil { 24 | return nil, err 25 | } 26 | return buffer, nil 27 | } 28 | 29 | // FormatString generates a random string based on BytesGenerator, alphabet and size. 30 | func FormatString(generateRandomBuffer BytesGenerator, alphabet string, size int) (string, error) { 31 | mask := 2< max { 91 | max = distribution 92 | } 93 | if distribution < min { 94 | min = distribution 95 | } 96 | } 97 | 98 | distribution := max - min 99 | if distribution >= 0.05 { 100 | t.Errorf("Algorithm does not provide enough distribution: %v", distribution) 101 | } 102 | } 103 | 104 | func TestHasOptions(t *testing.T) { 105 | id, err := nanoid.GenerateString("a", 5) 106 | if err != nil { 107 | panic(err) 108 | } 109 | 110 | target := "aaaaa" 111 | if id != target { 112 | t.Errorf("Expected %v, got %v", target, id) 113 | } 114 | } 115 | 116 | func TestGeneratesRandomString(t *testing.T) { 117 | sequence := []byte{2, 255, 3, 7, 7, 7, 7, 7, 0, 1} 118 | generateBytesBuffer := func(step int) ([]byte, error) { 119 | buffer := make([]byte, 0, step) 120 | for i := 0; i < step; i += len(sequence) { 121 | buffer = append(buffer, sequence[:step-i]...) 122 | } 123 | 124 | return buffer, nil 125 | } 126 | 127 | id, err := nanoid.FormatString(generateBytesBuffer, "abcde", 4) 128 | if err != nil { 129 | panic(err) 130 | } 131 | 132 | target := "cdac" 133 | if id != target { 134 | t.Errorf("Expected %v, got %v", target, id) 135 | } 136 | } 137 | --------------------------------------------------------------------------------