init push
This commit is contained in:
201
internal/shared/pkg/uuidv7/uuidv7.go
Normal file
201
internal/shared/pkg/uuidv7/uuidv7.go
Normal file
@@ -0,0 +1,201 @@
|
||||
package uuidv7
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// New returns a UUIDv7 (RFC 9562) as a 16-byte array.
|
||||
func New() ([16]byte, error) {
|
||||
var out [16]byte
|
||||
|
||||
var r [10]byte
|
||||
if _, err := rand.Read(r[:]); err != nil {
|
||||
return out, err
|
||||
}
|
||||
|
||||
// 48-bit Unix epoch milliseconds
|
||||
ms := uint64(time.Now().UnixNano() / int64(time.Millisecond))
|
||||
out[0] = byte(ms >> 40)
|
||||
out[1] = byte(ms >> 32)
|
||||
out[2] = byte(ms >> 24)
|
||||
out[3] = byte(ms >> 16)
|
||||
out[4] = byte(ms >> 8)
|
||||
out[5] = byte(ms)
|
||||
|
||||
// Version (0111) in high 4 bits of byte 6
|
||||
out[6] = (r[0] & 0x0f) | 0x70
|
||||
out[7] = r[1]
|
||||
|
||||
// Variant (10) in high 2 bits of byte 8
|
||||
out[8] = (r[2] & 0x3f) | 0x80
|
||||
out[9] = r[3]
|
||||
out[10] = r[4]
|
||||
out[11] = r[5]
|
||||
out[12] = r[6]
|
||||
out[13] = r[7]
|
||||
out[14] = r[8]
|
||||
out[15] = r[9]
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// MustNew panics if UUID generation fails.
|
||||
func MustNew() [16]byte {
|
||||
u, err := New()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
// Bytes returns a new UUIDv7 as []byte for BINARY(16) columns.
|
||||
func Bytes() ([]byte, error) {
|
||||
u, err := New()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b := make([]byte, 16)
|
||||
copy(b, u[:])
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// MustBytes panics if UUID generation fails.
|
||||
func MustBytes() []byte {
|
||||
u := MustNew()
|
||||
b := make([]byte, 16)
|
||||
copy(b, u[:])
|
||||
return b
|
||||
}
|
||||
|
||||
// String formats a UUID as a canonical string (8-4-4-4-12).
|
||||
func String(u [16]byte) (string, error) {
|
||||
if len(u) != 16 {
|
||||
return "", errors.New("invalid uuid length")
|
||||
}
|
||||
dst := make([]byte, 36)
|
||||
hex.Encode(dst[0:8], u[0:4])
|
||||
dst[8] = '-'
|
||||
hex.Encode(dst[9:13], u[4:6])
|
||||
dst[13] = '-'
|
||||
hex.Encode(dst[14:18], u[6:8])
|
||||
dst[18] = '-'
|
||||
hex.Encode(dst[19:23], u[8:10])
|
||||
dst[23] = '-'
|
||||
hex.Encode(dst[24:36], u[10:16])
|
||||
return string(dst), nil
|
||||
}
|
||||
|
||||
// BytesToString formats a UUID stored in []byte as a canonical string.
|
||||
func BytesToString(b []byte) (string, error) {
|
||||
if len(b) != 16 {
|
||||
return "", errors.New("invalid uuid length")
|
||||
}
|
||||
var u [16]byte
|
||||
copy(u[:], b)
|
||||
return String(u)
|
||||
}
|
||||
|
||||
// ParseString parses a canonical UUID string into []byte (16 bytes).
|
||||
func ParseString(s string) ([]byte, error) {
|
||||
if len(s) != 36 {
|
||||
return nil, errors.New("invalid uuid string length")
|
||||
}
|
||||
buf := make([]byte, 16)
|
||||
_, err := hex.Decode(buf[0:4], []byte(s[0:8]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = hex.Decode(buf[4:6], []byte(s[9:13]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = hex.Decode(buf[6:8], []byte(s[14:18]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = hex.Decode(buf[8:10], []byte(s[19:23]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = hex.Decode(buf[10:16], []byte(s[24:36]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// ParsePathLikeString parses a UUID from a path-like value. This is useful for
|
||||
// URL params that may arrive escaped, wrapped in a full URL, or with a trailing slash.
|
||||
func ParsePathLikeString(raw string) ([]byte, error) {
|
||||
for _, candidate := range candidateUUIDStrings(raw) {
|
||||
if parsed, err := ParseString(candidate); err == nil {
|
||||
return parsed, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("invalid uuid string")
|
||||
}
|
||||
|
||||
func candidateUUIDStrings(raw string) []string {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
candidates := make([]string, 0, 6)
|
||||
seen := make(map[string]struct{}, 6)
|
||||
push := func(value string) {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
return
|
||||
}
|
||||
if _, ok := seen[value]; ok {
|
||||
return
|
||||
}
|
||||
seen[value] = struct{}{}
|
||||
candidates = append(candidates, value)
|
||||
}
|
||||
|
||||
push(raw)
|
||||
if decoded, err := url.PathUnescape(raw); err == nil && decoded != raw {
|
||||
push(decoded)
|
||||
}
|
||||
if decoded, err := url.QueryUnescape(raw); err == nil && decoded != raw {
|
||||
push(decoded)
|
||||
}
|
||||
|
||||
for i := 0; i < len(candidates); i++ {
|
||||
value := candidates[i]
|
||||
value = strings.TrimSuffix(value, "/")
|
||||
push(value)
|
||||
|
||||
if idx := strings.IndexAny(value, "?#"); idx >= 0 {
|
||||
push(value[:idx])
|
||||
}
|
||||
if idx := strings.LastIndex(value, "/"); idx >= 0 && idx+1 < len(value) {
|
||||
push(value[idx+1:])
|
||||
}
|
||||
if idx := strings.LastIndex(value, "="); idx >= 0 && idx+1 < len(value) {
|
||||
push(value[idx+1:])
|
||||
}
|
||||
for _, part := range strings.Split(value, "/") {
|
||||
push(part)
|
||||
}
|
||||
for _, part := range strings.FieldsFunc(value, func(r rune) bool {
|
||||
switch r {
|
||||
case '/', '?', '#', '&', '=':
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}) {
|
||||
push(part)
|
||||
}
|
||||
}
|
||||
|
||||
return candidates
|
||||
}
|
||||
Reference in New Issue
Block a user