actus 1.5.0

The ergonomic web framework for Rust.
Documentation
//! # 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
//! — and the third answer is a declaration the build checks: each controller
//! states the least-privileged caller it accepts, and a `families` block in
//! `app_routes!` refuses to compile a controller that states nothing or claims
//! a floor its prefix does not accept.
//!
//! # 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:
//!
//! ```toml
//! [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.
//!
//! ```no_run
//! 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 server** — `Server::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 routing** — `app_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.
//! - **Replies** — `reply!` for JSON, chunked streams, and Server-Sent
//!   Events; `WebError` for structured RFC 7807 `application/problem+json`.
//! - **HTTP-protocol features** — `Server::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.
//! - **Middleware** — `before` / `after` hooks via `Server::with_middleware`;
//!   ships a `RequestLogger`.
//! - **Route families** — `#[controller(expects = "…")]` declares a
//!   controller's caller *floor*; `Router::mounts()` inventories every mount
//!   (absences included) for a boot-time coverage check; `Server::router()`
//!   lets a middleware gate on the declaration; and a `families { … }` block
//!   in `app_routes!` makes a missing or unaccepted declaration a **compile
//!   error** — see below.
//!
//! # Route families at compile time
//!
//! Coverage, not authorization: the label is opaque to Actus. With a
//! `families` block, every controller mounted under a listed prefix must carry
//! `#[controller(expects = "…")]`, and — when the entry names accepted floors —
//! the declared floor must be one of them. This compiles:
//!
//! ```no_run
//! use actus::prelude::*;
//!
//! struct Things;
//! #[controller(expects = "credential")]
//! impl Things {
//!     routes! { GET "" => list() }
//!     async fn list(&self) -> Reply { reply!() }
//! }
//!
//! struct Health;
//! #[controller]                 // declares nothing — fine outside every family
//! impl Health {
//!     routes! { GET "" => ok() }
//!     async fn ok(&self) -> Reply { reply!() }
//! }
//!
//! app_routes! {
//!     families { "api" => ["credential", "anonymous"] }
//!     routes {
//!         "api/things" => Things,
//!         "health"     => Health,
//!     }
//! }
//! ```
//!
//! A controller that declares **nothing** under a covered prefix does not
//! compile — the error names the controller and says what to add:
//!
//! ```compile_fail,E0277
//! use actus::prelude::*;
//!
//! struct Things;
//! #[controller]                 // forgot `expects = …`
//! impl Things {
//!     routes! { GET "" => list() }
//!     async fn list(&self) -> Reply { reply!() }
//! }
//!
//! app_routes! {
//!     families { "api" }
//!     routes { "api/things" => Things }
//! }
//! ```
//!
//! A floor the family does **not accept** fails the `const` membership check
//! when `init` is compiled — i.e. whenever it is reachable from something
//! that runs, which in an application it always is. ⚠️ "Compiled" means
//! codegen: `cargo check` (and IDE diagnostics built on it) does not evaluate
//! it; `cargo build`, `cargo test` and CI do. The presence check above is a
//! type error and shows under `check`.
//!
//! ```compile_fail,E0080
//! use actus::prelude::*;
//!
//! struct Hooks;
//! #[controller(expects = "signature")]
//! impl Hooks {
//!     routes! { POST "" => receive() }
//!     async fn receive(&self) -> Reply { reply!() }
//! }
//!
//! app_routes! {
//!     families { "api" => ["credential", "anonymous"] }   // "signature" is not accepted here
//!     routes { "api/hooks" => Hooks }
//! }
//!
//! #[tokio::main]
//! async fn main() { let _ = init().await; }
//! ```
//!
//! Families **nest, longest prefix winning** — the same rule as routing — so an
//! exception can be confined to the one subtree that earns it, without moving a
//! URL. Here `api/auth` — the controller that issues sessions, whose callers
//! by definition have none yet — may declare `"session-entry"`, and nothing
//! else under `api/` may; this doctest *runs* `init`, so the `const` check is
//! evaluated:
//!
//! ```
//! use actus::prelude::*;
//!
//! struct Things;
//! #[controller(expects = "credential")]
//! impl Things {
//!     routes! { GET "" => list() }
//!     async fn list(&self) -> Reply { reply!() }
//! }
//!
//! struct Login;
//! #[controller(expects = "session-entry")]
//! impl Login {
//!     routes! { POST "" => login() }
//!     async fn login(&self) -> Reply { reply!() }
//! }
//!
//! app_routes! {
//!     families {
//!         "api"      => ["credential"],
//!         "api/auth" => ["session-entry"],   // the deeper entry wins for its subtree
//!     }
//!     routes {
//!         "api/things" => Things,
//!         "api/auth"   => Login,
//!     }
//! }
//!
//! #[tokio::main]
//! async fn main() {
//!     let router = init().await.expect("init");
//!     assert_eq!(router.mounts().len(), 2);
//!     // A boot-time check must decide "which family?" by the same rule the
//!     // macro just applied; `routing::covering_family` IS that rule.
//!     let families = ["api", "api/auth"];
//!     assert_eq!(actus::routing::covering_family("api/things", families), Some("api"));
//!     assert_eq!(actus::routing::covering_family("api/auth", families), Some("api/auth"));
//! }
//! ```
//!
//! That nesting is the intended answer when a family needs an exception: name the
//! reason as its own floor, confine it to the subtree that earns it, and leave
//! the URLs alone — the top-level segment is what every client and intermediary
//! keys on, and moving routes to make a family uniform spends shipped URLs on a
//! property a declaration already provides. (The README's "Route families"
//! section spells this out.)
//!
//! And a family that covers **no mount** is a compile error at its literal —
//! a typo there would otherwise constrain nothing:
//!
//! ```compile_fail
//! use actus::prelude::*;
//!
//! struct Things;
//! #[controller(expects = "credential")]
//! impl Things {
//!     routes! { GET "" => list() }
//!     async fn list(&self) -> Reply { reply!() }
//! }
//!
//! app_routes! {
//!     families { "apo" }        // typo: nothing is mounted under `apo/`
//!     routes { "api/things" => Things }
//! }
//! ```
//!
//! 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.
//!
//! [hyper]: https://hyper.rs/
//! [tokio]: https://tokio.rs/
//! [`actus-server`]: https://docs.rs/actus-server
//! [`actus-controller`]: https://docs.rs/actus-controller
//! [`actus-reply`]: https://docs.rs/actus-reply
//! [repository]: https://github.com/uniweb/actus

pub use actus_reply::Finalizer;

// Re-exported at the crate root so the `app_routes!` macro can resolve
// `::actus::Router` / `::actus::RouterBuilder` from generated code without
// requiring downstream crates to depend on `actus-server` directly.
pub use actus_server::{GIB, KIB, MIB, Mount, RateLimitClass, Router, RouterBuilder, Server};

/// Route-resolution helpers, exposed for tools and boot-time checks —
/// notably [`routing::covering_family`], the rule the `families` block of
/// `app_routes!` uses to decide which family a mount belongs to, so a
/// startup coverage check written against it cannot disagree with the
/// compile-time one.
pub use actus_controller::routing;

/// WebSocket support — [`ws::upgrade`], [`ws::WebSocket`], [`ws::Message`].
/// Available with the `websocket` feature.
#[cfg(feature = "websocket")]
pub use actus_server::websocket as ws;

/// OpenAPI 3.x doc generation — [`openapi::generate`], [`openapi::Options`].
/// Available with the `openapi` feature.
#[cfg(feature = "openapi")]
pub use actus_server::openapi;

/// 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`.
pub type InitError = anyhow::Error;

/// `Result<T, actus::InitError>`. The macro-generated `init()` returns this.
pub type InitResult<T> = ::std::result::Result<T, InitError>;

/// Implementation re-exports used by the procedural macros.
///
/// This module exists so generated code can refer to types from the
/// implementation crates (`actus-controller`, etc.) via stable absolute paths
/// like `::actus::__internal::Verb`. End users should not import from here —
/// use [`prelude`] instead.
#[doc(hidden)]
pub mod __internal {
    pub use actus_controller::*;
    pub use anyhow;
}

/// Common imports for Actus applications.
pub mod prelude {
    pub use actus_controller::{
        Controller, ExtractedParams, Params, RouteDef, Verb, app_routes, controller, routes,
    };
    // Needed to `impl Middleware` (the trait is `#[async_trait]`).
    pub use actus_controller::async_trait;
    pub use actus_reply::prelude::*;
    #[cfg(feature = "compression")]
    pub use actus_server::CompressionLayer;
    #[cfg(feature = "websocket")]
    pub use actus_server::{Message, WebSocket};
    // The `ws` module itself, so `ws::upgrade(...)` works after a glob import.
    #[cfg(feature = "websocket")]
    pub use crate::ws;
    pub use actus_server::{
        CorsLayer, GIB, KIB, MIB, Middleware, Mount, Outcome, RateLimitClass, Request,
        RequestLogger, Router, RouterBuilder, Server, ServerError,
    };
    // Re-exported so handlers can declare `body: Bytes` in `routes!`
    // without an extra `use bytes::Bytes;` line. There is one canonical
    // zero-copy bytes type in the Rust HTTP ecosystem; pinning it here
    // means generated extraction code and the handler signature
    // resolve to the same `bytes::Bytes`.
    pub use bytes::Bytes;
}