├── .gitignore ├── LICENSE ├── README.md ├── nanoid.v ├── nanoid_test.v └── v.mod /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | main 3 | nanoid 4 | *.exe 5 | *.exe~ 6 | *.so 7 | *.dylib 8 | *.dll 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 invipal 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # V NanoID 2 | 3 | [![Test Status](https://img.shields.io/badge/Tests-passed-brightgreen?style=flat-square&logo=github)](https://github.com/invipal/nanoid/actions/) 4 | [![Lint Status](https://img.shields.io/badge/Lint-passed-brightgreen?style=flat-square&logo=github)](https://github.com/invipal/nanoid/actions/) 5 | [![GitHub issues](https://img.shields.io/github/issues/invipal/nanoid?style=flat-square&cacheSeconds=3600)](https://github.com/invipal/nanoid/issues) 6 | [![License](https://img.shields.io/github/license/invipal/nanoid?style=flat-square&cacheSeconds=3600)](LICENSE.md) 7 | [![Twitter Follow](https://img.shields.io/twitter/follow/invipal?style=flat-square&logo=twitter)](https://twitter.com/invipal) 8 | [![Twitter Follow](https://img.shields.io/twitter/follow/oguzhankurnuc?style=flat-square&logo=twitter)](https://twitter.com/oguzhankurnuc) 9 | 10 | This package is V implementation of [NanoID](https://github.com/ai/nanoid) 11 | 12 | Generated from [Go Nanoid](https://github.com/matoous/go-nanoid) 13 | 14 | **Safe.** It uses cryptographically strong random generator. 15 | 16 | **Compact.** It uses more symbols than UUID (`A-Za-z0-9_-`) 17 | and has the same number of unique options in just 22 symbols instead of 36. 18 | 19 | **Fast.** Nanoid is as fast as UUID but can be used in URLs. 20 | 21 | ## Install 22 | 23 | Via vpm 24 | 25 | ```bash 26 | $ v install invipal.nanoid 27 | ``` 28 | 29 | ## Usage 30 | 31 | Generate ID 32 | 33 | ```v 34 | id := nanoid.new() or { 'error' } 35 | ``` 36 | 37 | Generate ID with a custom alphabet and length 38 | 39 | ```v 40 | id := nanoid.generate('erzurum', 25) or { 'error' } 41 | ``` 42 | 43 | ## Contribution 44 | 45 | I would welcome your contribution! If you find any improvement or issue you want to fix, feel free to send a pull request, I like pull requests that include test cases for fix/enhancement. 46 | 47 | ## Credits 48 | 49 | - [ai](https://github.com/ai) - [nanoid](https://github.com/ai/nanoid) 50 | - [matoous](https://github.com/matoous) - [go nanoid](https://github.com/matoous/go-nanoid) 51 | 52 | ## License 53 | 54 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 55 | -------------------------------------------------------------------------------- /nanoid.v: -------------------------------------------------------------------------------- 1 | module nanoid 2 | 3 | import math 4 | import crypto.rand 5 | 6 | const ( 7 | // default_alphabet is the alphabet used for ID characters by default. 8 | default_alphabet = '_-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.runes() 9 | // default length for ID 10 | default_size = 21 11 | ) 12 | 13 | // get_mask generates bit mask used to obtain bits from the random bytes that are used to get index of random character 14 | // from the alphabet. Example: if the alphabet has 6 = (110)_2 characters it is sufficient to use mask 7 = (111)_2 15 | fn get_mask(alphabet_size int) int { 16 | for i := 1; i <= 8; i++ { 17 | mask := (2 << u64(i)) - 1 18 | if mask >= alphabet_size - 1 { 19 | return mask 20 | } 21 | } 22 | return 0 23 | } 24 | 25 | // generate is a low-level function to change alphabet and ID size. 26 | pub fn generate(alphabet string, size int) ?string { 27 | chars := alphabet.runes() 28 | 29 | if alphabet.len == 0 || alphabet.len > 255 { 30 | return error('alphabet must not be empty and contain no more than 255 chars') 31 | } 32 | if size <= 0 { 33 | return error('size must be positive integer') 34 | } 35 | 36 | mask := get_mask(chars.len) 37 | // estimate how many random bytes we will need for the ID, we might actually need more but this is tradeoff 38 | // between average case and worst case 39 | ceil_arg := 1.6 * f64(mask * size) / f64(alphabet.len) 40 | step := int(math.ceil(ceil_arg)) 41 | 42 | mut id := []rune{len: size} 43 | // bytes := []byte{len: step} 44 | bytes := rand.read(step) or { return error(err.msg()) } 45 | for j := 0; true; { 46 | for i := 0; i < step; i++ { 47 | curr_byte := bytes[i] & u8(mask) 48 | if curr_byte < u8(chars.len) { 49 | id[j] = chars[curr_byte] 50 | j++ 51 | if j == size { 52 | return id[..size].string() 53 | } 54 | } 55 | } 56 | } 57 | 58 | return error('could not generated') 59 | } 60 | 61 | // must_generate is the same as generate but panics on error. 62 | pub fn must_generate(alphabet string, size int) string { 63 | id := generate(alphabet, size) or { panic(err.msg()) } 64 | return id 65 | } 66 | 67 | // new generates secure URL-friendly unique ID. 68 | // Accepts optional parameter - length of the ID to be generated (21 by default). 69 | pub fn new(l ...int) ?string { 70 | mut size := int(0) 71 | if l.len == 0 { 72 | size = default_size 73 | } else if l.len == 1 { 74 | size = l[0] 75 | if size <= 0 { 76 | return error('size must be positive integer') 77 | } 78 | } else { 79 | return error('unexpected parameter') 80 | } 81 | 82 | bytes := rand.read(size) or { return error(err.msg()) } 83 | 84 | mut id := []rune{len: size} 85 | for i := 0; i < size; i++ { 86 | id[i] = default_alphabet[bytes[i] & 63] 87 | } 88 | 89 | return id[..size].string() 90 | } 91 | 92 | // must is the same as new but panics on error. 93 | pub fn must(l ...int) string { 94 | id := new(...l) or { panic(err.msg()) } 95 | return id 96 | } 97 | -------------------------------------------------------------------------------- /nanoid_test.v: -------------------------------------------------------------------------------- 1 | module nanoid 2 | 3 | fn test_new() ? { 4 | negative_number := new(-1) or { 'error' } 5 | assert negative_number == 'error' 6 | assert new() ?.len == 21 7 | assert new(10) ?.len == 10 8 | } 9 | 10 | fn test_generate() ? { 11 | nanoid := generate('invipal', 25) ? 12 | assert nanoid.len == 25 13 | assert nanoid.contains_any('bcdefghjkmorstuwxyz0123456789_-') == false 14 | bad_alphabet_nanoid := generate('', 20) or { 'error' } 15 | assert bad_alphabet_nanoid == 'error' 16 | bad_number_nanoid := generate('abcde', -1) or { 'error' } 17 | assert bad_number_nanoid == 'error' 18 | } 19 | -------------------------------------------------------------------------------- /v.mod: -------------------------------------------------------------------------------- 1 | Module { 2 | name: 'nanoid' 3 | description: 'V implementation of Nanoid' 4 | version: '0.0.1' 5 | license: 'MIT' 6 | dependencies: [] 7 | } 8 | --------------------------------------------------------------------------------