Skip to main content

autumn_web/
route.rs

1//! Route descriptor types used by macro-generated code.
2//!
3//! Each route macro ([`get`](crate::get), [`post`](crate::post), etc.)
4//! generates a companion function that returns a [`Route`]. The
5//! [`routes!`](crate::routes) macro collects these into a `Vec<Route>`
6//! for the [`AppBuilder`](crate::app::AppBuilder).
7//!
8//! Users do not construct `Route` values directly -- they use the
9//! proc macros and the `routes![]` collection macro.
10
11use axum::routing::MethodRouter;
12use http::Method;
13
14use crate::openapi::ApiDoc;
15use crate::state::AppState;
16
17/// Metadata attached to routes emitted by the `#[repository(api = ...)]` macro.
18///
19/// Lets the app builder validate, at startup, that every auto-mounted CRUD
20/// endpoint is paired with a registered
21/// [`Policy`](crate::authorization::Policy).
22#[derive(Debug, Clone, Copy)]
23pub struct RepositoryApiMeta {
24    /// Stringified resource type name (e.g., `"Post"`). Used for
25    /// log messages and to look up the registered policy via
26    /// [`std::any::TypeId`] indirectly through the generated check
27    /// function in [`Self::policy_check`].
28    pub resource_type_name: &'static str,
29
30    /// Path prefix mounted by this repository (e.g., `"/api/posts"`).
31    pub api_path: &'static str,
32
33    /// `true` when the macro form used `policy = SomePolicy`, so the
34    /// auto-generated handlers enforce a record-level check before
35    /// running. `false` when the macro form is just
36    /// `#[repository(api = "...")]` — that form is rejected in
37    /// `prod` profile builds unless
38    /// `[security] allow_unauthorized_repository_api = true`.
39    pub has_policy: bool,
40
41    /// Type-erased registry probe emitted by the macro when
42    /// `policy = ...` is set. Returns `true` if a [`Policy`](crate::authorization::Policy) is
43    /// registered on the runtime
44    /// [`PolicyRegistry`](crate::authorization::PolicyRegistry) for
45    /// the resource type. Lets the app builder fail fast at
46    /// startup when a developer wires `policy = X` on the
47    /// `#[repository]` macro but forgets to call
48    /// `.policy::<R, _>(X)` on the builder — without this check,
49    /// every protected request would 500 with "no policy
50    /// registered" instead of failing fast at boot. `None` when
51    /// the macro form omits `policy = ...`.
52    pub policy_check: Option<fn(&crate::authorization::PolicyRegistry) -> bool>,
53
54    /// Type-erased registry probe emitted by the macro when
55    /// `scope = ...` is set. Returns `true` if a [`Scope`](crate::authorization::Scope) is
56    /// registered for the resource type. Companion to
57    /// [`Self::policy_check`] for the scope-list code path: the
58    /// generated `GET /<api>` handler resolves the scope from the
59    /// registry on every request, so a missing
60    /// `.scope::<R, _>(...)` registration would 500 every list
61    /// call. The startup guard fails fast instead. `None` when
62    /// the macro form omits `scope = ...`.
63    pub scope_check: Option<fn(&crate::authorization::PolicyRegistry) -> bool>,
64}
65
66/// Declares how the app-level idempotency layer should replay cached responses
67/// for this route.
68#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
69pub enum RouteIdempotency {
70    /// Unknown/manual routes have no guaranteed generated replay consumer.
71    /// Autumn stores the first successful mutation but fails closed on cache
72    /// hits instead of directly replaying a stale success around any
73    /// route-local authorization, tenant, audit, or similar layers.
74    #[default]
75    Direct,
76    /// Autumn-generated routes install a replay consumer inside the route
77    /// stack or generated guard body, allowing route-local middleware and
78    /// guards to run before the cached response is returned.
79    ///
80    /// Manual layered routes can use this too, but they must place
81    /// [`crate::idempotency::IdempotencyReplayLayer`] after those checks and
82    /// before the mutating handler.
83    ReplayThroughInner,
84}
85
86/// A single route binding an HTTP method + path to an Axum handler.
87///
88/// Created by the `__autumn_route_info_{name}()` companion functions
89/// that route macros ([`get`](crate::get), [`post`](crate::post), etc.)
90/// generate. Users don't construct this directly -- they use the
91/// attribute macros and the [`routes!`](crate::routes) macro.
92///
93/// # Examples
94///
95/// ```rust,no_run
96/// use autumn_web::prelude::*;
97///
98/// #[get("/hello")]
99/// async fn hello() -> &'static str { "hi" }
100///
101/// // `routes!` expands to a Vec<Route>:
102/// let route_vec: Vec<autumn_web::Route> = routes![hello];
103/// assert_eq!(route_vec.len(), 1);
104/// ```
105pub struct Route {
106    /// HTTP method (`GET`, `POST`, `PUT`, `DELETE`, etc.).
107    pub method: Method,
108
109    /// URL path pattern (e.g., `"/users/{id}"`).
110    pub path: &'static str,
111
112    /// Axum [`MethodRouter`] that handles requests matching this route.
113    pub handler: MethodRouter<AppState>,
114
115    /// Handler function name, used for startup logging
116    /// (e.g., `"hello"`, `"create_item"`).
117    pub name: &'static str,
118
119    /// `OpenAPI` metadata inferred from the handler's signature and any
120    /// [`#[api_doc(...)]`](crate::api_doc) overrides. Consumed by
121    /// `AppBuilder::openapi` when
122    /// generating `/v3/api-docs`.
123    pub api_doc: ApiDoc,
124
125    /// API version of the route (e.g. "v1")
126    pub api_version: Option<&'static str>,
127
128    /// Whether this route opts out of sunset 410 response
129    pub sunset_opt_out: bool,
130
131    /// Repository auto-API metadata, populated by the
132    /// `#[repository(api = ...)]` macro. `None` for hand-written
133    /// route handlers.
134    pub repository: Option<RepositoryApiMeta>,
135
136    /// Idempotency replay behavior for this route.
137    pub idempotency: RouteIdempotency,
138}