github.com/richardwilkes/toolbox@v1.121.0/tid/tid.go (about) 1 // Copyright (c) 2016-2024 by Richard A. Wilkes. All rights reserved. 2 // 3 // This Source Code Form is subject to the terms of the Mozilla Public 4 // License, version 2.0. If a copy of the MPL was not distributed with 5 // this file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 // 7 // This Source Code Form is "Incompatible With Secondary Licenses", as 8 // defined by the Mozilla Public License, version 2.0. 9 10 package tid 11 12 import ( 13 "crypto/rand" 14 "encoding/base64" 15 "fmt" 16 "strings" 17 18 "github.com/richardwilkes/toolbox/errs" 19 ) 20 21 // TID is a unique identifier. These are similar to v4 UUIDs, but are shorter and have a different format that includes 22 // a kind byte as the first character. TIDs are 17 characters long, are URL safe, and contain 96 bits of entropy. 23 type TID string 24 25 // KindAlphabet is the set of characters that can be used as the first character of a TID. The kind has no intrinsic 26 // meaning, but can be used to differentiate between different types of ids. 27 const KindAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" 28 29 // MustNewTID creates a new TID with a random value and the specified kind. If an error occurs, this function panics. 30 func MustNewTID(kind byte) TID { 31 tid, err := NewTID(kind) 32 if err != nil { 33 panic(err) 34 } 35 return tid 36 } 37 38 // NewTID creates a new TID with a random value and the specified kind. 39 func NewTID(kind byte) (TID, error) { 40 if strings.IndexByte(KindAlphabet, kind) == -1 { 41 return "", errs.New("invalid kind") 42 } 43 var buffer [12]byte 44 if _, err := rand.Read(buffer[:]); err != nil { 45 return "", errs.Wrap(err) 46 } 47 return TID(fmt.Sprintf("%c%s", kind, base64.RawURLEncoding.EncodeToString(buffer[:]))), nil 48 } 49 50 // FromString converts a string to a TID. 51 func FromString(id string) (TID, error) { 52 tid := TID(id) 53 if !IsValid(tid) { 54 return "", errs.New("invalid TID") 55 } 56 return tid, nil 57 } 58 59 // FromStringOfKind converts a string to a TID and verifies that it has the specified kind. 60 func FromStringOfKind(id string, kind byte) (TID, error) { 61 tid := TID(id) 62 if !IsKindAndValid(tid, kind) { 63 return "", errs.New("invalid TID") 64 } 65 return tid, nil 66 } 67 68 // IsValid returns true if the TID is a valid TID. 69 func IsValid(id TID) bool { 70 if len(id) != 17 || strings.IndexByte(KindAlphabet, id[0]) == -1 { 71 return false 72 } 73 _, err := base64.RawURLEncoding.DecodeString(string(id[1:])) 74 return err == nil 75 } 76 77 // IsKind returns true if the TID has the specified kind. 78 func IsKind(id TID, kind byte) bool { 79 return len(id) == 17 && id[0] == kind && strings.IndexByte(KindAlphabet, kind) != -1 80 } 81 82 // IsKindAndValid returns true if the TID is a valid TID with the specified kind. 83 func IsKindAndValid(id TID, kind byte) bool { 84 if !IsKind(id, kind) { 85 return false 86 } 87 _, err := base64.RawURLEncoding.DecodeString(string(id[1:])) 88 return err == nil 89 }