Documentation
¶
Overview ¶
Package interceptor is the contract a third-party request interceptor implements — and nothing else.
Every other extension point in this framework is reached the way database/sql drivers are: a package registers itself in an init function, an application imports it for the side effect, and configuration names it. Storage providers, session stores, authentication backends and federated identity providers all work that way. The request lifecycle did not.
Middleware could be attached — but only by whoever CONSTRUCTS the application, or by a module that is mounted. A package that an application merely imports had no way in, which meant an interceptor could not be distributed as a plugin: it had to be code the application author pasted into their own bootstrap in the right order. That is the gap ADR-023 recorded as "observing is possible today, intercepting is not".
Ordering is the behaviour, so an operator declares it ¶
Middleware order is not a detail: authentication before rate limiting and rate limiting before authentication are different systems. So an interceptor is not merely enabled, it is placed — `http_interceptors` is an ORDERED list, the way `auth_backends` is, and the order in the file is the order requests pass through.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Register ¶
Register makes an interceptor selectable by name from configuration.
Call it from an init function in the implementing package, then import that package for its side effects:
func init() {
interceptor.Register("audit", New)
}
A name already taken is an error rather than a silent replacement: two packages claiming "audit" would make the effective interceptor depend on import order, and for something in the request path that is a security control whose identity depends on the order of an import block.
func Registered ¶
func Registered() []string
Registered returns every selectable interceptor name, sorted.
func Unregister ¶
func Unregister(name string)
Unregister removes a registered interceptor. It exists for tests that register a fake and must not leak it into the next one.
Types ¶
type Config ¶
type Config struct {
// Name is the registered name the operator selected this interceptor
// by.
Name string
// ProviderConfig is the raw `interceptors.<name>.*` subtree. Read it
// with Bind rather than reaching into the map.
ProviderConfig map[string]any
}
Config carries the `interceptors.<name>.*` subtree that belongs to one registered interceptor.
It is defined here rather than aliased from the authentication contract, even though the shape is identical. An interceptor has nothing to do with authentication, and borrowing that package's type would have made every third-party interceptor compile the auth contract to read a config map — a dependency inherited for a resemblance rather than for a reason. Bind behaves exactly as it does for a backend.
func (Config) Bind ¶
Bind decodes the interceptor's own configuration subtree into dst, applying `default:` tags to fields the file left unset.
A key the destination struct does not declare is an ERROR, not a silently ignored line: an interceptor is usually a protection, and a misspelled setting on a protection is the kind of thing an audit finds months later.
type Factory ¶
type Factory func(cfg Config) (Interceptor, error)
Factory builds a configured interceptor.
Returning an error fails BOOT rather than the first request. An interceptor that could not configure itself is a protection that is not there, and a protection that is not there must not be discovered by an audit six months later.
type Interceptor ¶
Interceptor wraps a handler. It is the standard Go middleware shape on purpose: an existing func(http.Handler) http.Handler needs no adapter, and an author already knows the contract.
func Build ¶
Build resolves an ORDERED list of registered names into the chain of interceptors, handing each one its own configuration subtree.
The returned slice is in the same order as names: first in the list is outermost, so it sees the request first and the response last — the order middleware is normally read in.
An unregistered name fails, naming what IS registered. That error is the only place an operator discovers the registry exists, and a typo in a list of request interceptors must not resolve to "one fewer protection, quietly".