Documentation
¶
Overview ¶
Package refid provides a shared, config-driven reference ID generation system for NSW services.
Overview ¶
Each ID format is defined as an ordered list of typed segments (literal, list, date, sequence) that are concatenated at generation time. Formats are grouped by issuer and identified by an idType, and are looked up from a Registry by (issuer, idType).
Usage ¶
cfg, err := refid.LoadConfig("refid_config.yaml")
store, err := postgres.New(db) // github.com/OpenNSW/core/refid/store/postgres
reg, err := refid.NewRegistry(cfg, store)
id, err := reg.Generate(ctx, "RTA", "application_id", map[string]string{
"officeCode": "COL",
})
// id == "RTA-APP-COL-20260817-000042"
Config format ¶
See the package-level example_config.yaml for a fully annotated example.
Scope key placeholders ¶
Sequence segments use a scopeKey template to determine counter scope. Curly braces '{' and '}' are reserved in scopeKey templates for placeholder delimiters. The following placeholders are resolved at generation time:
{issuer} — the issuer identifier for this format
{idType} — the ID type identifier for this format
{yyyy} — four-digit year (UTC)
{yyyyMM} — year and month (UTC)
{yyyyMMdd} — year, month, and day (UTC)
{<param>} — any caller-supplied param not already claimed above
Including {yyyyMMdd} in a scope key gives a daily-resetting counter; omitting all date components gives a counter that never resets.
Counter overflow ¶
If a counter exceeds the maximum value representable with the configured padding width, Generate returns ErrCounterOverflow. This is intentional: a wider-than-expected ID would silently break any downstream system that validates ID length. Operations should be alerted and the scope key configuration reviewed.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrUnknownIssuer is returned when Generate is called with an issuer // that was not present in the config used to build the registry. ErrUnknownIssuer = errors.New("refid: unknown issuer") // ErrUnknownIDType is returned when Generate is called with an idType // that was not declared under the given issuer. ErrUnknownIDType = errors.New("refid: unknown id type") // ErrInvalidParam is returned when a list segment's required caller-supplied // param is missing or its value is not in the allowed list. ErrInvalidParam = errors.New("refid: invalid or missing param") // ErrCounterOverflow is returned when the sequence counter value for a scope // key exceeds the number of digits allowed by the segment's padding setting. // For example, a counter of 1,000,001 with padding:6 would produce a 7-digit // string, breaking the expected ID format. Callers should alert operations // when this occurs; the scope key is likely configured too broadly. ErrCounterOverflow = errors.New("refid: sequence counter exceeds padding width") )
Sentinel errors returned by the refid package. Use errors.Is to check for these in calling code.
Functions ¶
This section is empty.
Types ¶
type Config ¶
type Config struct {
// Issuers is the ordered list of issuer definitions. Each issuer owns one
// or more ID type formats.
Issuers []IssuerConfig `yaml:"issuers"`
// Lists is a map of named value sets. Segments of type "list" reference
// these by name, and the value supplied by the caller at generation time is
// validated against the corresponding slice.
Lists map[string][]string `yaml:"lists"`
}
Config is the top-level configuration for the ID generation system. It is typically loaded from a YAML file via LoadConfig, but can also be constructed programmatically (e.g. in tests or for embedded defaults).
func LoadConfig ¶
LoadConfig reads and parses a YAML configuration file at the given path. It performs no semantic validation; call NewRegistry to validate the config against the full constraint set.
type FormatConfig ¶
type FormatConfig struct {
// IDType uniquely identifies this format within the issuer, e.g.
// "application_id" or "permit_id". It is also available as the {idType}
// placeholder in scope key templates.
IDType string `yaml:"idType"`
// Segments is the ordered list of typed segments whose rendered outputs are
// concatenated to form the final ID string.
Segments []SegmentConfig `yaml:"segments"`
}
FormatConfig describes how a single ID type is assembled from an ordered list of segments.
type IssuerConfig ¶
type IssuerConfig struct {
// Issuer is the unique identifier for this issuing authority, e.g. "RTA".
// It is also available as the {issuer} placeholder in scope key templates.
Issuer string `yaml:"issuer"`
// Formats is the list of ID type formats owned by this issuer.
Formats []FormatConfig `yaml:"formats"`
}
IssuerConfig groups all ID format definitions for a single issuer.
type Registry ¶
type Registry interface {
// Generate produces a new ID for the given issuer and idType.
//
// params supplies caller-provided values consumed by list and sequence
// segments (e.g. map[string]string{"officeCode": "COL"}). Unused keys are
// silently ignored; missing required keys return ErrInvalidParam.
//
// Errors:
// - ErrUnknownIssuer — issuer not found in config
// - ErrUnknownIDType — idType not found under the given issuer
// - ErrInvalidParam — a required param is missing or has an invalid value
// - ErrCounterOverflow — sequence counter exceeds padding width
Generate(ctx context.Context, issuer, idType string, params map[string]string) (string, error)
}
Registry is the entry point for generating IDs. Obtain one via NewRegistry.
Implementations must be safe for concurrent use by multiple goroutines.
func NewRegistry ¶
func NewRegistry(cfg Config, store SequenceStore) (Registry, error)
NewRegistry validates cfg and compiles all formats into an internal lookup table. It returns an error if the config contains:
- duplicate (issuer, idType) pairs
- a segment that references an undefined list name
- a segment with missing required fields (e.g. empty scopeKey, empty layout)
- an unrecognised segment type
Fail-fast at startup: every error that would surface at generation time is caught here instead.
type SegmentConfig ¶
type SegmentConfig struct {
// Type is one of: "literal", "list", "date", "sequence".
Type string `yaml:"type"`
// Value is the fixed text for a literal segment.
Value string `yaml:"value,omitempty"`
// List is the name of a list defined in Config.Lists, used by list segments.
List string `yaml:"list,omitempty"`
// Param is the key the caller must supply in their params map, used by list
// segments to look up the caller-provided value.
Param string `yaml:"param,omitempty"`
// Layout is a Go reference-date format string (e.g. "20060102"), used by
// date segments.
Layout string `yaml:"layout,omitempty"`
// ScopeKey is a template string for sequence segments. Placeholders are
// resolved at generation time. Curly braces '{' and '}' are reserved as
// placeholder delimiters in scope keys. See the Registry documentation for
// the full placeholder reference.
ScopeKey string `yaml:"scopeKey,omitempty"`
// Padding is the minimum number of digits for a sequence segment's counter.
// The counter is zero-padded to this width. Must be between 1 and 18.
// If the counter exceeds the maximum value representable with Padding digits,
// ErrCounterOverflow is returned.
Padding int `yaml:"padding,omitempty"`
}
SegmentConfig is the raw configuration for a single segment. Fields are interpreted according to Type; unused fields are ignored.
type SequenceStore ¶
type SequenceStore interface {
// Next atomically increments the counter for the given scope key and returns
// the new value, provided the current counter is less than max.
// If the counter would exceed max, Next returns ErrCounterOverflow without
// incrementing the counter in storage.
Next(ctx context.Context, scopeKey string, max int64) (int64, error)
}
SequenceStore is the persistence interface for durable, atomic sequence counters. Each distinct scope key gets its own counter, starting at 1.
The refid/store/postgres and refid/store/sqlite subpackages each provide an implementation via New. Any caller that needs a different backend (Redis, in-memory for tests, etc.) can provide their own implementation.
Directories
¶
| Path | Synopsis |
|---|---|
|
store
|
|
|
internal/sqlident
Package sqlident validates SQL identifiers (table names) that refid's storage backends interpolate directly into raw SQL strings.
|
Package sqlident validates SQL identifiers (table names) that refid's storage backends interpolate directly into raw SQL strings. |
|
postgres
Package postgres provides a PostgreSQL-backed implementation of refid.SequenceStore using database/sql and raw SQL — no ORM.
|
Package postgres provides a PostgreSQL-backed implementation of refid.SequenceStore using database/sql and raw SQL — no ORM. |
|
sqlite
Package sqlite provides a SQLite-backed implementation of refid.SequenceStore using database/sql and raw SQL — no ORM.
|
Package sqlite provides a SQLite-backed implementation of refid.SequenceStore using database/sql and raw SQL — no ORM. |