diff --git a/.gitignore b/.gitignore index 28ebd41..420bbe1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ .direnv/ target/ -wip/ diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 7d01687..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,92 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -## [Unreleased] - -- Nothing yet - -## [0.3.6] - 2025-04-17 - -- Better error messages when having route.rs files with invalid code - -## [0.3.5] - 2025-04-16 - -- Moved macrotest to dev deps - -## [0.3.4] - 2025-04-16 - -- Refactored huge lib.rs into 3 seperate files. -- Downgraded edition to 2021 for better compatability - -## [0.3.3] - 2025-04-15 - -### Added -- Add support for remaining HTTP methods - - we no support the full set as defined by rfc9110 - - trace & connect were missing specifically -- Add support for `any` axum router method (default method router, others will take precedence) - -## [0.3.2] - 2025-04-15 -- Refactor internals -- Add solid testing - - explicitly test generated macro output using macrotest - - test error output using trybuilt - -## [0.3.1] - 2025-04-15 - -- Fix invalid doc links - -## [0.3.0] - 2025-04-15 - -After some experimentation, the API has begun to stabilize. This should likely be the last breaking change for some time. - -### Breaking Changes - -- **Reworked implementation into an attribute macro** - - Previous implementation required function calls: - ```rust - folder_router!("./examples/simple/api", AppState); - // ... - let folder_router: Router = folder_router(); - ``` - - New implementation uses an attribute macro: - ```rust - #[folder_router("./examples/simple/api", AppState)] - struct MyFolderRouter; - // ... - let folder_router: Router = MyFolderRouter::into_router(); - ``` - - This approach provides a cleaner API and allows for multiple separate folder-based Routers - -## [0.2.3] - 2025-04-14 - -### Changed -- **Improved method detection** - Now properly parses files instead of using string matching - - Previous version checked if file contained ```pub async #method_name``` - - New version properly parses the file using `syn` for more accurate detection - -## [0.2.2] - 2025-04-14 - -### Changed -- **License changed to MIT** - -## [0.2.1] - 2025-04-14 - -### Improved -- Enhanced documentation -- Added more comprehensive tests - -## [0.2.0] - 2024-04-14 - -### Changed -- **Improved code integration** - - Generate module imports instead of using ```include!``` - - Makes the code compatible with rust-analyzer - - Provides better IDE support - -## [0.1.0] - 2024-04-14 - -### Added -- Initial release -- Minimum viable product adapted from https://github.com/richardanaya/axum-folder-router-htmx diff --git a/Cargo.lock b/Cargo.lock index 94c80a3..baf3ae4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,15 +17,6 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - [[package]] name = "anyhow" version = "1.0.98" @@ -93,19 +84,16 @@ dependencies = [ ] [[package]] -name = "axum-folder-router" -version = "0.3.6" +name = "axum_folder_router" +version = "0.1.0" dependencies = [ "anyhow", "axum", "glob", - "macrotest", "proc-macro2", "quote", - "regex", "syn", "tokio", - "trybuild", ] [[package]] @@ -141,24 +129,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "diff" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - [[package]] name = "fnv" version = "1.0.7" @@ -219,12 +189,6 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" -[[package]] -name = "hashbrown" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" - [[package]] name = "http" version = "1.3.1" @@ -306,16 +270,6 @@ dependencies = [ "tower-service", ] -[[package]] -name = "indexmap" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" -dependencies = [ - "equivalent", - "hashbrown", -] - [[package]] name = "itoa" version = "1.0.15" @@ -324,9 +278,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "libc" -version = "0.2.172" +version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] name = "lock_api" @@ -344,23 +298,6 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" -[[package]] -name = "macrotest" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0597a8d49ceeea5845b12d1970aa993261e68d4660b327eabab667b3e7ffd60" -dependencies = [ - "diff", - "fastrand", - "glob", - "prettyplease", - "serde", - "serde_derive", - "serde_json", - "syn", - "toml_edit", -] - [[package]] name = "matchit" version = "0.8.4" @@ -396,7 +333,7 @@ checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", "wasi", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -455,16 +392,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "prettyplease" -version = "0.2.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6" -dependencies = [ - "proc-macro2", - "syn", -] - [[package]] name = "proc-macro2" version = "1.0.94" @@ -485,42 +412,13 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.11" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" +checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" dependencies = [ "bitflags", ] -[[package]] -name = "regex" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" - [[package]] name = "rustc-demangle" version = "0.1.24" @@ -587,15 +485,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_spanned" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" -dependencies = [ - "serde", -] - [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -630,7 +519,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -650,21 +539,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -[[package]] -name = "target-triple" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790" - -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - [[package]] name = "tokio" version = "1.44.2" @@ -680,7 +554,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -694,40 +568,6 @@ dependencies = [ "syn", ] -[[package]] -name = "toml" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.22.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" -dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "winnow", -] - [[package]] name = "tower" version = "0.5.2" @@ -776,21 +616,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "trybuild" -version = "1.0.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ae08be68c056db96f0e6c6dd820727cca756ced9e1f4cc7fdd20e2a55e23898" -dependencies = [ - "glob", - "serde", - "serde_derive", - "serde_json", - "target-triple", - "termcolor", - "toml", -] - [[package]] name = "unicode-ident" version = "1.0.18" @@ -803,15 +628,6 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "winapi-util" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" -dependencies = [ - "windows-sys 0.59.0", -] - [[package]] name = "windows-sys" version = "0.52.0" @@ -821,15 +637,6 @@ dependencies = [ "windows-targets", ] -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -893,12 +700,3 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "winnow" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" -dependencies = [ - "memchr", -] diff --git a/Cargo.toml b/Cargo.toml index bc9f5ea..eeb1dcf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "axum-folder-router" -version = "0.3.6" +name = "axum_folder_router" +version = "0.1.0" edition = "2021" readme = "./README.md" authors = ["Tristan Druyen "] @@ -9,26 +9,24 @@ keywords = ["axum", "controller", "macro", "routing"] description = "Helper macro for simple folder based routing of axum handlers" homepage = "https://git.vlt81.de/vault81/axum-folder-router" repository = "https://git.vlt81.de/vault81/axum-folder-router" -license = "MIT" +license = "AGPL-3.0-or-later" + [lib] path = "./src/lib.rs" proc-macro = true [dependencies] -syn = { version = "2.0", features = ["full"] } +syn = "2.0" quote = "1.0" proc-macro2 = "1.0" glob = "0.3" -regex = "1.11.1" [dev-dependencies] anyhow = "1.0.98" axum = "0.8.3" tokio = { version = "1.44.2", features = ["full"] } -trybuild = "1.0.104" -macrotest = "1.1.0" -[lints.clippy] -pedantic = { level = "warn", priority = -1 } -unused-async = { level = "allow", priority = 0 } # required for examples without unecessary noise +[badges] +github = { repository = "vault81/axum-folder-router" } +maintenance = { status = "actively-developed" } diff --git a/LICENSE.md b/LICENSE.md deleted file mode 100644 index 89d04ef..0000000 --- a/LICENSE.md +++ /dev/null @@ -1,26 +0,0 @@ -The MIT License (MIT) -===================== - -Copyright © `2025` `Tristan Druyen` - -Permission is hereby granted, free of charge, to any person -obtaining a copy of this software and associated documentation -files (the “Software”), to deal in the Software without -restriction, including without limitation the rights to use, -copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - diff --git a/README.md b/README.md index 7d4e36f..0320548 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,35 @@ +[![Crates.io](https://img.shields.io/crates/v/axum-folder-router.svg)](https://crates.io/crates/axum-folder-router) +[![Workflow Status](https://github.com/vault81/axum-folder-router/workflows/main/badge.svg)](https://github.com/vault81/axum-folder-router/actions?query=workflow%3A%22main%22) +![Maintenance](https://img.shields.io/badge/maintenance-activly--developed-brightgreen.svg) -[![Crates.io](https://img.shields.io/crates/v/axum-folder-router)](https://crates.io/crates/axum-folder-router) -[![Documentation](https://docs.rs/axum-folder-router/badge.svg)](https://docs.rs/axum-folder-router) -![Maintenance](https://img.shields.io/badge/maintenance-actively--developed-brightgreen.svg) -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +# axum_folder_router -# axum-folder-router +## ```axum_folder_router``` Macro Documentation -```#[folder_router(...)]``` is a procedural attribute macro for the Axum web framework that automatically generates router boilerplate based on your direcory & file structure. -Inspired by popular frameworks like next.js. +```folder_router``` is a procedural macro for the Axum web framework that automatically generates router configurations based on your file structure. It simplifies route organization by using filesystem conventions to define your API routes. -## Features +### Installation -- **File System-Based Routing**: Define your API routes using intuitive folder structures -- **Reduced Boilerplate**: Automatically generates route mapping code -- **IDE Support**: Generates proper module imports for better rust-analyzer integration -- **Multiple Routers**: Create separate folder-based routers in the same application +Add the dependency to your ```Cargo.toml```: -## Usage +```toml +[dependencies] +axum_folder_router = "0.1.0" +axum = "0.7" +``` -For detailed instructions see [the examples](./examples) or [docs.rs](https://docs.rs/axum-folder-router). +### Basic Usage + +The macro scans a directory for ```route.rs``` files and automatically creates an Axum router based on the file structure: + +```rust ## License -This repository, is licensed permissively under the terms of the MIT license. +This repository, like all my personal projects, is licensed under the GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later). +This ensures that modifications to the code remain open source when used in network services. +Contact me if this doesn't suit your needs. ### Contribution -Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, shall be licensed as above, without any additional terms or conditions. - -### Attribution - -This macro is based on the [build.rs template by @richardanaya](https://github.com/richardanaya/axum-folder-router-htmx) +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later) license, shall be licensed as above, without any additional terms or conditions. diff --git a/README.tpl b/README.tpl new file mode 100644 index 0000000..94c989b --- /dev/null +++ b/README.tpl @@ -0,0 +1,16 @@ +[![Crates.io](https://img.shields.io/crates/v/axum-folder-router.svg)](https://crates.io/crates/axum-folder-router) +{{badges}} + +# {{crate}} + +{{readme}} + +## License + +This repository, like all my personal projects, is licensed under the GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later). +This ensures that modifications to the code remain open source when used in network services. +Contact me if this doesn't suit your needs. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later) license, shall be licensed as above, without any additional terms or conditions. diff --git a/examples/advanced/api/files/[...path]/route.rs b/examples/advanced/api/files/[...path]/route.rs deleted file mode 100644 index 785166f..0000000 --- a/examples/advanced/api/files/[...path]/route.rs +++ /dev/null @@ -1,5 +0,0 @@ -use axum::{extract::Path, response::IntoResponse}; - -pub async fn get(Path(path): Path) -> impl IntoResponse { - format!("Requested file path: {path}") -} diff --git a/examples/advanced/api/files/route.rs b/examples/advanced/api/files/route.rs deleted file mode 100644 index c9e9628..0000000 --- a/examples/advanced/api/files/route.rs +++ /dev/null @@ -1,9 +0,0 @@ -use axum::response::{Html, IntoResponse}; - -pub async fn get() -> impl IntoResponse { - Html("

Hello World!

").into_response() -} - -pub async fn post() -> impl IntoResponse { -"Posted successfully".into_response() -} diff --git a/examples/advanced/api/ping/route.rs b/examples/advanced/api/ping/route.rs deleted file mode 100644 index df7bc9a..0000000 --- a/examples/advanced/api/ping/route.rs +++ /dev/null @@ -1,12 +0,0 @@ -use axum::response::Html; -use axum::response::IntoResponse; - -pub async fn get() -> impl IntoResponse { - Html("

GET Pong!

").into_response() -} - -// This tests that our macro generates the routes in the correct order -// as any is only allowable as a first route. -pub async fn any() -> impl IntoResponse { - Html("

ANY Pong!

").into_response() -} diff --git a/examples/advanced/api/route.rs b/examples/advanced/api/route.rs deleted file mode 100644 index 6205103..0000000 --- a/examples/advanced/api/route.rs +++ /dev/null @@ -1,9 +0,0 @@ -use axum::response::{Html, IntoResponse}; - -pub async fn get() -> impl IntoResponse { - Html("

Hello World!

").into_response() -} - -pub async fn post() -> impl IntoResponse { - "Posted successfully".into_response() -} diff --git a/examples/advanced/api/users/[id]/route.rs b/examples/advanced/api/users/[id]/route.rs deleted file mode 100644 index 410f216..0000000 --- a/examples/advanced/api/users/[id]/route.rs +++ /dev/null @@ -1,5 +0,0 @@ -use axum::{extract::Path, response::IntoResponse}; - -pub async fn get(Path(id): Path) -> impl IntoResponse { - format!("User ID: {id}") -} diff --git a/examples/advanced/api/users/route.rs b/examples/advanced/api/users/route.rs deleted file mode 100644 index 6205103..0000000 --- a/examples/advanced/api/users/route.rs +++ /dev/null @@ -1,9 +0,0 @@ -use axum::response::{Html, IntoResponse}; - -pub async fn get() -> impl IntoResponse { - Html("

Hello World!

").into_response() -} - -pub async fn post() -> impl IntoResponse { - "Posted successfully".into_response() -} diff --git a/examples/advanced/main.rs b/examples/advanced/main.rs deleted file mode 100644 index 3a656e9..0000000 --- a/examples/advanced/main.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod server; - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - server::server().await?; - Ok(()) -} diff --git a/examples/advanced/server.rs b/examples/advanced/server.rs deleted file mode 100644 index ea07eaf..0000000 --- a/examples/advanced/server.rs +++ /dev/null @@ -1,29 +0,0 @@ -use axum::Router; -use axum_folder_router::folder_router; - -#[derive(Clone, Debug)] -struct AppState { - _foo: String, -} - -// Imports route.rs files & generates an ::into_router() fn -#[folder_router("examples/advanced/api", AppState)] -struct MyFolderRouter(); - -pub async fn server() -> anyhow::Result<()> { - // Create app state - let app_state = AppState { - _foo: String::new(), - }; - - // Use the init fn generated above - let folder_router: Router = MyFolderRouter::into_router(); - - // Build the router and provide the state - let app: Router<()> = folder_router.with_state(app_state); - - let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?; - println!("Listening on http://{}", listener.local_addr()?); - axum::serve(listener, app).await?; - Ok(()) -} diff --git a/examples/simple/api/route.rs b/examples/simple/api/route.rs index 8b47a5b..78034c2 100644 --- a/examples/simple/api/route.rs +++ b/examples/simple/api/route.rs @@ -3,3 +3,8 @@ use axum::response::{Html, IntoResponse}; pub async fn get() -> impl IntoResponse { Html("

Hello World!

").into_response() } + +pub async fn post() -> impl IntoResponse { +"Posted successfully".into_response() +} + diff --git a/examples/simple/main.rs b/examples/simple/main.rs index 23cf8c8..aafdf78 100644 --- a/examples/simple/main.rs +++ b/examples/simple/main.rs @@ -1,26 +1,31 @@ -use axum::Router; use axum_folder_router::folder_router; +use tokio; +use axum::Router; +use anyhow; -#[derive(Clone)] -struct AppState; -// Imports route.rs files & generates an ::into_router() fn -#[folder_router("./examples/simple/api", AppState)] -struct MyFolderRouter(); +#[derive(Clone, Debug)] +struct AppState { + _foo: String +} #[tokio::main] async fn main() -> anyhow::Result<()> { // Create app state - let app_state = AppState; + let app_state = AppState { + _foo: "".to_string() + }; - // Use the init fn generated above - let folder_router: Router = MyFolderRouter::into_router(); + // Generate the router using the macro + let folder_router: Router= folder_router!("./examples/simple/api", AppState); // 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); let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?; println!("Listening on http://{}", listener.local_addr()?); axum::serve(listener, app).await?; Ok(()) } + diff --git a/flake.nix b/flake.nix index b834fd3..7ff197f 100644 --- a/flake.nix +++ b/flake.nix @@ -72,8 +72,7 @@ cargo-udeps cargo-outdated cargo-release - cargo-readme - cargo-expand + cargo-readme calc fish inotify-tools @@ -94,8 +93,7 @@ packages = { # default = pkgs.callPackage ./package.nix { }; }; - }) - // { + }) // { hydraJobs = let system = "x86_64-linux"; diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..7190a60 --- /dev/null +++ b/renovate.json @@ -0,0 +1,3 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json" +} diff --git a/rustfmt.toml b/rustfmt.toml deleted file mode 100644 index cb06898..0000000 --- a/rustfmt.toml +++ /dev/null @@ -1,23 +0,0 @@ - -edition = "2024" -max_width = 100 -tab_spaces = 4 - - -# unstable -format_macro_bodies = true -format_macro_matchers = true -format_strings = true -group_imports = "StdExternalCrate" -imports_granularity = "Crate" -imports_layout = "HorizontalVertical" -overflow_delimited_expr = true -reorder_impl_items = true -struct_field_align_threshold = 4 -struct_lit_single_line = false -trailing_comma = "Vertical" -unstable_features = true -use_field_init_shorthand = true -use_try_shorthand = true -wrap_comments = true -# format_brace_macros = true diff --git a/src/generate.rs b/src/generate.rs deleted file mode 100644 index 62d7651..0000000 --- a/src/generate.rs +++ /dev/null @@ -1,234 +0,0 @@ -use std::{collections::BTreeMap, fmt::Write, path::Path}; - -use proc_macro2::TokenStream; -use quote::{format_ident, quote}; -use syn::LitStr; - -use crate::parse::methods_for_route; - -// A struct representing a directory in the module tree -#[derive(Debug)] -struct ModuleDir { - name: String, - has_route: bool, - children: BTreeMap, -} - -impl ModuleDir { - fn new(name: &str) -> Self { - ModuleDir { - name: name.to_string(), - has_route: false, - children: BTreeMap::new(), - } - } - - fn add_to_module_tree(&mut self, rel_path: &Path, _route_path: &Path) { - let components: Vec<_> = rel_path - .components() - .map(|c| c.as_os_str().to_string_lossy().to_string()) - .collect(); - - if components.is_empty() { - self.has_route = true; - return; - } - - let mut root = self; - - for (i, segment) in components.iter().enumerate() { - if i == components.len() - 1 && segment == "route.rs" { - root.has_route = true; - break; - } - - root = root - .children - .entry(segment.clone()) - .or_insert_with(|| ModuleDir::new(segment)); - } - } -} - -// Add a route to the module tree - -// Normalize a path segment for use as a module name -fn normalize_module_name(name: &str) -> String { - if name.starts_with('[') && name.ends_with(']') { - let inner = &name[1..name.len() - 1]; - if let Some(stripped) = inner.strip_prefix("...") { - format!("___{stripped}") - } else { - format!("__{inner}") - } - } else { - name.replace(['-', '.'], "_") - } -} - -// Convert a relative path to module path segments and axum route path -fn path_to_module_path(rel_path: &Path) -> (String, Vec) { - let mut axum_path = String::new(); - let mut mod_path = Vec::new(); - - let components: Vec<_> = rel_path - .components() - .map(|c| c.as_os_str().to_string_lossy().to_string()) - .collect(); - - // Handle root route - if components.is_empty() { - return ("/".to_string(), vec!["route".to_string()]); - } - - for (i, segment) in components.iter().enumerate() { - if i == components.len() - 1 && segment == "route.rs" { - mod_path.push("route".to_string()); - } else { - // Process directory name - let normalized = normalize_module_name(segment); - mod_path.push(normalized); - - // Process URL path - if segment.starts_with('[') && segment.ends_with(']') { - let param = &segment[1..segment.len() - 1]; - if let Some(stripped) = param.strip_prefix("...") { - write!(&mut axum_path, "/{{*{stripped}}}").unwrap(); - } else { - write!(&mut axum_path, "/{{:{param}}}").unwrap(); - } - } else { - write!(&mut axum_path, "/{segment}").unwrap(); - } - } - } - - if axum_path.is_empty() { - axum_path = "/".to_string(); - } - - (axum_path, mod_path) -} - -// Generate tokens for a module path -fn generate_mod_path_tokens(mod_path: &[String]) -> TokenStream { - let mut result = TokenStream::new(); - - for (i, segment) in mod_path.iter().enumerate() { - let segment_ident = format_ident!("{}", segment); - - if i == 0 { - result = quote! { #segment_ident }; - } else { - result = quote! { #result::#segment_ident }; - } - } - - result -} - -// Generate module hierarchy code -fn generate_module_hierarchy(dir: &ModuleDir) -> TokenStream { - let mut result = TokenStream::new(); - - // Add route.rs module if this directory has one - if dir.has_route { - let route_mod = quote! { - #[path = "route.rs"] - pub mod route; - }; - result.extend(route_mod); - } - - // Add subdirectories - for child in dir.children.values() { - let child_name = format_ident!("{}", normalize_module_name(&child.name)); - let child_path_lit = LitStr::new(&child.name, proc_macro2::Span::call_site()); - let child_content = generate_module_hierarchy(child); - - let child_mod = quote! { - #[path = #child_path_lit] - pub mod #child_name { - #child_content - } - }; - - result.extend(child_mod); - } - - result -} - -pub fn route_registrations( - root_namespace_str: &str, - routes: &Vec<(std::path::PathBuf, std::path::PathBuf)>, -) -> TokenStream { - let root_namespace_ident = format_ident!("{}", root_namespace_str); - - let mut route_registrations = Vec::new(); - for (route_path, rel_path) in routes { - // Generate module path and axum path - let (axum_path, mod_path) = path_to_module_path(rel_path); - - let method_registrations = methods_for_route(route_path); - - if !method_registrations.is_empty() { - let first_method = &method_registrations[0]; - let first_method_ident = format_ident!("{}", first_method); - - let mod_path_tokens = generate_mod_path_tokens(&mod_path); - - let mut builder = quote! { - axum::routing::#first_method_ident(#root_namespace_ident::#mod_path_tokens::#first_method_ident) - }; - - for method in &method_registrations[1..] { - let method_ident = format_ident!("{}", method); - - builder = quote! { - #builder.#method_ident(#root_namespace_ident::#mod_path_tokens::#method_ident) - }; - } - - let registration = quote! { - router = router.route(#axum_path, #builder); - }; - route_registrations.push(registration); - } - } - if route_registrations.is_empty() { - return quote! { - compile_error!(concat!( - "No routes defined in your route.rs's !\n", - "Ensure that at least one `pub async fn` named after an HTTP verb is defined. (e.g. get, post, put, delete)" - )); - }; - } - - TokenStream::from_iter(route_registrations) -} - -pub fn module_tree( - root_namespace_str: &str, - base_dir: &Path, - routes: &Vec<(std::path::PathBuf, std::path::PathBuf)>, -) -> TokenStream { - let root_namespace_ident = format_ident!("{}", root_namespace_str); - let base_path_lit = LitStr::new( - base_dir.to_str().unwrap_or("./"), - proc_macro2::Span::call_site(), - ); - - let mut root = ModuleDir::new(root_namespace_str); - for (route_path, rel_path) in routes { - root.add_to_module_tree(rel_path, route_path); - } - - let mod_hierarchy = generate_module_hierarchy(&root); - quote! { - #[path = #base_path_lit] - mod #root_namespace_ident { - #mod_hierarchy - } - } -} diff --git a/src/lib.rs b/src/lib.rs index 992ae7d..a72c829 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,6 @@ //! # ```axum_folder_router``` Macro Documentation //! -//! [macro@folder_router] is a procedural macro for the Axum web framework that -//! automatically generates router boilerplate based on your file structure. It -//! simplifies route organization by using filesystem conventions to define your -//! API routes. +//! ```folder_router``` is a procedural macro for the Axum web framework that automatically generates router configurations based on your file structure. It simplifies route organization by using filesystem conventions to define your API routes. //! //! ## Installation //! @@ -11,22 +8,22 @@ //! //! ```toml //! [dependencies] -//! axum_folder_router = "0.3" -//! axum = "0.8" +//! axum_folder_router = "0.1.0" +//! axum = "0.7" //! ``` //! //! ## Basic Usage //! -//! The macro scans a directory for ```route.rs``` files and automatically -//! creates an Axum router based on the file structure: +//! The macro scans a directory for ```route.rs``` files and automatically creates an Axum router based on the file structure: //! //! ```rust,no_run #![doc = include_str!("../examples/simple/main.rs")] //! ``` -//! +//! //! ## File Structure Convention //! //! The macro converts your file structure into routes: +//! //! ```text //! src/api/ //! ├── route.rs -> "/" @@ -40,21 +37,22 @@ //! └── [...path]/ //! └── route.rs -> "/files/*path" //! ``` -//! +//! //! Each ```route.rs``` file can contain HTTP method handlers that are automatically mapped to the corresponding route. //! //! ## Route Handlers //! //! Inside each ```route.rs``` file, define async functions named after HTTP methods: +//! //! ```rust #![doc = include_str!("../examples/simple/api/route.rs")] //! ``` -//! +//! //! ## Supported Features //! //! ### HTTP Methods //! -//! The macro supports all standard HTTP methods as defined in RFC9110. +//! The macro supports all standard HTTP methods: //! - ```get``` //! - ```post``` //! - ```put``` @@ -62,20 +60,17 @@ //! - ```patch``` //! - ```head``` //! - ```options``` -//! - ```trace``` -//! - ```connect``` -//! -//! And additionally -//! - ```any```, which maches all methods //! //! ### Path Parameters //! //! Dynamic path segments are defined using brackets: +//! //! ```text //! src/api/users/[id]/route.rs -> "/users/{id}" //! ``` -//! +//! //! Inside the route handler: +//! //! ```rust //! use axum::{ //! extract::Path, @@ -86,13 +81,15 @@ //! format!("User ID: {}", id) //! } //! ``` -//! +//! //! ### Catch-all Parameters //! //! Use the spread syntax for catch-all segments: +//! //! ```text //! src/api/files/[...path]/route.rs -> "/files/*path" //! ``` +//! //! ```rust //! use axum::{ //! extract::Path, @@ -103,11 +100,11 @@ //! format!("Requested file path: {}", path) //! } //! ``` -//! +//! //! ### State Extraction //! //! The state type provided to the macro is available in all route handlers: -//! All routes share the same state type, though you can use ```FromRef``` for more granular state extraction. +//! //! ```rust //! use axum::{ //! extract::State, @@ -121,102 +118,192 @@ //! format!("State: {:?}", state) //! } //! ``` -//! +//! //! ## Limitations //! //! - **Compile-time Only**: The routing is determined at compile time, so dynamic route registration isn't supported. -//! - **Expects seperate directory**: To make rust-analyzer & co work correctly the macro imports all route.rs files inside the given directory tree. -//! It is highly recommended to keep the route directory seperate from the rest of your module-tree. -use std::path::Path; +//! - **File I/O**: The macro performs file I/O during compilation, which may have implications in certain build environments. +//! - **Single State Type**: All routes share the same state type, though you can use ```FromRef``` for more granular state extraction. +//! +//! ## Best Practices +//! +//! 1. **Consistent Structure**: Maintain a consistent file structure to make your API organization predictable. +//! 2. **Individual Route Files**: Use one ```route.rs``` file per route path for clarity. +//! 3. **Module Organization**: Consider grouping related functionality in directories. +//! 4. **Documentation**: Add comments to each route handler explaining its purpose. + use proc_macro::TokenStream; -use quote::quote; -use syn::parse_macro_input; +use quote::{format_ident, quote}; +use std::fs; +use std::path::{Path, PathBuf}; +use syn::{parse::Parse, parse::ParseStream, parse_macro_input, Ident, LitStr, Result, Token}; -mod generate; -mod parse; +struct FolderRouterArgs { + path: String, + state_type: Ident, +} -/// Creates an Axum router module tree & creation function -/// by scanning a directory for `route.rs` files. +impl Parse for FolderRouterArgs { + fn parse(input: ParseStream) -> Result { + let path_lit = input.parse::()?; + input.parse::()?; + let state_type = input.parse::()?; + + Ok(FolderRouterArgs { + path: path_lit.value(), + state_type, + }) + } +} + +/// Creates an Axum router by scanning a directory for `route.rs` files. /// /// # Parameters /// -/// * `path` - A string literal pointing to the route directory, relative to the -/// Cargo manifest directory -/// * `state_type` - The type name of your application state that will be shared -/// across all routes -#[allow(clippy::missing_panics_doc)] -#[proc_macro_attribute] -pub fn folder_router(attr: TokenStream, item: TokenStream) -> TokenStream { - let args = parse_macro_input!(attr as parse::FolderRouterArgs); - let input_item = parse_macro_input!(item as syn::ItemStruct); - let struct_name = &input_item.ident; - +/// * `path` - A string literal pointing to the API directory, relative to the Cargo manifest directory +/// * `state_type` - The type name of your application state that will be shared across all routes +/// +/// # Example +/// +/// ```rust +/// # use axum_folder_router::folder_router; +/// # #[derive(Debug, Clone)] +/// # struct AppState (); +/// # +/// let router = folder_router!("./src/api", AppState); +/// ``` +/// +/// 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); let base_path = args.path; let state_type = args.state_type; - let manifest_dir = get_manifest_dir(); + // Get the project root directory + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); let base_dir = Path::new(&manifest_dir).join(&base_path); - let mod_namespace = format!( - "__folder_router__{}__{}", - struct_name - .to_string() - .chars() - .map(|c| if c.is_alphanumeric() { c } else { '_' }) - .map(|c| c.to_ascii_lowercase()) - .collect::(), - base_path - .chars() - .map(|c| if c.is_alphanumeric() { c } else { '_' }) - .collect::() - ); + // Collect route files + let mut routes = Vec::new(); + collect_route_files(&base_dir, &base_dir, &mut routes); - let routes = parse::collect_route_files(&base_dir, &base_dir); + // Generate module definitions and route registrations + let mut module_defs = Vec::new(); + let mut route_registrations = Vec::new(); - if routes.is_empty() { - return TokenStream::from(quote! { - compile_error!(concat!("No route.rs files found in the specified directory: '", - #base_path, - "'. Make sure the path is correct and contains route.rs files." - )); + for (route_path, rel_path) in routes { + // Generate module name and axum path + let (axum_path, mod_name) = path_to_route_info(&rel_path); + let mod_ident = format_ident!("{}", mod_name); + + // Create module path for include! + let rel_file_path = route_path.strip_prefix(&manifest_dir).unwrap(); + let rel_file_str = rel_file_path.to_string_lossy().to_string(); + + // Add module definition + module_defs.push(quote! { + #[allow(warnings)] + mod #mod_ident { + include!(concat!(env!("CARGO_MANIFEST_DIR"), "/", #rel_file_str)); + } }); + + // Read the file content to find HTTP methods + let file_content = fs::read_to_string(&route_path).unwrap_or_default(); + let methods = ["get", "post", "put", "delete", "patch", "head", "options"]; + + let mut method_registrations = Vec::new(); + for method in &methods { + if file_content.contains(&format!("pub async fn {}(", method)) { + let method_ident = format_ident!("{}", method); + method_registrations.push((method, method_ident)); + } + } + + if !method_registrations.is_empty() { + let (_first_method, first_method_ident) = &method_registrations[0]; + + let mut builder = quote! { + axum::routing::#first_method_ident(#mod_ident::#first_method_ident) + }; + + for (_method, method_ident) in &method_registrations[1..] { + builder = quote! { + #builder.#method_ident(#mod_ident::#method_ident) + }; + } + + let registration = quote! { + router = router.route(#axum_path, #builder); + }; + route_registrations.push(registration); + } } - let module_tree = generate::module_tree(&mod_namespace, &base_dir, &routes); - let route_registrations = generate::route_registrations(&mod_namespace, &routes); + // Generate the final code + let expanded = quote! { + { + #(#module_defs)* - quote! { - #module_tree + let mut router = axum::Router::<#state_type>::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 - } - } - } - .into() + expanded.into() } -// This is a workaround for macrotest behaviour -#[cfg(debug_assertions)] -fn get_manifest_dir() -> String { - use regex::Regex; - let dir = std::env::var("CARGO_MANIFEST_DIR").unwrap_or("./".to_string()); - let re = Regex::new(r"^(.+)/target/tests/axum-folder-router/[A-Za-z0-9]{42}$").unwrap(); +// Recursively collect route.rs files +fn collect_route_files(base_dir: &Path, current_dir: &Path, routes: &mut Vec<(PathBuf, PathBuf)>) { + if let Ok(entries) = fs::read_dir(current_dir) { + for entry in entries.filter_map(std::result::Result::ok) { + let path = entry.path(); - if let Some(captures) = re.captures(&dir) { - captures.get(1).unwrap().as_str().to_string() - } else { - dir + if path.is_dir() { + collect_route_files(base_dir, &path, routes); + } else if path.file_name().unwrap_or_default() == "route.rs" { + if let Some(parent) = path.parent() { + if let Ok(rel_dir) = parent.strip_prefix(base_dir) { + routes.push((path.clone(), rel_dir.to_path_buf())); + } + } + } + } } } -#[cfg(not(debug_assertions))] -fn get_manifest_dir() -> String { - std::env::var("CARGO_MANIFEST_DIR").unwrap_or("./".to_string()) +// Convert a relative path to (axum_path, mod_name) +fn path_to_route_info(rel_path: &Path) -> (String, String) { + if rel_path.components().count() == 0 { + return ("/".to_string(), "root".to_string()); + } + + let mut axum_path = String::new(); + let mut mod_name = String::new(); + + for segment in rel_path.iter() { + let s = segment.to_str().unwrap_or_default(); + if s.starts_with('[') && s.ends_with(']') { + let inner = &s[1..s.len() - 1]; + if inner.starts_with("...") { + let param = &inner[3..]; + axum_path.push_str(&format!("/*{}", param)); + mod_name.push_str(&format!("__{}", param)); + } else { + axum_path.push_str(&format!("/{{{}}}", inner)); + mod_name.push_str(&format!("__{}", inner)); + } + } else { + axum_path.push('/'); + axum_path.push_str(s); + mod_name.push_str("__"); + mod_name.push_str(s); + } + } + + (axum_path, mod_name.trim_start_matches('_').to_string()) } diff --git a/src/parse.rs b/src/parse.rs deleted file mode 100644 index 6b1d9fc..0000000 --- a/src/parse.rs +++ /dev/null @@ -1,98 +0,0 @@ -use std::{ - fs, - path::{Path, PathBuf}, -}; - -use syn::{ - parse::{Parse, ParseStream}, - parse_file, - Ident, - Item, - LitStr, - Result, - Token, - Visibility, -}; - -#[derive(Debug)] -pub struct FolderRouterArgs { - pub path: String, - pub state_type: Ident, -} - -impl Parse for FolderRouterArgs { - fn parse(input: ParseStream) -> Result { - let path_lit = input.parse::()?; - input.parse::()?; - let state_type = input.parse::()?; - - Ok(FolderRouterArgs { - path: path_lit.value(), - state_type, - }) - } -} - -/// Parses the file at the specified location and returns HTTP verb functions -pub fn methods_for_route(route_path: &PathBuf) -> Vec<&'static str> { - // Read the file content - let Ok(file_content) = fs::read_to_string(route_path) else { - return Vec::new(); - }; - - // Parse the file content into a syn syntax tree - let Ok(file) = parse_file(&file_content) else { - return Vec::new(); - }; - - // Define HTTP methods we're looking for - let allowed_methods = [ - "any", "get", "post", "put", "delete", "patch", "head", "options", "trace", "connect", - ]; - let mut found_methods = Vec::new(); - - // Collect all pub & async fn's - for item in &file.items { - if let Item::Fn(fn_item) = item { - let fn_name = fn_item.sig.ident.to_string(); - let is_public = matches!(fn_item.vis, Visibility::Public(_)); - let is_async = fn_item.sig.asyncness.is_some(); - - if is_public && is_async { - found_methods.push(fn_name); - } - } - } - - // Iterate through methods to ensure consistent order - allowed_methods - .into_iter() - .filter(|elem| { - found_methods - .clone() - .into_iter() - .any(|method| method == *elem) - }) - .collect() -} - -// Collect route.rs files recursively -pub fn collect_route_files(base_dir: &Path, dir: &Path) -> Vec<(PathBuf, PathBuf)> { - let mut routes = Vec::new(); - if let Ok(entries) = fs::read_dir(dir) { - for entry in entries.filter_map(std::result::Result::ok) { - let path = entry.path(); - - if path.is_dir() { - let mut nested_routes = collect_route_files(base_dir, &path); - routes.append(&mut nested_routes); - } else if path.file_name().unwrap_or_default() == "route.rs" { - if let Ok(rel_dir) = path.strip_prefix(base_dir) { - routes.push((path.clone(), rel_dir.to_path_buf())); - } - } - } - } - routes.sort(); - routes -} diff --git a/tests/expand.rs b/tests/expand.rs deleted file mode 100644 index 4ffadf5..0000000 --- a/tests/expand.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[test] -pub fn expand_snapshot_pass() { - macrotest::expand("tests/expand/*.rs"); -} diff --git a/tests/expand/advanced.expanded.rs b/tests/expand/advanced.expanded.rs deleted file mode 100644 index bbd9439..0000000 --- a/tests/expand/advanced.expanded.rs +++ /dev/null @@ -1,155 +0,0 @@ -use axum_folder_router::folder_router; -struct AppState { - _foo: String, -} -#[automatically_derived] -impl ::core::clone::Clone for AppState { - #[inline] - fn clone(&self) -> AppState { - AppState { - _foo: ::core::clone::Clone::clone(&self._foo), - } - } -} -#[path = "/home/tristand/code/axum-folder-router/examples/advanced/api"] -mod __folder_router__myfolderrouter__examples_advanced_api { - #[path = "route.rs"] - pub mod route { - use axum::response::{Html, IntoResponse}; - pub async fn get() -> impl IntoResponse { - Html("

Hello World!

").into_response() - } - pub async fn post() -> impl IntoResponse { - "Posted successfully".into_response() - } - } - #[path = "files"] - pub mod files { - #[path = "route.rs"] - pub mod route { - use axum::response::{Html, IntoResponse}; - pub async fn get() -> impl IntoResponse { - Html("

Hello World!

").into_response() - } - pub async fn post() -> impl IntoResponse { - "Posted successfully".into_response() - } - } - #[path = "[...path]"] - pub mod ___path { - #[path = "route.rs"] - pub mod route { - use axum::{extract::Path, response::IntoResponse}; - pub async fn get(Path(path): Path) -> impl IntoResponse { - ::alloc::__export::must_use({ - let res = ::alloc::fmt::format( - format_args!("Requested file path: {0}", path), - ); - res - }) - } - } - } - } - #[path = "ping"] - pub mod ping { - #[path = "route.rs"] - pub mod route { - use axum::response::Html; - use axum::response::IntoResponse; - pub async fn get() -> impl IntoResponse { - Html("

GET Pong!

").into_response() - } - pub async fn any() -> impl IntoResponse { - Html("

ANY Pong!

").into_response() - } - } - } - #[path = "users"] - pub mod users { - #[path = "route.rs"] - pub mod route { - use axum::response::{Html, IntoResponse}; - pub async fn get() -> impl IntoResponse { - Html("

Hello World!

").into_response() - } - pub async fn post() -> impl IntoResponse { - "Posted successfully".into_response() - } - } - #[path = "[id]"] - pub mod __id { - #[path = "route.rs"] - pub mod route { - use axum::{extract::Path, response::IntoResponse}; - pub async fn get(Path(id): Path) -> impl IntoResponse { - ::alloc::__export::must_use({ - let res = ::alloc::fmt::format(format_args!("User ID: {0}", id)); - res - }) - } - } - } - } -} -struct MyFolderRouter(); -impl MyFolderRouter { - pub fn into_router() -> axum::Router { - let mut router = axum::Router::new(); - router = router - .route( - "/files/{*path}", - axum::routing::get( - __folder_router__myfolderrouter__examples_advanced_api::files::___path::route::get, - ), - ); - router = router - .route( - "/files", - axum::routing::get( - __folder_router__myfolderrouter__examples_advanced_api::files::route::get, - ) - .post( - __folder_router__myfolderrouter__examples_advanced_api::files::route::post, - ), - ); - router = router - .route( - "/ping", - axum::routing::any( - __folder_router__myfolderrouter__examples_advanced_api::ping::route::any, - ) - .get( - __folder_router__myfolderrouter__examples_advanced_api::ping::route::get, - ), - ); - router = router - .route( - "/", - axum::routing::get( - __folder_router__myfolderrouter__examples_advanced_api::route::get, - ) - .post( - __folder_router__myfolderrouter__examples_advanced_api::route::post, - ), - ); - router = router - .route( - "/users/{:id}", - axum::routing::get( - __folder_router__myfolderrouter__examples_advanced_api::users::__id::route::get, - ), - ); - router = router - .route( - "/users", - axum::routing::get( - __folder_router__myfolderrouter__examples_advanced_api::users::route::get, - ) - .post( - __folder_router__myfolderrouter__examples_advanced_api::users::route::post, - ), - ); - router - } -} diff --git a/tests/expand/advanced.rs b/tests/expand/advanced.rs deleted file mode 100644 index ff1303d..0000000 --- a/tests/expand/advanced.rs +++ /dev/null @@ -1,9 +0,0 @@ -use axum_folder_router::folder_router; - -#[derive(Clone)] -struct AppState { - _foo: String, -} - -#[folder_router("examples/advanced/api", AppState)] -struct MyFolderRouter(); diff --git a/tests/expand/simple.expanded.rs b/tests/expand/simple.expanded.rs deleted file mode 100644 index d81d96c..0000000 --- a/tests/expand/simple.expanded.rs +++ /dev/null @@ -1,33 +0,0 @@ -use axum_folder_router::folder_router; -struct AppState; -#[automatically_derived] -impl ::core::clone::Clone for AppState { - #[inline] - fn clone(&self) -> AppState { - AppState - } -} -#[path = "/home/tristand/code/axum-folder-router/examples/simple/api"] -mod __folder_router__myfolderrouter__examples_simple_api { - #[path = "route.rs"] - pub mod route { - use axum::response::{Html, IntoResponse}; - pub async fn get() -> impl IntoResponse { - Html("

Hello World!

").into_response() - } - } -} -struct MyFolderRouter(); -impl MyFolderRouter { - pub fn into_router() -> axum::Router { - let mut router = axum::Router::new(); - router = router - .route( - "/", - axum::routing::get( - __folder_router__myfolderrouter__examples_simple_api::route::get, - ), - ); - router - } -} diff --git a/tests/expand/simple.rs b/tests/expand/simple.rs deleted file mode 100644 index 2aafb9b..0000000 --- a/tests/expand/simple.rs +++ /dev/null @@ -1,7 +0,0 @@ -use axum_folder_router::folder_router; - -#[derive(Clone)] -struct AppState; - -#[folder_router("examples/simple/api", AppState)] -struct MyFolderRouter(); diff --git a/tests/failures.rs b/tests/failures.rs deleted file mode 100644 index 5f5371a..0000000 --- a/tests/failures.rs +++ /dev/null @@ -1,5 +0,0 @@ -#[test] -fn ui() { - let t = trybuild::TestCases::new(); - t.compile_fail("tests/failures/*.rs"); -} diff --git a/tests/failures/no_files.rs b/tests/failures/no_files.rs deleted file mode 100644 index e419115..0000000 --- a/tests/failures/no_files.rs +++ /dev/null @@ -1,9 +0,0 @@ -use axum_folder_router::folder_router; - -#[derive(Clone)] -struct AppState; - -#[folder_router("some/non/existing/directory", AppState)] -struct MyFolderRouter(); - -fn main() {} diff --git a/tests/failures/no_files.stderr b/tests/failures/no_files.stderr deleted file mode 100644 index 756b163..0000000 --- a/tests/failures/no_files.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error: No route.rs files found in the specified directory: 'some/non/existing/directory'. Make sure the path is correct and contains route.rs files. - --> tests/failures/no_files.rs:6:1 - | -6 | #[folder_router("some/non/existing/directory", AppState)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: this error originates in the attribute macro `folder_router` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/failures/no_routes.rs b/tests/failures/no_routes.rs deleted file mode 100644 index 4b81905..0000000 --- a/tests/failures/no_routes.rs +++ /dev/null @@ -1,9 +0,0 @@ -use axum_folder_router::folder_router; - -#[derive(Clone)] -struct AppState; - -#[folder_router("../../../../tests/failures/no_routes", AppState)] -struct MyFolderRouter(); - -fn main() {} diff --git a/tests/failures/no_routes.stderr b/tests/failures/no_routes.stderr deleted file mode 100644 index f0c99d1..0000000 --- a/tests/failures/no_routes.stderr +++ /dev/null @@ -1,8 +0,0 @@ -error: No routes defined in your route.rs's ! - Ensure that at least one `pub async fn` named after an HTTP verb is defined. (e.g. get, post, put, delete) - --> tests/failures/no_routes.rs:6:1 - | -6 | #[folder_router("../../../../tests/failures/no_routes", AppState)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: this error originates in the attribute macro `folder_router` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/failures/no_routes/route.rs b/tests/failures/no_routes/route.rs deleted file mode 100644 index e69de29..0000000