1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
//! # 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 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 ;
/// 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 routing;
/// WebSocket support — [`ws::upgrade`], [`ws::WebSocket`], [`ws::Message`].
/// Available with the `websocket` feature.
pub use websocket as ws;
/// OpenAPI 3.x doc generation — [`openapi::generate`], [`openapi::Options`].
/// Available with the `openapi` feature.
pub use 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 = Error;
/// `Result<T, actus::InitError>`. The macro-generated `init()` returns this.
pub type InitResult<T> = Result;
/// 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.
/// Common imports for Actus applications.