Directives¶
A directive is a post-processing instruction attached to a Response that modifies how the API layer translates the response for the client — across HTTP, WebSocket, or any transport.
Directives are the mechanism by which handlers control HTTP status codes, set headers and cookies, trigger redirects, and signal error conditions, all without coupling business logic to transport details.
Hierarchy¶
ResponseDirective # edri.dataclass.directive
├── HTTPResponseDirective # General HTTP directives
│ ├── HTMLResponseDirective # HTML-specific (redirect with event resolution)
│ │ └── RedirectResponseDirective
│ └── RESTResponseDirective # REST-specific
├── WebSocketResponseDirective # WebSocket-specific (reserved for future use)
├── InternalServerErrorResponseDirective # 500 — transport-agnostic
└── UnauthorizedResponseDirective # 401 — transport-agnostic
All directive classes are frozen dataclasses and live in the edri.dataclass.directive package:
Module |
Contents |
|---|---|
|
Base classes: |
|
|
|
All HTTP status directives, |
|
|
Concrete class catalogue¶
Transport-agnostic (HTTP + WebSocket)¶
Directive |
HTTP status |
When to use |
|---|---|---|
|
500 |
Unexpected server errors |
|
401 |
Missing or invalid authentication |
These extend ResponseDirective directly and are handled by both the HTTP and WebSocket protocol layers.
HTTP directives¶
Directive |
HTTP status |
When to use |
|---|---|---|
|
400 |
Malformed or invalid client input |
|
401 |
Authentication failure |
|
403 |
Authorisation failure |
|
404 |
Resource not found |
|
409 |
Resource state conflict |
|
422 |
Semantically invalid request body |
|
415 |
Request payload in an unaccepted format |
|
429 |
Rate limiting (optional |
|
500 |
Unexpected errors |
|
501 |
Functionality not supported |
|
503 |
Temporary overload or maintenance |
|
304 |
Conditional GET — ETag match, no body |
|
206 |
Successful byte-range response |
|
416 |
Invalid or unsatisfiable |
|
410 |
Resource permanently removed |
|
201 |
Resource created (optional |
|
202 |
Accepted for async processing |
|
204 |
Success, no response body |
|
301 |
Permanent redirect, |
|
302 |
Temporary redirect (common default) |
|
303 |
Post/Redirect/GET pattern |
|
307 |
Temporary redirect, method preserved |
|
308 |
Permanent redirect, method preserved |
|
(no change) |
Set or override an arbitrary response header |
|
(no change) |
Set a cookie |
All HTTP directives live in edri.dataclass.directive.http.
HTML directives¶
Directive |
HTTP status |
When to use |
|---|---|---|
|
302 |
Redirect the browser to another page |
RedirectResponseDirective is special: its location argument accepts either a string URL or an Event class (subclass of Event). When given an Event class, the framework resolves it to the URL declared in the event's @api(url=…) decorator, with optional keyword arguments for path placeholders.
It lives in edri.dataclass.directive.html.
How to return from a handler¶
Directives are attached to event.response via add_directive():
from edri.dataclass.directive.http import UnauthorizedResponseDirective
event.response.set_status(ResponseStatus.FAILED)
event.response.add_directive(
UnauthorizedResponseDirective(message="Invalid token")
)
The first argument to every directive constructor is a message (where applicable). Additional keyword arguments are specific to the directive class.
Redirect with event class resolution¶
from edri.dataclass.directive.html import RedirectResponseDirective
event.response.add_directive(
RedirectResponseDirective(ContactThanks)
)
# Resolves ContactThanks to the URL in its @api(url=...) decorator
event.response.add_directive(
RedirectResponseDirective(UserPage, user_id=42)
)
# Path placeholders can be filled via kwargs
Setting arbitrary headers¶
from edri.dataclass.directive.http import HeaderResponseDirective
event.response.add_directive(
HeaderResponseDirective(name="X-Custom", value="my-value")
)
How to inspect in tests¶
The ManagerTestHarness provides get_directives() to verify directives without spawning child processes:
from edri.testing import ManagerTestHarness
harness = ManagerTestHarness(MyManager(router_queue=mock_queue))
harness.prepare()
event = MyEvent(...)
harness.resolve(event)
directives = harness.get_directives(event)
assert len(directives) == 1
assert isinstance(directives[0], NotFoundResponseDirective)
The underlying method is event.response.get_directives(), which returns the raw list of ResponseDirective instances attached to the response.
HTTP status code resolution¶
When the API layer processes a response, it determines the final HTTP status:
Start with 200 OK (default).
Iterate through directives in order.
The last directive that defines a status wins.
HeaderResponseDirectiveandCookieResponseDirectivedo not change the status.
This "last wins" behaviour applies per-response, not per-directive type — you can combine a cookie directive and a redirect directive in the same response.