Bustan 0.0.2 introduces three extension points — guards, pipes and interceptors.
Knowing the order they run in is most of knowing where to put code.
Why this order#
Middleware before routing. Middleware sees raw ASGI scope and does not know which handler will run — right for logging, tracing, compression.
Guards before pipes. Authorisation does not depend on parameters being well-formed. Validating input for a request you are about to reject wastes work and, worse, leaks information: careful 400s tell an unauthenticated caller which fields exist.
Pipes before the handler. By the time the handler runs, every value it receives has been coerced and checked. Handlers do no defensive parsing.
Interceptors around the handler. An interceptor wraps the call, so it sees both sides — the right place for timing, caching and response shaping.
Where to put what#
| Concern | Goes in |
|---|---|
| Request logging, tracing | Middleware |
| "May this caller do this?" | Guard |
| "Is this value usable?" | Pipe |
| Timing, caching, envelopes | Interceptor |
| Turning exceptions into responses | Error filter |
The tempting mistake#
Authorisation in a pipe. It works — a pipe can raise — and it puts the check next to the value it concerns.
But pipes run per parameter and only on parameters the handler declares. A route that takes no arguments runs no pipes and is silently unprotected. Guards run for every matched route regardless of signature, which is exactly the property an authorisation check needs.
Errors#
Anything raised in guards, pipes, the handler or interceptors reaches the error
filter, which maps exceptions to responses. Built-in exceptions carry their
status; anything else becomes a 500 with the detail logged, not returned.
That default is deliberate. A framework that echoes exception text by default will eventually echo a connection string.