Documentation
¶
Overview ¶
Package classify turns a request's User-Agent into a small, bounded device class ("desktop", "mobile", "tablet", "bot", …) for the `{device}` cache-key normalizer. It is the v2a classifier: a pure, deterministic, ordered ruleset (substring → class, first match wins, with a default) compiled once at config load and evaluated as a cheap per-request pre-pass — no I/O on the hot path.
The MECHANISM (UA → enum) lives here; the DATA (which substrings map to which class, and which classes to FOLD together) is configurable via the Cadishfile `device_detect { … }` block, with a sensible built-in ruleset (Default) when none is given.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func DefaultClassName ¶
func DefaultClassName() string
DefaultClassName is the built-in fallback class ("desktop").
Types ¶
type Classifier ¶
type Classifier struct {
// contains filtered or unexported fields
}
Classifier is a compiled, immutable UA→class ruleset (plus optional folds). The zero value is not usable; build one with New or Default. A nil *Classifier classifies everything as "desktop", so callers need not nil-check.
func Default ¶
func Default() *Classifier
Default is the built-in classifier, used when a site declares no `device_detect` block.
func FromSite ¶
func FromSite(site *cadishfile.Site) (*Classifier, error)
FromSite builds the site's device classifier (for the {device} cache-key normalizer) from its optional `device_detect { … }` block. When the block is absent the built-in default ruleset is used, so `cache_key … {device}` works out of the box; the block overrides or extends it.
This is the SINGLE source of truth for compiling a `device_detect` block: both the runtime config layer (internal/config) and the pipeline (internal/pipeline, which projects the ruleset into the Edge IR so the worker self-classifies — D70) call it, so the Go server and the edge worker classify a User-Agent identically.
The block is an ordered ruleset (first match wins) plus optional folds:
device_detect {
mobile ua_contains Mobile Android iPhone
tablet ua_contains Android ua_excludes Mobile # Android tablet
tablet ua_contains iPad Tablet
bot ua_contains bot crawler spider
default desktop
}
Forms:
- `CLASS ua_contains SUBSTR… [ua_excludes SUBSTR…]` — a rule: any contains substring (OR) selects CLASS, unless an excludes substring is also present.
- `default CLASS` — the fallback class (default "desktop").
- `fold FROM INTO` — remap class FROM onto INTO after matching (collapse the enum, e.g. `fold tablet desktop` / `fold bot desktop` for the cardinality-2 desktop/mobile case).
A block with ONLY folds/default (no rules) builds on the built-in ruleset, so `device_detect { fold tablet desktop; fold bot desktop }` reduces the default four classes to two.
func New ¶
func New(rules []Rule, defaultCls string, folds ...Fold) *Classifier
New compiles rules into a Classifier. Substrings/excludes are lower-cased and blanks dropped; a rule with no substrings never matches (but its class still appears in Classes). An empty defaultCls falls back to "desktop". Folds with a blank side are ignored.
func (*Classifier) Classes ¶
func (c *Classifier) Classes() []string
Classes returns the bounded, de-duplicated set of classes this classifier can actually emit — every rule's class plus the default, each run through the folds — in first-seen order. It lets `cadish check` confirm {device} is a low-cardinality key normalizer.
func (*Classifier) Classify ¶
func (c *Classifier) Classify(ua string) string
Classify returns the device class for a User-Agent. An empty UA (or a nil classifier) yields the default class. The matched class is then run through any configured folds.
func (*Classifier) DefaultClass ¶
func (c *Classifier) DefaultClass() string
DefaultClass returns the fallback class used when no rule matches.
func (*Classifier) Folds ¶
func (c *Classifier) Folds() []Fold
Folds returns the configured class remaps (FROM -> INTO) for projection, sorted by FROM for deterministic output. Empty when the classifier has no folds.
func (*Classifier) IsDefault ¶
func (c *Classifier) IsDefault() bool
IsDefault reports whether this classifier is the built-in default (the standard rules, the "desktop" default, and no folds) — so the edge projector can OMIT the ruleset from the IR when it would just be the default the worker already knows, keeping the zero-cost-when-unused invariant for a site that never customizes it.
func (*Classifier) Rules ¶
func (c *Classifier) Rules() []Rule
Rules returns the compiled rule list (substrings/excludes already lower-cased) for projection into the Edge IR, so the worker can run the identical first-match scan. A nil classifier returns the built-in default rules.
type Fold ¶
Fold remaps a classified class onto another (e.g. tablet→desktop) so a site can collapse the enum to fewer buckets (the cardinality-2 desktop/mobile case in the v2 plan). Folds are applied AFTER rule matching.
type Rule ¶
Rule maps a device class to the User-Agent substrings that select it. A rule matches when ANY Substrings substring is present (OR) AND NO Exclude substring is present (the exclusion lets the built-in ruleset say "Android but not Mobile" ⇒ tablet). Rules are evaluated in order, first match wins. Matching is case-insensitive.
func DefaultRules ¶
func DefaultRules() []Rule
DefaultRules is the built-in UA ruleset (exported so a `device_detect` block that only customizes folds/default can build on it). Order matters: bot is checked before tablet/mobile (a crawler UA may also contain "Mobile"); the tablet rules precede mobile (an iPad UA contains both "iPad" and "Mobile", and an Android TABLET sends "Android" WITHOUT "Mobile" while an Android phone includes "Mobile").