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