urnfield

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Apr 22, 2026 License: MIT Imports: 6 Imported by: 0

README

urnfield is a go library for using URN fields in structs or function params

How URNs work (RFC 8141)

See https://tools.ietf.org/html/rfc8141

A URN (Uniform Resource Name) is a persistent, location-independent identifier. The full syntax is:

urn:<NID>:<NSS>[?+<resolvers>][?=<query>][#<fragment>]
  • urn: — fixed scheme prefix
  • NID (Namespace Identifier) — names the namespace, e.g. isbn, ietf, payments. Case-insensitive, 2–32 chars.
  • NSS (Namespace-Specific String) — the actual identifier within that namespace. Structure is defined by the namespace; elements are commonly delimited by : or /.
  • Resolvers (?+) — optional hints about how to locate or access the resource (e.g. a service endpoint). Not part of the identity.
  • Query (?=) — optional key/value pairs for passing parameters. Also not part of the identity.
  • Fragment (#) — optional fragment, same semantics as in URLs.

Two URNs are considered equivalent if their NID and NSS match (case-insensitively for the NID, case-sensitively for the NSS by default). The resolvers, query, and fragment components do not affect identity.

Examples
urn:isbn:978-0-13-110362-7
urn:ietf:rfc:2648
urn:payments:account:au:123456:78901234?+resolve=https://api.example.com?=currency=AUD

Usage

The intent is that once a URN string is created and set it is immutable (except possibly the resolvers component).

Examples

Parsing and formatting
// Parse a URN string into a Urn struct
u, err := urnfield.Parse("urn:ietf:rfc:2648")
if err != nil {
    log.Fatal(err)
}

fmt.Println(u.Nid)  // "ietf"
fmt.Println(u.Nss)  // ["rfc", "2648"]

// Format it back to a string
s, err := u.Format()
if err != nil {
    log.Fatal(err)
}
fmt.Println(s) // "urn:ietf:rfc:2648"
Defining a schema

Schemas validate the structure of a namespace's NSS using a chain of element validators. The IETF namespace (RFC 2648) accepts several sub-namespaces (rfc, fyi, std, bcp, id, params) plus any other string:

var oneOrMoreDigits = &urnfield.NssSchema{
    Description:      "1*DIGIT",
    ElementValidator: urnfield.RegexNssElementValidatorFunc(regexp.MustCompile(`^\d+$`), nil),
}

var IETFSchema = &urnfield.Schema{
    Description: "IETF URN namespace (RFC 2648)",
    Nid:         "ietf",
    NssSchema: &urnfield.NssSchema{
        Description: "sub-namespace",
        ElementValidator: urnfield.ComplexOrNssElementValidatorFunc(
            []*urnfield.NssSchema{
                // rfc: 1*DIGIT  e.g. urn:ietf:rfc:2648
                {ElementValidator: urnfield.EqualsNssElementValidatorFunc("rfc", oneOrMoreDigits)},
                // fyi: 1*DIGIT  e.g. urn:ietf:fyi:20
                {ElementValidator: urnfield.EqualsNssElementValidatorFunc("fyi", oneOrMoreDigits)},
                // params: *    e.g. urn:ietf:params:xml:ns:allocationToken-1.0
                {
                    ElementValidator: urnfield.EqualsNssElementValidatorFunc("params",
                        &urnfield.NssSchema{
                            ElementValidator: urnfield.GlobNssElementValidatorFunc(glob.MustCompile("*")),
                        }),
                },
            },
        ),
    },
}
Validating a URN against a schema
err := IETFSchema.Validate("urn:ietf:rfc:2648")   // nil
err  = IETFSchema.Validate("urn:ietf:rfc:abc")    // error: "abc" is not digits
err  = IETFSchema.Validate("urn:isbn:123")         // error: NID mismatch

// Validate a pre-parsed Urn directly
u, _ := urnfield.Parse("urn:ietf:params:xml:ns:allocationToken-1.0")
err   = IETFSchema.ValidateUrn(u)                  // nil

A full working implementation of the IETF schema is available in examples/ietf/.

Use cases

Referencing resources by identity without fetching them:

payment := Payment{
  amount:      Currency.New("AUD", 1000),
  //assume we have a payments urn schema
  fromAccount: urn.MustParse("urn:payments:account:banka:au:123456:78901234"),
  toAccount: fromAccount: urn.MustParse("urn:payments:account:bankb:uk:98-99-00:945-234B"),
}

Concise claims in auth tokens

{
  "iss": "urn:payments:processor:acme",
  "sub": "urn:payments:user:acme:u-4f92a1",
  "aud": "urn:payments:processor:banka",
  "exp": 1744329600,
  "nbf": 1744243200,
  "iat": 1744243200,
  "jti": "76588473-b530-4e2b-8693-992f55a6c5b1",
  "permissions": [
    "urn:payments:account:banka:au:123456:78901234:read",
    "urn:payments:account:banka:au:123456:78901234:transfer",
    "urn:payments:account:bankb:uk:98-99-00:945-234B:read"
  ]
}

All seven registered claims from RFC 7519 are shown: iss (issuer), sub (subject), aud (audience), exp (expiry), nbf (not before), iat (issued at), and jti (JWT ID). URNs work naturally for the identity claims — they're globally unique, self-describing, and carry no location coupling.

Each scope URN precisely identifies the resource and the permitted action, without needing a separate schema document to interpret it.

Documentation

Overview

This file implements namespace-specific URN validation via a recursive continuation-passing validator chain.

Validator chain

The core abstraction is NssElementValidator — a function with the signature:

func(nss []string) (remainder []string, next *NssSchema, err error)

Each validator processes the head of the NSS slice and returns the unconsumed tail plus the NssSchema to use for the next element. Returning next == nil signals that no further elements are expected. This is a continuation-passing style: the schema structure is a linked list of validators built at definition time, and validate() walks it recursively at validation time.

OR branching

ComplexOrNssElementValidatorFunc implements branching by trying each alternative NssSchema in order and returning on the first success. It does not backtrack within a branch — once a validator succeeds it commits. This linear scan is sufficient because URN sub-namespaces are typically keyed on a fixed first element (e.g. "rfc", "params"), making branches mutually exclusive in practice.

SimpleOrNssElementValidatorFunc is an optimised variant for fixed-string branching backed by a map lookup (O(1)) rather than linear scan.

Termination

validate() enforces exact element consumption: if next == nil but remainder is non-empty, validation fails with "too many nss elements". If next != nil but remainder is empty, it fails with "not enough nss elements". This ensures schemas are fully structural — every element must be accounted for.

Package urnfield parses, formats, and validates URNs (Uniform Resource Names) per RFC 8141.

Parsing

Parse uses a single anchored regex (Pattern) to capture the five URN components in one pass: NID, NSS, query (?=), resolvers (?+), and fragment (#). The NSS capture is then split into a []string by scanning for ":" or "/" delimiters — whichever appears first is used exclusively (mixed delimiters are not supported). The chosen delimiter is recorded in NssSlashDelimiter for faithful round-trip formatting. Query and resolver components are parsed as "&"-delimited key=value pairs; keys without a "=" are stored with an empty value slice.

Formatting

Format is the inverse of Parse. Query and resolver map keys are sorted alphabetically before output. The URN spec does not require a key order, but sorting makes the output deterministic — without it, map iteration order would make round-trip equality tests unreliable. The sort is applied in writeKeyValuesMap and is the only place the library intentionally diverges from strict spec neutrality.

Validation

Structural validation (well-formedness) is handled by IsWellFormed. Namespace- specific validation is provided separately via the Schema type in schema.go.

Index

Constants

View Source
const Pattern = `` /* 145-byte string literal not displayed */

Pattern is the regex used to parse a complete URN string into its components. It is anchored with ^ and $ so it matches the full input string only; strings with surrounding content will not match.

Variables

This section is empty.

Functions

This section is empty.

Types

type NssElementValidator

type NssElementValidator func(nss []string) (nssRemainder []string, next *NssSchema, err error)

NssElementValidator is a function that validates one or more NSS elements. It returns the remaining unprocessed elements and the NssSchema to use for the next element, or a non-nil error if validation fails. When no further elements are expected, next is nil and nssRemainder should be empty.

func ComplexOrNssElementValidatorFunc

func ComplexOrNssElementValidatorFunc(alternatives []*NssSchema) NssElementValidator

ComplexOrNssElementValidatorFunc returns an NssElementValidator that tries each of the provided NssSchemas in order, returning the result of the first one that succeeds. Use this when alternatives need their own validation logic beyond a fixed string match (e.g. "rfc" followed by digits, vs "params" followed by an opaque glob). For simple fixed-string branching use SimpleOrNssElementValidatorFunc instead.

func EqualsNssElementValidatorFunc

func EqualsNssElementValidatorFunc(nssEquals string, next *NssSchema) NssElementValidator

EqualsNssElementValidatorFunc returns an NssElementValidator that requires the current NSS element to equal nssEquals exactly. The value operates on a single pre-split element — do not include ":" or "/" in nssEquals.

func GlobNssElementValidatorFunc

func GlobNssElementValidatorFunc(glob glob.Glob) NssElementValidator

GlobNssElementValidatorFunc returns an NssElementValidator that matches all remaining NSS elements (joined with ":") against the given glob pattern. This validator always terminates the chain — it consumes every remaining element in one match, so no next NssSchema is accepted. This is intentional: glob patterns are used for opaque or arbitrarily deep sub-namespaces (e.g. the IETF "params" sub-namespace) where per-element structure is not defined. For single-element pattern matching use RegexNssElementValidatorFunc instead. See https://github.com/gobwas/glob for pattern syntax.

func RegexNssElementValidatorFunc

func RegexNssElementValidatorFunc(pattern *regexp.Regexp, next *NssSchema) NssElementValidator

RegexNssElementValidatorFunc returns an NssElementValidator that matches the current NSS element against the given compiled regex pattern. Patterns operate on individual pre-split elements — do not include ":" or "/" in the pattern, as these delimiters are consumed by Parse before validation.

func SimpleOrNssElementValidatorFunc

func SimpleOrNssElementValidatorFunc(alternatives map[string]*NssSchema) NssElementValidator

SimpleOrNssElementValidatorFunc returns an NssElementValidator that matches the current NSS element against a map of allowed string values, each mapping to the next NssSchema to use. Use this when each alternative is a fixed string (e.g. "rfc", "fyi", "std"). For alternatives that require their own sub-validation logic, use ComplexOrNssElementValidatorFunc instead.

type NssSchema

type NssSchema struct {
	Description      string
	ElementValidator NssElementValidator
}

NssSchema defines the validation rules for a set of NSS elements.

type Schema

type Schema struct {
	Description string
	Nid         string
	NssSchema   *NssSchema
}

Schema defines a valid URN in a specific namespace. Note that Schema does not validate the query, resolvers, or fragment components.

func (*Schema) Validate

func (s *Schema) Validate(urn string) error

Validate parses urn and checks it against the schema.

func (*Schema) ValidateUrn

func (s *Schema) ValidateUrn(u Urn) error

ValidateUrn validates a parsed Urn against the schema.

type Urn

type Urn struct {
	// Nid is the Namespace Identifier.
	Nid string
	// NssSlashDelimiter indicates if this URN uses "/" as the NSS delimiter instead of ":".
	NssSlashDelimiter bool
	// Nss holds the Namespace-Specific String elements in order.
	Nss []string
	// Query holds the query component ("?=") if one exists.
	Query map[string][]string
	// Resolvers holds the resolvers component ("?+") if one exists.
	Resolvers map[string][]string
	// Fragment holds the fragment component ("#") if one exists.
	Fragment string
}

Urn represents a parsed URN see https://tools.ietf.org/html/rfc8141

func Parse

func Parse(urn string) (Urn, error)

Parse parses a complete URN string and returns the parsed Urn struct or an error. The input must be a standalone URN — strings with surrounding content will not match. If *any* NSS separators are "/" then NssSlashDelimiter will be true.

func (Urn) Format

func (u Urn) Format() (string, error)

Format formats the Urn as a URN string per RFC 8141. Returns an error if the Urn is not well-formed. If NssSlashDelimiter is true, all NSS delimiters will be "/" instead of ":".

func (*Urn) IsWellFormed

func (u *Urn) IsWellFormed() error

IsWellFormed reports whether u is well-formed per RFC 8141, returning a descriptive error if not. Returns an error if u is nil.

func (Urn) ToString

func (u Urn) ToString() (string, error)

ToString converts the Urn to its string representation. It is a synonym for Format; prefer Format for consistency.

Jump to

Keyboard shortcuts

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