You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit ad32eaf
Browse filesBrowse the repository at this point in the historyBrowse files
gluck
committed
middleware: one generic Chain, and interception stops being three shapes
Review asked what a more middlewarish pattern would look like, and whether
it could be generic. Both, and the answer changed more than the guard.
THE NAME. Go splits the word by transport: net/http says middleware, gRPC
and connectrpc say interceptor. A gRPC interceptor is NOT a decorator --
its signature is (ctx, req, info, handler) and the next handler arrives as
an argument -- so borrowing that word for a func(H) H would import the name
without the convention. go-kit generalised middleware off HTTP to exactly
this shape (type Middleware func(Endpoint) Endpoint), so: Middleware.
THE GENERIC. Not over the payload. In/Out would have exactly one
instantiation, because the wire is always (json.RawMessage -> any), and a
generic with one instantiation is inference noise that buys nothing. What
wants a type parameter is COMPOSITION:
type Middleware[H any] func(next H) H
func Chain[H any](ms ...Middleware[H]) Middleware[H]
Ten lines, no dependencies, no domain knowledge. One Chain for every
decorator in the tree -- MethodHandler on the hub, http.Handler on the
gateway, whatever comes next. A test instantiates it at http.Handler
purely to keep that claim honest.
WHAT IT UNIFIED. rpc.MethodHandler carries the method as an ARGUMENT
rather than as N closures that each captured their own name; without that
the unit of interception is a map, which is why the guard added last
commit had to be a map transformer. With it, the two cross-cutting
concerns on the aria endpoint are both middleware:
hb.dispatch = middleware.Chain(
authz.Middleware(authn, policy),
dressing(h.dressParams),
)(hb.route)
authz.Guard survives as an ADAPTER onto authz.Middleware for the
map-shaped angelus door, so there is one policy decision rather than two
that can disagree. Dressing was four lines hand-inlined at the top of
route(); route() is now only what its name says.
ORDER IS TESTED, NOT COMMENTED. Chain(a, b, c) means a is outermost.
Reversed, authorization would run after dressing -- and dressing resolves
outfit names by READING FILES FROM DISK, so an unauthorized caller would
reach the filesystem before anything refused them. Two tests: one on the
combinator, one on the real hub chain, because they regress independently
(reorder two lines in hubFor and every combinator test still passes).
No nilable middleware anywhere. Chain() of nothing is the identity and
authz.AllowAll() must be written, so "this door is open" is always a
sentence someone typed. That is the same rule the last commit's
fail-closed work rests on.
Full suite green.
0 commit comments