Documentation
¶
Overview ¶
Package trogonobjectid provides type-safe, prefixed object IDs such as "user_abc-123". It is the Go counterpart of the Elixir package trogon_object_id and uses the same string forms, so IDs read the same in both languages.
Defining a type ¶
Each ID type is an ID with its own marker type, so the compiler rejects a UserID where an OrderID is expected:
type invoiceType struct{}
func (invoiceType) Definition() trogonobjectid.Definition {
return trogonobjectid.Definition{
ObjectType: "inv",
StorageFormat: trogonobjectid.FormatDropPrefix,
JSONFormat: trogonobjectid.FormatDropPrefix,
Validate: trogonobjectid.ValidateUUID,
Generate: trogonobjectid.GenerateUUIDv7,
}
}
type InvoiceID = trogonobjectid.ID[invoiceType]
Emitted forms ¶
An ID has a full form, "inv_0199e3a1-...", and a bare form, "0199e3a1-...".
Method Emits Controlled by ------ ----- ------------- String, fmt verbs Full Always full LogValue (log/slog) Full Always full MarshalText, UnmarshalText Full Always full MarshalJSON, UnmarshalJSON Full or bare Definition.JSONFormat Storage, FromStorage Full or bare Definition.StorageFormat Value, Scan (database/sql) Full or bare Definition.StorageFormat
Logs always carry the full form, so a log line tells you which kind of ID it holds even when the database and the wire carry bare UUIDs. Decoding accepts only the form that the matching encoder emits, so every encoding round trips.
Unions ¶
A Union holds one ID of any of several member types and picks the member type by prefix. It differs from the Elixir UnionObjectId in two ways. When prefixes overlap, such as "acme_" and "acme_admin_", the longest prefix wins. JSON always uses the full form, even for a member that drops the prefix, because without the prefix the union cannot tell its member types apart when it decodes.
Zero value ¶
The zero ID means "absent". It encodes as SQL NULL and JSON null, String returns "", and IsZero reports true, so the json "omitzero" tag option skips it.
Example ¶
id := trogonobjectid.MustNew[invoiceType]("0199e3a1-7c2b-7d4e-9f10-2a3b4c5d6e7f")
data, _ := json.Marshal(id)
fmt.Println(id)
fmt.Println(id.Storage())
fmt.Println(string(data))
Output: inv_0199e3a1-7c2b-7d4e-9f10-2a3b4c5d6e7f 0199e3a1-7c2b-7d4e-9f10-2a3b4c5d6e7f "0199e3a1-7c2b-7d4e-9f10-2a3b4c5d6e7f"
Index ¶
- Constants
- Variables
- func GenerateUUIDv4() (string, error)
- func GenerateUUIDv7() (string, error)
- func NormalizeUUID(id string) (string, error)
- func ValidateInteger(id string) error
- func ValidateUUID(id string) error
- type Definer
- type Definition
- type Format
- type ID
- func (id ID[T]) Bare() string
- func (id *ID[T]) DecodeSpanner(val any) error
- func (id ID[T]) EncodeSpanner() (any, error)
- func (id ID[T]) IsZero() bool
- func (id ID[T]) LogValue() slog.Value
- func (id ID[T]) MarshalJSON() ([]byte, error)
- func (id ID[T]) MarshalText() ([]byte, error)
- func (id ID[T]) ObjectType() string
- func (id ID[T]) Prefix() string
- func (id *ID[T]) Scan(src any) error
- func (id ID[T]) Storage() string
- func (id ID[T]) String() string
- func (id *ID[T]) UnmarshalJSON(data []byte) error
- func (id *ID[T]) UnmarshalText(text []byte) error
- func (id ID[T]) Value() (driver.Value, error)
- type Member
- type MemberType
- type Union
- func (u *Union[U]) DecodeSpanner(val any) error
- func (u Union[U]) EncodeSpanner() (any, error)
- func (u Union[U]) IsZero() bool
- func (u Union[U]) LogValue() slog.Value
- func (u Union[U]) MarshalJSON() ([]byte, error)
- func (u Union[U]) MarshalText() ([]byte, error)
- func (u Union[U]) Member() Member
- func (u *Union[U]) Scan(src any) error
- func (u Union[U]) String() string
- func (u *Union[U]) UnmarshalJSON(data []byte) error
- func (u *Union[U]) UnmarshalText(text []byte) error
- func (u Union[U]) Value() (driver.Value, error)
- type UnionDefiner
- type ValidationError
Examples ¶
Constants ¶
const DefaultSeparator = "_"
DefaultSeparator is the separator used when Definition.Separator is empty.
Variables ¶
var ( // ErrEmptyValue means the bare value is empty. ErrEmptyValue = errors.New("empty value") // ErrInvalidFormat means the input does not have the expected prefix or Go type. ErrInvalidFormat = errors.New("invalid format") // ErrInvalidUUID means ValidateUUID rejected the bare value. ErrInvalidUUID = errors.New("invalid uuid") // ErrInvalidInteger means ValidateInteger rejected the bare value. ErrInvalidInteger = errors.New("invalid integer") // ErrInvalidDefinition means the marker type's Definition is not usable. ErrInvalidDefinition = errors.New("trogonobjectid: invalid definition") // ErrNotMember means the ID passed to NewUnion is not one of the union's member types. ErrNotMember = errors.New("not a union member") // ErrNoGenerator means Generate was called on a type without a Generate function. ErrNoGenerator = errors.New("trogonobjectid: no generator") )
Functions ¶
func GenerateUUIDv4 ¶
GenerateUUIDv4 returns a new random UUID string.
func GenerateUUIDv7 ¶
GenerateUUIDv7 returns a new time-ordered UUID string.
func NormalizeUUID ¶ added in v0.2.0
NormalizeUUID returns the canonical lowercase hyphenated form of any UUID form that uuid.Parse accepts, so upper-case and URN inputs name the same ID. It also rejects values that are not UUIDs, so a type that sets it needs no ValidateUUID.
Example ¶
id := trogonobjectid.MustNew[canonicalType]("0199E3A1-7C2B-7D4E-9F10-2A3B4C5D6E7F")
fmt.Println(id)
Output: inv_0199e3a1-7c2b-7d4e-9f10-2a3b4c5d6e7f
func ValidateInteger ¶
ValidateInteger accepts an optional sign followed by decimal digits, with no size limit, to match the Elixir package.
func ValidateUUID ¶
ValidateUUID accepts the UUID forms that uuid.Parse accepts. It keeps the value as given; use NormalizeUUID instead to store one canonical form.
Types ¶
type Definer ¶
type Definer interface {
Definition() Definition
}
Definer is implemented by the marker type that names an object ID type. Implement it on an empty struct with a value receiver:
type userType struct{}
func (userType) Definition() trogonobjectid.Definition {
return trogonobjectid.Definition{ObjectType: "user"}
}
type UserID = trogonobjectid.ID[userType]
type Definition ¶
type Definition struct {
// ObjectType is the prefix without the separator, for example "user". It is required.
ObjectType string
// Separator goes between the object type and the bare value. Empty means DefaultSeparator.
Separator string
// StorageFormat controls Storage, FromStorage, Value and Scan.
StorageFormat Format
// JSONFormat controls MarshalJSON and UnmarshalJSON.
JSONFormat Format
// Normalize rewrites the bare value into its canonical form before Validate runs, so
// equal IDs compare equal whatever form they arrived in. Nil keeps the value as given.
Normalize func(id string) (string, error)
// Validate checks the bare value. Nil accepts any non-empty value.
Validate func(id string) error
// Generate returns a new bare value for Generate. Nil makes Generate fail.
Generate func() (string, error)
}
Definition configures an object ID type.
type ID ¶
type ID[T Definer] struct { // contains filtered or unexported fields }
ID is a type-safe object ID. IDs with different marker types are different Go types, so a UserID cannot be passed where an OrderID is expected.
The zero value is an absent ID: it encodes as SQL NULL and JSON null. String, MarshalText and LogValue always use the prefixed form, so logs and URLs stay unambiguous no matter how the type is stored or sent as JSON.
func FromStorage ¶
FromStorage returns the ID for a value in the type's StorageFormat. It is the inverse of Storage.
func Parse ¶
Parse returns the ID for the prefixed form s, for example "user_abc-123".
Example ¶
id, err := trogonobjectid.Parse[userType]("user_abc-123")
if err != nil {
panic(err)
}
fmt.Println(id.Bare())
_, err = trogonobjectid.Parse[userType]("order_abc-123")
fmt.Println(err)
Output: abc-123 trogonobjectid: invalid user value "order_abc-123": invalid format
func (*ID[T]) DecodeSpanner ¶ added in v0.2.0
DecodeSpanner implements spanner.Decoder for STRING values in the type's StorageFormat. NULL gives the zero ID.
func (ID[T]) EncodeSpanner ¶ added in v0.2.0
EncodeSpanner implements spanner.Encoder with the type's StorageFormat. The zero ID is a NULL STRING; an untyped nil would leave Spanner without a column type.
Example ¶
value, _ := trogonobjectid.MustNew[invoiceType]("0199e3a1-7c2b-7d4e-9f10-2a3b4c5d6e7f").EncodeSpanner()
fmt.Println(value)
null, _ := trogonobjectid.ID[invoiceType]{}.EncodeSpanner()
fmt.Println(null == (*string)(nil))
Output: 0199e3a1-7c2b-7d4e-9f10-2a3b4c5d6e7f true
func (ID[T]) LogValue ¶
LogValue implements slog.LogValuer. Without it, slog.JSONHandler would call MarshalJSON and log the bare value for types that drop the prefix in JSON.
func (ID[T]) MarshalJSON ¶
MarshalJSON implements json.Marshaler with the type's JSONFormat. The zero ID is null.
func (ID[T]) MarshalText ¶
MarshalText implements encoding.TextMarshaler with the prefixed form. JSON map keys and flag values use it, and they must parse back without other context.
func (ID[T]) ObjectType ¶
ObjectType returns the type's object type, for example "user".
func (*ID[T]) Scan ¶
Scan implements sql.Scanner for values in the type's StorageFormat. NULL and the empty string give the zero ID.
func (ID[T]) Storage ¶
Storage returns the value in the type's StorageFormat, or "" for the zero ID.
func (ID[T]) String ¶
String returns the prefixed form, for example "user_abc-123", or "" for the zero ID.
func (*ID[T]) UnmarshalJSON ¶
UnmarshalJSON implements json.Unmarshaler with the type's JSONFormat. Null gives the zero ID.
func (*ID[T]) UnmarshalText ¶
UnmarshalText implements encoding.TextUnmarshaler. Empty text gives the zero ID.
type MemberType ¶
type MemberType struct {
// contains filtered or unexported fields
}
MemberType describes one ID type that a Union accepts. Make it with MemberOf.
type Union ¶
type Union[U UnionDefiner] struct { // contains filtered or unexported fields }
Union holds one ID of any of its member types, chosen by prefix when parsing. Use a type switch on Member to get the typed ID back.
Every encoding uses the member's full form, because the prefix is the only thing that tells the member types apart. The zero value is an absent ID.
func MustNewUnion ¶
func MustNewUnion[U UnionDefiner](m Member) Union[U]
MustNewUnion is like NewUnion but panics on error. Use it for constants and tests.
func MustParseUnion ¶
func MustParseUnion[U UnionDefiner](str string) Union[U]
MustParseUnion is like ParseUnion but panics on error. Use it for constants and tests.
func NewUnion ¶
func NewUnion[U UnionDefiner](m Member) (Union[U], error)
NewUnion wraps m, which must be a non-zero ID of one of the union's member types.
func ParseUnion ¶
func ParseUnion[U UnionDefiner](str string) (Union[U], error)
ParseUnion parses the full form of any member type. When prefixes overlap, the longest matching prefix wins.
func (*Union[U]) DecodeSpanner ¶ added in v0.2.0
DecodeSpanner implements spanner.Decoder for STRING values in the full form.
func (Union[U]) EncodeSpanner ¶ added in v0.2.0
EncodeSpanner implements spanner.Encoder with the full form. The zero union is a NULL STRING, the same as the zero ID.
func (Union[U]) MarshalJSON ¶
MarshalJSON implements json.Marshaler with the full form, ignoring the member's JSONFormat so that UnmarshalJSON can pick the member type again. The zero union is null.
func (Union[U]) MarshalText ¶
MarshalText implements encoding.TextMarshaler with the full form.
func (*Union[U]) Scan ¶
Scan implements sql.Scanner for the full form. NULL and the empty string give the zero union.
func (*Union[U]) UnmarshalJSON ¶
UnmarshalJSON implements json.Unmarshaler for the full form. Null gives the zero union.
func (*Union[U]) UnmarshalText ¶
UnmarshalText implements encoding.TextUnmarshaler. Empty text gives the zero union.
type UnionDefiner ¶
type UnionDefiner interface {
Members() []MemberType
}
UnionDefiner is implemented by the marker type that names a Union:
type principalUnion struct{}
func (principalUnion) Members() []trogonobjectid.MemberType {
return []trogonobjectid.MemberType{
trogonobjectid.MemberOf[userType](),
trogonobjectid.MemberOf[systemType](),
}
}
type PrincipalID = trogonobjectid.Union[principalUnion]
type ValidationError ¶
ValidationError reports a value that is not a valid ID of ObjectType. Err is ErrEmptyValue, ErrInvalidFormat, or the error from Definition.Validate.
func (*ValidationError) Error ¶
func (e *ValidationError) Error() string
func (*ValidationError) Unwrap ¶
func (e *ValidationError) Unwrap() error
Directories
¶
| Path | Synopsis |
|---|---|
|
Package objectidproto builds a trogonobjectid.Definition from the trogon.object_id.v1alpha1.enum_value annotation on a protobuf enum value:
|
Package objectidproto builds a trogonobjectid.Definition from the trogon.object_id.v1alpha1.enum_value annotation on a protobuf enum value: |