Documentation
¶
Overview ¶
Package xuid provides a compact, type-safe identifier system built on UUIDs.
XUID combines the robustness of UUIDs with practical enhancements: - Sortable identifiers using UUIDv7 for chronological ordering - Optional string prefixes for human-readable context (e.g., "user_", "order_") - Base58 encoding for shorter, URL-safe representations - Built-in JSON marshaling/unmarshaling and text encoding support
Prefixes are validated: they must be at most MaxPrefixLen bytes and contain only [a-zA-Z0-9_]. See ValidatePrefix. The nil UUID represents an absent identifier and never carries a prefix; see NewWith.
Example usage:
// Create a sortable identifier with prefix
userID := xuid.MustNewSortable("user")
fmt.Println(userID.String()) // e.g. user_Cf1k9VmUZGg55baoJFXnT
// Parse from string
parsed, err := xuid.Parse("user_Cf1k9VmUZGg55baoJFXnT")
if err != nil {
log.Fatal(err)
}
fmt.Println(parsed.GetPrefix()) // user
Index ¶
- Constants
- Variables
- func Compare(x, y XUID) int
- func IsEmpty(xid XUID) bool
- func IsValid(idstr string) bool
- func Less(x, y XUID) bool
- func ValidatePrefix(prefix string) error
- type NullXUID
- type XUID
- func Must(xid XUID, err error) XUID
- func MustNewRandom(prefix string) XUID
- func MustNewSortable(prefix string) XUID
- func MustParse(idstr string) XUID
- func NewRandom(prefix string) (XUID, error)
- func NewSortable(prefix string) (XUID, error)
- func NewWith(id uuid.UUID, prefix string) (XUID, error)
- func NilUUID() (XUID, error)
- func Parse(idstr string) (XUID, error)
- func (x XUID) Equal(y XUID) bool
- func (x XUID) EqualUUID(y XUID) bool
- func (x XUID) GetPrefix() string
- func (x XUID) GetUUID() uuid.UUID
- func (x *XUID) GobDecode(data []byte) error
- func (x XUID) GobEncode() ([]byte, error)
- func (x XUID) IsRandom() bool
- func (x XUID) IsSortable() bool
- func (x XUID) MarshalJSON() ([]byte, error)
- func (x XUID) MarshalText() ([]byte, error)
- func (x XUID) MustWithPrefix(prefix string) XUID
- func (x *XUID) Scan(value interface{}) error
- func (x *XUID) SetPrefix(prefix string) *XUIDdeprecated
- func (x XUID) String() string
- func (x XUID) Time() (time.Time, error)
- func (x *XUID) UnmarshalJSON(data []byte) error
- func (x *XUID) UnmarshalText(data []byte) error
- func (x XUID) Value() (driver.Value, error)
- func (x XUID) WithPrefix(prefix string) (XUID, error)
Constants ¶
const MaxPrefixLen = 32
MaxPrefixLen is the maximum length, in bytes, of an XUID prefix.
Variables ¶
var ( // ErrParse is returned by Parse when a string is not a valid XUID. // Parse wraps it with the underlying cause, so failures can be // detected with errors.Is(err, ErrParse). ErrParse = errors.New("XUID string cannot be parsed") // ErrScan is returned by XUID.Scan when a database value cannot be // converted to an XUID. Scan wraps it with context, so failures can be // detected with errors.Is(err, ErrScan). ErrScan = errors.New("XUID cannot be scanned from a SQL value") // ErrInvalidPrefix is returned when a prefix does not satisfy the // rules enforced by ValidatePrefix. Constructors and Parse wrap it // with context, so failures can be detected with // errors.Is(err, ErrInvalidPrefix). ErrInvalidPrefix = errors.New("XUID prefix is invalid") // ErrNilUUIDWithPrefix is returned when a non-empty prefix is combined // with the nil UUID. A nil UUID represents an absent identifier and // never carries a prefix: JSON, text and SQL all encode it as // null/empty, so a prefixed nil UUID cannot round-trip. Constructors, // Parse and WithPrefix wrap it with context, so failures can be // detected with errors.Is(err, ErrNilUUIDWithPrefix). ErrNilUUIDWithPrefix = errors.New("XUID cannot combine the nil UUID with a prefix") // ErrNotSortable is returned by Time when the XUID is not a // well-formed UUIDv7, such as a UUIDv4, the nil UUID, or a value // whose version nibble is 7 but whose variant is not the RFC 9562 // variant. Time wraps it with context, so failures can be detected // with errors.Is(err, ErrNotSortable), and IsSortable can be used to // check first. ErrNotSortable = errors.New("XUID does not embed a sortable timestamp") )
Functions ¶
func Compare ¶
Compare returns an integer comparing two XUIDs.
The result is -1 if x sorts before y, 0 if x and y are ordered identically, and +1 if x sorts after y.
XUIDs are ordered by prefix first (an empty prefix sorts before any non-empty prefix), then by UUID bytes. For UUIDv7 identifiers sharing a prefix, the UUID bytes preserve chronological order, so Compare yields time-ordered results within each prefix.
Note that Compare treats XUIDs as ordered values, not identical ones: Compare returns 0 only when both prefix and UUID match.
func IsEmpty ¶
IsEmpty reports whether xid is the empty XUID: one whose UUID is the nil UUID. The empty XUID never carries a prefix (see NewWith), so an empty XUID always has an empty prefix.
func ValidatePrefix ¶
ValidatePrefix reports whether prefix is a valid XUID prefix.
A prefix may be empty, meaning the identifier has no prefix. Otherwise it must be at most MaxPrefixLen bytes long and contain only ASCII letters, digits, and underscores ([a-zA-Z0-9_]). The underscore is allowed because it is a common word separator and, since Parse splits on the last underscore, a prefix containing underscores still round-trips. Hyphens are deliberately not allowed.
On failure it returns an error wrapping ErrInvalidPrefix.
Types ¶
type NullXUID ¶
NullXUID represents an XUID that may be NULL in a SQL database. It is the XUID counterpart to sql.NullString and implements driver.Valuer and sql.Scanner, so it can be used both as a query argument and as a scan destination for a nullable UUID column.
Valid reports whether the value is not NULL, mirroring the other sql.Null* types. Unlike XUID.Value, which maps a nil UUID to NULL, NullXUID treats Valid as the single source of truth: with Valid set, its Value is non-NULL even when it holds the nil UUID, so a non-NULL all-zero UUID round-trips without collapsing to NULL.
func (*NullXUID) Scan ¶
Scan implements the sql.Scanner interface. A NULL value sets Valid to false and leaves the XUID at its zero value. Any supported non-NULL value is scanned with XUID.Scan and sets Valid to true.
As with XUID.Scan, a UUID-format or binary value carries no prefix and scanning it yields an empty prefix; an XUID-format value preserves its prefix. Restore a lost prefix with WithPrefix or MustWithPrefix based on the table or column the value was read from.
type XUID ¶
type XUID struct {
// contains filtered or unexported fields
}
func MustNewRandom ¶
func MustNewSortable ¶
func NewSortable ¶
func NewWith ¶
NewWith returns an XUID carrying id and prefix.
The prefix must satisfy ValidatePrefix. A non-empty prefix is rejected when id is the nil UUID (wrapping ErrNilUUIDWithPrefix): the nil UUID represents an absent identifier and is always encoded as null/empty by JSON, text and SQL, so a prefixed nil UUID could not round-trip. Use an empty prefix for a nil UUID, as NilUUID does.
func NilUUID ¶
NilUUID returns the empty XUID: a nil UUID with no prefix. It is the same value as the zero-value XUID and satisfies IsEmpty.
func Parse ¶
Parse decodes an XUID string. The final underscore separates the optional prefix from the base58-encoded UUID, so underscores inside the prefix are preserved:
user_admin_Cf1k9VmUZGg55baoJFXnT -> prefix "user_admin"
The prefix must satisfy ValidatePrefix (at most MaxPrefixLen bytes of [a-zA-Z0-9_]); otherwise the string is rejected. A string whose decoded UUID is the nil UUID but whose prefix is non-empty is also rejected, because the nil UUID never carries a prefix. Because Parse enforces the same rules, IsValid does too.
Every failure wraps ErrParse with the underlying cause, so callers can detect them with errors.Is(err, ErrParse) while still logging a message that explains why parsing failed. Prefix failures also wrap ErrInvalidPrefix, and a prefixed nil UUID wraps ErrNilUUIDWithPrefix, so errors.Is works for those too.
func (XUID) Equal ¶
Equal reports whether x and y identify the same XUID.
Two XUIDs are equal only if both their UUID and prefix match. A different prefix means the identifiers are not equal, even when they carry the same UUID, because the prefix is part of the identifier's identity. To compare only the underlying UUIDs, use EqualUUID.
func (XUID) EqualUUID ¶
EqualUUID reports whether x and y carry the same UUID, ignoring their prefixes. It is useful when the same identifier is stored under different prefixes (e.g. after restoring a prefix from a database column).
func (*XUID) GobDecode ¶
GobDecode implements the gob.GobDecoder interface.
It accepts exactly the bytes produced by GobEncode: an empty slice decodes to the nil UUID, and anything else must be a valid XUID string. Because the text form carries the prefix, gob round-trips an XUID with its prefix intact.
func (XUID) GobEncode ¶
GobEncode implements the gob.GobEncoder interface.
XUID stores its state in unexported fields, so encoding/gob cannot encode it by reflection; without these methods it fails with "type xuid.XUID has no exported fields". Implementing GobEncode and GobDecode lets XUIDs be transmitted and stored with gob, including their prefixes.
The encoding is the canonical text form returned by MarshalText: the empty slice for the nil UUID, and prefix_base58 otherwise. It is stable for a given XUID, so encoded values remain decodable as the package evolves.
func (XUID) IsRandom ¶
IsRandom reports whether x is a well-formed UUIDv4 identifier, i.e. its version nibble is 4 and it carries the RFC 9562 variant. As with IsSortable, the variant bits are checked so a foreign UUID is not mistaken for one this package generates.
func (XUID) IsSortable ¶
IsSortable reports whether x is a well-formed UUIDv7 identifier, i.e. its version nibble is 7 and it carries the RFC 9562 variant. Only then does it embed a trustworthy 48-bit timestamp, so a foreign or malformed UUID that merely has a 7 in the version nibble is not reported as sortable and Time will not trust its timestamp.
func (XUID) MarshalJSON ¶
MarshalJSON implements the json.Marshaler interface.
It delegates to MarshalText and encodes the result as a JSON string, except for a zero-value XUID (empty text), which marshals to null.
func (XUID) MarshalText ¶
MarshalText implements the encoding.TextMarshaler interface.
It returns the canonical XUID string, so XUID works as a JSON map key and with text-based encoders such as YAML, TOML and encoding/xml.
A zero-value XUID (nil UUID) marshals to the empty string, mirroring the null value produced by MarshalJSON and the NULL value produced by Value for SQL storage.
func (XUID) MustWithPrefix ¶
MustWithPrefix returns a copy of x with the prefix set to prefix, and panics if prefix is invalid. It is the chainable counterpart of WithPrefix.
func (*XUID) Scan ¶
Scan implements the sql.Scanner interface. This allows XUID to be loaded from SQL databases.
It accepts the shapes drivers actually deliver UUID columns in:
- string in UUID format or in the package's own XUID format
- []byte of exactly 16 raw bytes
- []byte containing a UUID or XUID string (e.g. lib/pq)
- [16]byte or uuid.UUID (e.g. pgx's native UUID type)
A []byte of exactly 16 bytes is ambiguous: it may be the raw UUID bytes written by Value, or a 16-byte textual identifier such as the base58 form of the nil UUID, "1111111111111111" (which Parse still accepts). Scan prefers the text interpretation when the bytes are valid UUID/XUID text and otherwise treats them as raw UUID bytes, so text columns are read correctly. The trade-off is that the astronomically rare raw 16-byte UUID whose bytes also form valid XUID text is read as that text; pass such a value as a [16]byte or uuid.UUID to force the raw interpretation.
A UUID-format value never carries a prefix, so scanning one yields an empty prefix. An XUID-format value preserves the prefix it encodes. Binary columns store no prefix, so restore one with WithPrefix or MustWithPrefix based on the table or column the value was read from:
var loaded xuid.XUID
loaded.Scan(value)
restored := loaded.MustWithPrefix("user")
func (*XUID) SetPrefix
deprecated
SetPrefix sets the prefix field to the specified prefix. This is useful when loading XUIDs from database and need to restore the prefix.
The prefix is not validated; an invalid prefix produces an XUID whose String cannot be parsed back by Parse. Prefer WithPrefix, which validates the prefix and reports an error. Unlike WithPrefix, this method also does not enforce the nil-UUID/prefix invariant, so it can produce a value that JSON, text and SQL cannot round-trip.
Deprecated: use WithPrefix instead. SetPrefix mixes mutation with chaining semantics and cannot be chained off non-addressable values, such as function results.
func (XUID) String ¶
String returns the canonical string form of x: its optional prefix, followed by an underscore and the base58-encoded UUID.
The nil UUID (the zero-value XUID) renders as the empty string, the same form MarshalText, JSON and SQL use for it, so an unset identifier is obvious in logs instead of looking like a real one. Parse rejects the empty string by design, so the nil UUID does not round-trip through String and Parse; detect it with IsEmpty, or use MarshalText and UnmarshalText, which do round-trip.
func (XUID) Time ¶
Time returns the instant encoded in a UUIDv7 identifier's 48-bit millisecond timestamp. Because NewSortable generates UUIDv7 identifiers, their Time is the moment the identifier was created.
It returns the zero time and an error wrapping ErrNotSortable if x is not a well-formed UUIDv7, such as a UUIDv4, the nil UUID, or a value whose variant is not the RFC 9562 variant. Use IsSortable to check first when a zero time is not an option:
if id.IsSortable() {
created, _ := id.Time()
}
func (*XUID) UnmarshalJSON ¶
UnmarshalJSON implements the json.Unmarshaler interface.
A JSON null is a no-op: it leaves x unchanged, matching the convention of time.Time and other non-pointer json.Unmarshaler types, so decoding a payload such as {"id":null} does not wipe a previously-set value. Any other value must be a JSON string holding a valid XUID; an empty string maps to the zero-value XUID, so it remains possible to clear a value explicitly.
func (*XUID) UnmarshalText ¶
UnmarshalText implements the encoding.TextUnmarshaler interface.
An empty string maps to the zero-value XUID, so the empty form that MarshalText produces for the nil UUID round-trips. Any other value must be a valid XUID string.
func (XUID) Value ¶
Value implements the driver.Valuer interface.
It returns the 16 raw UUID bytes as a []byte so drivers store XUIDs in binary UUID columns (BYTEA in PostgreSQL, BINARY(16) in MySQL) rather than a text column. The prefix is not stored; restore it with WithPrefix or MustWithPrefix after scanning.
A nil UUID is encoded as SQL NULL.
func (XUID) WithPrefix ¶
WithPrefix returns a copy of x with the prefix set to prefix. It is the supported way to restore a prefix after loading the underlying UUID from a database column, since Scan discards prefixes:
restored, err := xuid.MustParse(s).WithPrefix("user")
The prefix is validated like it is in the constructors; an invalid prefix returns an error wrapping ErrInvalidPrefix. A non-empty prefix on the nil UUID is rejected with an error wrapping ErrNilUUIDWithPrefix, since a nil UUID is an absent identifier that cannot carry a prefix. Use MustWithPrefix for a chainable, panic-on-error form:
restored := xuid.MustParse(s).MustWithPrefix("user")
Being immutable, WithPrefix chains off any value, including non-addressable ones such as function results.