alias

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Aug 18, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

Package alias generates and validates the short codes that appear after the host in a LinkCtrl URL.

Two properties drive the design.

First, an alias sits in the redirect hot path and in the unique index, so validation must be cheap and total: every alias that reaches the database is already canonical, and canonicalization is idempotent.

Second, an alias is read aloud, typed from a printed page, and scanned from a QR code, so the alphabet excludes characters people confuse. Because 'i', 'l' and 'o' are absent, the digits '0' and '1' are unambiguous and are kept — which is what brings the alphabet to exactly 32 characters and lets each character consume five bits of randomness with no modulo bias.

Index

Examples

Constants

View Source
const (
	// DefaultLength is the length of a generated code. 32^7 is about 3.4e10,
	// which keeps the collision probability negligible at the scale this
	// project targets while staying short enough to print and read aloud.
	DefaultLength = 7

	// MaxGeneratedLength bounds the escalation in Generate. Reaching it means
	// something is wrong other than bad luck.
	MaxGeneratedLength = 12

	// MinLength and MaxLength bound user-supplied aliases. The lower bound
	// keeps the two-character namespace free for future routing use; the upper
	// bound is well under any practical URL limit and matches the column.
	MinLength = 3
	MaxLength = 64
)
View Source
const Alphabet = "023456789abcdefghjkmnpqrstuvwxyz"

Alphabet is the set of characters used for generated codes.

Excluded on purpose: 'i', 'l', 'o' (confusable with 1, 1 and 0). Length is exactly 32, a power of two, so a uniformly random byte reduced modulo 32 is still uniform. Changing the length breaks that guarantee — see TestAlphabetIsPowerOfTwo, which fails if anyone does.

Variables

This section is empty.

Functions

func Canonical

func Canonical(input string) string

Canonical folds an alias to its stored form.

Aliases are case-insensitive and stored lowercase. This means /GitHub and /github are the same link and the former renders as the latter. That is a deliberate trade: a single canonical form keeps the unique index correct and the cache key unambiguous, at the cost of not preserving display casing.

func Generate

func Generate(ctx context.Context, taken TakenFunc) (string, error)

Generate returns an unused random alias.

It tries a few codes at DefaultLength, then lengthens. Escalating matters because collision probability rises with the number of existing links, and a fixed length would degrade into an unbounded retry loop on a large instance rather than simply producing a slightly longer code.

The caller must still handle a unique-violation on insert. Between this check and the write, another request can take the same alias; this reduces the frequency of that race, it does not eliminate it.

func IsProfane

func IsProfane(s string) bool

IsProfane reports whether an alias contains disallowed language.

Two passes, for the reason documented in profanity.txt: whole-token matching for terms that legitimately appear inside other words, and substring matching (with separators stripped, defeating "n-i-g-g-e-r") for terms that do not.

func IsReserved

func IsReserved(s string) bool

IsReserved reports whether an alias is on the reserved list. The input is canonicalized first, so callers need not do it.

func Random

func Random(length int) (string, error)

Random returns a cryptographically random code of the requested length.

The result is guaranteed to satisfy Validate: generated codes are re-checked against the reserved and profanity lists, because leetspeak normalization maps '0' to 'o' and '1' to 'i', so a random code genuinely can normalize to a word we would refuse from a user.

func Reserved

func Reserved() []string

Reserved returns a copy of the reserved list. Used by the router test that asserts every registered top-level route is reserved.

func Validate

func Validate(input string) (string, error)

Validate canonicalizes a user-supplied alias under the default policy.

It is idempotent: Validate(Validate(x)) == Validate(x) for any x that validates. The property test relies on this, and so does the service layer, which validates on both create and update.

Example
canonical, err := Validate("  My-Link  ")
fmt.Println(canonical, err)
Output:
my-link <nil>

func WellFormed

func WellFormed(s string) bool

WellFormed reports whether a string has the shape of a stored alias: allowed length, allowed characters, no separator at either end.

Shape only. It says nothing about reserved words or profanity, which are policies about what may be *created* rather than what may exist, and it performs no list lookups and no allocation.

The redirect path uses it to answer "could this possibly be in the database" before touching the database. /favicon.ico, /robots.txt, /apple-touch-icon.png and every /wp-login.php-style scan fail here, so ordinary browser noise and bulk scanning cost one byte scan each instead of a query and a negative cache entry — and cannot be mistaken for probing, since refusing them costs nothing to begin with.

Types

type Error

type Error struct {
	Reason  Reason
	Message string
}

Error is a validation failure carrying a machine-readable reason.

func (*Error) Error

func (e *Error) Error() string

type Policy

type Policy struct {
	// ReservedExtra are additional words an operator wants refused, merged with
	// the built-in list rather than replacing it. Compared canonically, so entries
	// need no particular casing.
	ReservedExtra []string

	// ProfanityDisabled switches the built-in profanity filter off.
	//
	// Worth having as a switch: the list cannot know the context it is applied in,
	// and an instance used internally for engineering links has different needs
	// from a public shortener. It does not affect the reserved list.
	ProfanityDisabled bool

	// MinUserLength raises the floor on a user-supplied alias. Zero means the
	// package default, so the zero Policy keeps the documented behaviour.
	//
	// Raising it and lowering it are both legitimate: the two-character space is
	// held back for routing, and an operator who wants /go to be claimable can
	// say so, while a public instance may want short aliases kept scarce.
	// Clamped to the package bounds — a policy cannot permit an alias the column
	// or the redirect's WellFormed pre-filter would refuse.
	MinUserLength int

	// GeneratedLength is the starting length for generated codes. Zero means
	// DefaultLength. Larger values buy collision headroom on a big instance at
	// the cost of a longer URL; Generate still escalates from here.
	GeneratedLength int
}

Policy carries the operator-supplied part of alias validation.

Both fields are named for their non-default state so that the zero Policy is the safe one: no extra reservations, profanity filtering on. A struct whose zero value quietly disabled the filter would be wrong in the direction that matters, and Policy{} appears in tests and in the package-level Validate.

func (Policy) Generate

func (p Policy) Generate(ctx context.Context, taken TakenFunc) (string, error)

Generate is the policy-aware form, starting from the configured length.

func (Policy) IsReserved

func (p Policy) IsReserved(s string) bool

IsReserved reports whether an alias is reserved under this policy: on the built-in list, or in the operator's additions.

The built-in list is always consulted. Operator additions extend it and cannot shrink it, because every route the router registers is on that list and an alias shadowing one of them would take a working page out of service.

func (Policy) Validate

func (p Policy) Validate(input string) (string, error)

Validate canonicalizes a user-supplied alias and reports whether it is acceptable, returning the canonical form on success.

type Reason

type Reason string

Reason identifies why an alias was rejected. It is stable and safe to expose in an API error body, so a client can render a specific message or map the failure to a form field.

const (
	ReasonEmpty         Reason = "empty"
	ReasonTooShort      Reason = "too_short"
	ReasonTooLong       Reason = "too_long"
	ReasonInvalidChars  Reason = "invalid_characters"
	ReasonEdgeSeparator Reason = "edge_separator"
	ReasonReserved      Reason = "reserved"
	ReasonProfane       Reason = "profane"
)

type TakenFunc

type TakenFunc func(ctx context.Context, candidate string) (bool, error)

TakenFunc reports whether an alias is already in use. It returns the underlying error unchanged so Generate can distinguish "taken" from "the database is down".

Jump to

Keyboard shortcuts

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