xuid

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 14, 2026 License: MIT Imports: 7 Imported by: 0

README

XUID

Go Reference

A Go package for generating compact, sortable UUID-based identifiers with optional string prefixes and base58 encoding.

Features

  • 🎯 Sortable UUIDs: Generate UUIDv7 identifiers that maintain chronological order
  • 🎲 Random UUIDs: Generate UUIDv4 identifiers for non-sortable use cases
  • 🏷️ Optional Prefixes: Add human-readable prefixes to your identifiers (e.g., user_, order_)
  • 📦 Compact Encoding: Uses base58 encoding for shorter, URL-safe strings
  • 🔄 JSON Support: Built-in JSON marshaling and unmarshaling
  • 🔤 Text Support: Implements encoding.TextMarshaler/TextUnmarshaler for YAML, TOML, XML and map keys
  • 🗄️ SQL Database Support: Seamless integration with SQL databases (PostgreSQL, MySQL, etc.)
  • ✅ Type Safety: Strong typing with validation and parsing utilities

Requirements

  • Go 1.27 or newer. The package uses the standard library uuid package, which was added in Go 1.27.

Installation

go get github.com/47monad/xuid

To pin a specific release:

go get github.com/47monad/xuid@v0.1.0

Quick Start

package main

import (
    "fmt"
    "github.com/47monad/xuid"
)

func main() {
    // Generate a sortable XUID with prefix. The UUID is random, so the
    // exact output varies; it is always "user_" followed by at most 22
    // base58 characters.
    id := xuid.MustNewSortable("user")
    fmt.Println(id.String()) // e.g. user_Cf1k9VmUZGg55baoJFXnT

    // Generate a random XUID.
    randomID, _ := xuid.NewRandom("session")
    fmt.Println(randomID.String()) // e.g. session_8QMBv8hxcm3BpPJ9wYgzNt

    // Parse an existing XUID string.
    parsed, _ := xuid.Parse("user_Cf1k9VmUZGg55baoJFXnT")
    fmt.Println(parsed.GetPrefix()) // user
}

Usage

Creating XUIDs
Sortable UUIDs (UUIDv7)
// With error handling
id, err := xuid.NewSortable("order")
if err != nil {
    log.Fatal(err)
}

// Without error handling (panics on error)
id := xuid.MustNewSortable("order")
Random UUIDs (UUIDv4)
id, err := xuid.NewRandom("session")
if err != nil {
    log.Fatal(err)
}

// Without error handling (panics on error)
id := xuid.MustNewRandom("order")
From Existing UUID
existingUUID := uuid.New()
id, err := xuid.NewWith(existingUUID, "custom")

Requires Go 1.27+ for the standard library uuid package.

Nil UUID
nilID, err := xuid.NilUUID()

A nil UUID represents an absent identifier and never carries a prefix. NewWith rejects a non-empty prefix on the nil UUID (wrapping ErrNilUUIDWithPrefix), and Parse rejects strings such as user_1111111111111111 whose decoded UUID is the nil UUID. This keeps the nil UUID encodable as null/empty by JSON, text and SQL without losing information. String likewise renders the nil UUID as the empty string; Parse rejects the empty string, so test for a nil UUID with IsEmpty (or use MarshalText/UnmarshalText, which round-trip it).

Working with XUIDs
String Representation
id := xuid.MustNewSortable("user")
fmt.Println(id.String()) // e.g. user_Cf1k9VmUZGg55baoJFXnT
Access Properties
id := xuid.MustNewSortable("user")

// Get the underlying UUID
uuid := id.GetUUID()

// Get the prefix
prefix := id.GetPrefix() // "user"

// Check UUID version and variant (both must be RFC 9562)
isSortable := id.IsSortable() // true for well-formed UUIDv7
isRandom := id.IsRandom()     // true for well-formed UUIDv4

// Extract the creation instant from a UUIDv7 (errors for anything else)
created, err := id.Time()
Parsing and Validation
// Parse a XUID string
id, err := xuid.Parse("user_Cf1k9VmUZGg55baoJFXnT")
if err != nil {
    log.Fatal(err)
}

// Validate a XUID string
if xuid.IsValid("user_Cf1k9VmUZGg55baoJFXnT") {
    fmt.Println("Valid XUID")
}

// Validate a prefix on its own (used by the constructors and Parse)
if err := xuid.ValidatePrefix("user"); err != nil {
    fmt.Println("Invalid prefix:", err)
}

// Check if empty
if xuid.IsEmpty(id) {
    fmt.Println("Empty XUID")
}
Comparison
id1 := xuid.MustNewSortable("user")
id2 := xuid.MustNewSortable("user")

// Equal compares both the UUID and the prefix: a different prefix
// means the identifiers are not equal, even with the same UUID.
if id1.Equal(id2) {
    fmt.Println("XUIDs are equal")
}

// EqualUUID compares only the underlying UUIDs, ignoring prefixes.
if id1.EqualUUID(id2) {
    fmt.Println("Same identifier, prefix ignored")
}

// Compare orders XUIDs by prefix first (empty prefix sorts first),
// then by UUID bytes. For UUIDv7 IDs sharing a prefix, this is
// chronological order.
if xuid.Compare(id1, id2) < 0 {
    fmt.Println("id1 sorts before id2")
}

// Sort a slice of XUIDs.
ids := []xuid.XUID{id2, id1}
slices.SortFunc(ids, xuid.Compare)
JSON Support

XUIDs can be seamlessly marshaled to and from JSON:

type User struct {
    ID   xuid.XUID `json:"id"`
    Name string    `json:"name"`
}

user := User{
    ID:   xuid.MustNewSortable("user"),
    Name: "John Doe",
}

// Marshal to JSON
data, _ := json.Marshal(user)
// {"id":"user_Cf1k9VmUZGg55baoJFXnT","name":"John Doe"}

// Unmarshal from JSON
var parsed User
json.Unmarshal(data, &parsed)

A zero-value XUID marshals to null, and unmarshalling null is a no-op: it leaves the destination unchanged rather than clearing it, matching time.Time and other non-pointer json.Unmarshaler types. This keeps a payload such as {"id":null} from silently wiping a previously-set identifier. To clear a value explicitly, unmarshal an empty JSON string (""), which maps to the zero value.

Text Support

XUID implements encoding.TextMarshaler and encoding.TextUnmarshaler, so it also works with text-based encoders (YAML, TOML, encoding/xml), templates, and as a JSON map key:

id := xuid.MustNewSortable("user")

// Use as a JSON map key.
scores := map[xuid.XUID]int{id: 10}
data, _ := json.Marshal(scores)
// {"user_Cf1k9VmUZGg55baoJFXnT":10}

// Or marshal/unmarshal the text form directly.
text, _ := id.MarshalText()
var loaded xuid.XUID
loaded.UnmarshalText(text)

A zero-value XUID marshals to an empty string, and an empty string unmarshals to the zero value, mirroring how JSON uses null and SQL uses NULL. String renders the nil UUID as the empty string too, so an unset identifier is obvious in logs rather than looking like a real one. Parse rejects the empty string, so test for the nil UUID with IsEmpty instead of parsing String.

Gob Support

XUID implements gob.GobEncoder and gob.GobDecoder, so it works with encoding/gob (RPC, caches, session stores) even though its fields are unexported:

var buf bytes.Buffer
if err := gob.NewEncoder(&buf).Encode(id); err != nil {
    log.Fatal(err)
}

var loaded xuid.XUID
if err := gob.NewDecoder(&buf).Decode(&loaded); err != nil {
    log.Fatal(err)
}

The gob encoding is the canonical text form used by MarshalText, so prefixes are preserved and the nil UUID round-trips as the empty value.

SQL Support

XUIDs integrate seamlessly with SQL databases such as PostgreSQL and MySQL. However, there are a few caveats to keep in mind:

  • Only the UUID bytes are stored — Value writes the 16-byte UUID as a []byte (e.g., BYTEA in PostgreSQL or BINARY(16) in MySQL). This ensures efficient storage and indexing.
  • Prefixes are not stored — A binary or UUID-format value carries no prefix, so scanning one yields a XUID with an empty prefix. Restore the prefix with WithPrefix/MustWithPrefix in your repository/DAO layer, based on the table or column the value was read from:
// Load from database
var loaded xuid.XUID
loaded.Scan(value)

// Restore prefix (the repo layer knows this column is a user ID)
restored := loaded.MustWithPrefix("user")

// WithPrefix validates and returns an error instead of panicking:
if restored, err := loaded.WithPrefix("user"); err != nil {
    // invalid prefix
}

Scan accepts the shapes drivers deliver UUID columns in:

  • a string in UUID format or in the package's own XUID format (user_Cf1k9VmUZGg55baoJFXnT),
  • a []byte holding either of those textual forms (e.g., lib/pq),
  • a []byte of exactly 16 raw UUID bytes,
  • a [16]byte or uuid.UUID (e.g., pgx's native UUID type).

A []byte of exactly 16 bytes is ambiguous: it can be the raw UUID bytes written by Value or a 16-character textual identifier — most notably "1111111111111111", the base58 form of the nil UUID (Parse still accepts it even though String renders the nil UUID as empty). Scan prefers the text interpretation when the bytes are valid UUID/XUID text and otherwise treats them as raw UUID bytes, so a 16-character text column is never silently misread. The reverse case — a raw 16-byte UUID whose bytes happen to form valid XUID text — is astronomically rare; pass it as a [16]byte or uuid.UUID to force the raw interpretation.

Scanning an XUID-format value (or a []byte holding one) preserves the prefix it encodes. UUID-format and binary values cannot: the prefix is not stored in a binary column.

WithPrefix validates the prefix exactly like the constructors and Parse, and returns (XUID, error). It is immutable: it returns a copy. Like the constructors, it rejects a non-empty prefix on the nil UUID (ErrNilUUIDWithPrefix). MustWithPrefix is the panic-on-error variant, so it chains off any value, including non-addressable ones such as xuid.MustParse(s).MustWithPrefix("user"). The older SetPrefix method is deprecated and does not validate (and does not enforce the nil-UUID/prefix invariant).

Nullable Columns

Because XUID.Value maps a nil UUID to SQL NULL, a plain XUID cannot distinguish a NULL column from an all-zero UUID. Use NullXUID for nullable columns:

type User struct {
    ID xuid.NullXUID `db:"id"`
}

// Scan (NULL sets Valid to false)
var id xuid.NullXUID
if err := id.Scan(dbValue); err != nil {
    log.Fatal(err)
}
if id.Valid {
    fmt.Println("user ID:", id.XUID)
}

// Value (NULL when Valid is false)
value, err := id.Value()

NullXUID implements driver.Valuer and sql.Scanner, and mirrors the standard library's sql.Null* types. Valid is authoritative: a NullXUID with Valid set is stored as non-NULL, even when it holds the nil UUID. Assign its XUID field to get at the underlying identifier; prefixes are still lost on scan and can be restored with WithPrefix.

Format

XUIDs follow this format:

  • Without prefix: Cf1k9VmUZGg55baoJFXnT
  • With prefix: prefix_Cf1k9VmUZGg55baoJFXnT

The nil UUID (the zero-value XUID) is the exception: String renders it as the empty string, matching its empty/null JSON, text and SQL encodings, so an unset identifier is obvious rather than plausible-looking. Parse deliberately rejects the empty string, so the nil UUID does not round-trip through String/Parse; use IsEmpty to detect it, or MarshalText/UnmarshalText, which do round-trip.

Prefixes are validated by every constructor and by Parse:

  • At most 32 bytes long (xuid.MaxPrefixLen).
  • Only ASCII letters, digits, and underscores: [a-zA-Z0-9_]. Hyphens are not allowed.
  • An empty prefix means the identifier has no prefix.
  • A non-empty prefix is only allowed on a non-nil UUID. The nil UUID (the empty XUID) always has an empty prefix, so NewWith(uuid.Nil(), "user") and Parse("user_1111111111111111") are rejected.

Use xuid.ValidatePrefix(s) to check a prefix on its own without constructing an XUID. Because Parse applies the same rules, IsValid enforces them too.

The identifier part is a base58-encoded UUID, making it:

  • Shorter than standard UUID strings (at most 22 characters vs 36)
  • URL-safe (no special characters that need encoding)
  • Case-sensitive but avoids confusing characters (0, O, I, l)

The base58 encoding is not zero-padded, so its length varies: a UUIDv7 generated today renders in 21 characters, while a random UUIDv4 usually renders in 22.

Error Handling

The package defines sentinel errors:

var (
    ErrParse             = errors.New("XUID string cannot be parsed")
    ErrScan              = errors.New("XUID cannot be scanned from a SQL value")
    ErrInvalidPrefix     = errors.New("XUID prefix is invalid")
    ErrNilUUIDWithPrefix = errors.New("XUID cannot combine the nil UUID with a prefix")
    ErrNotSortable       = errors.New("XUID does not embed a sortable timestamp")
)

Parse wraps ErrParse with the underlying cause for malformed XUID strings, so failures can be detected with errors.Is while still carrying a message that explains what went wrong:

if _, err := xuid.Parse(s); errors.Is(err, xuid.ErrParse) {
    // s was not a valid XUID
}

Scan likewise wraps ErrScan with context:

var id xuid.XUID
if err := id.Scan(value); errors.Is(err, xuid.ErrScan) {
    // the database value was not a valid UUID
}

Dependencies

  • UUID generation uses the standard library uuid package (Go 1.27+)
  • Base58 encoding is implemented internally using the Bitcoin alphabet, which excludes the visually ambiguous characters 0, O, I, and l for readability — there is no external base58 dependency

License

MIT License - see LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

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

View Source
const MaxPrefixLen = 32

MaxPrefixLen is the maximum length, in bytes, of an XUID prefix.

Variables

View Source
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

func Compare(x, y XUID) int

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

func IsEmpty(xid XUID) bool

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 IsValid

func IsValid(idstr string) bool

func Less

func Less(x, y XUID) bool

Less reports whether x sorts before y. It is equivalent to Compare(x, y) < 0.

func ValidatePrefix

func ValidatePrefix(prefix string) error

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

type NullXUID struct {
	XUID  XUID
	Valid bool
}

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

func (n *NullXUID) Scan(value interface{}) error

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.

func (NullXUID) Value

func (n NullXUID) Value() (driver.Value, error)

Value implements the driver.Valuer interface. It returns SQL NULL when Valid is false, and the 16 raw UUID bytes otherwise.

type XUID

type XUID struct {
	// contains filtered or unexported fields
}

func Must

func Must(xid XUID, err error) XUID

func MustNewRandom

func MustNewRandom(prefix string) XUID

func MustNewSortable

func MustNewSortable(prefix string) XUID

func MustParse

func MustParse(idstr string) XUID

func NewRandom

func NewRandom(prefix string) (XUID, error)

func NewSortable

func NewSortable(prefix string) (XUID, error)

func NewWith

func NewWith(id uuid.UUID, prefix string) (XUID, error)

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

func NilUUID() (XUID, error)

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

func Parse(idstr string) (XUID, error)

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

func (x XUID) Equal(y XUID) bool

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

func (x XUID) EqualUUID(y XUID) bool

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) GetPrefix

func (x XUID) GetPrefix() string

func (XUID) GetUUID

func (x XUID) GetUUID() uuid.UUID

func (*XUID) GobDecode

func (x *XUID) GobDecode(data []byte) error

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

func (x XUID) GobEncode() ([]byte, error)

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

func (x XUID) IsRandom() bool

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

func (x XUID) IsSortable() bool

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

func (x XUID) MarshalJSON() ([]byte, error)

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

func (x XUID) MarshalText() ([]byte, error)

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

func (x XUID) MustWithPrefix(prefix string) XUID

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

func (x *XUID) Scan(value interface{}) error

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

func (x *XUID) SetPrefix(prefix string) *XUID

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

func (x XUID) String() 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

func (x XUID) Time() (time.Time, error)

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

func (x *XUID) UnmarshalJSON(data []byte) error

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

func (x *XUID) UnmarshalText(data []byte) error

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

func (x XUID) Value() (driver.Value, error)

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

func (x XUID) WithPrefix(prefix string) (XUID, error)

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL