ntex_macros/lib.rs
1//! Procedural macros for ntex.
2//!
3//! You don't need to depend on this crate directly, `ntex` re-exports every
4//! macro:
5//!
6//! - `#[ntex::main]` runs an async function on the ntex runtime, see
7//! [`rt_main`].
8//! - `#[ntex::test]` does the same for a test, see [`rt_test`].
9//! - `#[ntex::web::get]`, `#[ntex::web::post]` and friends turn an async
10//! function into a web handler with a path and a method guard, see
11//! [`web_get`].
12//!
13//! ## Route macros
14//!
15//! | `ntex::web` | Method | This crate |
16//! |-------------|-----------|-------------------|
17//! | `get` | `GET` | [`web_get`] |
18//! | `post` | `POST` | [`web_post`] |
19//! | `put` | `PUT` | [`web_put`] |
20//! | `delete` | `DELETE` | [`web_delete`] |
21//! | `head` | `HEAD` | [`web_head`] |
22//! | `connect` | `CONNECT` | [`web_connect`] |
23//! | `options` | `OPTIONS` | [`web_options`] |
24//! | `trace` | `TRACE` | [`web_trace`] |
25//! | `patch` | `PATCH` | [`web_patch`] |
26//! | `query` | `QUERY` | [`web_query`] |
27//!
28//! All of them take the same arguments, they are described on [`web_get`].
29//!
30//! ```rust
31//! use ntex::web::{App, HttpResponse, get, types::Path};
32//!
33//! #[get("/users/{id}")]
34//! async fn user(id: Path<u32>) -> HttpResponse {
35//! HttpResponse::Ok().body(format!("user {}", id.into_inner()))
36//! }
37//!
38//! // `user` is now a service, register it on an application
39//! let app = App::<()>::new().service(user);
40//! ```
41
42use proc_macro::TokenStream;
43use quote::quote;
44
45mod route;
46mod sys;
47
48/// Creates a route handler with a `GET` method guard.
49///
50/// Re-exported as `ntex::web::get`.
51///
52/// Syntax: `#[get("path"[, guard = "fn_name"]*[, state = Type])]`
53///
54/// The macro takes a handler function and replaces it with a unit struct of
55/// the same name. The struct is a web service, you register it with
56/// `.service()` on an `App` or a `scope`. The function itself ends up inside
57/// the generated code, so you can't call it directly anymore. The struct is
58/// always `pub`, whatever the visibility of the function.
59///
60/// The function is any handler `Route::to()` accepts: an `async fn` with up
61/// to 16 extractors, or a plain `fn` that returns a future. The function name
62/// is also used as the resource name, so it works with `url_for()`.
63///
64/// ## Arguments
65///
66/// - `"path"` - path of the resource, the same syntax as for
67/// `Resource::new()`, for example `"/users/{id}"`. Required, must come
68/// first.
69/// - `guard = "fn_name"` - adds a guard built with
70/// `ntex::web::guard::fn_guard()`. The value is the name of a function
71/// `fn(&RequestHead) -> bool` that is in scope where the macro is used. It
72/// must be a plain name, paths like `"guards::is_json"` are not supported,
73/// import the function instead. Can be given more than once, all guards must
74/// pass.
75/// - `state = Type` - type of the application state, written as a type path
76/// without quotes. The handler can then only be registered on an
77/// `App<Type>`, and its errors use the error type of `Type`. It does not
78/// give the handler access to the state. Defaults to `()`, the state of
79/// `App::new()`.
80///
81/// ## Examples
82///
83/// ```rust
84/// use ntex::http::RequestHead;
85/// use ntex::web::{App, HttpResponse, get, post};
86///
87/// #[get("/")]
88/// async fn index() -> HttpResponse {
89/// HttpResponse::Ok().body("hello")
90/// }
91///
92/// fn is_json(req: &RequestHead) -> bool {
93/// req.headers()
94/// .get("content-type")
95/// .is_some_and(|v| v == "application/json")
96/// }
97///
98/// // only matches POST requests with a json content type
99/// #[post("/items", guard = "is_json")]
100/// async fn create_item(body: String) -> HttpResponse {
101/// HttpResponse::Created().body(body)
102/// }
103///
104/// let app = App::<()>::new().service((index, create_item));
105/// ```
106///
107/// With a custom application state:
108///
109/// ```rust
110/// use ntex::web::{self, App, HttpResponse, get};
111///
112/// #[derive(Clone)]
113/// struct MyState;
114///
115/// impl web::State for MyState {
116/// type Error = web::DefaultError;
117/// }
118///
119/// #[get("/", state = MyState)]
120/// async fn index() -> HttpResponse {
121/// HttpResponse::Ok().build()
122/// }
123///
124/// let app = App::<MyState>::new().service(index);
125/// ```
126#[proc_macro_attribute]
127pub fn web_get(args: TokenStream, input: TokenStream) -> TokenStream {
128 let gen_code = match route::Route::new(args, input, route::MethodType::Get) {
129 Ok(gen_code) => gen_code,
130 Err(err) => return err.to_compile_error().into(),
131 };
132 gen_code.generate()
133}
134
135/// Creates a route handler with a `POST` method guard.
136///
137/// Re-exported as `ntex::web::post`.
138///
139/// Syntax: `#[post("path"[, guard = "fn_name"]*[, state = Type])]`
140///
141/// Works the same way and takes the same arguments as [`web_get`].
142#[proc_macro_attribute]
143pub fn web_post(args: TokenStream, input: TokenStream) -> TokenStream {
144 let gen_code = match route::Route::new(args, input, route::MethodType::Post) {
145 Ok(gen_code) => gen_code,
146 Err(err) => return err.to_compile_error().into(),
147 };
148 gen_code.generate()
149}
150
151/// Creates a route handler with a `PUT` method guard.
152///
153/// Re-exported as `ntex::web::put`.
154///
155/// Syntax: `#[put("path"[, guard = "fn_name"]*[, state = Type])]`
156///
157/// Works the same way and takes the same arguments as [`web_get`].
158#[proc_macro_attribute]
159pub fn web_put(args: TokenStream, input: TokenStream) -> TokenStream {
160 let gen_code = match route::Route::new(args, input, route::MethodType::Put) {
161 Ok(gen_code) => gen_code,
162 Err(err) => return err.to_compile_error().into(),
163 };
164 gen_code.generate()
165}
166
167/// Creates a route handler with a `DELETE` method guard.
168///
169/// Re-exported as `ntex::web::delete`.
170///
171/// Syntax: `#[delete("path"[, guard = "fn_name"]*[, state = Type])]`
172///
173/// Works the same way and takes the same arguments as [`web_get`].
174#[proc_macro_attribute]
175pub fn web_delete(args: TokenStream, input: TokenStream) -> TokenStream {
176 let gen_code = match route::Route::new(args, input, route::MethodType::Delete) {
177 Ok(gen_code) => gen_code,
178 Err(err) => return err.to_compile_error().into(),
179 };
180 gen_code.generate()
181}
182
183/// Creates a route handler with a `HEAD` method guard.
184///
185/// Re-exported as `ntex::web::head`.
186///
187/// Syntax: `#[head("path"[, guard = "fn_name"]*[, state = Type])]`
188///
189/// Works the same way and takes the same arguments as [`web_get`].
190#[proc_macro_attribute]
191pub fn web_head(args: TokenStream, input: TokenStream) -> TokenStream {
192 let gen_code = match route::Route::new(args, input, route::MethodType::Head) {
193 Ok(gen_code) => gen_code,
194 Err(err) => return err.to_compile_error().into(),
195 };
196 gen_code.generate()
197}
198
199/// Creates a route handler with a `CONNECT` method guard.
200///
201/// Re-exported as `ntex::web::connect`.
202///
203/// Syntax: `#[connect("path"[, guard = "fn_name"]*[, state = Type])]`
204///
205/// Works the same way and takes the same arguments as [`web_get`].
206#[proc_macro_attribute]
207pub fn web_connect(args: TokenStream, input: TokenStream) -> TokenStream {
208 let gen_code = match route::Route::new(args, input, route::MethodType::Connect) {
209 Ok(gen_code) => gen_code,
210 Err(err) => return err.to_compile_error().into(),
211 };
212 gen_code.generate()
213}
214
215/// Creates a route handler with a `OPTIONS` method guard.
216///
217/// Re-exported as `ntex::web::options`.
218///
219/// Syntax: `#[options("path"[, guard = "fn_name"]*[, state = Type])]`
220///
221/// Works the same way and takes the same arguments as [`web_get`].
222#[proc_macro_attribute]
223pub fn web_options(args: TokenStream, input: TokenStream) -> TokenStream {
224 let gen_code = match route::Route::new(args, input, route::MethodType::Options) {
225 Ok(gen_code) => gen_code,
226 Err(err) => return err.to_compile_error().into(),
227 };
228 gen_code.generate()
229}
230
231/// Creates a route handler with a `TRACE` method guard.
232///
233/// Re-exported as `ntex::web::trace`.
234///
235/// Syntax: `#[trace("path"[, guard = "fn_name"]*[, state = Type])]`
236///
237/// Works the same way and takes the same arguments as [`web_get`].
238#[proc_macro_attribute]
239pub fn web_trace(args: TokenStream, input: TokenStream) -> TokenStream {
240 let gen_code = match route::Route::new(args, input, route::MethodType::Trace) {
241 Ok(gen_code) => gen_code,
242 Err(err) => return err.to_compile_error().into(),
243 };
244 gen_code.generate()
245}
246
247/// Creates a route handler with a `PATCH` method guard.
248///
249/// Re-exported as `ntex::web::patch`.
250///
251/// Syntax: `#[patch("path"[, guard = "fn_name"]*[, state = Type])]`
252///
253/// Works the same way and takes the same arguments as [`web_get`].
254#[proc_macro_attribute]
255pub fn web_patch(args: TokenStream, input: TokenStream) -> TokenStream {
256 let gen_code = match route::Route::new(args, input, route::MethodType::Patch) {
257 Ok(gen_code) => gen_code,
258 Err(err) => return err.to_compile_error().into(),
259 };
260 gen_code.generate()
261}
262
263/// Creates a route handler with a `QUERY` method guard.
264///
265/// Re-exported as `ntex::web::query`.
266///
267/// Syntax: `#[query("path"[, guard = "fn_name"]*[, state = Type])]`
268///
269/// Works the same way and takes the same arguments as [`web_get`].
270#[proc_macro_attribute]
271pub fn web_query(args: TokenStream, input: TokenStream) -> TokenStream {
272 let gen_code = match route::Route::new(args, input, route::MethodType::Query) {
273 Ok(gen_code) => gen_code,
274 Err(err) => return err.to_compile_error().into(),
275 };
276 gen_code.generate()
277}
278
279/// Runs an async function on the ntex runtime.
280///
281/// Re-exported as `ntex::main`.
282///
283/// The function becomes a normal blocking function. When called, it builds a
284/// `System`, runs the body to completion and returns its result, so the
285/// function can return a value, for example `std::io::Result<()>`. It does
286/// not have to be `main`.
287///
288/// ```rust
289/// #[ntex::main]
290/// async fn main() -> std::io::Result<()> {
291/// println!("Hello world");
292/// Ok(())
293/// }
294/// ```
295///
296/// ## Arguments
297///
298/// - `name = "..."` - name of the system. Defaults to the function name.
299/// - `signals = true/false` - handle process signals. Off by default.
300/// - `panic_handling = true/false` - report application panics as
301/// `Signal::Panic`. Only useful together with `signals = true`. Off by
302/// default.
303/// - `ping_interval = N` - how often, in milliseconds, the system pings its
304/// arbiters to spot busy ones. Defaults to 2000, zero turns pings off.
305/// - `rt = path` - the runtime to run on, a value that implements
306/// `ntex::rt::Runner`. Defaults to `ntex::rt::DefaultRuntime`, the runtime
307/// picked by ntex features.
308///
309/// ```rust
310/// #[ntex::main(name = "server", signals = true, ping_interval = 250)]
311/// async fn main() {
312/// println!("Hello world");
313/// }
314/// ```
315#[proc_macro_attribute]
316pub fn rt_main(args: TokenStream, item: TokenStream) -> TokenStream {
317 let mut args = syn::parse_macro_input!(args as sys::MainArgs);
318 let mut input = syn::parse_macro_input!(item as syn::ItemFn);
319 let attrs = &input.attrs;
320 let vis = &input.vis;
321 let sig = &mut input.sig;
322 let body = &input.block;
323 let name = &sig.ident;
324
325 if sig.asyncness.is_none() {
326 return syn::Error::new_spanned(sig.fn_token, "only async fn is supported")
327 .to_compile_error()
328 .into();
329 }
330
331 sig.asyncness = None;
332
333 let runner = args.gen_sys_rt();
334 let config = args.gen_sys_config(name);
335
336 (quote! {
337 #(#attrs)*
338 #vis #sig {
339 ntex::rt::System::build()
340 #config
341 .build( #runner )
342 .block_on(async move #body)
343 }
344 })
345 .into()
346}
347
348/// Runs an async test on the ntex runtime.
349///
350/// Re-exported as `ntex::test`.
351///
352/// The macro adds `#[test]`, unless the function already has it, and runs the
353/// body on a new `System` named after the test. The system is built in
354/// testing mode, without signal and panic handling, and always uses
355/// `ntex::rt::DefaultRuntime`. The test can return a `Result`, like a normal
356/// test. The macro takes no arguments.
357///
358/// It also turns on `env_logger` at `trace` level, unless `RUST_LOG` is set.
359/// Set `NTEX_NO_TEST_LOG` or enable the `no-test-logging` feature of ntex to
360/// turn logging off.
361///
362/// ```no_run
363/// #[ntex::test]
364/// async fn my_test() {
365/// assert!(true);
366/// }
367///
368/// #[ntex::test]
369/// async fn my_fallible_test() -> std::io::Result<()> {
370/// Ok(())
371/// }
372/// ```
373#[proc_macro_attribute]
374pub fn rt_test(_: TokenStream, item: TokenStream) -> TokenStream {
375 let input = syn::parse_macro_input!(item as syn::ItemFn);
376
377 let ret = &input.sig.output;
378 let name = &input.sig.ident;
379 let body = &input.block;
380 let fut = boxed_future(ret, body);
381 let attrs = &input.attrs;
382 let mut has_test_attr = false;
383
384 for attr in attrs {
385 if attr.path().is_ident("test") {
386 has_test_attr = true;
387 }
388 }
389
390 if input.sig.asyncness.is_none() {
391 return syn::Error::new_spanned(
392 input.sig.fn_token,
393 format!("only async fn is supported, {}", input.sig.ident),
394 )
395 .to_compile_error()
396 .into();
397 }
398
399 let result = if has_test_attr {
400 quote! {
401 #(#attrs)*
402 fn #name() #ret {
403 ntex::util::enable_test_logging();
404 ntex::rt::System::build()
405 .name(stringify!(#name))
406 .testing()
407 .build(ntex::rt::DefaultRuntime)
408 .block_on(#fut)
409 }
410 }
411 } else {
412 quote! {
413 #[test]
414 #(#attrs)*
415 fn #name() #ret {
416 ntex::util::enable_test_logging();
417 ntex::rt::System::build()
418 .name(stringify!(#name))
419 .testing()
420 .build(ntex::rt::DefaultRuntime)
421 .block_on(#fut)
422 }
423 }
424 };
425
426 result.into()
427}
428
429/// Same as [`rt_test`] for crates that depend on `ntex-rt` directly. Doesn't
430/// enable test logging.
431#[doc(hidden)]
432#[proc_macro_attribute]
433pub fn rt_test2(_: TokenStream, item: TokenStream) -> TokenStream {
434 let input = syn::parse_macro_input!(item as syn::ItemFn);
435
436 let ret = &input.sig.output;
437 let name = &input.sig.ident;
438 let body = &input.block;
439 let fut = boxed_future(ret, body);
440 let attrs = &input.attrs;
441 let mut has_test_attr = false;
442
443 for attr in attrs {
444 if attr.path().is_ident("test") {
445 has_test_attr = true;
446 }
447 }
448
449 if input.sig.asyncness.is_none() {
450 return syn::Error::new_spanned(
451 input.sig.fn_token,
452 format!("only async fn is supported, {}", input.sig.ident),
453 )
454 .to_compile_error()
455 .into();
456 }
457
458 let result = if has_test_attr {
459 quote! {
460 #(#attrs)*
461 fn #name() #ret {
462 ntex_rt::System::build()
463 .name(stringify!(#name))
464 .testing()
465 .build(ntex::rt::DefaultRuntime)
466 .block_on(#fut)
467 }
468 }
469 } else {
470 quote! {
471 #[test]
472 #(#attrs)*
473 fn #name() #ret {
474 ntex_rt::System::build()
475 .name(stringify!(#name))
476 .testing()
477 .build(ntex::rt::DefaultRuntime)
478 .block_on(#fut)
479 }
480 }
481 };
482
483 result.into()
484}
485
486/// Same as [`rt_test`] for tests inside the `ntex` crate itself, it refers to
487/// `crate::` paths.
488#[doc(hidden)]
489#[proc_macro_attribute]
490pub fn rt_test_internal(_: TokenStream, item: TokenStream) -> TokenStream {
491 let input = syn::parse_macro_input!(item as syn::ItemFn);
492
493 let ret = &input.sig.output;
494 let name = &input.sig.ident;
495 let body = &input.block;
496 let fut = boxed_future(ret, body);
497 let attrs = &input.attrs;
498 let mut has_test_attr = false;
499
500 for attr in attrs {
501 if attr.path().is_ident("test") {
502 has_test_attr = true;
503 }
504 }
505
506 if input.sig.asyncness.is_none() {
507 return syn::Error::new_spanned(
508 input.sig.fn_token,
509 format!("only async fn is supported, {}", input.sig.ident),
510 )
511 .to_compile_error()
512 .into();
513 }
514
515 let result = if has_test_attr {
516 quote! {
517 #(#attrs)*
518 fn #name() #ret {
519 crate::util::enable_test_logging();
520 ntex_rt::System::build()
521 .name(stringify!(#name))
522 .testing()
523 .build(crate::rt::DefaultRuntime)
524 .block_on(#fut)
525 }
526 }
527 } else {
528 quote! {
529 #[test]
530 #(#attrs)*
531 fn #name() #ret {
532 crate::util::enable_test_logging();
533 ntex_rt::System::build()
534 .name(stringify!(#name))
535 .testing()
536 .build(crate::rt::DefaultRuntime)
537 .block_on(#fut)
538 }
539 }
540 };
541
542 result.into()
543}
544
545/// Box the test body so the runtime's `block_on` is generated once per
546/// return type instead of once per test function
547fn boxed_future(ret: &syn::ReturnType, body: &syn::Block) -> proc_macro2::TokenStream {
548 let output = match ret {
549 syn::ReturnType::Default => quote! { () },
550 syn::ReturnType::Type(_, ty) => quote! { #ty },
551 };
552 quote! {
553 ::std::boxed::Box::pin(async #body)
554 as ::std::pin::Pin<::std::boxed::Box<dyn ::std::future::Future<Output = #output>>>
555 }
556}