Documentation
¶
Overview ¶
Package ids is the kit's standard prefixed-ULID utility.
Services on gokit conventionally tag identifiers with a short type-prefix: `user_01H…`, `acc_01H…`, `prod_01H…`. The pattern is everywhere but was unstandardised — every consumer rewrote the same `NewID(prefix)` / `ParseID(prefix, s)` / `FormatID(prefix, raw)` helpers on top of github.com/oklog/ulid/v2. This package is the single canonical implementation.
Wire shape ¶
Every ID is `<prefix><26-char Crockford-Base32 ULID>`. Crockford Base32 is the ULID-spec encoding — case-insensitive on Parse, but New / Format always emit uppercase for stability.
The 26-char suffix decodes to exactly 16 raw bytes (the binary ULID layout: 6 bytes ms-precision timestamp + 10 bytes randomness). Same 16 bytes pgx writes to a Postgres `uuid` column via the [16]byte codec the kit registers in db.Connect (see `db/uuid_codec.go`, shipped in v1.0.1).
Functions
- New — mint a new prefixed ID. Time-sortable. Monotonic within a process under contention.
- Parse — validate + strip prefix, return raw 16 bytes for storage as `uuid`.
- Format — inverse of Parse for callers holding raw bytes (typical: row scanned from a `uuid` column).
- RegisterValidator — wire a `validate:"prefix=prod_"` struct tag for declarative DTO validation.
Error codes ¶
All parse-stage errors are *errs.Error of Kind = Validation:
- CodeBadPrefix — input doesn't start with the expected prefix (including length mismatch or empty input).
- CodeBadSuffix — 26-char suffix isn't a valid Crockford-Base32 ULID (wrong length, illegal character, etc).
`errors.Is` works against the package-level sentinels ErrBadPrefix and ErrBadSuffix for branching code, but most callers should match on `e.Code` for stable wire / log behaviour.
Goroutine safety ¶
New is goroutine-safe: it serialises around a package-level monotonic entropy source so two concurrent calls in the same millisecond still produce strictly increasing ULIDs (per the ULID spec). Parse / Format are pure functions over their inputs.
Example ¶
Example mints a fresh prefixed ULID and shows the round-trip: New → Parse → Format returns the original string. New is time-based and random, so the value differs every run — the invariants below hold regardless.
package main
import (
"fmt"
"strings"
"github.com/theizzatbek/gokit/ids"
)
func main() {
id := ids.New("user_")
raw, err := ids.Parse("user_", id)
fmt.Println(strings.HasPrefix(id, "user_"))
fmt.Println(err == nil)
fmt.Println(ids.Format("user_", raw) == id)
}
Output: true true true
Index ¶
Examples ¶
Constants ¶
const ( // CodeBadPrefix — input doesn't start with the prefix the caller // supplied. Also returned when the input is shorter than the // prefix or empty. Wire-safe to surface in HTTP 400 responses. CodeBadPrefix = "id_bad_prefix" // CodeBadSuffix — the 26-char tail after the prefix isn't a // valid Crockford-Base32 ULID. Covers wrong length, illegal // character, ULID-overflow. Wire-safe to surface in HTTP 400. CodeBadSuffix = "id_bad_suffix" )
Stable error codes returned in *errs.Error.Code from the ids package. Downstream alerting / log-routing can match on these constants stably.
const ValidatorTag = "id_prefix"
ValidatorTag is the struct-tag name RegisterValidator wires. Stable across the kit's semver contract; documented here so callers can grep for it.
Variables ¶
var ( // ErrBadPrefix is wrapped by the *errs.Error returned from Parse // when the input doesn't match the supplied prefix. ErrBadPrefix = errors.New("ids: bad prefix") // ErrBadSuffix is wrapped by the *errs.Error returned from Parse // when the suffix isn't a valid ULID. ErrBadSuffix = errors.New("ids: bad suffix") )
Sentinel errors for errors.Is checks. Most callers should branch on *errs.Error.Code instead — the codes are stable across the kit's semver contract; sentinel identity is not.
Functions ¶
func Format ¶
Format is the inverse of Parse for callers holding raw 16-byte ID material (typically a row scanned from a pgx `uuid` column). The output is the canonical wire form: `prefix + 26-char uppercase Crockford-Base32`.
Format does not validate raw — any 16 bytes encode cleanly to a 26-char ULID string. Callers that scanned from an authoritative source (DB, Parse output) can trust the result.
func New ¶
New returns "<prefix><26-char Crockford-Base32 ULID>".
The 16-byte binary layout is the ULID spec's:
6 bytes ms-precision Unix timestamp || 10 bytes randomness
IDs are time-sortable (lexicographic order matches creation order at ms resolution) and monotonic within a single process — two New() calls in the same millisecond produce IDs that compare as strictly increasing per the ULID monotonic-entropy contract.
New is safe for concurrent use; the entropy source is guarded by a package-level lock so two goroutines colliding in the same millisecond see deterministic monotonic increments rather than the underlying ulid package's panic-on-overflow behaviour.
func Parse ¶
Parse validates that s starts with prefix and that the 26-char tail decodes as a Crockford-Base32 ULID, returning the raw 16 bytes for storage as a Postgres uuid column (or any other 16-byte-keyed store).
Error mapping:
*errs.Error{Kind: Validation, Code: CodeBadPrefix}
when s doesn't start with prefix (or is shorter than prefix).
*errs.Error{Kind: Validation, Code: CodeBadSuffix}
when the tail isn't a valid ULID (wrong length, illegal
character, overflow).
Callers that want errors.Is can match against ErrBadPrefix / ErrBadSuffix; most code should match on e.Code instead — the codes are part of the kit's semver-stable contract.
Example ¶
ExampleParse validates a known ID and strips the prefix, returning the raw 16 bytes you'd store in a Postgres uuid column. Format is the inverse and always re-emits uppercase Crockford Base32.
package main
import (
"fmt"
"github.com/theizzatbek/gokit/ids"
)
func main() {
raw, err := ids.Parse("user_", "user_01ARZ3NDEKTSV4RRFFQ69G5FAV")
if err != nil {
panic(err)
}
fmt.Println(ids.Format("user_", raw))
}
Output: user_01ARZ3NDEKTSV4RRFFQ69G5FAV
Example (BadPrefix) ¶
ExampleParse_badPrefix shows the error contract: a prefix mismatch is a typed *errs.Error of KindValidation with a stable Code, so HTTP handlers map it to 400 without special-casing.
package main
import (
"errors"
"fmt"
"github.com/theizzatbek/gokit/errs"
"github.com/theizzatbek/gokit/ids"
)
func main() {
_, err := ids.Parse("user_", "acct_01ARZ3NDEKTSV4RRFFQ69G5FAV")
var e *errs.Error
if errors.As(err, &e) {
fmt.Println(e.Kind, e.Code)
}
}
Output: validation id_bad_prefix
func RegisterValidator ¶
RegisterValidator wires the `validate:"id_prefix=<prefix>"` tag on the supplied *validator.Validate so DTOs can validate inbound IDs declaratively:
type CreatePolicyReq struct {
ProductID string `json:"product_id" validate:"required,id_prefix=prod_"`
}
The tag enforces both halves of Parse: the field's string starts with the param-supplied prefix AND the tail is a valid 26-char Crockford-Base32 ULID. A field that fails either half surfaces a normal validator.ValidationErrors entry — the fibermap.ErrsvalBindError handler picks it up as a 400 with per-field Details[] without any additional wiring.
Returns the error from validator.RegisterValidation (rare: only on a duplicate registration on the same *validator.Validate instance, which is itself a programmer-error condition).
Pure-stdlib safety: the registered function does NOT depend on any package state beyond the `prefix` param, so it is safe to call against shared *validator.Validate instances (the typical case: [service.New] builds one default validator and the same instance binds every handler's bind step).
Types ¶
This section is empty.