Skip to main content

Crate actus

Crate actus 

Source
Expand description

§Actus

The pragmatic web framework for Rust: auditable controllers, persistent services, real HTTP — out of the box. Built directly on hyper and tokio; there is no separate server to run it on.

Actus gives you a clear two-tier structure — a top-level routing blueprint and self-contained controllers — while letting you mix REST, RPC-style actions, and legacy URL migrations in the same codebase. A reviewer can answer what endpoints exist, what they require, and who can call them by reading two macros, without grepping for attribute decorators across files.

§Philosophy

Most Rust web frameworks are either unopinionated (you invent the structure) or rigidly opinionated (you bend to their paradigm). Actus picks a middle:

  • A clear hierarchy. The whole URL layout is declared once, in app_routes! { ... } — the entire backend is visible at a glance.
  • A clear unit of code. Each controller owns a URL prefix and declares its routes, access points, and parameters in one routes! { ... } block.
  • Pragmatism inside that structure. REST verbs (GET/POST/PUT/ DELETE), RPC-style action names (/charge, /refund), path parameters ({id}), and legacy URLs (login.php) all coexist in the same block.

§Design principles

  • Two kinds of cross-cutting concern get two shapes. HTTP-protocol concerns (CORS, body limits, compression) are named Server::with_X(...) methods with their lifecycle position built in; application concerns (logging, auth gates, request IDs, rate-limit policy) are Middleware. You never have to position CORS in a stack.
  • Auditability over uniformity. “What does this server do?” and “what endpoints exist?” are answerable from Server::new(...) and the two macros — without walking a chain of layers.
  • Explicit over magic. No DI container, no extractors reaching into thin air: the app_routes! deps block is constructor injection, and routes are declared, not discovered.
  • HTTP correctness out of the box. You shouldn’t need to know that compression goes outermost, or that the body cap gates the body parse — that is framework knowledge, not application knowledge.
  • Policy-agnostic. No roles, no Access enum, no built-in RBAC. Authorization lives in your policy layer, called from a controller’s prepare hook or a handler.

This crate is the façade you depend on. It re-exports the public API of the implementation crates (actus-server, actus-controller, actus-reply) and the two macros that declare your application’s URL surface. Add it with:

[dependencies]
actus = "1.0"
tokio = { version = "1", features = ["full"] }
serde_json = "1"

Optional features: compression (gzip/brotli responses), websocket (ws::upgrade), and openapi (OpenAPI 3.x generation).

§Quick start

Two macros declare everything: routes! (one controller’s API surface) and app_routes! (the whole application’s URL blueprint). A reviewer can see every endpoint by reading just those two places.

use actus::prelude::*;
use serde_json::json;

// A controller owns a URL prefix and declares its routes in one block.
struct Greeter;

#[controller]
impl Greeter {
    routes! {
        GET ""       => index(),
        GET "{name}" => greet(name: String),
    }

    pub async fn index(&self) -> Reply {
        reply!(json!({ "hello": "world" }))
    }

    pub async fn greet(&self, name: String) -> Reply {
        reply!(json!({ "hello": name }))
    }
}

// The application's URL blueprint, declared in one place. (The `deps`
// block — for injected services — is optional and omitted here.)
app_routes! {
    routes {
        "greet" => Greeter,
    }
}

// `init()` is generated by `app_routes!`; it builds the router.
#[tokio::main]
async fn main() -> actus::InitResult<()> {
    let router = init().await?;
    Server::new(router).run(3000).await?;
    Ok(())
}

§What’s in the box

  • Hyper-based HTTP serverServer::run(port) binds 127.0.0.1; Server::run_on(addr) binds anywhere (e.g. 0.0.0.0:port). Graceful shutdown on SIGTERM / SIGINT with a configurable drain deadline.
  • Two-macro routingapp_routes! (the app’s URL blueprint, with a deps block for injected services) and #[controller] + routes! (per-controller verbs, path patterns, typed query/body extraction, a prepare hook, and per-controller max_body_bytes / rate_limit).
  • Longest-prefix routing at arbitrary depth, with a trailing {...rest} catch-all and distinct 404 vs 405 (carrying Allow).
  • Typed extraction & state — query as a multimap, form-urlencoded bodies, and typed path/query/body params; prepare hooks stash typed values via params.insert::<T>(...) that handlers read back.
  • Repliesreply! for JSON, chunked streams, and Server-Sent Events; WebError for structured RFC 7807 application/problem+json.
  • HTTP-protocol featuresServer::with_cors, with_compression (gzip/brotli, compression feature), a per-request timeout, and three DoS guards (max connections, in-flight body budget, header-read timeout).
  • WebSocket (websocket feature) — ws::upgrade(...) from a handler.
  • OpenAPI 3.x (openapi feature) — openapi::generate(...) walks the route tree and emits a spec.
  • Middlewarebefore / after hooks via Server::with_middleware; ships a RequestLogger.

See the prelude for the common imports, and the repository for the full guide — philosophy, framework comparisons, and the examples/ directory with auth, typed bodies, CORS, compression, WebSockets, SSE, and middleware in working code.

Modules§

openapi
OpenAPI 3.x doc generation — openapi::generate, openapi::Options. Available with the openapi feature. OpenAPI 3.1 doc generation. Behind the openapi feature.
prelude
Common imports for Actus applications.
ws
WebSocket support — ws::upgrade, ws::WebSocket, ws::Message. Available with the websocket feature. WebSocket support (RFC 6455). Behind the websocket feature.

Structs§

Finalizer
Converts a ReplyData into a concrete hyper HTTP response — setting status, headers, and body, and driving buffered, streaming, SSE, and connection-upgrade replies.
RateLimitClass
One controller’s declared rate-limit class, as returned by Router::rate_limit_classes: the controller’s mount path and the class label it declared via #[controller(rate_limit = "…")]. Used by a startup coverage check that asserts every declared class has a policy.
Router
The Actus router. Dispatches requests to controllers by longest-prefix match over the route tree at arbitrary depth.
RouterBuilder
A builder for constructing the router.
Server
The main Actus server.

Constants§

GIB
One gibibyte (1024 × 1024 × 1024 bytes).
KIB
One kibibyte (1024 bytes). For readable byte-size limits, e.g. #[controller(max_body_bytes = 4 * KIB)].
MIB
One mebibyte (1024 × 1024 bytes), e.g. Server::with_max_body_bytes(2 * MIB).

Type Aliases§

InitError
Error type used by app_routes!’s generated init() for startup-time failures (DB connection refused, env var missing, migrations failing, etc.). Aliased to anyhow::Error so any error implementing std::error::Error + Send + Sync + 'static converts via ?, and the generated init() slots cleanly into an anyhow::Result<()> main.
InitResult
Result<T, actus::InitError>. The macro-generated init() returns this.