Skip to main content

ntex_macros/
lib.rs

1//! ntex macros module
2//!
3//! Generators for routes
4//!
5//! ## Route
6//!
7//! Macros:
8//!
9//! - [get](attr.web_get.html)
10//! - [post](attr.web_post.html)
11//! - [put](attr.web_put.html)
12//! - [delete](attr.web_delete.html)
13//! - [head](attr.web_head.html)
14//! - [connect](attr.web_connect.html)
15//! - [options](attr.web_options.html)
16//! - [trace](attr.web_trace.html)
17//! - [patch](attr.web_patch.html)
18//!
19//! ### Attributes:
20//!
21//! - `"path"` - Raw literal string with path for which to register handle. Mandatory.
22//! - `guard = "function_name"` - Registers function as guard using `ntex::web::guard::fn_guard`
23//! - `error = "ErrorRenderer"` - Register handler for specified error renderer
24//!
25//! ## Notes
26//!
27//! Function name can be specified as any expression that is going to be accessible to the generate
28//! code (e.g `my_guard` or `my_module::my_guard`)
29//!
30//! ## Example:
31//!
32//! ```rust
33//! use ntex::web::{get, Error, HttpResponse};
34//! use futures::{future, Future};
35//!
36//! #[get("/test")]
37//! async fn async_test() -> Result<HttpResponse, Error> {
38//!     Ok(HttpResponse::Ok().finish())
39//! }
40//! ```
41
42extern crate proc_macro;
43
44mod route;
45
46use proc_macro::TokenStream;
47use quote::quote;
48
49/// Creates route handler with `GET` method guard.
50///
51/// Syntax: `#[get("path"[, attributes])]`
52///
53/// ## Attributes:
54///
55/// - `"path"` - Raw literal string with path for which to register handler. Mandatory.
56/// - `guard = "function_name"` - Registers function as guard using `ntex::web::guard::fn_guard`
57/// - `error = "ErrorRenderer"` - Register handler for different error renderer
58#[proc_macro_attribute]
59pub fn web_get(args: TokenStream, input: TokenStream) -> TokenStream {
60    let gen_code = match route::Route::new(args, input, route::MethodType::Get) {
61        Ok(gen_code) => gen_code,
62        Err(err) => return err.to_compile_error().into(),
63    };
64    gen_code.generate()
65}
66
67/// Creates route handler with `POST` method guard.
68///
69/// Syntax: `#[post("path"[, attributes])]`
70///
71/// Attributes are the same as in [get](attr.get.html)
72#[proc_macro_attribute]
73pub fn web_post(args: TokenStream, input: TokenStream) -> TokenStream {
74    let gen_code = match route::Route::new(args, input, route::MethodType::Post) {
75        Ok(gen_code) => gen_code,
76        Err(err) => return err.to_compile_error().into(),
77    };
78    gen_code.generate()
79}
80
81/// Creates route handler with `PUT` method guard.
82///
83/// Syntax: `#[put("path"[, attributes])]`
84///
85/// Attributes are the same as in [get](attr.get.html)
86#[proc_macro_attribute]
87pub fn web_put(args: TokenStream, input: TokenStream) -> TokenStream {
88    let gen_code = match route::Route::new(args, input, route::MethodType::Put) {
89        Ok(gen_code) => gen_code,
90        Err(err) => return err.to_compile_error().into(),
91    };
92    gen_code.generate()
93}
94
95/// Creates route handler with `DELETE` method guard.
96///
97/// Syntax: `#[delete("path"[, attributes])]`
98///
99/// Attributes are the same as in [get](attr.get.html)
100#[proc_macro_attribute]
101pub fn web_delete(args: TokenStream, input: TokenStream) -> TokenStream {
102    let gen_code = match route::Route::new(args, input, route::MethodType::Delete) {
103        Ok(gen_code) => gen_code,
104        Err(err) => return err.to_compile_error().into(),
105    };
106    gen_code.generate()
107}
108
109/// Creates route handler with `HEAD` method guard.
110///
111/// Syntax: `#[head("path"[, attributes])]`
112///
113/// Attributes are the same as in [head](attr.head.html)
114#[proc_macro_attribute]
115pub fn web_head(args: TokenStream, input: TokenStream) -> TokenStream {
116    let gen_code = match route::Route::new(args, input, route::MethodType::Head) {
117        Ok(gen_code) => gen_code,
118        Err(err) => return err.to_compile_error().into(),
119    };
120    gen_code.generate()
121}
122
123/// Creates route handler with `CONNECT` method guard.
124///
125/// Syntax: `#[connect("path"[, attributes])]`
126///
127/// Attributes are the same as in [connect](attr.connect.html)
128#[proc_macro_attribute]
129pub fn web_connect(args: TokenStream, input: TokenStream) -> TokenStream {
130    let gen_code = match route::Route::new(args, input, route::MethodType::Connect) {
131        Ok(gen_code) => gen_code,
132        Err(err) => return err.to_compile_error().into(),
133    };
134    gen_code.generate()
135}
136
137/// Creates route handler with `OPTIONS` method guard.
138///
139/// Syntax: `#[options("path"[, attributes])]`
140///
141/// Attributes are the same as in [options](attr.options.html)
142#[proc_macro_attribute]
143pub fn web_options(args: TokenStream, input: TokenStream) -> TokenStream {
144    let gen_code = match route::Route::new(args, input, route::MethodType::Options) {
145        Ok(gen_code) => gen_code,
146        Err(err) => return err.to_compile_error().into(),
147    };
148    gen_code.generate()
149}
150
151/// Creates route handler with `TRACE` method guard.
152///
153/// Syntax: `#[trace("path"[, attributes])]`
154///
155/// Attributes are the same as in [trace](attr.trace.html)
156#[proc_macro_attribute]
157pub fn web_trace(args: TokenStream, input: TokenStream) -> TokenStream {
158    let gen_code = match route::Route::new(args, input, route::MethodType::Trace) {
159        Ok(gen_code) => gen_code,
160        Err(err) => return err.to_compile_error().into(),
161    };
162    gen_code.generate()
163}
164
165/// Creates route handler with `PATCH` method guard.
166///
167/// Syntax: `#[patch("path"[, attributes])]`
168///
169/// Attributes are the same as in [patch](attr.patch.html)
170#[proc_macro_attribute]
171pub fn web_patch(args: TokenStream, input: TokenStream) -> TokenStream {
172    let gen_code = match route::Route::new(args, input, route::MethodType::Patch) {
173        Ok(gen_code) => gen_code,
174        Err(err) => return err.to_compile_error().into(),
175    };
176    gen_code.generate()
177}
178
179/// Marks async function to be executed by ntex system.
180///
181/// ## Usage
182///
183/// ```rust
184/// #[ntex::main]
185/// async fn main() {
186///     println!("Hello world");
187/// }
188/// ```
189#[proc_macro_attribute]
190pub fn rt_main(_: TokenStream, item: TokenStream) -> TokenStream {
191    let mut input = syn::parse_macro_input!(item as syn::ItemFn);
192    let attrs = &input.attrs;
193    let vis = &input.vis;
194    let sig = &mut input.sig;
195    let body = &input.block;
196    let name = &sig.ident;
197
198    if sig.asyncness.is_none() {
199        return syn::Error::new_spanned(sig.fn_token, "only async fn is supported")
200            .to_compile_error()
201            .into();
202    }
203
204    sig.asyncness = None;
205
206    (quote! {
207        #(#attrs)*
208        #vis #sig {
209            ntex::rt::System::build()
210                .name(stringify!(#name))
211                .build(ntex::rt::DefaultRuntime)
212                .block_on(async move { #body })
213        }
214    })
215    .into()
216}
217
218/// Marks async test function to be executed by ntex runtime.
219///
220/// ## Usage
221///
222/// ```no_run
223/// #[ntex::test]
224/// async fn my_test() {
225///     assert!(true);
226/// }
227/// ```
228#[proc_macro_attribute]
229pub fn rt_test(_: TokenStream, item: TokenStream) -> TokenStream {
230    let input = syn::parse_macro_input!(item as syn::ItemFn);
231
232    let ret = &input.sig.output;
233    let name = &input.sig.ident;
234    let body = &input.block;
235    let attrs = &input.attrs;
236    let mut has_test_attr = false;
237
238    for attr in attrs {
239        if attr.path().is_ident("test") {
240            has_test_attr = true;
241        }
242    }
243
244    if input.sig.asyncness.is_none() {
245        return syn::Error::new_spanned(
246            input.sig.fn_token,
247            format!("only async fn is supported, {}", input.sig.ident),
248        )
249        .to_compile_error()
250        .into();
251    }
252
253    let result = if has_test_attr {
254        quote! {
255            #(#attrs)*
256            fn #name() #ret {
257                ntex::util::enable_test_logging();
258                ntex::rt::System::build()
259                    .name(stringify!(#name))
260                    .testing()
261                    .build(ntex::rt::DefaultRuntime)
262                    .block_on(async { #body })
263            }
264        }
265    } else {
266        quote! {
267            #[test]
268            #(#attrs)*
269            fn #name() #ret {
270                ntex::util::enable_test_logging();
271                ntex::rt::System::build()
272                    .name(stringify!(#name))
273                    .testing()
274                    .build(ntex::rt::DefaultRuntime)
275                    .block_on(async { #body })
276            }
277        }
278    };
279
280    result.into()
281}
282
283/// Marks async test function to be executed by ntex runtime.
284///
285/// ## Usage
286///
287/// ```no_run
288/// #[ntex::test]
289/// async fn my_test() {
290///     assert!(true);
291/// }
292/// ```
293#[doc(hidden)]
294#[proc_macro_attribute]
295pub fn rt_test2(_: TokenStream, item: TokenStream) -> TokenStream {
296    let input = syn::parse_macro_input!(item as syn::ItemFn);
297
298    let ret = &input.sig.output;
299    let name = &input.sig.ident;
300    let body = &input.block;
301    let attrs = &input.attrs;
302    let mut has_test_attr = false;
303
304    for attr in attrs {
305        if attr.path().is_ident("test") {
306            has_test_attr = true;
307        }
308    }
309
310    if input.sig.asyncness.is_none() {
311        return syn::Error::new_spanned(
312            input.sig.fn_token,
313            format!("only async fn is supported, {}", input.sig.ident),
314        )
315        .to_compile_error()
316        .into();
317    }
318
319    let result = if has_test_attr {
320        quote! {
321            #(#attrs)*
322            fn #name() #ret {
323                ntex_rt::System::build()
324                    .name(stringify!(#name))
325                    .testing()
326                    .build(ntex::rt::DefaultRuntime)
327                    .block_on(async { #body })
328            }
329        }
330    } else {
331        quote! {
332            #[test]
333            #(#attrs)*
334            fn #name() #ret {
335                ntex_rt::System::build()
336                    .name(stringify!(#name))
337                    .testing()
338                    .build(ntex::rt::DefaultRuntime)
339                    .block_on(async { #body })
340            }
341        }
342    };
343
344    result.into()
345}
346
347/// Marks async test function to be executed by ntex runtime.
348///
349/// ## Usage
350///
351/// ```no_run
352/// #[ntex::test]
353/// async fn my_test() {
354///     assert!(true);
355/// }
356/// ```
357#[doc(hidden)]
358#[proc_macro_attribute]
359pub fn rt_test_internal(_: TokenStream, item: TokenStream) -> TokenStream {
360    let input = syn::parse_macro_input!(item as syn::ItemFn);
361
362    let ret = &input.sig.output;
363    let name = &input.sig.ident;
364    let body = &input.block;
365    let attrs = &input.attrs;
366    let mut has_test_attr = false;
367
368    for attr in attrs {
369        if attr.path().is_ident("test") {
370            has_test_attr = true;
371        }
372    }
373
374    if input.sig.asyncness.is_none() {
375        return syn::Error::new_spanned(
376            input.sig.fn_token,
377            format!("only async fn is supported, {}", input.sig.ident),
378        )
379        .to_compile_error()
380        .into();
381    }
382
383    let result = if has_test_attr {
384        quote! {
385            #(#attrs)*
386            fn #name() #ret {
387                crate::util::enable_test_logging();
388                ntex_rt::System::build()
389                    .name(stringify!(#name))
390                    .testing()
391                    .build(crate::rt::DefaultRuntime)
392                    .block_on(async { #body })
393            }
394        }
395    } else {
396        quote! {
397            #[test]
398            #(#attrs)*
399            fn #name() #ret {
400                crate::util::enable_test_logging();
401                ntex_rt::System::build()
402                    .name(stringify!(#name))
403                    .testing()
404                    .build(crate::rt::DefaultRuntime)
405                    .block_on(async { #body })
406            }
407        }
408    };
409
410    result.into()
411}