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}