diff --git a/CHANGELOG.md b/CHANGELOG.md index 41acacf..59da0bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,28 @@ +# Unreleased + +## Breaking +### Rework into attribute macro. + +Instead of using it like this + +```rust +// ... +folder_router!("./examples/simple/api", AppState); +// ... +let folder_router: Router = folder_router(); +``` + +It now works like this: +```rust +// ... +#[folder_router("./examples/simple/api", AppState)] +struct MyFolderRouter +// ... +let folder_router: Router = MyFolderRouter::into_router(); +``` + +This is a bit cleaner & it allows you to have multiple separate folder-based Routers. + # 0.2.3 - Refactored the detection of which methods exist, we actually parse the file now instead of just checking that it contains `pub async #method_name` diff --git a/examples/advanced/server.rs b/examples/advanced/server.rs index 7aa1acb..6689b53 100644 --- a/examples/advanced/server.rs +++ b/examples/advanced/server.rs @@ -7,7 +7,8 @@ struct AppState { } // Imports route.rs files & generates an init fn -folder_router!("examples/advanced/api", AppState); +#[folder_router("examples/advanced/api", AppState)] +struct MyFolderRouter(); pub async fn server() -> anyhow::Result<()> { // Create app state @@ -16,7 +17,7 @@ pub async fn server() -> anyhow::Result<()> { }; // Use the init fn generated above - let folder_router: Router = folder_router(); + let folder_router: Router = MyFolderRouter::into_router(); // Build the router and provide the state let app: Router<()> = folder_router.with_state(app_state); diff --git a/examples/simple/main.rs b/examples/simple/main.rs index c315ee1..8f1be8e 100644 --- a/examples/simple/main.rs +++ b/examples/simple/main.rs @@ -7,7 +7,8 @@ struct AppState { } // Imports route.rs files & generates an init fn -folder_router!("./examples/simple/api", AppState); +#[folder_router("./examples/simple/api", AppState)] +struct MyFolderRouter(); #[tokio::main] async fn main() -> anyhow::Result<()> { @@ -17,7 +18,7 @@ async fn main() -> anyhow::Result<()> { }; // Use the init fn generated above - let folder_router: Router = folder_router(); + let folder_router: Router = MyFolderRouter::into_router(); // Build the router and provide the state let app: Router<()> = folder_router.with_state(app_state); diff --git a/src/lib.rs b/src/lib.rs index bf40c86..e9192b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -177,7 +177,6 @@ impl ModuleDir { } } } - /// Creates an Axum router module tree & creation function /// by scanning a directory for `route.rs` files. /// @@ -191,9 +190,12 @@ impl ModuleDir { /// This will scan all `route.rs` files in the `./src/api` directory and its /// subdirectories, automatically mapping their path structure to URL routes /// with the specified state type. -#[proc_macro] -pub fn folder_router(input: TokenStream) -> TokenStream { - let args = parse_macro_input!(input as FolderRouterArgs); +#[proc_macro_attribute] +pub fn folder_router(attr: TokenStream, item: TokenStream) -> TokenStream { + let args = parse_macro_input!(attr as FolderRouterArgs); + let input_item = parse_macro_input!(item as syn::ItemStruct); + let struct_name = &input_item.ident; + let base_path = args.path; let state_type = args.state_type; @@ -214,8 +216,15 @@ pub fn folder_router(input: TokenStream) -> TokenStream { }); } + fn replace_special_chars(input: &str) -> String { + input + .chars() + .map(|c| if c.is_alphanumeric() { c } else { '_' }) + .collect() + } + // Build module tree - let mut root = ModuleDir::new("__folder_router"); + let mut root = ModuleDir::new(&format!("__folder_router_{}", replace_special_chars(&base_path))); for (route_path, rel_path) in &routes { add_to_module_tree(&mut root, rel_path, route_path); } @@ -268,16 +277,20 @@ pub fn folder_router(input: TokenStream) -> TokenStream { // Generate the final code let expanded = quote! { - #[path = #base_path_lit] - mod #root_mod_ident { - #mod_hierarchy - } + #[path = #base_path_lit] + mod #root_mod_ident { + #mod_hierarchy + } - pub fn folder_router() -> axum::Router<#state_type> { - let mut router = axum::Router::new(); - #(#route_registrations)* - router - } + #input_item + + impl #struct_name { + pub fn into_router() -> axum::Router<#state_type> { + let mut router = axum::Router::new(); + #(#route_registrations)* + router + } + } }; expanded.into() @@ -288,11 +301,11 @@ pub fn folder_router(input: TokenStream) -> TokenStream { /// e.g. for the file /// /// ```rust -/// pub async fn get() {} # ✅ => "get" be added to vec -/// pub fn post() {} # not async -/// async fn delete() {} # not pub -/// fn patch() {} # not pub nor async -/// pub fn non_verb() {} # not a http verb +/// pub async fn get() {} // ✅ => "get" be added to vec +/// pub fn post() {} // not async +/// async fn delete() {} // not pub +/// fn patch() {} // not pub nor async +/// pub fn non_verb() {} // not a http verb /// ``` /// /// it returns: `vec!["get"]`