leptos_routes_macro/
lib.rs

1mod expr_wrapper;
2mod generate;
3mod module_path;
4mod path;
5mod route_def;
6mod route_macro_args;
7mod util;
8
9use crate::expr_wrapper::ExprWrapper;
10use crate::module_path::ModulePath;
11use crate::route_def::{collect_route_definitions, RouteDef};
12use darling::ast::NestedMeta;
13use darling::FromMeta;
14use proc_macro::TokenStream;
15use proc_macro_error2::{abort, proc_macro_error};
16use quote::quote;
17use syn::{parse_macro_input, Item, ItemMod};
18
19#[proc_macro_attribute]
20#[proc_macro_error]
21pub fn route(_attr: TokenStream, input: TokenStream) -> TokenStream {
22    input
23}
24
25#[derive(Debug, FromMeta)]
26struct RoutesMacroArgs {
27    #[darling(default)]
28    with_views: bool,
29
30    #[darling(default)]
31    fallback: Option<ExprWrapper>,
32}
33
34/// This is the entry point for route-declarations. Put it on a module. Declare your routes using
35/// the `route` attribute on nested modules. You can freely nest your routes.
36///
37/// ```
38/// use leptos_routes::routes;
39///
40/// #[routes]
41/// pub mod routes {
42///
43///     #[route("/users")]
44///     pub mod users {
45///
46///         #[route("/:id")]
47///         pub mod user {
48///
49///             #[route("/details")]
50///             pub mod details {}
51///         }
52///     }
53/// }
54/// ```
55///
56/// You can also define the view for each route on the route declaration and simply let `leptos-routes` generate
57/// your router implementation.
58/// 
59/// ```
60/// use assertr::assert_that;
61/// use assertr::prelude::PartialEqAssertions;
62/// use leptos::prelude::*;
63/// use leptos_router::components::{Outlet, Router};
64/// use leptos_router::location::RequestUrl;
65/// use leptos_routes::routes;
66/// 
67/// #[routes(with_views, fallback = "|| view! { <Err404/> }")]
68/// pub mod routes {
69/// 
70///     #[route("/", layout = "MainLayout", fallback = "Dashboard")]
71///     pub mod root {
72/// 
73///         #[route("/welcome", view = "Welcome")]
74///         pub mod welcome {}
75/// 
76///         #[route("/users", layout = "UsersLayout", fallback = "NoUser")]
77///         pub mod users {
78/// 
79///             #[route("/:id", layout = "UserLayout", fallback="User")]
80///             pub mod user {
81/// 
82///                 #[route("/details", view = "UserDetails")]
83///                 pub mod details {}
84///             }
85///         }
86///     }
87/// }
88/// 
89/// #[component]
90/// fn Err404() -> impl IntoView { view! { "Err404" } }
91/// #[component]
92/// fn MainLayout() -> impl IntoView { view! { <div id="main-layout"> <Outlet/> </div> } }
93/// #[component]
94/// fn UsersLayout() -> impl IntoView { view! { <div id="users-layout"> <Outlet/> </div> } }
95/// #[component]
96/// fn UserLayout() -> impl IntoView { view! { <div id="user-layout"> <Outlet/> </div> } }
97/// #[component]
98/// fn Dashboard() -> impl IntoView { view! { "Dashboard" } }
99/// #[component]
100/// fn Welcome() -> impl IntoView { view! { "Welcome" } }
101/// #[component]
102/// fn NoUser() -> impl IntoView { view! { "NoUser" } }
103/// #[component]
104/// fn User() -> impl IntoView { view! {"User" } }
105/// #[component]
106/// fn UserDetails() -> impl IntoView { view! { "UserDetails" } }
107/// 
108/// fn main() {
109///     fn app() -> impl IntoView {
110///         view! {
111///             <Router>
112///                 { routes::generated_routes() }
113///             </Router>
114///         }
115///     }
116/// 
117///     let _ = Owner::new_root(None);
118/// 
119///     provide_context::<RequestUrl>(RequestUrl::new(
120///         routes::root::users::user::Details
121///             .materialize("42")
122///             .as_str(),
123///     ));
124///     assert_that(app().to_html()).is_equal_to(r#"<div id="main-layout"><div id="users-layout"><div id="user-layout">UserDetails</div></div></div>"#);
125/// }
126/// ```
127#[proc_macro_attribute]
128#[proc_macro_error]
129pub fn routes(args: TokenStream, input: TokenStream) -> TokenStream {
130    let attr_args = match NestedMeta::parse_meta_list(args.into()) {
131        Ok(v) => v,
132        Err(e) => {
133            return TokenStream::from(darling::Error::from(e).write_errors());
134        }
135    };
136    let args = match RoutesMacroArgs::from_list(&attr_args) {
137        Ok(v) => v,
138        Err(e) => {
139            return TokenStream::from(e.write_errors());
140        }
141    };
142
143    let mut root_mod: ItemMod = parse_macro_input!(input as ItemMod);
144
145    // Make sure we have module contents to work with.
146    let (_brace, ref mut content) = match root_mod.content {
147        Some((brace, ref mut content)) => (brace, content),
148        None => {
149            abort!(root_mod.ident, "routes macro requires a module with a body");
150        }
151    };
152
153    // Add the route import at the start of the module.
154    let route_import: Item = syn::parse_quote! {
155        use ::leptos_routes::route;
156    };
157    content.insert(0, route_import);
158
159    let mut route_defs: Vec<RouteDef> = Vec::new();
160    for item in content.iter_mut() {
161        if let Item::Mod(child_module) = item {
162            add_additional_imports_to_modules(child_module);
163
164            collect_route_definitions(
165                child_module,
166                None,
167                None,
168                &mut route_defs,
169                ModulePath::root(root_mod.ident.clone()),
170            );
171        }
172    }
173
174    generate::impls(&mut root_mod, args, route_defs);
175
176    let (brace, ref mut content) = match root_mod.content {
177        Some((brace, ref mut content)) => (brace, content),
178        None => unreachable!("Already checked for empty module"),
179    };
180
181    // Reconstruct the module with all additions.
182    root_mod.content = Some((brace, content.to_vec()));
183
184    Into::into(quote! { #root_mod })
185}
186
187fn add_additional_imports_to_modules(module: &mut ItemMod) {
188    if let Some((_, items)) = &mut module.content {
189        let imports: Item = syn::parse_quote! {
190            use ::leptos_routes::route;
191        };
192        items.insert(0, imports);
193
194        for item in items.iter_mut() {
195            if let Item::Mod(child_module) = item {
196                add_additional_imports_to_modules(child_module);
197            }
198        }
199    }
200}