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
//! Route descriptor types used by macro-generated code.
//!
//! Each route macro ([`get`](crate::get), [`post`](crate::post), etc.)
//! generates a companion function that returns a [`Route`]. The
//! [`routes!`](crate::routes) macro collects these into a `Vec<Route>`
//! for the [`AppBuilder`](crate::app::AppBuilder).
//!
//! Users do not construct `Route` values directly -- they use the
//! proc macros and the `routes![]` collection macro.
use axum::routing::MethodRouter;
use http::Method;
use crate::openapi::ApiDoc;
use crate::state::AppState;
/// Metadata attached to routes emitted by the `#[repository(api = ...)]` macro.
///
/// Lets the app builder validate, at startup, that every auto-mounted CRUD
/// endpoint is paired with a registered
/// [`Policy`](crate::authorization::Policy).
#[derive(Debug, Clone, Copy)]
pub struct RepositoryApiMeta {
/// Stringified resource type name (e.g., `"Post"`). Used for
/// log messages and to look up the registered policy via
/// [`std::any::TypeId`] indirectly through the generated check
/// function in [`Self::policy_check`].
pub resource_type_name: &'static str,
/// Path prefix mounted by this repository (e.g., `"/api/posts"`).
pub api_path: &'static str,
/// `true` when the macro form used `policy = SomePolicy`, so the
/// auto-generated handlers enforce a record-level check before
/// running. `false` when the macro form is just
/// `#[repository(api = "...")]` — that form is rejected in
/// `prod` profile builds unless
/// `[security] allow_unauthorized_repository_api = true`.
pub has_policy: bool,
/// Type-erased registry probe emitted by the macro when
/// `policy = ...` is set. Returns `true` if a [`Policy`](crate::authorization::Policy) is
/// registered on the runtime
/// [`PolicyRegistry`](crate::authorization::PolicyRegistry) for
/// the resource type. Lets the app builder fail fast at
/// startup when a developer wires `policy = X` on the
/// `#[repository]` macro but forgets to call
/// `.policy::<R, _>(X)` on the builder — without this check,
/// every protected request would 500 with "no policy
/// registered" instead of failing fast at boot. `None` when
/// the macro form omits `policy = ...`.
pub policy_check: Option<fn(&crate::authorization::PolicyRegistry) -> bool>,
/// Type-erased registry probe emitted by the macro when
/// `scope = ...` is set. Returns `true` if a [`Scope`](crate::authorization::Scope) is
/// registered for the resource type. Companion to
/// [`Self::policy_check`] for the scope-list code path: the
/// generated `GET /<api>` handler resolves the scope from the
/// registry on every request, so a missing
/// `.scope::<R, _>(...)` registration would 500 every list
/// call. The startup guard fails fast instead. `None` when
/// the macro form omits `scope = ...`.
pub scope_check: Option<fn(&crate::authorization::PolicyRegistry) -> bool>,
}
/// Declares how the app-level idempotency layer should replay cached responses
/// for this route.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum RouteIdempotency {
/// Unknown/manual routes have no guaranteed generated replay consumer.
/// Autumn stores the first successful mutation but fails closed on cache
/// hits instead of directly replaying a stale success around any
/// route-local authorization, tenant, audit, or similar layers.
#[default]
Direct,
/// Autumn-generated routes install a replay consumer inside the route
/// stack or generated guard body, allowing route-local middleware and
/// guards to run before the cached response is returned.
///
/// Manual layered routes can use this too, but they must place
/// [`crate::idempotency::IdempotencyReplayLayer`] after those checks and
/// before the mutating handler.
ReplayThroughInner,
}
/// A single route binding an HTTP method + path to an Axum handler.
///
/// Created by the `__autumn_route_info_{name}()` companion functions
/// that route macros ([`get`](crate::get), [`post`](crate::post), etc.)
/// generate. Users don't construct this directly -- they use the
/// attribute macros and the [`routes!`](crate::routes) macro.
///
/// # Examples
///
/// ```rust,no_run
/// use autumn_web::prelude::*;
///
/// #[get("/hello")]
/// async fn hello() -> &'static str { "hi" }
///
/// // `routes!` expands to a Vec<Route>:
/// let route_vec: Vec<autumn_web::Route> = routes![hello];
/// assert_eq!(route_vec.len(), 1);
/// ```
pub struct Route {
/// HTTP method (`GET`, `POST`, `PUT`, `DELETE`, etc.).
pub method: Method,
/// URL path pattern (e.g., `"/users/{id}"`).
pub path: &'static str,
/// Axum [`MethodRouter`] that handles requests matching this route.
pub handler: MethodRouter<AppState>,
/// Handler function name, used for startup logging
/// (e.g., `"hello"`, `"create_item"`).
pub name: &'static str,
/// `OpenAPI` metadata inferred from the handler's signature and any
/// [`#[api_doc(...)]`](crate::api_doc) overrides. Consumed by
/// `AppBuilder::openapi` when
/// generating `/v3/api-docs`.
pub api_doc: ApiDoc,
/// API version of the route (e.g. "v1")
pub api_version: Option<&'static str>,
/// Whether this route opts out of sunset 410 response
pub sunset_opt_out: bool,
/// Repository auto-API metadata, populated by the
/// `#[repository(api = ...)]` macro. `None` for hand-written
/// route handlers.
pub repository: Option<RepositoryApiMeta>,
/// Idempotency replay behavior for this route.
pub idempotency: RouteIdempotency,
}