Rework API to attribute macro

This commit is contained in:
Tristan D. 2025-04-15 00:28:17 +02:00
parent a8864786aa
commit d05df1bb4d
Signed by: tristan
SSH key fingerprint: SHA256:9oFM1J63hYWJjCnLG6C0fxBS15rwNcWwdQNMOHYKJ/4
4 changed files with 63 additions and 23 deletions

View file

@ -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<AppState> = folder_router();
```
It now works like this:
```rust
// ...
#[folder_router("./examples/simple/api", AppState)]
struct MyFolderRouter
// ...
let folder_router: Router<AppState> = MyFolderRouter::into_router();
```
This is a bit cleaner & it allows you to have multiple separate folder-based Routers.
# 0.2.3 # 0.2.3
- Refactored the detection of which methods exist, - Refactored the detection of which methods exist,
we actually parse the file now instead of just checking that it contains `pub async #method_name` we actually parse the file now instead of just checking that it contains `pub async #method_name`

View file

@ -7,7 +7,8 @@ struct AppState {
} }
// Imports route.rs files & generates an init fn // 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<()> { pub async fn server() -> anyhow::Result<()> {
// Create app state // Create app state
@ -16,7 +17,7 @@ pub async fn server() -> anyhow::Result<()> {
}; };
// Use the init fn generated above // Use the init fn generated above
let folder_router: Router<AppState> = folder_router(); let folder_router: Router<AppState> = MyFolderRouter::into_router();
// Build the router and provide the state // Build the router and provide the state
let app: Router<()> = folder_router.with_state(app_state); let app: Router<()> = folder_router.with_state(app_state);

View file

@ -7,7 +7,8 @@ struct AppState {
} }
// Imports route.rs files & generates an init fn // Imports route.rs files & generates an init fn
folder_router!("./examples/simple/api", AppState); #[folder_router("./examples/simple/api", AppState)]
struct MyFolderRouter();
#[tokio::main] #[tokio::main]
async fn main() -> anyhow::Result<()> { async fn main() -> anyhow::Result<()> {
@ -17,7 +18,7 @@ async fn main() -> anyhow::Result<()> {
}; };
// Use the init fn generated above // Use the init fn generated above
let folder_router: Router<AppState> = folder_router(); let folder_router: Router<AppState> = MyFolderRouter::into_router();
// Build the router and provide the state // Build the router and provide the state
let app: Router<()> = folder_router.with_state(app_state); let app: Router<()> = folder_router.with_state(app_state);

View file

@ -177,7 +177,6 @@ impl ModuleDir {
} }
} }
} }
/// Creates an Axum router module tree & creation function /// Creates an Axum router module tree & creation function
/// by scanning a directory for `route.rs` files. /// 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 /// This will scan all `route.rs` files in the `./src/api` directory and its
/// subdirectories, automatically mapping their path structure to URL routes /// subdirectories, automatically mapping their path structure to URL routes
/// with the specified state type. /// with the specified state type.
#[proc_macro] #[proc_macro_attribute]
pub fn folder_router(input: TokenStream) -> TokenStream { pub fn folder_router(attr: TokenStream, item: TokenStream) -> TokenStream {
let args = parse_macro_input!(input as FolderRouterArgs); 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 base_path = args.path;
let state_type = args.state_type; 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 // 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 { for (route_path, rel_path) in &routes {
add_to_module_tree(&mut root, rel_path, route_path); 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 // Generate the final code
let expanded = quote! { let expanded = quote! {
#[path = #base_path_lit] #[path = #base_path_lit]
mod #root_mod_ident { mod #root_mod_ident {
#mod_hierarchy #mod_hierarchy
} }
pub fn folder_router() -> axum::Router<#state_type> { #input_item
let mut router = axum::Router::new();
#(#route_registrations)* impl #struct_name {
router pub fn into_router() -> axum::Router<#state_type> {
} let mut router = axum::Router::new();
#(#route_registrations)*
router
}
}
}; };
expanded.into() expanded.into()
@ -288,11 +301,11 @@ pub fn folder_router(input: TokenStream) -> TokenStream {
/// e.g. for the file /// e.g. for the file
/// ///
/// ```rust /// ```rust
/// pub async fn get() {} # ✅ => "get" be added to vec /// pub async fn get() {} // ✅ => "get" be added to vec
/// pub fn post() {} # not async /// pub fn post() {} // not async
/// async fn delete() {} # not pub /// async fn delete() {} // not pub
/// fn patch() {} # not pub nor async /// fn patch() {} // not pub nor async
/// pub fn non_verb() {} # not a http verb /// pub fn non_verb() {} // not a http verb
/// ``` /// ```
/// ///
/// it returns: `vec!["get"]` /// it returns: `vec!["get"]`