tlog.app/go/tlog@v0.23.1/id.go (about) 1 package tlog 2 3 import ( 4 "encoding/hex" 5 "fmt" 6 "math/rand" 7 "sync" 8 "time" 9 10 "tlog.app/go/errors" 11 12 "tlog.app/go/tlog/low" 13 ) 14 15 type ( 16 ID [16]byte 17 18 // ShortIDError is an ID parsing error. 19 ShortIDError struct { 20 Bytes int // Bytes successfully parsed 21 } 22 23 concurrentRand struct { 24 mu sync.Mutex 25 r *rand.Rand 26 } 27 ) 28 29 var rnd = &concurrentRand{r: rand.New(rand.NewSource(time.Now().UnixNano()))} //nolint:gosec 30 31 // String returns short string representation. 32 // 33 // It's not supposed to be able to recover it back to the same value as it was. 34 func (id ID) String() string { 35 var b [8]byte 36 id.FormatTo(b[:], 0, 'v') 37 return string(b[:]) 38 } 39 40 // StringFull returns full id represented as string. 41 func (id ID) StringFull() string { 42 var b [32]byte 43 id.FormatTo(b[:], 0, 'v') 44 return string(b[:]) 45 } 46 47 func (id ID) StringUUID() string { 48 var b [36]byte 49 id.FormatTo(b[:], 0, 'u') 50 return string(b[:]) 51 } 52 53 // IDFromBytes decodes ID from bytes slice. 54 // 55 // If byte slice is shorter than type length result is returned as is and ShortIDError as error value. 56 // You may use result if you expected short ID prefix. 57 func IDFromBytes(b []byte) (id ID, err error) { 58 n := copy(id[:], b) 59 60 if n < len(id) { 61 err = ShortIDError{Bytes: n} 62 } 63 64 return 65 } 66 67 // IDFromString parses ID from string. 68 // 69 // If parsed string is shorter than type length result is returned as is and ShortIDError as error value. 70 // You may use result if you expected short ID prefix (profuced by ID.String, for example). 71 func IDFromString(s string) (id ID, err error) { 72 var j int 73 74 for i := 0; i < len(s); i++ { 75 var c byte 76 77 switch { 78 case '0' <= s[i] && s[i] <= '9': 79 c = s[i] - '0' 80 case 'a' <= s[i] && s[i] <= 'f': 81 c = s[i] - 'a' + 10 82 case 'A' <= s[i] && s[i] <= 'F': 83 c = s[i] - 'A' + 10 84 case s[i] == '-': 85 continue 86 case s[i] == '_': 87 c = 0 88 default: 89 err = hex.InvalidByteError(s[i]) 90 return 91 } 92 93 c <<= (4 * (1 - j&1)) 94 95 id[j/2] |= c 96 j++ 97 } 98 99 if j != 2*len(id) { 100 err = ShortIDError{Bytes: j / 2} 101 } 102 103 return 104 } 105 106 // IDFromStringAsBytes is the same as IDFromString. It avoids alloc in IDFromString(string(b)). 107 func IDFromStringAsBytes(s []byte) (id ID, err error) { 108 return IDFromString(low.UnsafeBytesToString(s)) 109 } 110 111 // ShouldID wraps IDFrom* call and skips error if any. 112 func ShouldID(id ID, err error) ID { 113 return id 114 } 115 116 // MustID wraps IDFrom* call and panics if error occurred. 117 func MustID(id ID, err error) ID { 118 if err != nil { 119 panic(err) 120 } 121 122 return id 123 } 124 125 // Error is an error interface implementation. 126 func (e ShortIDError) Error() string { 127 return fmt.Sprintf("too short id: %d bytes, wanted %d", e.Bytes, len(ID{})) 128 } 129 130 // Format is fmt.Formatter interface implementation. 131 // It supports width. '+' flag sets width to full ID length. 132 func (id ID) Format(s fmt.State, c rune) { 133 var buf0 [36]byte 134 buf := low.NoEscapeBuffer(buf0[:]) 135 136 w := 8 137 138 if W, ok := s.Width(); ok { 139 w = W 140 } 141 142 if s.Flag('+') { 143 w = 2 * len(id) 144 } 145 146 if s.Flag('+') && (c == 'u' || c == 'U') { 147 w = 2*len(id) + 4 148 } 149 150 i := id.FormatTo(buf[:w], 0, c) 151 152 _, _ = s.Write(buf[:i]) 153 } 154 155 // FormatTo is alloc free Format alternative. 156 func (id ID) FormatTo(b []byte, i int, f rune) int { 157 if id == (ID{}) { 158 switch f { 159 case 'v', 'V': 160 i += copy(b, "________________________________") 161 case 'u', 'U': 162 i += copy(b, "00000000-0000-0000-0000-000000000000") 163 default: 164 i += copy(b, "00000000000000000000000000000000") 165 } 166 167 return i 168 } 169 170 const digitsx = "0123456789abcdef" 171 const digitsX = "0123456789ABCDEF" 172 173 dg := digitsx 174 if f == 'X' || f == 'V' || f == 'U' { 175 dg = digitsX 176 } 177 178 uuid := f == 'u' || f == 'U' 179 limit := len(b[i:]) 180 181 if uuid { 182 if limit > 36 { 183 limit = 36 184 } 185 } else { 186 if limit > 32 { 187 limit = 32 188 } 189 } 190 191 limit += i 192 193 j := 0 194 195 for i < limit { 196 if uuid && (j == 4 || j == 6 || j == 8 || j == 10) { 197 b[i] = '-' 198 i++ 199 } 200 201 if i == limit { 202 break 203 } 204 205 b[i] = dg[id[j]>>4] 206 i++ 207 208 if i == limit { 209 break 210 } 211 212 b[i] = dg[id[j]&0xf] 213 i++ 214 215 j++ 216 } 217 218 return i 219 } 220 221 func (id ID) MarshalJSON() ([]byte, error) { 222 var b [38]byte 223 224 i := 0 225 226 b[i] = '"' 227 i++ 228 229 i = id.FormatTo(b[:], i, 'u') 230 231 b[i] = '"' 232 i++ 233 234 return b[:i], nil 235 } 236 237 func (id *ID) UnmarshalJSON(b []byte) error { 238 if len(b) < 4 { 239 return errors.New("bad id") 240 } 241 242 if b[0] != '"' || b[len(b)-1] != '"' { 243 return errors.New("bad id encoding") 244 } 245 246 x, err := IDFromStringAsBytes(b[1 : len(b)-1]) 247 if err != nil { 248 return err 249 } 250 251 *id = x 252 253 return nil 254 } 255 256 func MathRandID() (id ID) { 257 rnd.mu.Lock() 258 259 for id == (ID{}) { 260 _, _ = rnd.r.Read(id[:]) 261 } 262 263 rnd.mu.Unlock() 264 265 return 266 } 267 268 func RandIDFromReader(read func(p []byte) (int, error)) func() ID { 269 return func() (id ID) { 270 n, err := read(id[:]) 271 if err != nil { 272 panic(err) 273 } 274 if n != len(id) { 275 panic(n) 276 } 277 278 return id 279 } 280 } 281 282 /* will repeat at most after 2 ** (32 - 2) ids 283 func FastRandID() (id ID) { 284 *(*uint32)(unsafe.Pointer(&id[0])) = fastrand() 285 *(*uint32)(unsafe.Pointer(&id[4])) = fastrand() 286 *(*uint32)(unsafe.Pointer(&id[8])) = fastrand() 287 *(*uint32)(unsafe.Pointer(&id[12])) = fastrand() 288 return 289 } 290 */ 291 292 // UUID creates ID generation function. 293 // read is a random Read method. Function panics on Read error. 294 // read must be safe for concurrent use. 295 // 296 // It's got from github.com/google/uuid. 297 func UUID(read func(p []byte) (int, error)) func() ID { 298 return func() (uuid ID) { 299 n, err := read(uuid[:]) 300 if err != nil { 301 panic(err) 302 } 303 if n != len(uuid) { 304 panic(n) 305 } 306 307 uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4 308 uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10 309 310 return uuid 311 } 312 }