hashira_macros/
lib.rs

1mod impls;
2use impls::{ActionAttr, PageComponentAttr};
3use proc_macro::TokenStream;
4use syn::spanned::Spanned;
5
6// FIXME: rename to `#[page]` ?
7
8/// Provides an implementation of `PageComponent`.
9///
10/// # Usage
11/// You need to pass the route of the page and an optional loader
12/// which renders the page, if no loader is specified, a default loader
13/// will be provided that renders the component providing `Default` of the props.
14///
15/// - `#[page_component("/route")]`
16/// - `#[page_component("/route", loader = "path::to::function")]`
17/// - `#[page_component(None, loader = "path::to::function")]`
18///
19/// # Example
20///
21/// ```rs,no_run
22/// async fn render_page(ctx: RenderContext) -> Result<Response, Error> {
23///     let res = ctx.render::<HelloPage, App>().await;
24///     Ok(res)
25/// }
26///
27/// #[page_component("/hello", render = "render_page")]
28/// fn HelloPage() -> yew::Html {
29///     yew::html! {
30///         "Hello World!"
31///     }
32/// }
33/// ```
34#[proc_macro_attribute]
35pub fn page_component(attr: TokenStream, item: TokenStream) -> TokenStream {
36    let item_fn = syn::parse_macro_input!(item as syn::ItemFn);
37    let attr = syn::parse_macro_input!(attr as PageComponentAttr);
38
39    match impls::page_component_impl(attr, item_fn) {
40        Ok(tokens) => tokens.into(),
41        Err(err) => err.into_compile_error().into(),
42    }
43}
44
45/// Mark a function as a server action.
46/// 
47/// # Usage
48/// You can decorate a function using any:
49/// - `[action]` to create a server action with a generated route
50/// - `[action("/route/to/action")]` to create a server action with an explicit route.
51///
52/// # Example
53/// ```rs,no_run
54///
55///  #[action]
56///  fn ExampleServerAction(data: Json<Data>) {
57///     todo!()   
58///  }
59/// ```
60#[proc_macro_attribute]
61pub fn action(attr: TokenStream, item: TokenStream) -> TokenStream {
62    let item_fn = syn::parse_macro_input!(item as syn::ItemFn);
63    let attr = syn::parse_macro_input!(attr as ActionAttr);
64
65    match impls::action_impl(attr, item_fn) {
66        Ok(tokens) => tokens.into(),
67        Err(err) => err.into_compile_error().into(),
68    }
69}
70
71/// Marks a method as a page component render function.
72/// 
73/// # Remarks
74/// When compiling for the `client` all the parameters will be striped from the function
75/// except the `RenderContext` and the body will be replaced by `unreachable!()`.
76#[proc_macro_attribute]
77#[allow(clippy::redundant_clone)]
78pub fn render(_attr: TokenStream, item: TokenStream) -> TokenStream {
79    let input = syn::parse_macro_input!(item as syn::ItemFn);
80
81    if input.sig.asyncness.is_none() {
82        return syn::Error::new(input.span(), "render functions must be async")
83            .into_compile_error()
84            .into();
85    }
86
87    let vis = input.vis.clone();
88    let name = input.sig.ident.clone();
89    let attrs = input.attrs.clone();
90
91    let result = quote::quote! {
92        #[cfg(not(feature = "client"))]
93        #[allow(dead_code, unused_variables)]
94        #input
95        
96        #(#attrs)*
97        #[cfg(feature = "client")]
98        #[allow(dead_code, unused_variables)]
99        #vis async fn #name(_ctx: ::hashira::app::RenderContext) -> ::hashira::Result<::hashira::web::Response> {
100            std::unreachable!()
101        }
102    };
103
104    result.into()
105}