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

edri.dataclass.directive.__init__

Base classes: ResponseDirective, HTTPResponseDirective, HTMLResponseDirective, RESTResponseDirective, WebSocketResponseDirective

edri.dataclass.directive.base

InternalServerErrorResponseDirective, UnauthorizedResponseDirective

edri.dataclass.directive.http

All HTTP status directives, CookieResponseDirective, HeaderResponseDirective

edri.dataclass.directive.html

RedirectResponseDirective (event-aware HTML redirect)


Concrete class catalogue

Transport-agnostic (HTTP + WebSocket)

Directive

HTTP status

When to use

InternalServerErrorResponseDirective

500

Unexpected server errors

UnauthorizedResponseDirective

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

BadRequestResponseDirective

400

Malformed or invalid client input

UnauthorizedResponseDirective

401

Authentication failure

AccessDeniedResponseDirective

403

Authorisation failure

NotFoundResponseDirective

404

Resource not found

ConflictResponseDirective

409

Resource state conflict

UnprocessableContentResponseDirective

422

Semantically invalid request body

UnsupportedMediaTypeResponseDirective

415

Request payload in an unaccepted format

TooManyRequestsResponseDirective

429

Rate limiting (optional retry_after)

InternalServerErrorResponseDirective

500

Unexpected errors

NotImplementedResponseDirective

501

Functionality not supported

ServiceUnavailableResponseDirective

503

Temporary overload or maintenance

NotModifiedResponseDirective

304

Conditional GET — ETag match, no body

PartialContentResponseDirective

206

Successful byte-range response

RangeNotSatisfiableResponseDirective

416

Invalid or unsatisfiable Range

GoneResponseDirective

410

Resource permanently removed

CreatedResponseDirective

201

Resource created (optional Location)

AcceptedResponseDirective

202

Accepted for async processing

NoContentResponseDirective

204

Success, no response body

MovedPermanentlyResponseDirective

301

Permanent redirect, Location

FoundResponseDirective

302

Temporary redirect (common default)

SeeOtherResponseDirective

303

Post/Redirect/GET pattern

TemporaryRedirectResponseDirective

307

Temporary redirect, method preserved

PermanentRedirectResponseDirective

308

Permanent redirect, method preserved

HeaderResponseDirective

(no change)

Set or override an arbitrary response header

CookieResponseDirective

(no change)

Set a cookie

All HTTP directives live in edri.dataclass.directive.http.

HTML directives

Directive

HTTP status

When to use

RedirectResponseDirective

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")
)

Setting cookies

from edri.dataclass.directive.http import CookieResponseDirective

event.response.add_directive(
    CookieResponseDirective(
        name="session",
        value="abc123",
        httponly=True,
        secure=True,
        path="/",
        max_age=3600,
    )
)

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:

  1. Start with 200 OK (default).

  2. Iterate through directives in order.

  3. The last directive that defines a status wins.

  4. HeaderResponseDirective and CookieResponseDirective do 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.