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 ¶
- Constants
- func Canonical(input string) string
- func Generate(ctx context.Context, taken TakenFunc) (string, error)
- func IsProfane(s string) bool
- func IsReserved(s string) bool
- func Random(length int) (string, error)
- func Reserved() []string
- func Validate(input string) (string, error)
- func WellFormed(s string) bool
- type Error
- type Policy
- type Reason
- type TakenFunc
Examples ¶
Constants ¶
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 )
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 ¶
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 ¶
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 ¶
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 ¶
IsReserved reports whether an alias is on the reserved list. The input is canonicalized first, so callers need not do it.
func Random ¶
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 ¶
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 ¶
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 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) IsReserved ¶
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.