Skip to main content

murgamu_macros/
lib.rs

1// Copyright (C) 2026  Emerson Alexandre Tieppo Jr. - Murgamü
2
3// This program is free software: you can redistribute it and/or modify
4// it under the terms of the GNU Lesser General Public License as published by
5// the Free Software Foundation, either version 3 of the License, or
6// (at your option) any later version.
7
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11// GNU Lesser General Public License for more details.
12
13// You should have received a copy of the GNU Lesser General Public License
14// along with this program.  If not, see <https://www.gnu.org/licenses/>.
15
16mod controller;
17mod core;
18mod derive;
19mod guard;
20mod injectable;
21mod interceptor;
22mod main_entry;
23mod module;
24mod pipe;
25mod response;
26mod service;
27mod types;
28mod use_pipe;
29
30use proc_macro::TokenStream;
31use syn::{ItemImpl, parse_macro_input};
32
33/// Marks an `impl` block as a Murgamu controller and registers its route handlers.
34///
35/// The optional argument sets the base path prefix for all routes in the block.
36/// If omitted, the base path defaults to `"/"`.
37///
38/// # Example
39///
40/// ```rust,ignore
41/// #[controller("/users")]
42/// impl UserController {
43///     fn new() -> Self { Self }
44///
45///     #[get]
46///     async fn list(&self) -> MurRes { /* … */ }
47///
48///     #[post]
49///     async fn create(&self, body: CreateUserDto) -> MurRes { /* … */ }
50///
51///     #[get(":id")]
52///     async fn find_one(&self, #[param] id: Param<u64>) -> MurRes { /* … */ }
53/// }
54/// ```
55///
56/// Every method annotated with an HTTP verb macro (`#[get]`, `#[post]`, etc.)
57/// becomes a route handler. The macro generates the `MurController` and
58/// `MurControllerFactory` trait implementations automatically.
59#[proc_macro_attribute]
60pub fn controller(args: TokenStream, input: TokenStream) -> TokenStream {
61	let input = parse_macro_input!(input as ItemImpl);
62	controller::controller_impl(args, input).into()
63}
64
65/// Declares a route handler for `GET` requests.
66///
67/// The optional argument is the route path relative to the controller's base
68/// path. Omitting it (or using `""` / `"/"`) makes the handler respond to
69/// the base path itself.
70///
71/// # Examples
72///
73/// ```rust,ignore
74/// #[get]
75/// async fn list(&self) -> MurRes { /* GET /base */ }
76///
77/// #[get(":id")]
78/// async fn find_one(&self, #[param] id: Param<u64>) -> MurRes { /* GET /base/:id */ }
79///
80/// #[get("/search")]
81/// async fn search(&self, #[query] q: SearchDto) -> MurRes { /* GET /base/search */ }
82/// ```
83#[proc_macro_attribute]
84pub fn get(args: TokenStream, input: TokenStream) -> TokenStream {
85	controller::get_impl(args, input)
86}
87
88/// Declares a route handler for `POST` requests.
89///
90/// The optional argument is the route path relative to the controller's base path.
91///
92/// # Example
93///
94/// ```rust,ignore
95/// #[post]
96/// async fn create(&self, body: CreateDto) -> MurRes { /* POST /base */ }
97///
98/// #[post("/bulk")]
99/// async fn bulk_create(&self, body: Vec<CreateDto>) -> MurRes { /* POST /base/bulk */ }
100/// ```
101#[proc_macro_attribute]
102pub fn post(args: TokenStream, input: TokenStream) -> TokenStream {
103	controller::post_impl(args, input)
104}
105
106/// Declares a route handler for `PUT` requests.
107///
108/// The optional argument is the route path relative to the controller's base path.
109///
110/// # Example
111///
112/// ```rust,ignore
113/// #[put(":id")]
114/// async fn replace(&self, #[param] id: Param<u64>, body: ReplaceDto) -> MurRes { /* … */ }
115/// ```
116#[proc_macro_attribute]
117pub fn put(args: TokenStream, input: TokenStream) -> TokenStream {
118	controller::put_impl(args, input)
119}
120
121/// Declares a route handler for `DELETE` requests.
122///
123/// The optional argument is the route path relative to the controller's base path.
124///
125/// # Example
126///
127/// ```rust,ignore
128/// #[delete(":id")]
129/// async fn remove(&self, #[param] id: Param<u64>) -> MurRes { /* DELETE /base/:id */ }
130/// ```
131#[proc_macro_attribute]
132pub fn delete(args: TokenStream, input: TokenStream) -> TokenStream {
133	controller::delete_impl(args, input)
134}
135
136/// Declares a route handler for `PATCH` requests.
137///
138/// The optional argument is the route path relative to the controller's base path.
139///
140/// # Example
141///
142/// ```rust,ignore
143/// #[patch(":id")]
144/// async fn update(&self, #[param] id: Param<u64>, body: UpdateDto) -> MurRes { /* … */ }
145/// ```
146#[proc_macro_attribute]
147pub fn patch(args: TokenStream, input: TokenStream) -> TokenStream {
148	controller::patch_impl(args, input)
149}
150
151/// Declares a route handler for `HEAD` requests.
152///
153/// Behaves identically to `#[get]` but the response body is omitted.
154/// The optional argument is the route path relative to the controller's base path.
155#[proc_macro_attribute]
156pub fn head(args: TokenStream, input: TokenStream) -> TokenStream {
157	controller::head_impl(args, input)
158}
159
160/// Declares a route handler for `OPTIONS` requests.
161///
162/// The optional argument is the route path relative to the controller's base path.
163#[proc_macro_attribute]
164pub fn options(args: TokenStream, input: TokenStream) -> TokenStream {
165	controller::options_impl(args, input)
166}
167
168/// Marks a struct as a Murgamu service and registers it with the DI container.
169///
170/// Generates the `MurService` and `MurServiceFactory` trait implementations.
171/// Fields are resolved automatically from the container at startup.
172/// For manual assignment use _ before property, e.g. AppService { _local_generated_id: u32 };
173///
174/// # Example
175///
176/// ```rust,ignore
177/// #[service]
178/// struct UserService {
179///     db: DatabaseService,
180/// }
181/// ```
182#[proc_macro_attribute]
183pub fn service(args: TokenStream, input: TokenStream) -> TokenStream {
184	service::service_impl(args, input)
185}
186
187/// Marks a field as an injected dependency.
188///
189/// Used inside structs annotated with `#[service]`, `#[guard]`,
190/// `#[interceptor]`, or `#[controller]` to declare a dependency that the
191/// framework resolves from the DI container.
192///
193/// ```rust,ignore
194/// #[service]
195/// struct ReportService {
196///     db: DatabaseService,
197///     cache: CacheService,
198/// }
199/// ```
200#[proc_macro_attribute]
201pub fn inject(_args: TokenStream, input: TokenStream) -> TokenStream {
202	input
203}
204
205/// Marks a struct as an injectable value that can be registered with `.inject()`.
206///
207/// `#[injectable]` is the lightweight counterpart to `#[service]`. Use it for
208/// configuration structs, external clients, or shared state that does not need
209/// the full service lifecycle (`on_init` / `on_shutdown`).
210///
211/// # Example
212///
213/// ```rust,ignore
214/// #[injectable]
215/// struct AppConfig {
216///     pub database_url: String,
217///     pub jwt_secret: String,
218/// }
219/// ```
220#[proc_macro_attribute]
221pub fn injectable(args: TokenStream, input: TokenStream) -> TokenStream {
222	injectable::injectable_impl(args, input)
223}
224
225/// Declares a Murgamu module that groups controllers, services, and imports.
226///
227/// Accepts a comma-separated list of sections:
228///
229/// - `controllers: [T, …]` — controllers registered in this module.
230/// - `providers: [T, …]` — services available within this module.
231/// - `imports: [M::new(), …]` — other modules whose exports are visible here.
232/// - `exports: [T, …]` — services made available to importing modules.
233///
234/// # Example
235///
236/// ```rust,ignore
237/// #[module(
238///     controllers: [UserController, AuthController],
239///     providers:   [UserService, AuthService],
240///     imports:     [DatabaseModule::new()],
241///     exports:     [UserService],
242/// )]
243/// struct AppModule;
244/// ```
245#[proc_macro_attribute]
246pub fn module(args: TokenStream, input: TokenStream) -> TokenStream {
247	module::module_impl(args, input)
248}
249
250/// Marks a struct as a Murgamu route guard.
251///
252/// Generates the `MurGuard` and `MurGuardFactory` trait implementations.
253/// Guards protect routes from unauthorized access; they are evaluated before
254/// the route handler runs.
255/// For manual assignment use _ before property, e.g. AppGuard { _local_generated_id: u32 };
256///
257/// # Example
258///
259/// ```rust,ignore
260/// #[guard]
261/// struct JwtGuard {
262///     auth: AuthService,
263/// }
264///
265/// impl MurGuard for JwtGuard {
266///     fn check_can_activate<'a>(&'a self, ctx: &'a MurRequestContext) -> MurGuardFuture<'a> {
267///         Box::pin(async move {
268///             self.auth.validate_token(ctx.header("Authorization")).await
269///         })
270///     }
271/// }
272/// ```
273#[proc_macro_attribute]
274pub fn guard(args: TokenStream, input: TokenStream) -> TokenStream {
275	guard::guard_impl(args, input)
276}
277
278/// Marks a struct as a Murgamu transformation pipe.
279///
280/// Pipes transform handler parameters before they reach the handler body.
281/// Generates the `MurPipe` and `MurPipeFactory` trait implementations.
282///
283/// # Example
284///
285/// ```rust,ignore
286/// #[pipe]
287/// struct TrimPipe;
288///
289/// impl MurPipe<MurText> for TrimPipe {
290///     type Output = String;
291///
292///     fn transform(&self, input: MurText, _ctx: MurRequestContext) -> Result<String, MurError> {
293///         Ok(input.into_inner().trim().to_string())
294///     }
295/// }
296/// ```
297#[proc_macro_attribute]
298pub fn pipe(args: TokenStream, input: TokenStream) -> TokenStream {
299	pipe::pipe_impl(args, input)
300}
301
302/// Marks a struct as a Murgamu interceptor.
303///
304/// Interceptors wrap individual route handler invocations, running logic
305/// before and/or after the handler. Generates the `MurInterceptor` and
306/// `MurInterceptorFactory` trait implementations.
307///
308/// # Example
309///
310/// ```rust,ignore
311/// #[interceptor]
312/// struct TimingInterceptor;
313///
314/// impl MurInterceptor for TimingInterceptor {
315///     fn before(&self, _ctx: &MurRequestContext) -> MurInterceptorFuture {
316///         Box::pin(async { Ok(()) })
317///     }
318/// }
319/// ```
320#[proc_macro_attribute]
321pub fn interceptor(args: TokenStream, input: TokenStream) -> TokenStream {
322	interceptor::interceptor_impl(args, input)
323}
324
325/// Sets up the Tokio async runtime for the application entry point.
326///
327/// Place `#[murgamu::main]` on an `async fn main()` to replace the standard
328/// `#[tokio::main]` annotation with Murgamu's runtime wrapper.
329///
330/// # Example
331///
332/// ```rust,ignore
333/// #[murgamu::main]
334/// async fn main() -> MurMainResult {
335///     MurServer::new()
336///         .module(AppModule::new())
337///         .bind(("0.0.0.0", 3000))?
338///         .run()
339///         .await
340/// }
341/// ```
342#[proc_macro_attribute]
343pub fn main(args: TokenStream, input: TokenStream) -> TokenStream {
344	main_entry::main_impl(args, input)
345}
346
347/// Declares a route handler for a custom or non-standard HTTP method.
348///
349/// Use `#[route]` when none of the standard verb macros (`#[get]`, `#[post]`,
350/// etc.) fits the desired method.
351///
352/// # Example
353///
354/// ```rust,ignore
355/// #[route("PURGE", "/cache")]
356/// async fn purge_cache(&self) -> MurRes {
357///     mur_json!(serde_json::json!({ "purged": true }))
358/// }
359/// ```
360#[proc_macro_attribute]
361pub fn route(args: TokenStream, input: TokenStream) -> TokenStream {
362	main_entry::route_impl(args, input)
363}
364
365/// Marks a handler parameter as a single URL path segment.
366///
367/// When applied to a plain type (e.g. `String`, `u64`), the parameter is
368/// extracted from the matching named segment in the route pattern.
369///
370/// Prefer [`MurPath<T>`](crate::MurPath) for structs that capture multiple
371/// segments at once.
372///
373/// # Example
374///
375/// ```rust,ignore
376/// #[get("/users/:id")]
377/// async fn get_user(&self, #[param] id: u64) -> MurRes { /* … */ }
378/// ```
379#[proc_macro_attribute]
380pub fn param(args: TokenStream, input: TokenStream) -> TokenStream {
381	main_entry::param_impl(args, input)
382}
383
384/// Marks a handler parameter as a deserialized query string.
385///
386/// The query string is parsed with `serde_urlencoded` and the result is
387/// deserialized directly into the annotated type (no `MurQuery<T>` wrapper).
388///
389/// # Example
390///
391/// ```rust,ignore
392/// #[derive(Deserialize)]
393/// struct Filters { page: Option<u32>, q: Option<String> }
394///
395/// #[get("/items")]
396/// async fn list(&self, #[query] filters: Filters) -> MurRes { /* … */ }
397/// ```
398#[proc_macro_attribute]
399pub fn query(args: TokenStream, input: TokenStream) -> TokenStream {
400	main_entry::query_impl(args, input)
401}
402
403/// Marks a handler parameter as a single named query string value.
404///
405/// The named key is extracted from the query string and parsed into the
406/// annotated type. Returns an error if the key is absent or unparseable.
407///
408/// # Example
409///
410/// ```rust,ignore
411/// #[get("/search")]
412/// async fn search(&self, #[queryparam] q: String) -> MurRes { /* … */ }
413/// ```
414#[proc_macro_attribute]
415pub fn queryparam(args: TokenStream, input: TokenStream) -> TokenStream {
416	main_entry::query_impl(args, input)
417}
418
419/// Marks a handler parameter as a request header value.
420///
421/// The header name is derived from the parameter name. The value is provided
422/// as a raw `String`; parsing is the caller's responsibility.
423///
424/// # Example
425///
426/// ```rust,ignore
427/// #[get("/protected")]
428/// async fn protected(&self, #[header] authorization: String) -> MurRes { /* … */ }
429/// ```
430#[proc_macro_attribute]
431pub fn header(args: TokenStream, input: TokenStream) -> TokenStream {
432	main_entry::header_impl(args, input)
433}
434
435/// Marks a handler parameter as the deserialized JSON request body.
436///
437/// The body bytes are deserialized directly into the annotated type using
438/// `serde_json` (no `MurJson<T>` wrapper). The type must implement
439/// `serde::Deserialize`.
440///
441/// # Example
442///
443/// ```rust,ignore
444/// #[post("/users")]
445/// async fn create(&self, #[body] dto: CreateUserDto) -> MurRes { /* … */ }
446/// ```
447#[proc_macro_attribute]
448pub fn body(args: TokenStream, input: TokenStream) -> TokenStream {
449	main_entry::body_impl(args, input)
450}
451
452/// Triggers automatic validation of a handler parameter before execution.
453///
454/// The annotated type must implement a `validate(&self) -> Result<(), String>`
455/// method (generated automatically by `#[derive(MurDto)]`). If validation
456/// fails, a `400 Bad Request` response is returned before the handler runs.
457///
458/// # Example
459///
460/// ```rust,ignore
461/// #[derive(MurDto, Deserialize)]
462/// struct CreateUserDto {
463///     pub name: String,
464///     pub email: String,
465/// }
466///
467/// #[post("/users")]
468/// async fn create(&self, #[validate] dto: CreateUserDto) -> MurRes { /* … */ }
469/// ```
470#[proc_macro_attribute]
471pub fn validate(args: TokenStream, input: TokenStream) -> TokenStream {
472	main_entry::validate_impl(args, input)
473}
474
475/// Attaches OpenAPI metadata to a route handler.
476///
477/// The metadata is used when generating API documentation. All fields are
478/// optional; omit any that are not relevant to the endpoint.
479///
480/// # Example
481///
482/// ```rust,ignore
483/// #[api(
484///     summary     = "List all users",
485///     description = "Returns a paginated list of registered users.",
486///     tags        = ["users"],
487///     responses   = [(200, "Paginated user list"), (401, "Unauthorized")],
488/// )]
489/// #[get("/users")]
490/// async fn list(&self) -> MurRes { /* … */ }
491/// ```
492#[proc_macro_attribute]
493pub fn api(args: TokenStream, input: TokenStream) -> TokenStream {
494	main_entry::api_impl(args, input)
495}
496
497/// Constructs a `200 OK` JSON response from an expression.
498///
499/// The expression is serialized with `serde_json` and wrapped in an `Ok(…)`
500/// result. Equivalent to `MurHttpResponse::ok().json(expr)`.
501///
502/// # Example
503///
504/// ```rust,ignore
505/// #[get("/health")]
506/// async fn health(&self) -> MurRes {
507///     json_response!({ "status": "ok" })
508/// }
509/// ```
510#[proc_macro]
511pub fn json_response(input: TokenStream) -> TokenStream {
512	response::json_impl(input)
513}
514
515/// Constructs a `200 OK` plain-text response from an expression.
516///
517/// The expression must evaluate to a type that implements `Into<String>`.
518/// Equivalent to `MurHttpResponse::ok().text(expr)`.
519///
520/// # Example
521///
522/// ```rust,ignore
523/// text_response!("Hello, world!")
524/// ```
525#[proc_macro]
526pub fn text_response(input: TokenStream) -> TokenStream {
527	response::text_impl(input)
528}
529
530/// Constructs a `200 OK` HTML response from an expression.
531///
532/// The expression must evaluate to a type that implements `Into<String>`.
533/// Sets `Content-Type: text/html; charset=utf-8` automatically.
534///
535/// # Example
536///
537/// ```rust,ignore
538/// html_response!("<h1>Hello</h1>")
539/// ```
540#[proc_macro]
541pub fn html_response(input: TokenStream) -> TokenStream {
542	response::html_impl(input)
543}
544
545/// Constructs a `200 OK` response with an empty body.
546///
547/// Equivalent to `MurHttpResponse::ok().empty()`.
548#[proc_macro]
549pub fn ok_response(input: TokenStream) -> TokenStream {
550	response::ok_impl(input)
551}
552
553/// Constructs a `204 No Content` response with an empty body.
554///
555/// Equivalent to `MurHttpResponse::no_content()`.
556#[proc_macro]
557pub fn no_content_response(input: TokenStream) -> TokenStream {
558	response::no_content_impl(input)
559}
560
561/// Derives a default `validate` method on a DTO struct.
562///
563/// The generated implementation returns `Ok(())` unconditionally. Override it
564/// manually or use custom validation logic when field-level constraints are
565/// needed.
566///
567/// The derive also enables the struct to be used with the `#[validate]`
568/// parameter attribute on route handlers.
569///
570/// # Example
571///
572/// ```rust,ignore
573/// #[derive(MurDto, Deserialize)]
574/// pub struct CreateUserDto {
575///     pub name: String,
576///     pub email: String,
577/// }
578/// ```
579#[proc_macro_derive(MurDto)]
580pub fn derive_dto(input: TokenStream) -> TokenStream {
581	derive::derive_dto_impl(input)
582}
583
584/// Derives a thin entity helper on a struct.
585///
586/// When the struct has a field named `id: String`, the derive generates an
587/// `id(&self) -> &str` accessor. For structs without an `id` field the derive
588/// is a no-op.
589///
590/// # Example
591///
592/// ```rust,ignore
593/// #[derive(MurEntity)]
594/// pub struct User {
595///     pub id: String,
596///     pub name: String,
597/// }
598///
599/// let user = User { id: "abc".into(), name: "Alice".into() };
600/// assert_eq!(user.id(), "abc");
601/// ```
602#[proc_macro_derive(MurEntity)]
603pub fn derive_entity(input: TokenStream) -> TokenStream {
604	derive::derive_entity_impl(input)
605}
606
607/// Marks a route as publicly accessible, bypassing global authentication guards.
608///
609/// Without this attribute every route requires the caller to pass all
610/// registered global guards. Applying `#[public]` exempts the route from
611/// those checks while still allowing per-route guards.
612///
613/// # Example
614///
615/// ```rust,ignore
616/// #[public]
617/// #[post("/auth/login")]
618/// async fn login(&self, #[body] dto: LoginDto) -> MurRes { /* … */ }
619/// ```
620#[proc_macro_attribute]
621pub fn public(_args: TokenStream, input: TokenStream) -> TokenStream {
622	input
623}
624
625/// Restricts a route to callers that hold one or more specific roles.
626///
627/// Accepts a comma-separated list of role identifiers. The route's access
628/// control list is checked after all guards pass.
629///
630/// # Example
631///
632/// ```rust,ignore
633/// #[role(UserEnum::Admin, UserEnum::Moderator)]
634/// #[delete("/users/:id")]
635/// async fn delete_user(&self, #[param] id: Param<u64>) -> MurRes { /* … */ }
636/// ```
637#[proc_macro_attribute]
638pub fn role(_args: TokenStream, input: TokenStream) -> TokenStream {
639	input
640}
641
642/// Applies a transformation pipe to a handler parameter.
643///
644/// The argument must be the concrete pipe type. The pipe's `apply_transform`
645/// is called with the [`MurRequestContext`](murgamu::MurRequestContext) before
646/// the handler runs, and the transformed value is injected as the parameter.
647///
648/// # Example
649///
650/// ```rust,ignore
651/// #[post("/upload")]
652/// async fn upload(&self, #[use_pipe(FileSizePipe)] file: FileData) -> MurRes { /* … */ }
653/// ```
654#[proc_macro_attribute]
655pub fn use_pipe(args: TokenStream, input: TokenStream) -> TokenStream {
656	use_pipe::use_pipe_impl(args, input)
657}