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 ¶
- Variables
- func Check[T Definer]() error
- func CheckUnion[U UnionDefiner]() error
- func GenerateUUIDv4() (string, error)
- func GenerateUUIDv7() (string, error)
- func ValidateInteger(id string) error
- func ValidateUUID(id string) error
- type Definer
- type Definition
- type Format
- type ID
- func FromStorage[T Definer](value string) (ID[T], error)
- func FromUUID[T Definer](u uuid.UUID) (ID[T], error)
- func Generate[T Definer]() (ID[T], error)
- func MustNew[T Definer](id string) ID[T]
- func MustParse[T Definer](s string) ID[T]
- func New[T Definer](id string) (ID[T], error)
- func Parse[T Definer](s string) (ID[T], error)
- func UnionAs[T Definer, U UnionDefiner](u Union[U]) (ID[T], bool)
- 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() ObjectType
- func (id ID[T]) Prefix() Prefix
- func (id *ID[T]) Scan(src any) error
- func (id ID[T]) Storage() string
- func (id ID[T]) String() string
- func (id ID[T]) UUID() (uuid.UUID, error)
- 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 ObjectType
- type Prefix
- type Separator
- 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
- type WrongPrefixError
Examples ¶
Constants ¶
This section 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 Check ¶ added in v0.4.0
Check reports whether T's Definition is usable: a valid object type, and a generator whose values pass the type's own Validate. Call it from a test or init, the way the Elixir package checks options at compile time.
func CheckUnion ¶ added in v0.4.0
func CheckUnion[U UnionDefiner]() error
CheckUnion reports whether U's member list is usable: at least one member, valid member definitions and no duplicate prefixes.
func GenerateUUIDv4 ¶
GenerateUUIDv4 returns a new random UUID string.
func GenerateUUIDv7 ¶
GenerateUUIDv7 returns a new time-ordered UUID string.
func ValidateInteger ¶
ValidateInteger accepts an optional sign followed by decimal digits, with no size limit, to match the Elixir package.
func ValidateUUID ¶
ValidateUUID accepts only the canonical form: 36 characters, lowercase, hyphenated. It rejects upper case, URN and brace forms, so one UUID cannot be spelled as two IDs.
Types ¶
type Definer ¶
type Definer interface {
Definition() Definition
}
Definer is implemented by the marker type that names an object ID type. The result of Definition is cached per type, so it must always return the same value. 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 ObjectType
// Separator goes between the object type and the bare value. Empty means DefaultSeparator.
Separator Separator
// StorageFormat controls Storage, FromStorage, Value and Scan.
StorageFormat Format
// JSONFormat controls MarshalJSON and UnmarshalJSON.
JSONFormat Format
// 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)
// Aliases are earlier object types that parsing still accepts. Every encoder emits
// ObjectType, so stored and sent values move to the new prefix as they are rewritten.
Aliases []ObjectType
}
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 FromUUID ¶ added in v0.4.0
FromUUID returns the ID for u, after the type's Validate.
Example ¶
u := uuid.MustParse("0199e3a1-7c2b-7d4e-9f10-2a3b4c5d6e7f")
id, _ := trogonobjectid.FromUUID[invoiceType](u)
back, _ := id.UUID()
fmt.Println(id)
fmt.Println(back == u)
Output: inv_0199e3a1-7c2b-7d4e-9f10-2a3b4c5d6e7f true
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: want prefix ["user_"]
func UnionAs ¶ added in v0.4.0
func UnionAs[T Definer, U UnionDefiner](u Union[U]) (ID[T], bool)
UnionAs returns u's member as an ID[T], and false when u holds another type or is zero. It narrows a union without a type switch.
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 ¶
func (id ID[T]) ObjectType() 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]) UUID ¶ added in v0.4.0
UUID returns the bare value as a uuid.UUID, so callers that work with UUIDs do not parse Bare themselves. The zero ID fails with ErrEmptyValue.
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 ObjectType ¶ added in v0.4.0
type ObjectType string
ObjectType names an object ID type, for example "user". It is lowercase snake_case of at most 63 characters, the TypeID prefix rule.
func ObjectTypeOf ¶ added in v0.4.0
func ObjectTypeOf[T Definer]() ObjectType
ObjectTypeOf returns T's object type, for example "user". It panics if T's Definition is invalid.
type Prefix ¶ added in v0.4.0
type Prefix string
Prefix is an object type followed by its separator, for example "user_".
type Separator ¶ added in v0.4.0
type Separator string
Separator goes between the object type and the bare value.
const DefaultSeparator Separator = "_"
DefaultSeparator is the separator used when Definition.Separator is empty.
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 UnionAs, or 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 {
Types() []MemberType
}
UnionDefiner is implemented by the marker type that names a Union:
type principalUnion struct{}
func (principalUnion) Types() []trogonobjectid.MemberType {
return []trogonobjectid.MemberType{
trogonobjectid.MemberOf[userType](),
trogonobjectid.MemberOf[systemType](),
}
}
type PrincipalID = trogonobjectid.Union[principalUnion]
type ValidationError ¶
type ValidationError struct {
ObjectTypes []ObjectType
Value string
Err error
}
ValidationError reports a value that is not a valid ID of the listed object types: one for an ID, every member's for a union. Err is ErrEmptyValue, ErrInvalidFormat, a *WrongPrefixError, or the error from Definition.Validate.
func (*ValidationError) Error ¶
func (e *ValidationError) Error() string
func (*ValidationError) Unwrap ¶
func (e *ValidationError) Unwrap() error
type WrongPrefixError ¶ added in v0.4.0
type WrongPrefixError struct {
Want []Prefix
}
WrongPrefixError reports a value without one of the prefixes the type accepts, for example an order ID where an invoice ID is expected. ValidationError.Value holds the input. It unwraps to ErrInvalidFormat.
func (*WrongPrefixError) Error ¶ added in v0.4.0
func (e *WrongPrefixError) Error() string
func (*WrongPrefixError) Unwrap ¶ added in v0.4.0
func (e *WrongPrefixError) 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: |
|
Package objectidtest builds deterministic object IDs for tests, so a test can name "the buyer" or "the seller" and get the same valid ID on every run:
|
Package objectidtest builds deterministic object IDs for tests, so a test can name "the buyer" or "the seller" and get the same valid ID on every run: |