Skip to main content

nidus_macros/
lib.rs

1#![deny(missing_docs)]
2
3//! Procedural macros for Nidus modules, providers, controllers, and routes.
4//!
5//! The examples in this crate are marked `ignore` because application-facing
6//! macro use goes through the `nidus` facade crate, which depends on this
7//! proc-macro crate. Adding a reverse doctest dependency here would create a
8//! crate cycle. Macro expansion behavior is covered by the facade UI and CLI
9//! tests instead.
10
11mod controller;
12mod diagnostics;
13mod entrypoint;
14mod guard;
15mod injectable;
16mod module;
17mod pipe;
18mod routes;
19mod routes_metadata;
20mod routes_openapi;
21mod utils;
22
23use proc_macro::TokenStream;
24
25/// Runs an async Nidus entrypoint on a Tokio runtime.
26///
27/// Use `#[nidus::main]` on the crate's top-level `async fn main()` when an
28/// application should use the runtime configuration provided by the Nidus
29/// facade. The macro is executable behavior: it rewrites the async function
30/// into a synchronous `main` that builds a Tokio runtime and blocks on the
31/// original body.
32///
33/// Basic syntax:
34///
35/// ```ignore
36/// #[nidus::main]
37/// async fn main() -> nidus::Result<()> {
38///     nidus::prelude::Nidus::bootstrap::<AppModule>()?
39///         .with_router(UsersController.into_router())
40///         .listen("127.0.0.1:3000")
41///         .await
42/// }
43/// ```
44///
45/// The annotated function must be async and must not already be wrapped in
46/// another runtime macro such as `#[tokio::main]`. Although the macro is
47/// intended for binary entrypoints, it also accepts async functions with other
48/// names and arguments. Each invocation constructs a runtime, so such functions
49/// must not be called from inside an active Tokio runtime. Returning `Result` is
50/// idiomatic because bootstrap, router construction, and server startup can all
51/// fail. A common error is using the macro on a non-async function.
52#[proc_macro_attribute]
53pub fn main(attr: TokenStream, item: TokenStream) -> TokenStream {
54    entrypoint::expand(attr.into(), item.into()).into()
55}
56
57/// Declares a Nidus module from a Rust struct.
58///
59/// `#[module]` is valid on named structs that represent application modules.
60/// It is metadata-generating behavior: the macro keeps the struct in place and
61/// implements Nidus module metadata so the runtime and CLI can inspect imports,
62/// providers, controllers, and exports without runtime reflection.
63///
64/// Basic syntax:
65///
66/// ```ignore
67/// #[module(
68///     imports = [UsersModule],
69///     providers = [EmailService],
70///     controllers = [HealthController],
71///     exports = [EmailService],
72/// )]
73/// struct AppModule;
74/// ```
75///
76/// The generated module definition composes with `Nidus::bootstrap::<AppModule>()`,
77/// graph validation, CLI graph/check commands, and generated starter projects.
78/// Module entries are Rust types, not string names, so missing imports or renamed
79/// providers are caught by the compiler. Common errors include omitting brackets
80/// around metadata lists, placing the macro on enums or tuple structs, and
81/// exporting a provider that is not registered by the module or its imports.
82#[proc_macro_attribute]
83pub fn module(attr: TokenStream, item: TokenStream) -> TokenStream {
84    module::expand(attr.into(), item.into()).into()
85}
86
87/// Declares a struct as an injectable provider.
88///
89/// `#[injectable]` is valid on named structs. It is metadata-generating
90/// behavior: the macro preserves the struct and implements provider registration
91/// metadata so modules can list the type in `providers = [...]` and consumers can
92/// request it through typed injection primitives.
93///
94/// Basic syntax:
95///
96/// ```ignore
97/// #[injectable]
98/// struct UsersService {
99///     repository: nidus::prelude::Inject<UsersRepository>,
100/// }
101/// ```
102///
103/// Injectable providers compose with `Inject<T>`, `Optional<T>`, `Lazy<T>`,
104/// `Factory<T>`, and request-scoped providers exposed by the Nidus core. The
105/// macro is intentionally explicit: constructor behavior is not hidden behind
106/// runtime string lookup. Common errors are applying it to tuple structs,
107/// unsupported generic/lifetime shapes, or fields that are not valid provider
108/// dependencies.
109#[proc_macro_attribute]
110pub fn injectable(attr: TokenStream, item: TokenStream) -> TokenStream {
111    injectable::expand(attr.into(), item.into()).into()
112}
113
114/// Declares the path prefix for a controller type.
115///
116/// `#[controller]` is valid on structs that own HTTP handler methods. It is
117/// metadata-generating behavior by itself: it records the controller prefix and
118/// exposes controller metadata used by `#[routes]`, CLI route inspection, and
119/// OpenAPI tooling.
120///
121/// Basic syntax:
122///
123/// ```ignore
124/// #[controller("/users")]
125/// struct UsersController {
126///     service: UsersService,
127/// }
128/// ```
129///
130/// `#[controller("/users")]` combines with route methods inside a `#[routes]`
131/// impl block. The final route path is the controller prefix plus each method's
132/// route path, for example `"/users"` and `"/:id"` become `"/users/:id"`.
133/// Common errors are missing the string literal path, using an empty dynamic
134/// segment such as `"/:"`, or expecting this macro alone to register Tower
135/// routes. Executable routing is generated by `#[routes]`.
136#[proc_macro_attribute]
137pub fn controller(attr: TokenStream, item: TokenStream) -> TokenStream {
138    controller::expand(attr.into(), item.into()).into()
139}
140
141/// Generates route metadata and executable Axum routers for a controller impl.
142///
143/// `#[routes]` is valid on inherent `impl` blocks for controller types. It is
144/// both metadata-generating and executable behavior: the macro validates route
145/// attributes, emits `routes()` metadata for CLI/OpenAPI tooling, and generates
146/// `try_into_router()` plus `into_router()` when route methods are present.
147///
148/// Basic syntax:
149///
150/// ```ignore
151/// #[controller("/users")]
152/// struct UsersController;
153///
154/// #[routes]
155/// impl UsersController {
156///     #[get("/:id")]
157///     async fn show(
158///         &self,
159///         nidus::prelude::Path(id): nidus::prelude::Path<String>,
160///         scoped: nidus::prelude::RequestScoped<nidus::prelude::RequestContext>,
161///     ) -> nidus::prelude::Json<UserDto> {
162///         nidus::prelude::Json(UserDto::from_id(id, scoped.request_id().to_owned()))
163///     }
164/// }
165/// ```
166///
167/// `try_into_router()` returns `Result<Router, RoutePathError>` and should be
168/// preferred when path validation errors must be handled. `into_router()` unwraps
169/// that result and panics with the route error, which is convenient in examples
170/// and tests where invalid static paths are programming mistakes.
171///
172/// Handler parameters are normal Axum extractors such as `Path<T>`, `Query<T>`,
173/// `Json<T>`, and Nidus extractors such as `RequestScoped<T>`. Module-composed
174/// controllers resolve and execute `#[guard]` providers before the handler. A
175/// controller converted directly with `into_router()` has no provider container,
176/// so it requires an explicit guard layer instead. Pipes, validation, and OpenAPI
177/// attributes remain metadata or extractor-driven behavior. Common errors include
178/// using route macros outside a `#[routes]` impl, omitting `&self`, duplicating
179/// method metadata, or assuming pipe metadata transforms a request by itself.
180#[proc_macro_attribute]
181pub fn routes(attr: TokenStream, item: TokenStream) -> TokenStream {
182    routes::expand_routes(attr.into(), item.into()).into()
183}
184
185/// Declares a GET handler inside a `#[routes]` impl block.
186///
187/// `#[get]` is valid only on async controller methods that receive `&self`. It
188/// is metadata for `#[routes]`; the executable Axum route is generated when the
189/// enclosing impl block is expanded.
190///
191/// Basic syntax:
192///
193/// ```ignore
194/// #[routes]
195/// impl UsersController {
196///     #[get("/:id")]
197///     async fn show(&self, Path(id): Path<String>) -> Json<UserDto> {
198///         Json(self.service.find(id).await)
199///     }
200/// }
201/// ```
202///
203/// The path is relative to the controller prefix. Method parameters can use Axum
204/// extractors such as `Path`, `Query`, and `Json`, plus Nidus extractors such as
205/// `RequestScoped<T>`. Route metadata feeds `routes()`, CLI route output, and
206/// OpenAPI generation when combined with `#[openapi]`. Common errors are
207/// omitting the path string, using invalid path parameters, or placing the
208/// method outside `#[routes]`.
209#[proc_macro_attribute]
210pub fn get(attr: TokenStream, item: TokenStream) -> TokenStream {
211    routes::expand_route("get", attr.into(), item.into()).into()
212}
213
214/// Declares a POST handler inside a `#[routes]` impl block.
215///
216/// `#[post]` has the same placement and generation rules as `#[get]`, but emits
217/// POST route metadata and executable Axum routing from the enclosing
218/// `#[routes]` impl block.
219///
220/// Basic syntax:
221///
222/// ```ignore
223/// #[routes]
224/// impl UsersController {
225///     #[post("/")]
226///     async fn create(&self, Json(input): Json<CreateUser>) -> (StatusCode, Json<UserDto>) {
227///         (StatusCode::CREATED, Json(self.service.create(input).await))
228///     }
229/// }
230/// ```
231///
232/// Use `Json<T>`, `Query<T>`, `Path<T>`, request extensions, or
233/// `RequestScoped<T>` exactly as you would in Axum handlers. Common errors are
234/// treating guard or pipe metadata as request execution by itself, duplicating
235/// route method attributes, or returning a type that does not implement
236/// `IntoResponse`.
237#[proc_macro_attribute]
238pub fn post(attr: TokenStream, item: TokenStream) -> TokenStream {
239    routes::expand_route("post", attr.into(), item.into()).into()
240}
241
242/// Declares a PUT handler inside a `#[routes]` impl block.
243///
244/// `#[put]` records PUT route metadata and participates in `#[routes]`
245/// generation of `routes()`, `try_into_router()`, and `into_router()`.
246///
247/// Basic syntax:
248///
249/// ```ignore
250/// #[routes]
251/// impl UsersController {
252///     #[put("/:id")]
253///     async fn replace(&self, Path(id): Path<String>, Json(input): Json<UserInput>) -> Json<UserDto> {
254///         Json(self.service.replace(id, input).await)
255///     }
256/// }
257/// ```
258///
259/// The macro is valid only on methods with a receiver. It does not run request
260/// validation by itself; attach extractor types, Tower layers, validation pipes,
261/// or explicit application logic for executable behavior.
262#[proc_macro_attribute]
263pub fn put(attr: TokenStream, item: TokenStream) -> TokenStream {
264    routes::expand_route("put", attr.into(), item.into()).into()
265}
266
267/// Declares a PATCH handler inside a `#[routes]` impl block.
268///
269/// `#[patch]` records PATCH route metadata and is consumed by `#[routes]` to
270/// build the controller router.
271///
272/// Basic syntax:
273///
274/// ```ignore
275/// #[routes]
276/// impl UsersController {
277///     #[patch("/:id")]
278///     async fn update(&self, Path(id): Path<String>, Json(input): Json<UpdateUser>) -> Json<UserDto> {
279///         Json(self.service.update(id, input).await)
280///     }
281/// }
282/// ```
283///
284/// Use this macro for partial update endpoints where the handler return type is
285/// any Axum-compatible `IntoResponse`. Common errors are invalid relative paths
286/// and forgetting that the controller prefix is prepended by generated router
287/// construction.
288#[proc_macro_attribute]
289pub fn patch(attr: TokenStream, item: TokenStream) -> TokenStream {
290    routes::expand_route("patch", attr.into(), item.into()).into()
291}
292
293/// Declares a DELETE handler inside a `#[routes]` impl block.
294///
295/// `#[delete]` records DELETE route metadata and is consumed by `#[routes]` to
296/// build executable router methods.
297///
298/// Basic syntax:
299///
300/// ```ignore
301/// #[routes]
302/// impl UsersController {
303///     #[delete("/:id")]
304///     async fn delete(&self, Path(id): Path<String>) -> StatusCode {
305///         self.service.delete(id).await;
306///         StatusCode::NO_CONTENT
307///     }
308/// }
309/// ```
310///
311/// The generated route composes with normal Axum extractors and responses.
312/// Guard, pipe, validate, and OpenAPI annotations on the same method are
313/// available through route metadata for tooling and explicit integration code.
314#[proc_macro_attribute]
315pub fn delete(attr: TokenStream, item: TokenStream) -> TokenStream {
316    routes::expand_route("delete", attr.into(), item.into()).into()
317}
318
319/// Declares OpenAPI metadata for a controller route method.
320///
321/// `#[openapi]` is valid only on methods inside a `#[routes]` impl block. It is
322/// metadata-only behavior: it does not change request execution, but it enriches
323/// the `RouteMetadata` emitted by `#[routes]` so CLI and OpenAPI builders can
324/// generate useful documentation.
325///
326/// Basic syntax:
327///
328/// ```ignore
329/// #[routes]
330/// impl UsersController {
331///     #[get("/:id")]
332///     #[openapi(
333///         summary = "Fetch a user by id",
334///         tags = ["users"],
335///         status = 200,
336///         response = UserDto,
337///     )]
338///     async fn show(&self, Path(id): Path<String>) -> Json<UserDto> {
339///         Json(self.service.find(id).await)
340///     }
341/// }
342/// ```
343///
344/// Supported metadata includes summaries, tags, status codes, request schemas,
345/// and response schemas. DTO schema names are recorded for tooling; they do not
346/// serialize or validate requests at runtime. Common errors include missing
347/// `summary`, non-string tags, invalid status values, duplicate metadata, or
348/// placing the attribute outside a route method.
349#[proc_macro_attribute]
350pub fn openapi(attr: TokenStream, item: TokenStream) -> TokenStream {
351    routes_openapi::expand_openapi(attr.into(), item.into()).into()
352}
353
354/// Declares guard metadata for a controller route method.
355///
356/// `#[guard]` is valid only on methods inside a `#[routes]` impl block. The guard
357/// type is recorded on the generated `RouteMetadata`. When Nidus builds the
358/// controller through a module container, it also resolves that provider and
359/// executes the guard before the handler. Direct `into_router()` conversion has
360/// no provider container and therefore requires an explicit `guard_layer`.
361///
362/// Basic syntax:
363///
364/// ```ignore
365/// #[routes]
366/// impl AdminController {
367///     #[get("/reports")]
368///     #[guard(AdminGuard)]
369///     async fn reports(&self) -> Json<Vec<ReportDto>> {
370///         Json(self.service.reports().await)
371///     }
372/// }
373/// ```
374///
375/// Guard metadata composes with `nidus-auth` guard implementations and CLI or
376/// OpenAPI route inspection. Common errors are omitting the guard type, failing
377/// to register it as a provider for module composition, or expecting a directly
378/// converted controller to resolve providers without a container.
379#[proc_macro_attribute]
380pub fn guard(attr: TokenStream, item: TokenStream) -> TokenStream {
381    guard::expand(attr.into(), item.into()).into()
382}
383
384/// Declares pipe metadata for a controller route method.
385///
386/// `#[pipe]` is valid only on methods inside a `#[routes]` impl block. It is
387/// metadata-only behavior in the macro layer: the pipe type is recorded on the
388/// generated route metadata for tooling and explicit integration, but request
389/// transformation still happens through extractors, validation wrappers, Tower
390/// layers, or handler code.
391///
392/// Basic syntax:
393///
394/// ```ignore
395/// #[routes]
396/// impl UsersController {
397///     #[post("/")]
398///     #[pipe(TrimInputPipe)]
399///     async fn create(&self, Json(input): Json<CreateUser>) -> Json<UserDto> {
400///         Json(self.service.create(input).await)
401///     }
402/// }
403/// ```
404///
405/// Pipes compose with validation metadata and Nidus validation types, but the
406/// macro intentionally avoids hidden runtime mutation. Common errors include
407/// omitting the pipe type or placing `#[pipe]` outside a controller route method.
408#[proc_macro_attribute]
409pub fn pipe(attr: TokenStream, item: TokenStream) -> TokenStream {
410    pipe::expand(attr.into(), item.into()).into()
411}
412
413/// Marks a controller route method as participating in validation metadata.
414///
415/// `#[validate]` is valid only on methods inside a `#[routes]` impl block. It is
416/// metadata-only behavior: generated `RouteMetadata` records that the route is
417/// intended to use validation, while runtime validation should be expressed with
418/// typed extractors such as `ValidatedJson<T>`, validation pipes, Tower layers,
419/// or explicit handler logic.
420///
421/// Basic syntax:
422///
423/// ```ignore
424/// #[routes]
425/// impl UsersController {
426///     #[post("/")]
427///     #[validate]
428///     async fn create(&self, input: ValidatedJson<CreateUser>) -> Json<UserDto> {
429///         Json(self.service.create(input.into_inner()).await)
430///     }
431/// }
432/// ```
433///
434/// The attribute composes with `#[openapi]`, `#[guard]`, and `#[pipe]` by adding
435/// validation intent to the same route metadata record. Common errors are using
436/// it outside a route method or expecting validation to run without a validation
437/// extractor, pipe, or layer in the executable request path.
438#[proc_macro_attribute]
439pub fn validate(attr: TokenStream, item: TokenStream) -> TokenStream {
440    pipe::expand_validate(attr.into(), item.into()).into()
441}