pub trait Controller: Send + Sync {
// Required methods
fn actus_dispatch<'life0, 'life1, 'async_trait>(
&'life0 self,
action: &'life1 str,
params: Params,
) -> Pin<Box<dyn Future<Output = Reply> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
fn __name(&self) -> &'static str;
// Provided methods
fn actus_describe_routes(&self) -> Vec<RouteDef> { ... }
fn actus_max_body_bytes(&self) -> Option<usize> { ... }
fn actus_rate_limit(&self) -> Option<&'static str> { ... }
fn actus_expects(&self) -> Option<&'static str> { ... }
fn actus_prepare(&self) -> Option<&'static str> { ... }
}Expand description
The runtime interface every controller implements. Hand-writing this is
possible but unusual — the #[controller] macro generates the
implementation (dispatch table, parameter extraction, the metadata methods)
from a controller’s impl block and its routes! declaration.
Required Methods§
Sourcefn actus_dispatch<'life0, 'life1, 'async_trait>(
&'life0 self,
action: &'life1 str,
params: Params,
) -> Pin<Box<dyn Future<Output = Reply> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn actus_dispatch<'life0, 'life1, 'async_trait>(
&'life0 self,
action: &'life1 str,
params: Params,
) -> Pin<Box<dyn Future<Output = Reply> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Route action (the path below this controller’s mount) to the matching
handler and run it, returning its Reply. Generated by the macro.
Provided Methods§
Sourcefn actus_describe_routes(&self) -> Vec<RouteDef>
fn actus_describe_routes(&self) -> Vec<RouteDef>
The controller’s declared routes, for introspection (OpenAPI generation, route audits). Defaults to empty; the macro overrides it.
Sourcefn actus_max_body_bytes(&self) -> Option<usize>
fn actus_max_body_bytes(&self) -> Option<usize>
Per-controller maximum buffered body size, in bytes. Returned by the
#[controller(max_body_bytes = …)] attribute when set; None means the
controller defers to the server-level cap (Server::with_max_body_bytes).
Resolution at request time (see Server::handle_request_inner):
controller value if Some, otherwise the server-wide cap, otherwise
DEFAULT_MAX_BODY_BYTES (2 MiB).
The framework calls this before buffering the body — so a 1 KB controller cap rejects a 50 KB request before the bytes are allocated. (A request body big enough to be a memory concern shouldn’t get past the framework regardless of where the handler would have rejected it.)
Sourcefn actus_rate_limit(&self) -> Option<&'static str>
fn actus_rate_limit(&self) -> Option<&'static str>
Per-controller rate-limit class label, as declared by
#[controller(rate_limit = "name")]. None (the default) means the
controller declared no class.
This is a label, not a policy. Actus is policy-agnostic: it ships
no limiter algorithm, key function, or store, because the framework
can’t pick those correctly for someone else (which key — IP / user /
API key? which algorithm — token bucket / sliding window? which store
— in-memory / Redis?). Those are application decisions, so the limiter
itself stays an application Middleware.
What the framework does own is auditability and the response shape.
The server stamps this label onto the matched request (surfaced as
Request::rate_limit_class in actus-server), so a reviewer can read
each endpoint’s rate-limit class straight off the #[controller(...)]
line, and an application’s rate-limit Middleware can map class →
policy and reject over-limit requests with
WebError::TooManyRequests (429 + Retry-After, also framework-owned).
Two controllers sharing a class share a limit namespace; what each
class means is the application’s call.
Resolution is per-controller, mirroring Controller::actus_max_body_bytes.
A per-route override would be an additive future change (the same shape
as the per-route body-cap proposal).
Sourcefn actus_expects(&self) -> Option<&'static str>
fn actus_expects(&self) -> Option<&'static str>
The controller’s declared caller expectation, as set by
#[controller(expects = "…")]. None (the default) means the
controller declared nothing.
This is a label, not a policy — and specifically a floor: it
names the least-privileged caller the controller is written to
accept ("credential", "anonymous", "signature", …). It is not a
ceiling — individual routes may demand more in their handlers — and it
is not authorization: Actus never interprets the value, compares it
only for presence/equality in application code, and hands it back
untouched. What each label means, and what happens to a caller below
the floor, is entirely the application’s (typically: a startup
coverage check over Router::mounts, a declaration-keyed gate in a
prepare hook or middleware, and a probe test — see the README’s
“Route families” section).
Resolution is per-controller, mirroring
Controller::actus_rate_limit. A controller with routes above its
floor declares the floor and enforces the stricter routes in their
handlers.
Sourcefn actus_prepare(&self) -> Option<&'static str>
fn actus_prepare(&self) -> Option<&'static str>
The path of the controller’s prepare hook, as written in
#[controller(prepare = …)] (e.g. "Self::auth"), or None when the
controller declared no hook.
Presence is the payload. A route-family coverage check reads this
to enforce rules like “a controller whose floor is "credential"
must have a hook to refuse anonymous callers with” — the string
itself is a courtesy for route dumps and diagnostics, not an
invocation handle.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".