diff --git a/.golangci.yml b/.golangci.yml index 73cca9a..0b71d9a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -4,6 +4,9 @@ issues: max-same-issues: 0 uniq-by-line: true formatters: + exclusions: + paths: + - internal/uuid enable: - gofmt - goimports @@ -117,6 +120,8 @@ linters: multi-func: false exclusions: generated: lax + paths: + - internal/uuid presets: - comments - common-false-positives diff --git a/encoding.go b/encoding.go index 9808866..bb51ccd 100644 --- a/encoding.go +++ b/encoding.go @@ -3,8 +3,9 @@ package typeid import ( "fmt" - "github.com/gofrs/uuid/v5" "github.com/jackc/pgx/v5/pgtype" + + "github.com/sumup/typeid/internal/uuid" ) // idImplementation is a helper constraint asserting the existence of the typeID methods on the type. diff --git a/generate.go b/generate.go index 240ca8f..eafb0b1 100644 --- a/generate.go +++ b/generate.go @@ -4,7 +4,7 @@ import ( "fmt" "unsafe" - "github.com/gofrs/uuid/v5" + "github.com/sumup/typeid/internal/uuid" ) // processor is an internal structure to handle different types of typedIDs, as they @@ -17,7 +17,7 @@ type processor struct { // b32Decode decode a UUID using the resp. base32 decoding. b32Decode func(string) (uuid.UUID, error) // Generates a new universal unique identifier. - generateUUID func() (uuid.UUID, error) + generateUUID func() uuid.UUID } func from[P Prefix](suffix string, p *processor) (typedID[P], error) { @@ -37,23 +37,16 @@ func from[P Prefix](suffix string, p *processor) (typedID[P], error) { // generate generates func generate[P Prefix](p *processor) (typedID[P], error) { - var err error - - if err = validatePrefix(getPrefix[P]()); err != nil { + if err := validatePrefix(getPrefix[P]()); err != nil { return nilID[P](), err } - tid := typedID[P]{} - tid.uuid, err = p.generateUUID() - if err != nil { - return nilID[P](), err - } - return tid, nil + return typedID[P]{uuid: p.generateUUID()}, nil } func nilID[P Prefix]() typedID[P] { return typedID[P]{ - uuid: uuid.Nil, + uuid: uuid.UUID{}, } } @@ -78,11 +71,11 @@ func validatePrefix(prefix string) error { func decodeSuffix(suffix string, p *processor) (uuid.UUID, error) { if len(suffix) != suffixStrLen { - return uuid.Nil, fmt.Errorf("invalid suffix: %s. Suffix length is %d, expected %d", suffix, len(suffix), suffixStrLen) + return uuid.UUID{}, fmt.Errorf("invalid suffix: %s. Suffix length is %d, expected %d", suffix, len(suffix), suffixStrLen) } if suffix[0] > '7' { - return uuid.Nil, fmt.Errorf("invalid suffix: '%s'. Suffix must start with a 0-7 digit to avoid overflows", suffix) + return uuid.UUID{}, fmt.Errorf("invalid suffix: '%s'. Suffix must start with a 0-7 digit to avoid overflows", suffix) } return p.b32Decode(suffix) diff --git a/go.mod b/go.mod index b56d4b1..a649d7e 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,4 @@ module github.com/sumup/typeid go 1.25.0 -require ( - github.com/gofrs/uuid/v5 v5.5.1 - github.com/jackc/pgx/v5 v5.10.0 -) +require github.com/jackc/pgx/v5 v5.11.0 diff --git a/go.sum b/go.sum index 0e84927..fd3c718 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,11 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gofrs/uuid/v5 v5.5.1 h1:z1Ce19/JwNidXpy3tOQc3241lnJLKdKyq/xlNvlD4Ng= -github.com/gofrs/uuid/v5 v5.5.1/go.mod h1:bbAA98EoIlxyRHIVg6ektCSsZ5n8mSbwgEhvhMYlZgg= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0= -github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +github.com/jackc/pgx/v5 v5.11.0 h1:IzBBtyK9AHqf98cctWFifYSci2hgQR/cd56wB4p+ogg= +github.com/jackc/pgx/v5 v5.11.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/internal/uuid/LICENSE b/internal/uuid/LICENSE new file mode 100644 index 0000000..2a7cf70 --- /dev/null +++ b/internal/uuid/LICENSE @@ -0,0 +1,27 @@ +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/internal/uuid/README.md b/internal/uuid/README.md new file mode 100644 index 0000000..94f56b8 --- /dev/null +++ b/internal/uuid/README.md @@ -0,0 +1,13 @@ +# Vendored Go UUID implementation + +`uuid.go` is copied from Go **1.27.1**: + +- https://github.com/golang/go/blob/go1.27.1/src/uuid/uuid.go + +The BSD 3-Clause license is reproduced in `LICENSE`. The implementation is +unchanged. +Keep local compatibility code outside this directory so future updates can be +compared directly with upstream. + +This copy allows typeid to keep supporting Go 1.25 without a UUID module +dependency. It uses only APIs available in Go 1.25. diff --git a/internal/uuid/uuid.go b/internal/uuid/uuid.go new file mode 100644 index 0000000..6304d1d --- /dev/null +++ b/internal/uuid/uuid.go @@ -0,0 +1,273 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package uuid provides support for generating and manipulating UUIDs. +// +// See [RFC 9562] for details. +// +// Random components of new UUIDs are generated with a +// cryptographically secure random number generator. +// +// UUIDs may be generated using various algorithms. +// The [New] function returns a new UUID generated using +// an algorithm suitable for most purposes. +// +// [RFC 9562]: https://www.rfc-editor.org/rfc/rfc9562.html +package uuid + +import ( + "bytes" + "cmp" + "crypto/rand" + "encoding/binary" + "encoding/hex" + "errors" + "sync" + "time" +) + +// A UUID is a Universally Unique Identifier as specified in RFC 9562. +// +// UUIDs are comparable, such as with the == operator. +type UUID [16]byte + +// Parse returns the UUID represented by s. +// +// It accepts strings in the following forms: +// +// f81d4fae-7dec-11d0-a765-00a0c91e6bf6 +// {f81d4fae-7dec-11d0-a765-00a0c91e6bf6} +// urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6 +// f81d4fae7dec11d0a76500a0c91e6bf6 +// +// Alphabetic characters in the hexadecimal digits may be any case. +func Parse(s string) (UUID, error) { + var u UUID + err := u.UnmarshalText([]byte(s)) + return u, err +} + +// MustParse returns the UUID represented by s. +// +// It panics if s is not a valid string representation of a UUID as defined by [Parse]. +func MustParse(s string) UUID { + u, err := Parse(s) + if err != nil { + panic(err) + } + return u +} + +// New returns a new UUID. +// +// Programs which do not have a need for a specific UUID generation algorithm should use New. +// At this time, New is equivalent to [NewV4]. +func New() UUID { + return NewV4() +} + +// Nil returns the Nil UUID 00000000-0000-0000-0000-000000000000. +// +// The Nil UUID is defined in [Section 5.9 of RFC 9562]. +// Note that this is not the same as the Go value nil. +// +// [Section 5.9 of RFC 9562]: https://www.rfc-editor.org/rfc/rfc9562#section-5.9 +func Nil() UUID { + return UUID{} +} + +// Max returns the Max UUID ffffffff-ffff-ffff-ffff-ffffffffffff. +// +// The Max UUID is defined in [Section 5.10 of RFC 9562]. +// +// [Section 5.10 of RFC 9562]: https://www.rfc-editor.org/rfc/rfc9562#section-5.10 +func Max() UUID { + return UUID{ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + } +} + +// String returns the string representation of u. +// +// It uses the lowercase hex-and-dash representation defined in RFC 9562. +func (u UUID) String() string { + b, _ := u.MarshalText() + return string(b) +} + +// MarshalText implements the [encoding.TextMarshaler] interface. +// The encoding is the same as returned by [UUID.String] +func (u UUID) MarshalText() ([]byte, error) { + return u.AppendText(make([]byte, 0, 36)) +} + +// AppendText implements the [encoding.TextAppender] interface. +// The encoding is the same as returned by [UUID.String] +func (u UUID) AppendText(b []byte) ([]byte, error) { + off := len(b) + b = append(b, "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"...) + dst := b[off:] + hex.Encode(dst[0:8], u[0:4]) + hex.Encode(dst[9:13], u[4:6]) + hex.Encode(dst[14:18], u[6:8]) + hex.Encode(dst[19:23], u[8:10]) + hex.Encode(dst[24:36], u[10:16]) + return b, nil +} + +var errInvalid = errors.New("invalid uuid") + +// UnmarshalText implements the [encoding.TextUnmarshaler] interface. +// The UUID is expected in a form accepted by [Parse]. +func (u *UUID) UnmarshalText(b []byte) error { + var dst UUID + switch len(b) { + case len("urn:uuid:") + 36: + // urn:uuid:00000000-0000-0000-0000-000000000000 + b = bytes.TrimPrefix(b, []byte("urn:uuid:")) + case 2 + 36: + // {00000000-0000-0000-0000-000000000000} + b = bytes.TrimPrefix(b, []byte("{")) + b = bytes.TrimSuffix(b, []byte("}")) + case 32: + // 00000000000000000000000000000000 + if _, err := hex.Decode(dst[:], b); err != nil { + return errInvalid + } + *u = dst + return nil + } + // 00000000-0000-0000-0000-000000000000 + if len(b) != 36 { + return errInvalid + } + if b[8] != '-' || b[13] != '-' || b[18] != '-' || b[23] != '-' { + return errInvalid + } + if _, err := hex.Decode(dst[0:4], b[0:8]); err != nil { + return errInvalid + } + if _, err := hex.Decode(dst[4:6], b[9:13]); err != nil { + return errInvalid + } + if _, err := hex.Decode(dst[6:8], b[14:18]); err != nil { + return errInvalid + } + if _, err := hex.Decode(dst[8:10], b[19:23]); err != nil { + return errInvalid + } + if _, err := hex.Decode(dst[10:16], b[24:36]); err != nil { + return errInvalid + } + *u = dst + return nil +} + +// Compare compares the UUID u with v. +// If u is before v, it returns -1. +// If u is after v, it returns +1. +// If they are the same, it returns 0. +// +// Compare uses the big-endian byte order defined in +// [Section 6.11 of RFC 9562] for sorting. +// +// [Section 6.11 of RFC 9562]: https://www.rfc-editor.org/rfc/rfc9562#section-6.11 +func (u UUID) Compare(v UUID) int { + for i := range u { + if c := cmp.Compare(u[i], v[i]); c != 0 { + return c + } + } + return 0 +} + +// NewV4 returns a new version 4 UUID. +// +// Version 4 UUIDs contain 122 bits of random data. +func NewV4() UUID { + var u UUID + rand.Read(u[:]) + u.setVersion(4) + u.setVariant(0b10) + return u +} + +var ( + v7mu sync.Mutex + v7lastSecs uint64 + v7lastTimestamp uint64 +) + +// NewV7 returns a new version 7 UUID. +// +// Version 7 UUIDs contain a timestamp in the most significant 48 bits, +// and at least 62 bits of random data. +// +// NewV7 always returns UUIDs which sort in increasing order, +// except when the system clock moves backwards. +func NewV7() UUID { + // UUIDv7 is defined in RFC 9562 section 5.7 as: + // + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | unix_ts_ms | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | unix_ts_ms | ver | rand_a | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // |var| rand_b | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | rand_b | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // + // We store a 12 bit sub-millisecond timestamp fraction in the rand_a section, + // as optionally permitted by the RFC. + v7mu.Lock() + + // Generate our 60-bit timestamp: 48 bits of millisecond-resolution, + // followed by 12 bits of 1/4096-millisecond resolution. + now := time.Now() + secs := uint64(now.Unix()) + nanos := uint64(now.Nanosecond()) + msecs := nanos / 1000000 + frac := nanos - (1000000 * msecs) + timestamp := (1000*secs + msecs) << 12 // ms shifted into position + timestamp += (frac * 4096) / 1000000 // ns converted to 1/4096-ms units + + if v7lastSecs > secs { + // Time has gone backwards. + // This presumably indicates the system clock has changed. + // Ignore previously-generated UUIDs. + } else if timestamp <= v7lastTimestamp { + // This timestamp is the same as a previously-generated UUID. + // To preserve the property that we generate UUIDs in order, + // use a timestamp 1/4096 millisecond later than the most recently + // generated UUID. + timestamp = v7lastTimestamp + 1 + } + + v7lastSecs = secs + v7lastTimestamp = timestamp + v7mu.Unlock() + + // Insert a gap for the 4 bits of the ver field into the timestamp. + hibits := ((timestamp << 4) & 0xffff_ffff_ffff_0000) | (timestamp & 0x0ffff) + + var u UUID + binary.BigEndian.PutUint64(u[0:8], hibits) + rand.Read(u[8:]) + u.setVersion(7) + u.setVariant(0b10) + + return u +} + +func (u *UUID) setVersion(version byte) { + u[6] = (u[6] & 0b0000_1111) | (version << 4) +} + +func (u *UUID) setVariant(variant byte) { + u[8] = (u[8] & 0b0011_1111) | (variant << 6) +} diff --git a/random.go b/random.go index 16744ec..c92fc93 100644 --- a/random.go +++ b/random.go @@ -3,10 +3,10 @@ package typeid import ( "database/sql/driver" - "github.com/gofrs/uuid/v5" "github.com/jackc/pgx/v5/pgtype" "github.com/sumup/typeid/base32" + "github.com/sumup/typeid/internal/uuid" ) // Random represents an unique identifier that is entirely random. @@ -23,9 +23,9 @@ var randomIDProc = &processor{ b32Decode: func(s string) (uuid.UUID, error) { decoded, err := base32.DecodeUpper(s) if err != nil { - return uuid.Nil, err + return uuid.UUID{}, err } - return uuid.FromBytes(decoded) + return uuid.UUID(decoded), nil }, generateUUID: uuid.NewV4, } diff --git a/sortable.go b/sortable.go index d80bdee..afabac8 100644 --- a/sortable.go +++ b/sortable.go @@ -3,14 +3,15 @@ package typeid import ( "database/sql/driver" - "github.com/gofrs/uuid/v5" "github.com/jackc/pgx/v5/pgtype" "github.com/sumup/typeid/base32" + "github.com/sumup/typeid/internal/uuid" ) // Sortable represents an unique identifier that is k-sortable. // Internally, it's based on UUIDv7. +// Generated IDs sort in increasing order unless the system clock moves backward. type Sortable[P Prefix] struct{ typedID[P] } var sortableIDProc = &processor{ @@ -23,9 +24,9 @@ var sortableIDProc = &processor{ b32Decode: func(s string) (uuid.UUID, error) { decoded, err := base32.DecodeLower(s) if err != nil { - return uuid.Nil, err + return uuid.UUID{}, err } - return uuid.FromBytes(decoded) + return uuid.UUID(decoded), nil }, generateUUID: uuid.NewV7, } diff --git a/testdata/testdata_test.go b/testdata/testdata_test.go index d01e8e1..8cbcf9a 100644 --- a/testdata/testdata_test.go +++ b/testdata/testdata_test.go @@ -6,8 +6,8 @@ import ( "sync" "testing" - "github.com/gofrs/uuid/v5" "github.com/sumup/typeid" + "github.com/sumup/typeid/internal/uuid" ) type testPrefix struct{} diff --git a/typeid.go b/typeid.go index cac9dff..154b312 100644 --- a/typeid.go +++ b/typeid.go @@ -5,7 +5,7 @@ import ( "fmt" "strings" - "github.com/gofrs/uuid/v5" + "github.com/sumup/typeid/internal/uuid" ) var ( @@ -99,7 +99,9 @@ func FromString[T instance[P], P Prefix](s string) (T, error) { return T{tid}, nil } -func FromUUID[T instance[P], P Prefix](u uuid.UUID) (T, error) { +// FromUUID creates a TypeID from a 16-byte UUID, including named array types +// from other UUID packages. It does not validate the UUID version or variant. +func FromUUID[T instance[P], P Prefix](u [16]byte) (T, error) { if err := validatePrefix(getPrefix[P]()); err != nil { return Nil[T](), err } @@ -108,7 +110,7 @@ func FromUUID[T instance[P], P Prefix](u uuid.UUID) (T, error) { } func FromUUIDStr[T instance[P], P Prefix](uuidStr string) (T, error) { - u, err := uuid.FromString(uuidStr) + u, err := uuid.Parse(uuidStr) if err != nil { return Nil[T](), fmt.Errorf("%w: uuid from string: %s", ErrParse, err.Error()) } @@ -116,9 +118,8 @@ func FromUUIDStr[T instance[P], P Prefix](uuidStr string) (T, error) { } func FromUUIDBytes[T instance[P], P Prefix](bytes []byte) (T, error) { - u, err := uuid.FromBytes(bytes) - if err != nil { - return Nil[T](), fmt.Errorf("%w: uuid from bytes: %s", ErrParse, err.Error()) + if len(bytes) != 16 { + return Nil[T](), fmt.Errorf("%w: uuid from bytes: expected 16 bytes, got %d", ErrParse, len(bytes)) } - return FromUUID[T](u) + return FromUUID[T]([16]byte(bytes)) } diff --git a/typeid_test.go b/typeid_test.go index 0ccb070..01054d3 100644 --- a/typeid_test.go +++ b/typeid_test.go @@ -5,8 +5,6 @@ import ( "reflect" "testing" "testing/quick" - - "github.com/gofrs/uuid/v5" ) const ( @@ -45,16 +43,16 @@ func TestTypeID_New(t *testing.T) { if err != nil { t.Fatalf("create UserID: unexpected error:\n%+v", err) } - if uuid.V4 != userID.UUID().Version() { - t.Errorf("expected UUIDv4, got version byte: %x", userID.UUID().Version()) + if version := userID.UUID()[6] >> 4; version != 4 { + t.Errorf("expected UUIDv4, got version byte: %x", version) } accountID, err := New[AccountID]() if err != nil { t.Fatalf("create AserID: unexpected error:\n%+v", err) } - if uuid.V7 != accountID.UUID().Version() { - t.Errorf("expected UUIDv7, got version byte: %x", accountID.UUID().Version()) + if version := accountID.UUID()[6] >> 4; version != 7 { + t.Errorf("expected UUIDv7, got version byte: %x", version) } } @@ -162,7 +160,8 @@ func fromUUIDStringTester[T idImplementation[P], P Prefix](t *testing.T) func(wi func fromUUIDBytesTester[T idImplementation[P], P Prefix](t *testing.T) func(wid wrappedID[T, P]) bool { t.Helper() return func(wid wrappedID[T, P]) bool { - parsedID, err := FromUUIDBytes[T](wid.id.UUID().Bytes()) + u := wid.id.UUID() + parsedID, err := FromUUIDBytes[T](u[:]) if err != nil { t.Fatalf("parse type id from UUID bytes: unexpected error:\n%+v", err) } @@ -180,31 +179,8 @@ func (w wrappedID[T, P]) ID() T { return w.id } -func (w wrappedID[T, P]) Generate(rnd *rand.Rand, _ int) reflect.Value { - // gen the processor to determine the UUID version to use - procGenUUID, err := (T{}).processor().generateUUID() - if err != nil { - panic(err) - } - version := procGenUUID.Version() - - uuidGen := uuid.NewGenWithOptions(uuid.WithRandomReader(rnd)) - var uid uuid.UUID - - switch version { - case uuid.V4: - if uid, err = uuidGen.NewV4(); err != nil { - panic("failed to generate uuid v4") - } - case uuid.V7: - if uid, err = uuidGen.NewV7(); err != nil { - panic("failed to generate uuid v7") - } - default: - panic("unknown uuid version") - } - - tid, err := FromUUID[T](uid) +func (w wrappedID[T, P]) Generate(_ *rand.Rand, _ int) reflect.Value { + tid, err := FromUUID[T]((T{}).processor().generateUUID()) if err != nil { panic(err) }