httpapi

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Aug 19, 2026 License: Apache-2.0 Imports: 43 Imported by: 0

Documentation

Overview

Package httpapi wires the barqr HTTP surface: routing, middleware, request decoding, and error mapping.

The package owns the server lifecycle as well as the routes, so that cmd/barqr stays a thin wiring layer: parse config, build a Server, Run it until the context is cancelled.

Index

Constants

View Source
const (
	CodeBadRequest       = "BAD_REQUEST"
	CodeUnknownField     = "UNKNOWN_FIELD"
	CodeInvalidValue     = "INVALID_VALUE"
	CodeMissingData      = "MISSING_DATA"
	CodeUnknownType      = "UNKNOWN_TYPE"
	CodeInvalidPayload   = "INVALID_PAYLOAD"
	CodeUnknownSymbology = "UNKNOWN_SYMBOLOGY"
	CodeUnavailable      = "SYMBOLOGY_UNAVAILABLE"
	CodeDataTooLong      = "DATA_TOO_LONG"
	CodeInvalidData      = "INVALID_DATA"
	CodeUnsupported      = "UNSUPPORTED_OPTION"
	CodeUnknownFormat    = "UNKNOWN_FORMAT"
	CodeUnknownShape     = "UNKNOWN_SHAPE"
	CodeInvalidColor     = "INVALID_COLOR"
	CodeCanvasTooLarge   = "CANVAS_TOO_LARGE"
	CodeUnscannable      = "UNSCANNABLE"
	CodeBodyTooLarge     = "BODY_TOO_LARGE"
	CodeUnauthorized     = "UNAUTHORIZED"
	CodeRateLimited      = "RATE_LIMITED"
	CodeTimeout          = "TIMEOUT"
	CodeOverloaded       = "OVERLOADED"
	CodeNotFound         = "NOT_FOUND"
	CodeMethodNotAllowed = "METHOD_NOT_ALLOWED"
	CodeInternal         = "INTERNAL"
)

Error codes. These are the stable contract: a client switches on the code, never on the message, so a code is never renamed once released.

View Source
const (
	// CodeFetchNotAllowed means the host is not on BARQR_FETCH_ALLOWLIST, or
	// remote fetching is on but nothing was ever allowlisted.
	CodeFetchNotAllowed = "FETCH_NOT_ALLOWED"
	// CodeFetchBlocked means the host resolved to an address barqr will not
	// connect to.
	CodeFetchBlocked = "FETCH_BLOCKED"
)

Error codes for the remote-fetch guards. Both are refusals of policy rather than of syntax — the request is well formed and barqr will not make it — so neither reuses a code that means "you typed it wrong".

Variables

View Source
var Auto = AutoInt{}

Auto is the automatic AutoInt, and the zero value of the type.

Functions

func Codes

func Codes() []string

Codes is every error code this build can return, sorted.

It exists so the catalogue in docs/API.md can be checked against the code rather than against a second hand-maintained list — which would be the same drift the documentation tests exist to prevent, one level up.

A new code must be added here as well as declared above. That is deliberate friction: a code is API surface, and adding one without documenting it should cost something.

func Defaults added in v0.2.0

func Defaults(cfg *config.Config) map[string]any

Defaults maps every request path that has a real, expressible default onto that default, read from the code that actually applies it.

Nothing here is a retyped literal. A default typed out a second time is a second source of truth, and the two drift apart silently — which is exactly what happened to style.hri: the service defaulted it on, docs/API.md said so correctly, and the generated OpenAPI document said nothing at all, so a caller reading the spec and assuming false got true. Sourcing the value from render.DefaultStyle() and friends means the document cannot make that claim wrongly, and a test asserts the document and this map agree in both directions.

It takes the configuration because two of the defaults are per-instance: a document generated by one deployment describes that deployment, which is the whole point of generating it at request time rather than committing a snapshot (ADR-013).

Types

type AutoInt

type AutoInt struct {
	// Set reports whether an explicit value was supplied.
	Set bool
	// Value is meaningful only when Set is true.
	Value int
}

AutoInt is an integer option that also accepts the literal "auto".

The QR specification calls these "automatic" rather than "0", and `0` is a legal value for a mask pattern, so a plain int cannot express "not set". The zero value is automatic, which is what a caller who omits the field means.

func (AutoInt) IsZero

func (a AutoInt) IsZero() bool

IsZero reports whether the value is automatic, which drives `omitzero`.

func (AutoInt) MarshalJSON

func (a AutoInt) MarshalJSON() ([]byte, error)

MarshalJSON emits the number, or "auto" when unset.

func (AutoInt) String

func (a AutoInt) String() string

String renders the value as it is accepted, for echoing in diagnostics.

func (*AutoInt) UnmarshalJSON

func (a *AutoInt) UnmarshalJSON(b []byte) error

UnmarshalJSON accepts a number or the string "auto".

type EncodeSection

type EncodeSection struct {
	// ECC is the error-correction level. Empty means the configured default.
	ECC string `json:"ecc,omitempty"`
	// Version pins the symbology version. Zero or "auto" selects it.
	Version AutoInt `json:"version,omitzero"`
	// Mask pins the data-mask pattern. Zero value or "auto" selects it.
	Mask AutoInt `json:"mask,omitzero"`
	// QuietZone overrides the margin in modules. Nil uses the symbology's own.
	QuietZone *int `json:"quiet_zone,omitempty"`
}

EncodeSection holds the symbology-level options.

type Fault

type Fault struct {
	// Code is the stable machine-readable identifier.
	Code string `json:"code"`
	// Message is a human-readable summary.
	Message string `json:"message"`
	// Field is the offending request field in dot notation, when there is one.
	Field string `json:"field,omitempty"`
	// Expected and Got describe the mismatch.
	Expected string `json:"expected,omitempty"`
	Got      string `json:"got,omitempty"`
	// Hint is the next thing to try.
	Hint string `json:"hint,omitempty"`
	// RequestID correlates with the server log line.
	RequestID string `json:"request_id,omitempty"`
	// contains filtered or unexported fields
}

Fault is the single error shape barqr returns, from every endpoint and every layer. It is deliberately flat and self-describing: a caller should be able to fix their request from the response alone, without reading the docs.

It never carries a stack trace, a file path, an environment value, or an internal type name.

func (*Fault) Error

func (f *Fault) Error() string

Error makes Fault an error so handlers can return it directly.

func (*Fault) Status

func (f *Fault) Status() int

Status is the HTTP status this fault is served with.

type MetaSection

type MetaSection struct {
	// Filename names the file in Content-Disposition.
	Filename string `json:"filename,omitempty"`
	// Attachment switches Content-Disposition from inline to attachment.
	Attachment bool `json:"attachment,omitempty"`
}

MetaSection holds response-shaping options that do not affect the image.

type OutputSection

type OutputSection struct {
	// Format names a registered writer. Empty means the configured default.
	Format string `json:"format,omitempty"`
	// Scale is pixels per module. Zero means derive from Size, else default.
	Scale int `json:"scale,omitempty"`
	// Size is the target width in Unit. Zero means derive from Scale.
	Size float64 `json:"size,omitempty"`
	// Unit interprets Size: px, mm, or in.
	Unit string `json:"unit,omitempty"`
	// DPI converts physical units to pixels. Zero means 300.
	DPI int `json:"dpi,omitempty"`
	// Quality is the JPEG and WebP quality, 1..100. Zero means 92.
	Quality int `json:"quality,omitempty"`
}

OutputSection holds the serialisation options.

type Request

type Request struct {
	// Type names a builder. When set, Payload is built into the encoded
	// string and Data is ignored.
	Type string `json:"type,omitempty"`
	// Payload is the builder's input. Query and multipart set its members
	// with `payload.<key>=<value>`.
	Payload map[string]any `json:"payload,omitempty"`
	// Data is the raw string to encode when no Type is given.
	Data string `json:"data,omitempty"`

	// Symbology names the encoder. Empty means the endpoint's default: "qr"
	// for /v1/qr, the path parameter for /v1/barcode/{symbology}.
	Symbology string `json:"symbology,omitempty"`

	Encode EncodeSection `json:"encode,omitzero"`
	Style  StyleSection  `json:"style,omitzero"`
	Output OutputSection `json:"output,omitzero"`
	Meta   MetaSection   `json:"meta,omitzero"`
}

Request is the one request shape barqr accepts, from all three transports.

A query string, a JSON body, and a multipart form all decode into this same struct: the query uses dot notation (`style.module=dot`), JSON uses nesting, and multipart uses the query's flat keys. Keeping a single struct is what makes "every option reachable as a query param or a body field" true by construction rather than by discipline.

Unset is distinguishable from zero throughout. Scalars use a sentinel — an empty string, or zero where zero is not a legal value — and options where zero *is* legal use a pointer or AutoInt.

func Decode

func Decode(r *http.Request, defaultSymbology string) (Request, error)

Decode builds a Request from any of the three transports.

The order is deliberate: query parameters first, then the body. A body is the more specific statement of intent, so on a POST with both, the body wins. For multipart, file parts are applied last so an uploaded logo overrides a string field of the same name.

defaultSymbology is what the endpoint implies — "qr" for /v1/qr, the path parameter for /v1/barcode/{symbology}.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server is the barqr HTTP service.

A Server is created by New, its routes are fixed at construction, and it is driven by Run. Beyond readiness and the rate-limiter's buckets it holds no state, in keeping with the stateless process model.

func New

func New(cfg *config.Config, log *slog.Logger) *Server

New builds a Server from an already validated configuration.

func (*Server) Handler

func (s *Server) Handler() http.Handler

Handler exposes the routed handler for testing and for embedding barqr in another server.

func (*Server) Listen

func (s *Server) Listen(ctx context.Context) (net.Listener, error)

Listen binds the configured address and returns the listener. It is separate from Serve so that a caller — a test, or a socket-activated deployment — can supply its own listener.

func (*Server) Run

func (s *Server) Run(ctx context.Context) error

Run binds the configured address and serves until ctx is cancelled.

func (*Server) Serve

func (s *Server) Serve(ctx context.Context, ln net.Listener) error

Serve accepts connections on ln until ctx is cancelled, then stops advertising readiness and drains in-flight requests for at most BARQR_SHUTDOWN_GRACE before forcing connections closed.

It returns nil on a clean shutdown.

type StyleSection

type StyleSection struct {
	// Module, Eye, and EyeBall name registered shapes.
	Module  string `json:"module,omitempty"`
	Eye     string `json:"eye,omitempty"`
	EyeBall string `json:"eye_ball,omitempty"`
	// FG, BG, and EyeFG are colours: #rgb, #rrggbb, #rrggbbaa, or a name.
	FG    string `json:"fg,omitempty"`
	BG    string `json:"bg,omitempty"`
	EyeFG string `json:"eye_fg,omitempty"`
	// BarHeight is the height of a linear code in modules. Zero is automatic.
	BarHeight int `json:"bar_height,omitempty"`
	// HRI controls the human-readable text under a linear code. Nil means on.
	HRI *bool `json:"hri,omitempty"`
	// HRISize is the height of that text in modules. Zero means the default.
	HRISize float64 `json:"hri_size,omitempty"`
	// HRIFont is the type family for it: mono or sans. Empty means mono.
	HRIFont string `json:"hri_font,omitempty"`
	Logo string `json:"logo,omitempty"`
	// LogoScale is the logo's width as a fraction of the code, 0.05..0.35.
	LogoScale float64 `json:"logo_scale,omitempty"`
	// Excavate clears the modules behind the logo instead of drawing over
	// them, which reads better but costs error-correction headroom.
	Excavate *bool `json:"excavate,omitempty"`
	// LogoPadding is the clear space around the logo, in modules.
	LogoPadding *int `json:"logo_padding,omitempty"`
	// Caption is text drawn beneath the code.
	Caption string `json:"caption,omitempty"`
	// Frame names a frame style drawn around the code: border, rounded,
	// banner, or bubble.
	Frame string `json:"frame,omitempty"`
	// FrameColor and FrameWidth style that frame. An unset colour follows the
	// foreground, and an unset width takes the renderer's default.
	FrameColor string `json:"frame_color,omitempty"`
	FrameWidth *int   `json:"frame_width,omitempty"`
	// CaptionColor colours the caption text.
	CaptionColor string `json:"caption_color,omitempty"`
	// Gradient replaces the flat foreground on data modules, e.g.
	// "linear(45deg,#000,#00f)" or "radial(#000,#333)".
	Gradient string `json:"gradient,omitempty"`
}

StyleSection holds the appearance options.

Jump to

Keyboard shortcuts

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