Skip to main content

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}