Skip to content

Add random string generation capabilities #26

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions random/random.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Package random provides utilities and functions for generating random data.
package random

import (
"bytes"
"crypto/rand"
"math/big"
)

// Character sets that you can use when passing into RandomString
const Digits = "0123456789"
const UpperLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
const LowerLetters = "abcdefghijklmnopqrstuvwxyz"
const SpecialChars = "<>[]{}()-_*%&/?\"'\\"

var Base62Chars = Digits + UpperLetters + LowerLetters
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neat 👍

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh wait, I think with all the special characters, this is not base 62. At the least, the variable should be renamed. Note also that the special characters may limit reusability of this unique ID, as many identifiers and even passwords don't allow those characters, whereas almost everything supports alphanumeric.

Copy link
Contributor Author

@yorinasub17 yorinasub17 Aug 13, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't have the special characters 😉

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh... Right. So is that variable unused?

Copy link
Contributor Author

@yorinasub17 yorinasub17 Aug 13, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a convenience variable. The function allows you to control what characters to use to generate the string. Examples:

// Only lower case chars + digits
random.RandomString(6, random.Digits + random.LowerLetters)

// alphanumerics + special chars
random.RandomString(6, random.Base62Chars + random.SpecialChars)

// Only alphanumerics (base62)
random.RandomString(6, random.Base62Chars)

// Only abc
random.RandomString(6, "abc")

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ohhh, I gotcha now. Perhaps add a test case using some of the other character sets to make that a bit clearer?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in d9def43


// RandomString generates a random string of length strLength, composing only of characters in allowedChars. Based on
// code here: http://stackoverflow.com/a/9543797/483528
// For convenience, the random package exposes various character sets you can use for the allowedChars parameter. Here
// are a few examples:
//
// // Only lower case chars + digits
// random.RandomString(6, random.Digits + random.LowerLetters)
//
// // alphanumerics + special chars
// random.RandomString(6, random.Base62Chars + random.SpecialChars)
//
// // Only alphanumerics (base62)
// random.RandomString(6, random.Base62Chars)
//
// // Only abc
// random.RandomString(6, "abc")
func RandomString(strLength int, allowedChars string) (string, error) {
var out bytes.Buffer

for i := 0; i < strLength; i++ {
id, err := rand.Int(rand.Reader, big.NewInt(int64(len(allowedChars))))
if err != nil {
return out.String(), err
}
out.WriteByte(allowedChars[id.Int64()])
}

return out.String(), nil
}
46 changes: 46 additions & 0 deletions random/random_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package random

import (
"strconv"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestRandomStringIsMostlyRandom(t *testing.T) {
t.Parallel()

// Ensure that there is no overlap in 32 character random strings generated 100 times
seen := map[string]bool{}
for i := 0; i < 100; i++ {
newStr, err := RandomString(32, Base62Chars)
require.NoError(t, err)
_, hasSeen := seen[newStr]
require.False(t, hasSeen)
seen[newStr] = true
}
}

func TestRandomStringRespectsStrLen(t *testing.T) {
t.Parallel()

for i := 0; i < 40; i++ {
newStr, err := RandomString(i, Base62Chars)
require.NoError(t, err)
assert.Equal(t, len(newStr), i)
}
}

func TestRandomStringRespectsAllowedChars(t *testing.T) {
t.Parallel()

for i := 0; i < 100; i++ {
newStr, err := RandomString(10, Digits)
require.NoError(t, err)
// Since the new string should only be composed of digits, if RandomString respects allowed chars you should
// always be able to convert the string to an int
_, err = strconv.Atoi(newStr)
require.NoError(t, err)
}
}