Documentation
¶
Overview ¶
Package builder turns a structured payload into the raw string that gets encoded into a code, and parses that string back.
A Builder is the layer above the encoder: the encoder knows how to draw modules for a string, the builder knows that a Wi-Fi credential is spelled "WIFI:T:WPA;S:home;P:hunter2;;" and that the semicolon inside an SSID has to be escaped. Builders are registered by name in an init function and looked up through the registry, never through a switch in a caller, so adding a payload type is one new file plus one Register call.
Two invariants hold for every builder in this package:
- Build is a pure function of its payload. No clock, no randomness, no network. The same payload always produces byte-identical output, which is what makes caching and the round-trip test possible.
- Parse(Build(p)) recovers a payload that rebuilds to the same string. Parse is deliberately strict: it reports ok=false for anything that is not of its own form, so a caller can try every builder in turn to classify a scanned code.
Payloads arrive as any, in practice a map[string]any decoded from a JSON body or from query parameters, or one of the exported payload structs in this package. Both forms go through toMap, so a builder only ever sees a map, and unknown keys are rejected with a suggestion rather than silently ignored — a typo in "sbuject" must not produce a code missing its subject.
Index ¶
- Constants
- Variables
- func Names() []string
- func Register(b Builder)
- type AppPayload
- type BookmarkPayload
- type Builder
- type CryptoPayload
- type Describer
- type EPCPayload
- type EmailPayload
- type EventPayload
- type Field
- type GeoPayload
- type LocationPayload
- type MeCardPayload
- type OTPPayload
- type RawPayload
- type SMSPayload
- type TelPayload
- type TextPayload
- type URLPayload
- type VCardPayload
- type WhatsAppPayload
- type WiFiPayload
Constants ¶
const ( // TypeString is a text field. TypeString = "string" // TypeBool is a boolean field; strings such as "true" are coerced. TypeBool = "bool" // TypeNumber is a numeric field; numeric strings are coerced. TypeNumber = "number" )
Field value types, used in Field.Type.
const App = "app"
App is the registry name of the app-store-link builder.
const Bookmark = "bookmark"
Bookmark is the registry name of the MEBKM bookmark builder.
const Crypto = "crypto"
Crypto is the registry name of the cryptocurrency-payment builder.
const EPC = "epc"
EPC is the registry name of the SEPA credit-transfer builder.
const Email = "email"
Email is the registry name of the mailto builder.
const Event = "event"
Event is the registry name of the calendar-event builder.
const Geo = "geo"
Geo is the registry name of the geographic-location builder.
const Location = "location"
Location is the registry name of the Google Maps location builder.
const MeCard = "mecard"
MeCard is the registry name of the MECARD contact builder.
const OTP = "otp"
OTP is the registry name of the one-time-password builder.
const Raw = "raw"
Raw is the registry name of the unvalidated passthrough builder.
const SMS = "sms"
SMS is the registry name of the text-message builder.
const Tel = "tel"
Tel is the registry name of the telephone builder.
const Text = "text"
Text is the registry name of the plain-text builder.
const URL = "url"
URL is the registry name of the URL builder.
const VCard = "vcard"
VCard is the registry name of the contact-card builder.
const WhatsApp = "whatsapp"
WhatsApp is the registry name of the WhatsApp builder.
const WiFi = "wifi"
WiFi is the registry name of the Wi-Fi credential builder.
Variables ¶
var ( // ErrUnknownType means no builder is registered under that name. ErrUnknownType = errors.New("unknown payload type") // ErrInvalidPayload means the payload is structurally wrong for this // builder: an unknown field, a value of the wrong type, or a value that // violates the format's rules. ErrInvalidPayload = errors.New("payload invalid for this type") // ErrMissingField means a field the format requires was absent or empty. ErrMissingField = errors.New("required field missing") )
Sentinel errors. The HTTP layer maps these onto stable error codes, so a caller can switch on the code rather than on message text.
Functions ¶
Types ¶
type AppPayload ¶
type AppPayload struct {
Platform string `json:"platform"`
IOSID string `json:"ios_id"`
AndroidPackage string `json:"android_package"`
}
AppPayload carries a link to an application listing.
type BookmarkPayload ¶
BookmarkPayload carries a titled link.
type Builder ¶
type Builder interface {
// Name is the registry key.
Name() string
// Build renders the payload as the raw string to encode.
Build(payload any) (string, error)
// Parse recovers a payload from a raw string. ok is false when the string
// is not of this builder's form.
Parse(raw string) (payload any, ok bool)
// Fields describes the accepted payload fields, for /v1/build docs and
// validation.
Fields() []Field
}
Builder converts a structured payload to and from the raw string encoded into a code.
Implementations must be safe for concurrent use: one instance is shared across every request and holds no per-build state.
type CryptoPayload ¶
type CryptoPayload struct {
Coin string `json:"coin"`
Address string `json:"address"`
Amount float64 `json:"amount"`
Label string `json:"label"`
Message string `json:"message"`
}
CryptoPayload carries a payment request.
type Describer ¶
type Describer interface {
// Describe reports what the builder made of the payload. The map is
// serialised straight to JSON, so its keys are part of the API.
Describe(payload any) (map[string]any, error)
}
Describer is an optional interface a Builder may implement to report what it understood about a payload, beyond the string it produced.
Most builders have nothing to add: a vCard is a vCard, and the payload the caller sent is the whole story. It exists for the builders that *detect* something — `location` infers whether it was handed an address, a coordinate pair, a plus code, or somebody else's map link, and a caller acting on that answer needs to see it and how confident the guess was.
The HTTP layer includes the result in GET /v1/build/{type} when a builder implements it, and omits the field entirely when it does not.
type EPCPayload ¶
type EPCPayload struct {
BIC string `json:"bic"`
Name string `json:"name"`
IBAN string `json:"iban"`
Amount float64 `json:"amount"`
Purpose string `json:"purpose"`
Reference string `json:"reference"`
Remittance string `json:"remittance"`
Information string `json:"information"`
}
EPCPayload carries a SEPA credit transfer request.
type EmailPayload ¶
type EmailPayload struct {
Email string `json:"email"`
Subject string `json:"subject"`
Body string `json:"body"`
}
EmailPayload carries a pre-addressed message.
type EventPayload ¶
type EventPayload struct {
Summary string `json:"summary"`
Start string `json:"start"`
End string `json:"end"`
Location string `json:"location"`
Description string `json:"description"`
}
EventPayload carries a calendar event.
type Field ¶
type Field struct {
// Name is the payload key, snake_case to match both JSON bodies and
// query parameters.
Name string `json:"name"`
// Type is the accepted value type: string, bool, or number. Query
// parameters arrive as strings and are coerced, so "bool" also accepts
// "true"/"false".
Type string `json:"type"`
// Description is one line of prose for the API docs.
Description string `json:"description"`
// Required reports whether Build fails without this field.
Required bool `json:"required"`
// Example is a value that is valid on its own and valid alongside every
// other field's Example. It is empty for a field that conflicts with
// another field's example, which is why the docs carry the sample in the
// description instead.
Example string `json:"example,omitempty"`
}
Field describes one accepted payload field. The slice a builder returns from Fields is what /v1/build serves as documentation and what validation uses to reject unknown keys, so the two can never drift apart.
type GeoPayload ¶
type GeoPayload struct {
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
Altitude float64 `json:"altitude"`
Query string `json:"query"`
}
GeoPayload carries a point on the earth.
type LocationPayload ¶
type LocationPayload struct {
Location string `json:"location"`
Origin string `json:"origin"`
Mode string `json:"mode"`
Zoom int `json:"zoom"`
Language string `json:"language"`
Region string `json:"region"`
}
LocationPayload carries a place to open in Google Maps.
type MeCardPayload ¶
type MeCardPayload struct {
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Phone string `json:"phone"`
PhoneRegion string `json:"phone_region"`
Email string `json:"email"`
URL string `json:"url"`
Address string `json:"address"`
Note string `json:"note"`
}
MeCardPayload carries a compact contact card.
type OTPPayload ¶
type OTPPayload struct {
Type string `json:"type"`
Issuer string `json:"issuer"`
Account string `json:"account"`
Secret string `json:"secret"`
Algorithm string `json:"algorithm"`
Digits float64 `json:"digits"`
Period float64 `json:"period"`
Counter float64 `json:"counter"`
}
OTPPayload carries an authenticator enrolment.
The secret is the whole credential: anyone who reads it can generate codes for the account forever. It must never reach a log, a metric label, or an error message, which is why this type has a String method.
func (OTPPayload) String ¶
func (p OTPPayload) String() string
String renders the payload with the secret redacted.
type RawPayload ¶
type RawPayload struct {
Data string `json:"data"`
}
RawPayload carries bytes that are already in their final form.
type SMSPayload ¶
SMSPayload carries a pre-addressed text message.
type TelPayload ¶
TelPayload carries a telephone number.
type TextPayload ¶
type TextPayload struct {
Text string `json:"text"`
}
TextPayload carries free-form text.
type URLPayload ¶
URLPayload carries a link.
type VCardPayload ¶
type VCardPayload struct {
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Org string `json:"org"`
Title string `json:"title"`
Phone string `json:"phone"`
PhoneRegion string `json:"phone_region"`
Mobile string `json:"mobile"`
Email string `json:"email"`
URL string `json:"url"`
Street string `json:"street"`
City string `json:"city"`
Region string `json:"region"`
PostalCode string `json:"postal_code"`
Country string `json:"country"`
Note string `json:"note"`
}
VCardPayload carries a contact card.
type WhatsAppPayload ¶
type WhatsAppPayload struct {
Phone string `json:"phone"`
PhoneRegion string `json:"phone_region"`
Message string `json:"message"`
}
WhatsAppPayload carries a WhatsApp click-to-chat link.
type WiFiPayload ¶
type WiFiPayload struct {
SSID string `json:"ssid"`
Password string `json:"password"`
Auth string `json:"auth"`
Hidden bool `json:"hidden"`
}
WiFiPayload carries a Wi-Fi network credential.
The password is a secret. It must never reach a log, a metric label, or an error message, which is why this type has a String method: a stray %v on the struct prints the redaction rather than the key to the network.
func (WiFiPayload) String ¶
func (p WiFiPayload) String() string
String renders the payload with the password redacted.