devicex

package module
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Aug 11, 2026 License: MIT Imports: 4 Imported by: 0

README


title: devicex — the Go package

devicex

Device model code → the device it names.

d, ok := devicex.Lookup("SM-G973F")
// d.Brand = "Samsung", d.Name = "Galaxy S10"

25,475 devices, snapshot 2026-08-08. ~50 ns per lookup, zero allocations, zero dependencies.


The problem this solves

A User-Agent from an Android phone tells you a model code and nothing else:

Mozilla/5.0 (Linux; Android 14; SM-G973F) AppleWebKit/537.36 …
                                 ^^^^^^^^

SM-G973F is a Galaxy S10. There is no derivable relationship between the two — Samsung assigned it. No regex, no heuristic and no amount of cleverness recovers the name. It requires a catalogue, which is why almost nothing in the Go ecosystem does it.

This package is that catalogue and nothing else. It does not parse User-Agents; it answers one question, quickly, and admits when it cannot.

Install

go get github.com/bakhod1r/devicex

Use

// The device, when the catalogue knows it.
d, ok := devicex.Lookup("CPH2451")   // OnePlus 11 5G

// The manufacturer, which is answerable even when the handset is not.
brand, ok := devicex.BrandOf("SM-S928B")   // "Samsung" — code shape, not a record

// Everything, without copying megabytes.
devicex.All(func(d devicex.Device) bool {
    if d.Brand == "Sony" {
        fmt.Println(d.Code, d.Name)
    }
    return true
})

devicex.Len()      // 25475
devicex.Brands()   // every manufacturer, sorted
devicex.Source     // where the data came from
devicex.Version()  // "2026-08-08/25475" — the snapshot the answer came from

Everything at once

Resolve answers from the catalogue, the code-shape rules and the User-Agent rules together, in that order of authority:

code := devicex.Code("SM-T870 Build/UP1A.231005.007")  // strips the build suffix

r, ok := devicex.Resolve(code, ua)
// r.Name = "Galaxy Tab S7", r.Brand = "Samsung", r.Type = devicex.TypeTablet
// r.ID   = "catalog" — what produced the answer

devicex.Resolve("", "Mozilla/5.0 (PlayStation; PlayStation 5/9.60) …")
// PlayStation 5 / Sony / Console — hardware that carries no model code

ResolveCode and ResolveUA take the two kinds of evidence separately. Prefix rules are never tested against a whole User-Agent, and the MatchContains rules that describe prose are never tested against a model code.

For a consumer that will not import this package, Describe is the same answer in a signature naming no type from it:

name, brand, model, family, deviceType, confidence, ok := devicex.Describe(code, ua)

The same catalogue over HTTP

Every code has its own JSON document, at a path that is the code. No server, no client library, no page to render — a GET returns the answer:

curl -s https://bakhod1r.github.io/devicex/api/d/SM-S928B.json
{"code":"SM-S928B","ok":true,"id":"catalog","name":"Galaxy S24 Ultra",
 "brand":"Samsung","model":"SM-S928B","confidence":0.99}

family and type appear when a rule identifies them, exactly as Resolve reports them: api/d/SM-X710.json carries "family":"Galaxy Tab" and "type":"Tablet". A code the catalogue does not hold has no document, and the 404 is the answer — not in the catalogue. Nothing is approximated.

GET api/d/SM-S928B.json   one code, the whole answer            137 B gzipped
GET api/SM.json           every code starting SM, {code: [brand, name]}  11 KB
GET api/rules.json        the code-shape rules                    1 KB
GET api/meta.json         snapshot date, device count, source

The shards are for a client resolving many codes at once: one request covers a whole code space, and a shard is named after the first two characters of a code, uppercased, with anything that is not a letter or digit replaced by _. The rule table is what turns a 404 into a manufacturer — SM- is Samsung whether or not that handset is catalogued.

Two things to know about the paths. A code containing / has it percent-encoded in the filename, so the URL carries it twice-encoded: api/d/Doro%208030%252F8031%252F8028.json. And 188 codes that differ from another only in case — almost all of them television model strings like 2K SMART TV and 2K Smart TV — have no document at all, because a macOS or Windows checkout of this repository cannot hold both files. They are in the shards.

lookup.html is a reader for the same files, and /d/SM-S928B renders in the browser: Pages serves 404.html for the unpublished path and it answers from the path it was asked for. Anything programmatic should use api/d/… instead, where a hit is a 200 and a miss is a real 404.

Rebuild the Go catalogue and the JSON together with make catalog-full, and serve them locally with make site.

Two tiers, deliberately separate

Answers How When unknown
Lookup which handset catalogue record falsenever approximated
BrandOf which manufacturer the code's shape false
Resolve both, plus form factor record first, then shape false

The split matters. Manufacturers allocate codes in per-vendor spaces, so SM- has meant Samsung for two decades and a phone released tomorrow still resolves its maker. The handset's name has no such structure and cannot degrade gracefully — so it does not degrade at all.

Lookup is checked first inside BrandOf, because a recorded device outranks a prefix. Sub-brands share their parent's code space: CPH is OPPO's prefix and also covers OnePlus models, which is exactly the case where the record is right and the shape is not.

What it will not do

A code the catalogue does not hold returns false. Not a nearest match, not a family guess, not the most likely device. A wrong device name is worse than no device name: it is a fact-shaped value that a caller will store, aggregate and report, and nothing downstream will ever question it.

Lookup is case-sensitive. Model codes are copied verbatim out of a User-Agent, so matching a different case would be a weaker claim than the one the API appears to make. LookupFold exists for input whose case is known to be unreliable.

Coverage, honestly

The bundled catalogue comes from a community list that has not kept pace with recent releases. Handsets from 2024 onward are largely absent:

SM-G973F   Galaxy S10      ✓
SM-S911B   Galaxy S23      ✓
SM-S928B   —               ✗   brand resolves, name does not

Regenerating from a current Google Play Console export fixes this and takes one command:

make catalog CSV=supported_devices.csv

The export is updated weekly. See NOTICE.md for provenance and licensing of the bundled data.

Design

  • Sorted array, binary search. No map, so nothing is built at init and the strings live in read-only memory. Startup cost is zero and lookup is ~14 comparisons — 50 ns on an M4 Pro, and make bench reports the number for your own machine rather than asking you to trust this one.
  • Generated Go, not embedded YAML or JSON. The catalogue is bulk imported data with no hand-written rows, so a parseable format would buy nothing and cost megabytes plus a decode at startup.
  • No dependencies. The importer reads UTF-16 CSV and JSON with the standard library.

uax parses the User-Agent that carries the code. It consumes this package through uax/devices rather than bundling the data, so the catalogue can be updated on its own schedule — phones ship weekly, parsers do not.

License

MIT — see LICENSE. The bundled catalogue is third-party data; its source and licence are recorded in NOTICE.md.

Documentation

Overview

Package devicex resolves a device model code to the device it names.

A User-Agent from an Android phone carries a model code — "SM-S928B", "CPH2451", "2201116SG" — and nothing else about the hardware. The code is evidence; the handset's name is not derivable from it. Samsung assigned "SM-S928B" to the Galaxy S24 Ultra, and no amount of pattern matching recovers that. It requires a catalogue.

d, ok := devicex.Lookup("SM-G973F")
// d.Brand = "Samsung", d.Name = "Galaxy S10"

This package is the catalogue and the rules that read a model code's shape. It parses no User-Agents, has no dependencies, and holds no state. It is separate from the parser that usually consumes it so that the data can be updated on its own schedule: phones ship weekly, parsers do not.

Lookup is the catalogue alone. Resolve is everything the package knows about one request — the catalogue, the code-shape rules, and the rules for hardware that carries no model code at all:

r, ok := devicex.Resolve(devicex.Code(field), ua)
// r.Name, r.Brand, r.Model, r.Type, r.Family, r.Confidence, and r.ID

What it will not do

A code the catalogue does not hold returns ok == false. It never returns an approximation, a nearest match or a guess derived from the code's shape, because a wrong device name is worse than no device name — it is a fact- shaped value that a caller will store, aggregate and report.

Brand is a separate question and is answerable without this package: model codes have manufacturer-specific shapes, so "SM-" implies Samsung whether or not the specific handset is known. See BrandOf.

Provenance

Every entry comes from a published catalogue, cited in Source. Nothing here is remembered or inferred. See NOTICE.md.

Index

Constants

View Source
const Generated = catalog.Generated

Generated is the date of the snapshot the catalogue was built from, YYYY-MM-DD. It is the age of the data, not of the release: a catalogue imported from a source that stopped updating carries the source's date, which is what decides which handsets it cannot name.

View Source
const Source = catalog.Source

Source cites where the catalogue came from.

Variables

View Source
var Rules = []Rule{

	{ID: "pixel_9_pro_xl", Name: "Pixel 9 Pro XL", Brand: "Google", Model: "Pixel 9 Pro XL", Family: "Pixel", Type: TypeMobile, Priority: 240, Match: MatchToken, Value: "Pixel 9 Pro XL", Confidence: 0.98},
	{ID: "pixel_9_pro", Name: "Pixel 9 Pro", Brand: "Google", Model: "Pixel 9 Pro", Family: "Pixel", Type: TypeMobile, Priority: 239, Match: MatchToken, Value: "Pixel 9 Pro", Confidence: 0.98},
	{ID: "pixel_9", Name: "Pixel 9", Brand: "Google", Model: "Pixel 9", Family: "Pixel", Type: TypeMobile, Priority: 238, Match: MatchToken, Value: "Pixel 9", Confidence: 0.98},
	{ID: "pixel_8_pro", Name: "Pixel 8 Pro", Brand: "Google", Model: "Pixel 8 Pro", Family: "Pixel", Type: TypeMobile, Priority: 237, Match: MatchToken, Value: "Pixel 8 Pro", Confidence: 0.98},
	{ID: "pixel_8a", Name: "Pixel 8a", Brand: "Google", Model: "Pixel 8a", Family: "Pixel", Type: TypeMobile, Priority: 236, Match: MatchToken, Value: "Pixel 8a", Confidence: 0.98},
	{ID: "pixel_8", Name: "Pixel 8", Brand: "Google", Model: "Pixel 8", Family: "Pixel", Type: TypeMobile, Priority: 235, Match: MatchToken, Value: "Pixel 8", Confidence: 0.98},
	{ID: "pixel_7_pro", Name: "Pixel 7 Pro", Brand: "Google", Model: "Pixel 7 Pro", Family: "Pixel", Type: TypeMobile, Priority: 234, Match: MatchToken, Value: "Pixel 7 Pro", Confidence: 0.98},
	{ID: "pixel_7a", Name: "Pixel 7a", Brand: "Google", Model: "Pixel 7a", Family: "Pixel", Type: TypeMobile, Priority: 233, Match: MatchToken, Value: "Pixel 7a", Confidence: 0.98},
	{ID: "pixel_7", Name: "Pixel 7", Brand: "Google", Model: "Pixel 7", Family: "Pixel", Type: TypeMobile, Priority: 232, Match: MatchToken, Value: "Pixel 7", Confidence: 0.98},
	{ID: "pixel_6_pro", Name: "Pixel 6 Pro", Brand: "Google", Model: "Pixel 6 Pro", Family: "Pixel", Type: TypeMobile, Priority: 231, Match: MatchToken, Value: "Pixel 6 Pro", Confidence: 0.98},
	{ID: "pixel_6a", Name: "Pixel 6a", Brand: "Google", Model: "Pixel 6a", Family: "Pixel", Type: TypeMobile, Priority: 230, Match: MatchToken, Value: "Pixel 6a", Confidence: 0.98},
	{ID: "pixel_6", Name: "Pixel 6", Brand: "Google", Model: "Pixel 6", Family: "Pixel", Type: TypeMobile, Priority: 229, Match: MatchToken, Value: "Pixel 6", Confidence: 0.98},

	{ID: "iphone", Name: "iPhone", Brand: "Apple", Model: "iPhone", Type: TypeMobile, Priority: 200, Match: MatchToken, Value: "iPhone", Confidence: 0.97},
	{ID: "ipad", Name: "iPad", Brand: "Apple", Model: "iPad", Type: TypeTablet, Priority: 200, Match: MatchToken, Value: "iPad", Confidence: 0.97},
	{ID: "ipod", Name: "iPod touch", Brand: "Apple", Model: "iPod touch", Type: TypeMobile, Priority: 200, Match: MatchToken, Value: "iPod touch", Confidence: 0.95},

	{ID: "nintendo_switch", Name: "Nintendo Switch", Brand: "Nintendo", Model: "Nintendo Switch", Type: TypeConsole, Priority: 200, Match: MatchContains, Value: "Nintendo Switch", Confidence: 0.97},
	{ID: "playstation_5", Name: "PlayStation 5", Brand: "Sony", Model: "PlayStation 5", Type: TypeConsole, Priority: 200, Match: MatchContains, Value: "PlayStation 5", Confidence: 0.97},
	{ID: "playstation_4", Name: "PlayStation 4", Brand: "Sony", Model: "PlayStation 4", Type: TypeConsole, Priority: 200, Match: MatchContains, Value: "PlayStation 4", Confidence: 0.97},
	{ID: "xbox_console", Name: "Xbox", Brand: "Microsoft", Model: "Xbox", Type: TypeConsole, Priority: 190, Match: MatchContains, Value: "Xbox", Confidence: 0.95},

	{ID: "macintosh", Name: "Mac", Brand: "Apple", Type: TypeDesktop, Priority: 150, Match: MatchToken, Value: "Macintosh", Confidence: 0.95},

	{ID: "playstation", Brand: "Sony", Type: TypeConsole, Priority: 145, Match: MatchContains, Value: "PlayStation", Confidence: 0.90},

	{ID: "samsung_prefix_sm_t", Brand: "Samsung", Family: "Galaxy Tab", Type: TypeTablet, Priority: 110, Match: MatchPrefix, Value: "SM-T", Confidence: 0.93},
	{ID: "samsung_prefix_sm_x", Brand: "Samsung", Family: "Galaxy Tab", Type: TypeTablet, Priority: 110, Match: MatchPrefix, Value: "SM-X", Confidence: 0.93},
	{ID: "samsung_prefix_sm_p", Brand: "Samsung", Family: "Galaxy Tab", Type: TypeTablet, Priority: 110, Match: MatchPrefix, Value: "SM-P", Confidence: 0.93},

	{ID: "samsung_prefix_sm", Brand: "Samsung", Priority: 100, Match: MatchPrefix, Value: "SM-", Confidence: 0.93},
	{ID: "samsung_prefix_sm_underscore", Brand: "Samsung", Priority: 100, Match: MatchPrefix, Value: "SM_", Confidence: 0.90},
	{ID: "samsung_prefix_sch", Brand: "Samsung", Priority: 100, Match: MatchPrefix, Value: "SCH-", Confidence: 0.90},
	{ID: "samsung_prefix_sph", Brand: "Samsung", Priority: 100, Match: MatchPrefix, Value: "SPH-", Confidence: 0.90},
	{ID: "samsung_prefix_sgh", Brand: "Samsung", Priority: 100, Match: MatchPrefix, Value: "SGH-", Confidence: 0.90},
	{ID: "samsung_prefix_gt", Brand: "Samsung", Priority: 100, Match: MatchPrefix, Value: "GT-", Confidence: 0.90},
	{ID: "samsung_prefix_sc", Brand: "Samsung", Priority: 100, Match: MatchPrefix, Value: "SC-", Confidence: 0.88},

	{ID: "google_prefix_pixel", Brand: "Google", Family: "Pixel", Priority: 100, Match: MatchPrefix, Value: "Pixel", Confidence: 0.95},

	{ID: "realme_prefix", Brand: "realme", Priority: 100, Match: MatchPrefix, Value: "RMX", Confidence: 0.93},

	{ID: "huawei_prefix_els", Brand: "Huawei", Priority: 100, Match: MatchPrefix, Value: "ELS-", Confidence: 0.92},
	{ID: "huawei_prefix_ana", Brand: "Huawei", Priority: 100, Match: MatchPrefix, Value: "ANA-", Confidence: 0.92},
	{ID: "huawei_prefix_vog", Brand: "Huawei", Priority: 100, Match: MatchPrefix, Value: "VOG-", Confidence: 0.92},
	{ID: "huawei_prefix_lya", Brand: "Huawei", Priority: 100, Match: MatchPrefix, Value: "LYA-", Confidence: 0.92},
	{ID: "huawei_prefix_noh", Brand: "Huawei", Priority: 100, Match: MatchPrefix, Value: "NOH-", Confidence: 0.92},

	{ID: "motorola_prefix_moto", Brand: "Motorola", Priority: 100, Match: MatchPrefix, Value: "moto ", Confidence: 0.93},

	{ID: "redmi_prefix", Brand: "Xiaomi", Family: "Redmi", Priority: 100, Match: MatchPrefix, Value: "Redmi", Confidence: 0.94},
	{ID: "poco_prefix", Brand: "Xiaomi", Family: "POCO", Priority: 100, Match: MatchPrefix, Value: "POCO", Confidence: 0.94},
	{ID: "mi_prefix", Brand: "Xiaomi", Priority: 100, Match: MatchPrefix, Value: "MI ", Confidence: 0.90},

	{ID: "lg_prefix_lm", Brand: "LG", Priority: 100, Match: MatchPrefix, Value: "LM-", Confidence: 0.90},
	{ID: "lg_prefix_lg", Brand: "LG", Priority: 100, Match: MatchPrefix, Value: "LG-", Confidence: 0.90},

	{ID: "oneplus_prefix_word", Brand: "OnePlus", Priority: 100, Match: MatchPrefix, Value: "ONEPLUS", Confidence: 0.93},
	{ID: "oneplus_prefix_kb2", Brand: "OnePlus", Priority: 100, Match: MatchPrefix, Value: "KB2", Confidence: 0.88},
	{ID: "oneplus_prefix_le2", Brand: "OnePlus", Priority: 100, Match: MatchPrefix, Value: "LE2", Confidence: 0.88},

	{ID: "sony_prefix", Brand: "Sony", Priority: 100, Match: MatchPrefix, Value: "XQ-", Confidence: 0.90},

	{ID: "amazon_prefix", Brand: "Amazon", Type: TypeTablet, Priority: 100, Match: MatchPrefix, Value: "KF", Confidence: 0.88},

	{ID: "vivo_prefix", Brand: "vivo", Priority: 100, Match: MatchPrefix, Value: "vivo ", Confidence: 0.93},

	{ID: "asus_prefix", Brand: "Asus", Priority: 100, Match: MatchPrefix, Value: "ASUS_", Confidence: 0.93},
	{ID: "nokia_prefix", Brand: "Nokia", Priority: 100, Match: MatchPrefix, Value: "Nokia", Confidence: 0.93},
	{ID: "lenovo_prefix", Brand: "Lenovo", Priority: 100, Match: MatchPrefix, Value: "Lenovo", Confidence: 0.93},
	{ID: "tecno_prefix", Brand: "Tecno", Priority: 100, Match: MatchPrefix, Value: "TECNO", Confidence: 0.93},
	{ID: "infinix_prefix", Brand: "Infinix", Priority: 100, Match: MatchPrefix, Value: "Infinix", Confidence: 0.93},

	{ID: "oppo_prefix_cph", Brand: "OPPO", Priority: 95, Match: MatchPrefix, Value: "CPH", Confidence: 0.80},
	{ID: "oppo_prefix_pbe", Brand: "OPPO", Priority: 95, Match: MatchPrefix, Value: "PBE", Confidence: 0.80},
	{ID: "oppo_prefix_pcl", Brand: "OPPO", Priority: 95, Match: MatchPrefix, Value: "PCL", Confidence: 0.80},

	{ID: "motorola_prefix_xt", Brand: "Motorola", Priority: 95, Match: MatchPrefix, Value: "XT", Confidence: 0.88},

	{ID: "xiaomi_prefix_m2", Brand: "Xiaomi", Priority: 90, Match: MatchPrefix, Value: "M2", Confidence: 0.85},
	{ID: "xiaomi_numeric_prefix", Brand: "Xiaomi", Priority: 85, Match: MatchPrefix, Value: "22", Confidence: 0.80},
}

Rules is every hand-written device rule, ordered by descending priority so the first match is the most specific.

Order within one priority is significant and hand-chosen: a longer prefix is listed before a shorter one it would otherwise shadow.

Functions

func All

func All(fn func(Device) bool)

All calls fn for every device, in code order. It is a range function rather than a returned slice because the catalogue is large and copying it to answer "which devices does Samsung make" would allocate megabytes.

Returning false stops the iteration.

func BrandOf

func BrandOf(code string) (string, bool)

BrandOf reports the manufacturer a model code belongs to, and whether the code's shape identified one.

The catalogue is consulted first, because a recorded device is stronger evidence than a prefix. Only when the code is unknown do the shape rules decide, which is what lets a handset released after this release still resolve its maker.

An unrecognised shape returns "" and false. It never falls back to the most common manufacturer, and never infers a brand from a code's length or character mix.

func Brands

func Brands() []string

Brands returns every manufacturer in the catalogue, sorted.

func Code

func Code(field string) string

Code extracts the model code from the device field of an Android User-Agent.

Android writes the build fingerprint after the model, separated by a space:

"SM-A546E Build/UP1A.231005.007" -> "SM-A546E"

The suffix is a build identifier, not part of the code, and leaving it on turns every catalogue lookup into a miss. Stripping it is a fact about the shape of a model code, which is this package's subject, so it lives here rather than being re-derived by each parser.

A field with no build suffix is returned trimmed and otherwise unchanged. Codes containing spaces — "moto g play (2023)", "Pixel 9 Pro XL" — survive, because only a " Build/" boundary is cut.

func Describe

func Describe(code, ua string) (name, brand, model, family, deviceType string, confidence float64, ok bool)

Describe is Names with everything the rules know, in the same dependency-free shape.

Names only reaches the catalogue, which makes it silent about most of what this package can answer: a handset newer than the catalogue resolves no brand, an iPhone resolves nothing at all, and a catalogued Galaxy Tab loses the fact that it is a tablet. Describe is Resolve behind a signature that mentions no type from this package, so a consumer can still accept it as a function value without importing devicex:

type Config struct {
    Device func(code, ua string) (name, brand, model, family, deviceType string, confidence float64, ok bool)
}

p := uax.New(uax.Config{Device: devicex.Describe})

A caller willing to import devicex should call Resolve instead and read the Rule, which additionally carries the rule ID that produced the answer — the thing a bug report needs to name. This flat form exists for the caller who will not.

deviceType is one of "Mobile", "Tablet", "Desktop", "Console", or empty when no rule could tell. It is never defaulted to "Mobile".

model is the exact code the answer is about, and is empty when the rule names a class of machine rather than a model. That distinction is carried here rather than left to the caller because it cannot be recovered downstream: "iPhone" is both the name and the model an iPhone request carries, while "Macintosh" names a class and no model at all, and any heuristic that copies name into model when the User-Agent contains it invents a "Mac" model for every desktop.

func Len

func Len() int

Len is how many devices the catalogue holds.

func Names

func Names(code string) (name, brand string, ok bool)

Names resolves a model code for a caller that only wants the two strings.

The signature is deliberately plain — no types from this package appear in it — so a consumer can accept it as a function value without importing devicex at all:

// in the consumer, with no dependency on devicex:
type Config struct {
    DeviceNames func(code string) (name, brand string, ok bool)
}

// in the application, which imports both:
p := uax.New(uax.Config{DeviceNames: devicex.Names})

That keeps the catalogue and its consumers independent in both directions: neither module imports the other, and an application that does not want 25,000 device names does not link them.

It allocates nothing: the returned strings are compile-time constants.

func Version

func Version() string

Version is a fingerprint of the catalogue: its snapshot date and the number of devices it holds, "2026-08-08/25475".

A caller that stored a device name six months ago and wants to know what it was decided from needs the data's identity, and Source alone does not carry it — the same source yields a different catalogue every time it is re-imported. Log this beside a resolved name and the answer stays explainable after the catalogue moves.

Types

type Device

type Device struct {
	// Code is the model code as it appears in a User-Agent.
	Code string

	// Brand is the manufacturer, normalised to one spelling per company.
	// Sub-brands report their parent: a Redmi Note is made by Xiaomi.
	Brand string

	// Name is what the device is sold as, for example "Galaxy S24 Ultra".
	Name string
}

Device is a handset the catalogue knows.

func At added in v0.3.0

func At(i int) (Device, bool)

At returns the i'th device in code order. It reports false when i is out of range.

Len and At together let a caller address one device without materialising the catalogue: picking a random handset needs an index, and All, being iteration only, forces a caller to spill all 25,475 entries into a slice first. At costs nothing.

func Lookup

func Lookup(code string) (Device, bool)

Lookup returns the device a model code names.

The comparison is exact. Model codes are case-significant and are copied verbatim out of the User-Agent, so a case-insensitive match would be a different, weaker claim; use LookupFold when the input has been through something that changed its case.

func LookupFold

func LookupFold(code string) (Device, bool)

LookupFold is Lookup, ignoring case. It is slower — the catalogue is sorted case-sensitively, so this scans — and should only be used when the caller knows the input's case is unreliable.

type DeviceType

type DeviceType string

DeviceType is the form factor a rule asserts.

const (
	TypeMobile  DeviceType = "Mobile"
	TypeTablet  DeviceType = "Tablet"
	TypeDesktop DeviceType = "Desktop"
	TypeConsole DeviceType = "Console"
)

Form factors. A rule that cannot tell leaves Type empty rather than defaulting to Mobile.

type MatchKind

type MatchKind uint8

MatchKind is how a rule compares itself against a model code or a User-Agent.

const (
	// MatchToken is an exact, case-sensitive comparison against the model
	// code.
	MatchToken MatchKind = iota

	// MatchPrefix recognises a manufacturer's code space. It claims the maker,
	// never the specific handset.
	MatchPrefix

	// MatchContains looks for the value anywhere in the User-Agent. It exists
	// for hardware that carries no model code at all: a console names itself
	// in prose.
	MatchContains
)

type Rule

type Rule struct {
	// ID is stable and unique; it is what a changelog or a bug report names.
	ID string

	// Name is the marketing name. Empty on a shape rule, which is the whole
	// point of the tier split: recognising "SM-" says Samsung built it, not
	// which phone it is.
	Name string

	// Brand is the manufacturer, normalised to one spelling per company.
	Brand string

	// Model is the exact code this rule names. Empty on a shape rule.
	Model string

	// Family is the product line, when the rule identifies one.
	Family string

	// Type is the form factor, empty when the rule cannot tell.
	Type DeviceType

	// Priority orders the rules; higher is tried first.
	Priority int

	// Match is how the rule recognises a code.
	Match MatchKind

	// Value is what Match compares against.
	Value string

	// Confidence is how strong the claim is, 0..1.
	Confidence float64
}

Rule is one device rule.

func Resolve

func Resolve(code, ua string) (Rule, bool)

Resolve answers everything the package knows about one request's device evidence: the model code, and the User-Agent it came out of.

Either argument may be empty. A code alone is the Android case; a User-Agent alone covers the hardware that publishes no code.

The catalogue decides first, because a recorded device is stronger evidence than a code's shape — it names the specific handset, which no rule can. When it answers, the rules are still consulted for what a catalogue row does not carry: the form factor and the product line. That is how a Galaxy Tab resolves as a tablet while keeping its catalogued name.

The returned Rule for a catalogued device has ID "catalog" and cites Source. A code the catalogue does not hold falls through to the shape rules, and then to the User-Agent. Nothing is guessed at any step: an unrecognised code and an unrecognised User-Agent return false.

func ResolveCode

func ResolveCode(code string) (Rule, bool)

ResolveCode resolves a model code against the rules.

It reads the shape of the code and nothing else, so it answers for hardware the catalogue has never heard of: a Samsung handset released after this release still resolves its maker from "SM-". It does not consult the catalogue — use Resolve for that.

MatchContains rules are skipped. They describe prose in a User-Agent ("PlayStation 5"), and a model code is not prose.

func ResolveUA

func ResolveUA(ua string) (Rule, bool)

ResolveUA resolves a whole User-Agent against the rules.

It exists for hardware that carries no model code at all and names itself in prose instead: "Macintosh", "PlayStation 5", "Nintendo Switch". Those tokens are the entire identifier such a request carries.

Both MatchContains and MatchToken rules are tested, because a named token is a literal substring of the User-Agent that carries it. Prefix rules are not: "SM-" appears in a User-Agent only as part of a model code, and matching it against the whole string would claim Samsung for any text containing those three characters.

Rule order decides overlaps, which is why "Pixel 9 Pro XL" is listed above "Pixel 9" — the first match on a Pixel 9 Pro XL User-Agent is the specific one.

Directories

Path Synopsis
Command gen builds internal/catalog/catalog_gen.go from a published Android device catalogue.
Command gen builds internal/catalog/catalog_gen.go from a published Android device catalogue.
internal

Jump to

Keyboard shortcuts

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