Skip to main content

rust_api/
controller.rs

1//! Controller trait — pure descriptor for composable route groups.
2//!
3//! A `Controller` is a zero-knowledge marker type. It knows only two things:
4//!   1. `type State` — the service it needs (`Arc<State>` is resolved and
5//!      passed in)
6//!   2. `fn mount` — a Kleisli arrow `Arc<State> -> (Router -> Result<Router>)`
7//!
8//! Controllers have **no dependency on `Router`, `RouteSet`, or any DI
9//! container**. The `mount_handlers!` macro generates the `mount`
10//! implementation from a simple list of `(route_constant, handler_fn)` pairs.
11//! Users only write handlers.
12//!
13//! # Kleisli Composition
14//!
15//! `mount` returns `impl FnOnce(Router<()>) -> Result<Router<()>>` — a Kleisli
16//! arrow in the `Result` monad. The `RouterPipeline` composes these arrows with
17//! `and_then` (`>>=`), threading the router through each controller in
18//! sequence. A failed arrow short-circuits the rest.
19//!
20//! # Immutability Contract
21//!
22//! State is passed as `Arc<S>` — shared, immutable after construction. Services
23//! must not expose `&mut self` methods. All state mutation goes through
24//! `Atomic*` primitives or channels (never `Mutex<T>` on the service struct).
25//!
26//! # Example
27//!
28//! ```ignore
29//! pub struct HealthController;
30//!
31//! #[get("/health")]
32//! pub async fn health_check(State(svc): State<Arc<HealthService>>) -> Json<HealthResponse> {
33//!     Json(svc.health_check())
34//! }
35//!
36//! // mount_handlers! generates the full Controller impl — user never writes Router.
37//! mount_handlers!(HealthController, HealthService, [
38//!     (__health_check_route, health_check),
39//! ]);
40//! ```
41
42use std::sync::Arc;
43
44use crate::{error::Result, router::Router};
45
46/// A pure descriptor for a group of HTTP routes sharing a common service
47/// dependency.
48///
49/// Do not implement this trait manually. Use the [`mount_handlers!`] macro,
50/// which generates the correct Kleisli arrow from your handler list.
51///
52/// The trait's only concern is: given `Arc<State>`, produce a Kleisli arrow
53/// that registers this controller's routes into a `Router<()>`.
54pub trait Controller: Sized + 'static {
55    /// The service this controller depends on.
56    ///
57    /// The framework wraps it in `Arc<State>` — `Clone` is not required on the
58    /// service itself. Use `Atomic*` primitives or channels for any state that
59    /// changes after construction.
60    type State: Send + Sync + 'static;
61
62    /// Returns the Kleisli arrow for this controller.
63    ///
64    /// Signature: `Arc<State> -> (Router<()> -> Result<Router<()>>)`
65    ///
66    /// Generated by `mount_handlers!`. The returned closure:
67    ///   1. Builds a scoped `Router<Arc<State>>` with this controller's routes.
68    ///   2. Provides state via `.with_state(state)` → `Router<()>`.
69    ///   3. Merges into the outer `router` and returns `Ok(merged)`.
70    fn mount(state: Arc<Self::State>) -> impl FnOnce(Router<()>) -> Result<Router<()>>;
71}