mirror of
https://github.com/jimeh/rands.git
synced 2026-02-19 11:26:38 +00:00
Merge pull request #2 from jimeh/add-uuid
feat(strings): add UUID function to generate random RFC 4122 UUID v4 strings
This commit is contained in:
33
strings.go
33
strings.go
@@ -18,6 +18,7 @@ const (
|
||||
alphabeticChars = upperChars + lowerChars
|
||||
alphanumericChars = alphabeticChars + numericChars
|
||||
dnsLabelChars = lowerNumericChars + "-"
|
||||
uuidHyphen = byte('-')
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -164,11 +165,9 @@ func UnicodeString(n int, alphabet []rune) (string, error) {
|
||||
}
|
||||
|
||||
// DNSLabel returns a random string of n length in a DNS label compliant format
|
||||
// as defined in RFC 1035, section 2.3.1:
|
||||
// https://www.rfc-editor.org/rfc/rfc1035.html#section-2.3.1
|
||||
// as defined in RFC 1035, section 2.3.1.
|
||||
//
|
||||
// It also adheres to RFC 5891, section 4.2.3.1:
|
||||
// https://www.rfc-editor.org/rfc/rfc5891.html#section-4.2.3.1
|
||||
// It also adheres to RFC 5891, section 4.2.3.1.
|
||||
//
|
||||
// In summary, the generated random string will:
|
||||
//
|
||||
@@ -235,6 +234,32 @@ func DNSLabel(n int) (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// UUID returns a random UUID v4 in string format as defined by RFC 4122,
|
||||
// section 4.4.
|
||||
func UUID() (string, error) {
|
||||
b, err := Bytes(16)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
b[6] = (b[6] & 0x0f) | 0x40 // Version: 4 (random)
|
||||
b[8] = (b[8] & 0x3f) | 0x80 // Variant: RFC 4122
|
||||
|
||||
// Construct a UUID v4 string according to RFC 4122 specifications.
|
||||
dst := make([]byte, 36)
|
||||
hex.Encode(dst[0:8], b[0:4]) // time-low
|
||||
dst[8] = uuidHyphen
|
||||
hex.Encode(dst[9:13], b[4:6]) // time-mid
|
||||
dst[13] = uuidHyphen
|
||||
hex.Encode(dst[14:18], b[6:8]) // time-high-and-version
|
||||
dst[18] = uuidHyphen
|
||||
hex.Encode(dst[19:23], b[8:10]) // clock-seq-and-reserved, clock-seq-low
|
||||
dst[23] = uuidHyphen
|
||||
hex.Encode(dst[24:], b[10:]) // node
|
||||
|
||||
return string(dst), nil
|
||||
}
|
||||
|
||||
func isASCII(s string) bool {
|
||||
for _, c := range s {
|
||||
if c > unicode.MaxASCII {
|
||||
|
||||
@@ -70,3 +70,8 @@ func ExampleDNSLabel() {
|
||||
s, _ := rands.DNSLabel(16)
|
||||
fmt.Println(s) // => z0ij9o8qkbs0ru-h
|
||||
}
|
||||
|
||||
func ExampleUUID() {
|
||||
s, _ := rands.UUID()
|
||||
fmt.Println(s) // => a62b8712-f238-43ba-a47e-333f5fffe785
|
||||
}
|
||||
|
||||
134
strings_test.go
134
strings_test.go
@@ -2,6 +2,7 @@ package rands
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"regexp"
|
||||
"strings"
|
||||
@@ -497,54 +498,55 @@ func BenchmarkUnicodeString(b *testing.B) {
|
||||
}
|
||||
}
|
||||
|
||||
var dnsLabelTestCases = []struct {
|
||||
name string
|
||||
n int
|
||||
errIs error
|
||||
errStr string
|
||||
}{
|
||||
{
|
||||
name: "n=-128",
|
||||
n: -128,
|
||||
errIs: errDNSLabelLength,
|
||||
errStr: "rands: DNS labels must be between 1 and 63 characters " +
|
||||
"in length",
|
||||
},
|
||||
{
|
||||
name: "n=0",
|
||||
n: 0,
|
||||
errIs: errDNSLabelLength,
|
||||
errStr: "rands: DNS labels must be between 1 and 63 characters " +
|
||||
"in length",
|
||||
},
|
||||
{name: "n=1", n: 1},
|
||||
{name: "n=2", n: 2},
|
||||
{name: "n=3", n: 3},
|
||||
{name: "n=4", n: 4},
|
||||
{name: "n=5", n: 5},
|
||||
{name: "n=6", n: 6},
|
||||
{name: "n=7", n: 7},
|
||||
{name: "n=8", n: 8},
|
||||
{name: "n=16", n: 16},
|
||||
{name: "n=32", n: 32},
|
||||
{name: "n=63", n: 63},
|
||||
{
|
||||
name: "n=64",
|
||||
n: 64,
|
||||
errIs: errDNSLabelLength,
|
||||
errStr: "rands: DNS labels must be between 1 and 63 characters " +
|
||||
"in length",
|
||||
},
|
||||
{
|
||||
name: "n=128",
|
||||
n: 128,
|
||||
errIs: errDNSLabelLength,
|
||||
errStr: "rands: DNS labels must be between 1 and 63 characters " +
|
||||
"in length",
|
||||
},
|
||||
}
|
||||
|
||||
func TestDNSLabel(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
n int
|
||||
errIs error
|
||||
errStr string
|
||||
}{
|
||||
{
|
||||
name: "n=-128",
|
||||
n: -128,
|
||||
errIs: errDNSLabelLength,
|
||||
errStr: "rands: DNS labels must be between 1 and 63 characters " +
|
||||
"in length",
|
||||
},
|
||||
{
|
||||
name: "n=0",
|
||||
n: 0,
|
||||
errIs: errDNSLabelLength,
|
||||
errStr: "rands: DNS labels must be between 1 and 63 characters " +
|
||||
"in length",
|
||||
},
|
||||
{name: "n=1", n: 1},
|
||||
{name: "n=2", n: 2},
|
||||
{name: "n=3", n: 3},
|
||||
{name: "n=4", n: 4},
|
||||
{name: "n=5", n: 5},
|
||||
{name: "n=6", n: 6},
|
||||
{name: "n=7", n: 7},
|
||||
{name: "n=8", n: 8},
|
||||
{name: "n=16", n: 16},
|
||||
{name: "n=32", n: 32},
|
||||
{name: "n=63", n: 63},
|
||||
{
|
||||
name: "n=64",
|
||||
n: 64,
|
||||
errIs: errDNSLabelLength,
|
||||
errStr: "rands: DNS labels must be between 1 and 63 characters " +
|
||||
"in length",
|
||||
},
|
||||
{
|
||||
name: "n=128",
|
||||
n: 128,
|
||||
errIs: errDNSLabelLength,
|
||||
errStr: "rands: DNS labels must be between 1 and 63 characters " +
|
||||
"in length",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
for _, tt := range dnsLabelTestCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// generate lots of labels to increase the chances of catching any
|
||||
// obscure bugs
|
||||
@@ -568,6 +570,44 @@ func TestDNSLabel(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDNSLabel(b *testing.B) {
|
||||
for _, tt := range dnsLabelTestCases {
|
||||
b.Run(tt.name, func(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
_, _ = DNSLabel(tt.n)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUUID(t *testing.T) {
|
||||
m := regexp.MustCompile(
|
||||
`^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$`,
|
||||
)
|
||||
|
||||
for i := 0; i < 10000; i++ {
|
||||
got, err := UUID()
|
||||
require.NoError(t, err)
|
||||
require.Regexp(t, m, got)
|
||||
|
||||
raw := strings.ReplaceAll(got, "-", "")
|
||||
b := make([]byte, 16)
|
||||
_, err = hex.Decode(b, []byte(raw))
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, 4, int(b[6]>>4), "version is not 4")
|
||||
require.Equal(t, byte(0x80), b[8]&0xc0,
|
||||
"variant is not RFC 4122",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUUID(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
_, _ = UUID()
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Helpers
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user