webx_macros/lib.rs
1//! Procedural macros for the rust-webx framework (package `rust-webx-macros`,
2//! imported as `webx_macros`).
3//!
4//! - `#[get]` / `#[post]` / `#[put]` / `#[delete]` / `#[endpoint]` — route registration
5//! - `#[handler]` — handler auto-registration via `inventory`
6//! - `#[webx::main]` / `#[webx::main(embed)]` — application entry + asset embedding
7//! - `#[webx::embed_assets]` — asset table include for integration tests
8//! - `#[derive(WebxRequestMeta)]` — OpenAPI parameter metadata
9//! - `#[claims]` / `#[authorize]` — authentication and authorization
10
11mod app_main;
12mod claims;
13mod endpoint;
14mod handler;
15mod request_meta;
16
17use proc_macro::TokenStream;
18
19/// Application entry — ASP.NET Core `Main` analogue (`#[tokio::main]`).
20///
21/// | Attribute | Baked-in wwwroot |
22/// |-----------|------------------|
23/// | `#[webx::main]` | No — disk SPA only (`.use_spa` / auto-detect) |
24/// | `#[webx::main(embed)]` | Yes — requires `build.rs` `web_root(...).build()` |
25///
26/// ```ignore
27/// // Disk-only (no build.rs embed step)
28/// #[webx::main]
29/// async fn main() {
30/// Host::builder().use_spa("wwwroot").build().run().await.unwrap();
31/// }
32///
33/// // Baked into the binary
34/// // build.rs: webx::builder().web_root("wwwroot").build()?;
35/// #[webx::main(embed)]
36/// async fn main() {
37/// Host::builder().use_spa("wwwroot").build().run().await.unwrap();
38/// }
39/// ```
40///
41/// - **`web_root` (build.rs)** — which files are compiled into the executable
42/// - **`.use_spa(...)` (runtime)** — disk directory consulted first (operator overrides)
43/// - Without `build.rs` / without `(embed)` — nothing is baked in
44#[proc_macro_attribute]
45pub fn main(attr: TokenStream, item: TokenStream) -> TokenStream {
46 app_main::main_impl(attr, item)
47}
48
49/// Include the `OUT_DIR` asset table (for integration tests that never run `main`).
50#[proc_macro_attribute]
51pub fn embed_assets(attr: TokenStream, item: TokenStream) -> TokenStream {
52 app_main::embed_assets_impl(attr, item)
53}
54
55// ---------------------------------------------------------------------------
56// Route macros: full form + shortcuts
57// ---------------------------------------------------------------------------
58
59/// Full form: marks an IRequest impl with HTTP method and route path.
60///
61/// ```ignore
62/// #[endpoint(HttpGet, "/users/{id}")]
63/// impl IRequest<UserModel> for GetUserRequest {}
64/// ```
65#[proc_macro_attribute]
66pub fn endpoint(attr: TokenStream, item: TokenStream) -> TokenStream {
67 endpoint::endpoint_impl(attr, item)
68}
69
70/// Shortcut: registers an IRequest impl at GET /path.
71///
72/// ```ignore
73/// #[get("/users/{id}")]
74/// impl IRequest<UserModel> for GetUserRequest {}
75/// ```
76#[proc_macro_attribute]
77pub fn get(attr: TokenStream, item: TokenStream) -> TokenStream {
78 endpoint::shortcut_get(attr, item)
79}
80
81/// Shortcut: registers an IRequest impl at POST /path.
82///
83/// ```ignore
84/// #[post("/users")]
85/// impl IRequest<UserModel> for CreateUserRequest {}
86/// ```
87#[proc_macro_attribute]
88pub fn post(attr: TokenStream, item: TokenStream) -> TokenStream {
89 endpoint::shortcut_post(attr, item)
90}
91
92/// Shortcut: registers an IRequest impl at PUT /path.
93///
94/// ```ignore
95/// #[put("/users/{id}")]
96/// impl IRequest<UserModel> for UpdateUserRequest {}
97/// ```
98#[proc_macro_attribute]
99pub fn put(attr: TokenStream, item: TokenStream) -> TokenStream {
100 endpoint::shortcut_put(attr, item)
101}
102
103/// Shortcut: registers an IRequest impl at DELETE /path.
104///
105/// ```ignore
106/// #[delete("/users/{id}")]
107/// impl IRequest<String> for DeleteUserRequest {}
108/// ```
109#[proc_macro_attribute]
110pub fn delete(attr: TokenStream, item: TokenStream) -> TokenStream {
111 endpoint::shortcut_delete(attr, item)
112}
113
114// ---------------------------------------------------------------------------
115// Handler auto-registration
116// ---------------------------------------------------------------------------
117
118/// Auto-registers an IRequestHandler implementation at compile time via inventory.
119///
120/// Place on `impl IRequestHandler<T, R> for Handler` blocks.
121/// The handler struct MUST implement `Default`.
122///
123/// ```ignore
124/// #[handler]
125/// #[async_trait]
126/// impl IRequestHandler<HelloRequest, String> for HelloHandler {
127/// async fn handle(&mut self, _req: HelloRequest) -> Result<String> { ... }
128/// }
129/// ```
130#[proc_macro_attribute]
131pub fn handler(attr: TokenStream, item: TokenStream) -> TokenStream {
132 handler::handler_impl(attr, item)
133}
134
135// ---------------------------------------------------------------------------
136// Parameter binding (OpenAPI metadata via WebxRequestMeta derive helpers)
137// ---------------------------------------------------------------------------
138//
139// `from_query`, `from_route`, and `from_body` are helper attributes for
140// `#[derive(WebxRequestMeta)]` — not standalone attribute macros (avoids
141// rustc conflicts on struct fields).
142
143/// Registers OpenAPI parameter metadata from `#[from_query]` / `#[from_route]` / `#[from_body]` / `#[from_form]` fields.
144///
145/// `FormFile` fields are recognised automatically and reported as
146/// `multipart/form-data` binary parts.
147///
148/// Add `#[webx_request(query_all)]` to treat all non-skipped fields as query parameters.
149#[proc_macro_derive(
150 WebxRequestMeta,
151 attributes(from_query, from_route, from_body, from_form, webx_request)
152)]
153pub fn webx_request_meta(input: TokenStream) -> TokenStream {
154 request_meta::derive_request_meta(input)
155}
156
157// ---------------------------------------------------------------------------
158// Declaration macros
159// ---------------------------------------------------------------------------
160
161// ---------------------------------------------------------------------------
162// Claims attribute
163// ---------------------------------------------------------------------------
164
165/// Marks a request struct as carrying authentication claims.
166///
167/// Appends a `#[serde(skip)] pub claims: Option<Box<dyn IClaims>>` field to
168/// the struct and generates an inherent `set_claims` method that shadows the
169/// blanket no-op `IClaimsCarrier::set_claims`. The dispatcher injects claims
170/// into the request *before* calling `IRequestHandler::handle`.
171///
172/// Must be the outermost attribute so that `#[derive(...)]` sees the injected
173/// field:
174///
175/// ```ignore
176/// #[claims]
177/// #[derive(Default, Deserialize)]
178/// pub struct CreateCommentRequest {
179/// pub blog_id: i32,
180/// pub content: String,
181/// }
182/// ```
183#[proc_macro_attribute]
184pub fn claims(attr: TokenStream, item: TokenStream) -> TokenStream {
185 claims::claims_impl(attr, item)
186}
187
188// ---------------------------------------------------------------------------
189// Authorization attribute (declarative, handled by emit_endpoint)
190// ---------------------------------------------------------------------------
191
192/// Declares authorization requirements on a route.
193///
194/// ```ignore
195/// #[get("/api/users")]
196/// #[authorize(role = "admin")]
197/// impl IRequest<Vec<UserModel>> for ListUsersRequest {}
198///
199/// #[get("/api/auth/me")]
200/// #[authorize]
201/// impl IRequest<UserView> for AuthMeRequest {}
202/// ```
203#[proc_macro_attribute]
204pub fn authorize(_attr: TokenStream, item: TokenStream) -> TokenStream {
205 // Pass through — the attribute is read by emit_endpoint inside
206 // the route macro (get, post, etc.) via item_impl.attrs.
207 item
208}