├── doc.go ├── README.md ├── uuid_test.go └── uuid.go /doc.go: -------------------------------------------------------------------------------- 1 | // uuid is a lightweight UUID implementation 2 | package uuid 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # uuid 2 | 3 | uuid is a lightweight implementation for [Univerally unique identifier](http://en.wikipedia.org/wiki/Universally_unique_identifier). 4 | 5 | ## Usage 6 | 7 | var id UUID = uuid.Rand() 8 | fmt.Println(id.Hex()) 9 | fmt.Println(id.Raw()) 10 | 11 | 12 | id1, err := uuid.FromStr("1870747d-b26c-4507-9518-1ca62bc66e5d") 13 | id2 := uuid.MustFromStr("1870747db26c450795181ca62bc66e5d") 14 | fmt.Println(id1 == id2) // true 15 | 16 | -------------------------------------------------------------------------------- /uuid_test.go: -------------------------------------------------------------------------------- 1 | package uuid 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | // TestDuplicates check the first 1024 generated UUID for duplicates 9 | func TestDuplicates(t *testing.T) { 10 | var id UUID 11 | var hex string 12 | 13 | dups := make(map[string]bool) 14 | for i := 0; i < 1024; i++ { 15 | id = Rand() 16 | hex = id.Hex() 17 | if dups[hex] { 18 | t.Errorf("Duplicates after %d iterations", i+1) 19 | t.FailNow() 20 | } 21 | dups[hex] = true 22 | } 23 | } 24 | 25 | // TestFromStrSanity test FromStr(id.Hex()) == id 26 | func TestFromStrSanity(t *testing.T) { 27 | var id, id2 UUID 28 | for i := 0; i < 18; i++ { 29 | id = Rand() 30 | id2 = MustFromStr(id.Hex()) 31 | if id2 != id { 32 | t.Errorf("Sanity check fail for UUID string %s\n\tid: %v\n\tid2: %v", id.Hex(), id, id2) 33 | t.FailNow() 34 | } 35 | } 36 | } 37 | 38 | // TestHex does a simple test to make sure Hex string returns the elements in the right position 39 | func TestHex(t *testing.T) { 40 | x := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} 41 | s := strings.ToLower(UUID(x).Hex()) 42 | if s != "01020304-0506-0708-090a-0b0c0d0e0f10" { 43 | t.Errorf("Hex fail:\n\tBinary: %v,\n\tBad hex: %s", x, s) 44 | t.FailNow() 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /uuid.go: -------------------------------------------------------------------------------- 1 | package uuid 2 | 3 | import ( 4 | crand "crypto/rand" 5 | "encoding/hex" 6 | "errors" 7 | "fmt" 8 | mrand "math/rand" 9 | "regexp" 10 | "strings" 11 | "time" 12 | ) 13 | 14 | // seeded indicates if math/rand has been seeded 15 | var seeded bool = false 16 | 17 | // uuidRegex matches the UUID string 18 | var uuidRegex *regexp.Regexp = regexp.MustCompile(`^\{?([a-fA-F0-9]{8})-?([a-fA-F0-9]{4})-?([a-fA-F0-9]{4})-?([a-fA-F0-9]{4})-?([a-fA-F0-9]{12})\}?$`) 19 | 20 | // UUID type. 21 | type UUID [16]byte 22 | 23 | // Hex returns a hex string representation of the UUID in xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx format. 24 | func (this UUID) Hex() string { 25 | x := [16]byte(this) 26 | return fmt.Sprintf("%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", 27 | x[0], x[1], x[2], x[3], x[4], 28 | x[5], x[6], 29 | x[7], x[8], 30 | x[9], x[10], x[11], x[12], x[13], x[14], x[15]) 31 | 32 | } 33 | 34 | // Rand generates a new version 4 UUID. 35 | func Rand() UUID { 36 | var x [16]byte 37 | randBytes(x[:]) 38 | x[6] = (x[6] & 0x0F) | 0x40 39 | x[8] = (x[8] & 0x3F) | 0x80 40 | return x 41 | } 42 | 43 | // FromStr returns a UUID based on a string. 44 | // The string could be in the following format: 45 | // 46 | // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 47 | // 48 | // xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 49 | // 50 | // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} 51 | // 52 | // If the string is not in one of these formats, it'll return an error. 53 | func FromStr(s string) (id UUID, err error) { 54 | if s == "" { 55 | err = errors.New("Empty string") 56 | return 57 | } 58 | 59 | parts := uuidRegex.FindStringSubmatch(s) 60 | if parts == nil { 61 | err = errors.New("Invalid string format") 62 | return 63 | } 64 | 65 | var array [16]byte 66 | slice, _ := hex.DecodeString(strings.Join(parts[1:], "")) 67 | copy(array[:], slice) 68 | id = array 69 | return 70 | } 71 | 72 | // MustFromStr behaves similarly to FromStr except that it'll panic instead of 73 | // returning an error. 74 | func MustFromStr(s string) UUID { 75 | id, err := FromStr(s) 76 | if err != nil { 77 | panic(err) 78 | } 79 | return id 80 | } 81 | 82 | // randBytes uses crypto random to get random numbers. If fails then it uses math random. 83 | func randBytes(x []byte) { 84 | 85 | length := len(x) 86 | n, err := crand.Read(x) 87 | 88 | if n != length || err != nil { 89 | if !seeded { 90 | mrand.Seed(time.Now().UnixNano()) 91 | seeded = true 92 | } 93 | 94 | for length > 0 { 95 | length-- 96 | x[length] = byte(mrand.Int31n(256)) 97 | } 98 | } 99 | } 100 | --------------------------------------------------------------------------------