From 565df04e3bc51985b85e41868133cc31a7074941 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Wed, 1 Oct 2025 10:50:07 +0200 Subject: [PATCH] model: introduce ID type Based on UUIDv7 but represented to users in a more compact base32 form, though all of that are internal implementation details. --- go.mod | 3 ++- go.sum | 3 +++ model/id.go | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 model/id.go diff --git a/go.mod b/go.mod index 086c0e73ff1e72d3567c09131a4e6d6cf97d2edc..0fdc7088aa89f00c6d99f7fadedda12ab6787534 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/aws/aws-sdk-go-v2 v1.37.1 github.com/aws/aws-sdk-go-v2/credentials v1.18.2 github.com/aws/aws-sdk-go-v2/service/s3 v1.85.1 + github.com/aws/smithy-go v1.22.5 github.com/emersion/go-message v0.18.2 github.com/emersion/go-pgpmail v0.2.2 github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43 @@ -26,6 +27,7 @@ require ( github.com/google/uuid v1.6.0 github.com/kavu/go_reuseport v1.5.0 github.com/lib/pq v1.10.9 + github.com/oklog/ulid/v2 v2.1.1 github.com/prometheus/client_golang v1.16.0 github.com/stretchr/testify v1.10.0 github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec @@ -42,7 +44,6 @@ require ( github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.1 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.1 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.1 // indirect - github.com/aws/smithy-go v1.22.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cloudflare/circl v1.6.0 // indirect diff --git a/go.sum b/go.sum index 2b1b0c6656d82a836f8724a2f1e1635758b4a1f5..d06849168cdc3ea7ad7480b1e329f5f779b41563 100644 --- a/go.sum +++ b/go.sum @@ -150,10 +150,13 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s= +github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= +github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/model/id.go b/model/id.go new file mode 100644 index 0000000000000000000000000000000000000000..f23a25a0474d3d0fe5a779fcd8f30df85981f033 --- /dev/null +++ b/model/id.go @@ -0,0 +1,71 @@ +package model + +import ( + "encoding/base32" + "fmt" + "io" + + "github.com/google/uuid" +) + +// Crockford's low-ambiguity base32 alphabet +var base32Encoding = base32. + NewEncoding("0123456789ABCDEFGHJKMNPQRSTVWXYZ"). + WithPadding(base32.NoPadding) + +type ID struct { + uuid uuid.UUID +} + +// Creates a new resource ID. +func NewID() ID { + uuid, err := uuid.NewV7() + if err != nil { + panic(err) + } + return ID { + uuid: uuid, + } +} + +// Returns the UUID representation of this ID. +func (id *ID) UUID() uuid.UUID { + return id.uuid +} + +// Returns the string representation of this ID. +func (id *ID) String() string { + return base32Encoding.EncodeToString(id.uuid[:]) +} + +func (id ID) MarshalGQL(w io.Writer) { + w.Write([]byte(fmt.Sprintf(`"%s"`, id.String()))) +} + +func (id *ID) UnmarshalGQL(v interface{}) error { + switch v := v.(type) { + case string: + bytes, err := base32Encoding.DecodeString(v) + if err != nil { + return err + } + id.uuid, err = uuid.FromBytes(bytes) + if err != nil { + return err + } + return nil + default: + return fmt.Errorf("%T is not a valid ID", v) + } +} + +// database/sql.Scanner +func (id *ID) Scan(src any) error { + var uuid uuid.UUID + err := uuid.Scan(src) + if err != nil { + return err + } + id.uuid = uuid + return nil +}