Initial commit

This commit is contained in:
Tristan D. 2025-04-14 15:36:01 +02:00
commit e137a74979
Signed by: tristan
SSH key fingerprint: SHA256:9oFM1J63hYWJjCnLG6C0fxBS15rwNcWwdQNMOHYKJ/4
12 changed files with 1426 additions and 0 deletions

4
.envrc Normal file
View file

@ -0,0 +1,4 @@
if ! has nix_direnv_version || ! nix_direnv_version 2.3.0; then
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/2.3.0/direnvrc" "sha256-Dmd+j63L84wuzgyjITIfSxSD57Tx7v51DMxVZOsiUD8="
fi
use flake

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
.direnv/
target/

702
Cargo.lock generated Normal file
View file

@ -0,0 +1,702 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "addr2line"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
dependencies = [
"gimli",
]
[[package]]
name = "adler2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "anyhow"
version = "1.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "axum"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de45108900e1f9b9242f7f2e254aa3e2c029c921c258fe9e6b4217eeebd54288"
dependencies = [
"axum-core",
"bytes",
"form_urlencoded",
"futures-util",
"http",
"http-body",
"http-body-util",
"hyper",
"hyper-util",
"itoa",
"matchit",
"memchr",
"mime",
"percent-encoding",
"pin-project-lite",
"rustversion",
"serde",
"serde_json",
"serde_path_to_error",
"serde_urlencoded",
"sync_wrapper",
"tokio",
"tower",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "axum-core"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6"
dependencies = [
"bytes",
"futures-core",
"http",
"http-body",
"http-body-util",
"mime",
"pin-project-lite",
"rustversion",
"sync_wrapper",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "axum_folder_router"
version = "0.1.0"
dependencies = [
"anyhow",
"axum",
"glob",
"proc-macro2",
"quote",
"syn",
"tokio",
]
[[package]]
name = "backtrace"
version = "0.3.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
dependencies = [
"addr2line",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
"windows-targets",
]
[[package]]
name = "bitflags"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
[[package]]
name = "bytes"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "form_urlencoded"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
dependencies = [
"percent-encoding",
]
[[package]]
name = "futures-channel"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
dependencies = [
"futures-core",
]
[[package]]
name = "futures-core"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]]
name = "futures-task"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
[[package]]
name = "futures-util"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [
"futures-core",
"futures-task",
"pin-project-lite",
"pin-utils",
]
[[package]]
name = "gimli"
version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "glob"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
[[package]]
name = "http"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]]
name = "http-body"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
dependencies = [
"bytes",
"http",
]
[[package]]
name = "http-body-util"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
dependencies = [
"bytes",
"futures-core",
"http",
"http-body",
"pin-project-lite",
]
[[package]]
name = "httparse"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
[[package]]
name = "httpdate"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "hyper"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80"
dependencies = [
"bytes",
"futures-channel",
"futures-util",
"http",
"http-body",
"httparse",
"httpdate",
"itoa",
"pin-project-lite",
"smallvec",
"tokio",
]
[[package]]
name = "hyper-util"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2"
dependencies = [
"bytes",
"futures-util",
"http",
"http-body",
"hyper",
"pin-project-lite",
"tokio",
"tower-service",
]
[[package]]
name = "itoa"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "libc"
version = "0.2.171"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
[[package]]
name = "lock_api"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "matchit"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "mime"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "miniz_oxide"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a"
dependencies = [
"adler2",
]
[[package]]
name = "mio"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
dependencies = [
"libc",
"wasi",
"windows-sys",
]
[[package]]
name = "object"
version = "0.36.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "parking_lot"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-targets",
]
[[package]]
name = "percent-encoding"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "pin-project-lite"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "proc-macro2"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1"
dependencies = [
"bitflags",
]
[[package]]
name = "rustc-demangle"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "rustversion"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
[[package]]
name = "ryu"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]]
name = "serde_path_to_error"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a"
dependencies = [
"itoa",
"serde",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
dependencies = [
"form_urlencoded",
"itoa",
"ryu",
"serde",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
dependencies = [
"libc",
]
[[package]]
name = "smallvec"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
[[package]]
name = "socket2"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "syn"
version = "2.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "sync_wrapper"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
[[package]]
name = "tokio"
version = "1.44.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48"
dependencies = [
"backtrace",
"bytes",
"libc",
"mio",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys",
]
[[package]]
name = "tokio-macros"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tower"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
dependencies = [
"futures-core",
"futures-util",
"pin-project-lite",
"sync_wrapper",
"tokio",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "tower-layer"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
[[package]]
name = "tower-service"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
[[package]]
name = "tracing"
version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
dependencies = [
"log",
"pin-project-lite",
"tracing-core",
]
[[package]]
name = "tracing-core"
version = "0.1.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
dependencies = [
"once_cell",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"

32
Cargo.toml Normal file
View file

@ -0,0 +1,32 @@
[package]
name = "axum_folder_router"
version = "0.1.0"
edition = "2021"
readme = "./README.md"
authors = ["Tristan Druyen <ek36g2vcc@mozmail.com>"]
categories = ["web-programming"]
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 = "AGPL-3.0-or-later"
[lib]
path = "./src/lib.rs"
proc-macro = true
[dependencies]
syn = "2.0"
quote = "1.0"
proc-macro2 = "1.0"
glob = "0.3"
[dev-dependencies]
anyhow = "1.0.98"
axum = "0.8.3"
tokio = { version = "1.44.2", features = ["full"] }
[badges]
github = { repository = "vault81/axum-folder-router" }
maintenance = { status = "actively-developed" }

35
README.md Normal file
View file

@ -0,0 +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)
# axum_folder_router
## ```axum_folder_router``` Macro Documentation
```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
Add the dependency to your ```Cargo.toml```:
```toml
[dependencies]
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:
```rust
## 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.

16
README.tpl Normal file
View file

@ -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.

View file

@ -0,0 +1,10 @@
use axum::response::{Html, IntoResponse};
pub async fn get() -> impl IntoResponse {
Html("<h1>Hello World!</h1>").into_response()
}
pub async fn post() -> impl IntoResponse {
"Posted successfully".into_response()
}

31
examples/simple/main.rs Normal file
View file

@ -0,0 +1,31 @@
use axum_folder_router::folder_router;
use tokio;
use axum::Router;
use anyhow;
#[derive(Clone, Debug)]
struct AppState {
_foo: String
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Create app state
let app_state = AppState {
_foo: "".to_string()
};
// Generate the router using the macro
let folder_router: Router<AppState>= folder_router!("./examples/simple/api", AppState);
// 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(())
}

162
flake.lock generated Normal file
View file

@ -0,0 +1,162 @@
{
"nodes": {
"devshell": {
"inputs": {
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1735644329,
"narHash": "sha256-tO3HrHriyLvipc4xr+Ewtdlo7wM1OjXNjlWRgmM7peY=",
"owner": "numtide",
"repo": "devshell",
"rev": "f7795ede5b02664b57035b3b757876703e2c3eac",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "devshell",
"type": "github"
}
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1735774679,
"narHash": "sha256-soePLBazJk0qQdDVhdbM98vYdssfs3WFedcq+raipRI=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "f2f7418ce0ab4a5309a4596161d154cfc877af66",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1722073938,
"narHash": "sha256-OpX0StkL8vpXyWOGUD6G+MA26wAXK6SpT94kLJXo6B4=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "e36e9f57337d0ff0cf77aceb58af4c805472bfae",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-lib": {
"locked": {
"lastModified": 1735774519,
"narHash": "sha256-CewEm1o2eVAnoqb6Ml+Qi9Gg/EfNAxbRx1lANGVyoLI=",
"type": "tarball",
"url": "https://github.com/NixOS/nixpkgs/archive/e9b51731911566bbf7e4895475a87fe06961de0b.tar.gz"
},
"original": {
"type": "tarball",
"url": "https://github.com/NixOS/nixpkgs/archive/e9b51731911566bbf7e4895475a87fe06961de0b.tar.gz"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1741010256,
"narHash": "sha256-WZNlK/KX7Sni0RyqLSqLPbK8k08Kq7H7RijPJbq9KHM=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "ba487dbc9d04e0634c64e3b1f0d25839a0a68246",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_3": {
"locked": {
"lastModified": 1736320768,
"narHash": "sha256-nIYdTAiKIGnFNugbomgBJR+Xv5F1ZQU+HfaBqJKroC0=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "4bc9c909d9ac828a039f288cf872d16d38185db8",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"devshell": "devshell",
"flake-parts": "flake-parts",
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs_2",
"rust-overlay": "rust-overlay"
}
},
"rust-overlay": {
"inputs": {
"nixpkgs": "nixpkgs_3"
},
"locked": {
"lastModified": 1741055476,
"narHash": "sha256-52vwEV0oS2lCnx3c/alOFGglujZTLmObit7K8VblnS8=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "aefb7017d710f150970299685e8d8b549d653649",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

107
flake.nix Normal file
View file

@ -0,0 +1,107 @@
{
description = "A Nix-flake-based Rust development environment";
nixConfig = {
extra-substituters = [
"https://nixcache.vlt81.de"
"https://cuda-maintainers.cachix.org"
];
extra-trusted-public-keys = [
"nixcache.vlt81.de:nw0FfUpePtL6P3IMNT9X6oln0Wg9REZINtkkI9SisqQ="
];
};
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
rust-overlay.url = "github:oxalica/rust-overlay";
flake-utils.url = "github:numtide/flake-utils";
flake-parts.url = "github:hercules-ci/flake-parts";
devshell.url = "github:numtide/devshell";
};
outputs =
{ self
, nixpkgs
, rust-overlay
, flake-utils
, devshell
, ...
}:
flake-utils.lib.eachDefaultSystem
(system:
let
overlays = [
rust-overlay.overlays.default
devshell.overlays.default
(final: prev: {
customRustToolchain = prev.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
})
];
pkgs = import nixpkgs {
inherit system overlays;
config = {
allowUnfree = true;
};
};
buildInputs = with pkgs; [
zlib
clang
libclang
gzip
coreutils
gdb
glib
glibc
];
in
{
devShells.default = pkgs.mkShell {
packages = with pkgs;
[
customRustToolchain
bacon
binaryen
cacert
cargo-bloat
cargo-docset
cargo-machete
cargo-limit
cargo-deny
cargo-edit
cargo-watch
cargo-make
cargo-generate
cargo-udeps
cargo-outdated
cargo-release
cargo-readme
calc
fish
inotify-tools
mold
pkg-config
sccache
unzip
]
++ buildInputs;
buildInputs = buildInputs;
shellHook = ''
# export NIX_LD_LIBRARY_PATH=${pkgs.lib.makeLibraryPath buildInputs}:$NIX_LD_LIBRARY_PATH
export LD_LIBRARY_PATH="${pkgs.lib.makeLibraryPath buildInputs}"
export MALLOC_CONF=thp:always,metadata_thp:always
'';
};
packages = {
# default = pkgs.callPackage ./package.nix { };
};
}) // {
hydraJobs =
let
system = "x86_64-linux";
# packages = self.packages."${system}";
devShells = self.devShells."${system}";
in
{
inherit devShells;
};
};
}

16
rust-toolchain.toml Normal file
View file

@ -0,0 +1,16 @@
[toolchain]
channel = "nightly-2025-03-01"
components = [
"cargo",
"rust-analyzer",
"rust-src",
"rustc-codegen-cranelift",
"rustc-dev",
"rustfmt",
]
profile = "default"
targets = [
"wasm32-unknown-unknown",
"x86_64-pc-windows-msvc",
"x86_64-unknown-linux-gnu",
]

309
src/lib.rs Normal file
View file

@ -0,0 +1,309 @@
//! # ```axum_folder_router``` Macro Documentation
//!
//! ```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
//!
//! Add the dependency to your ```Cargo.toml```:
//!
//! ```toml
//! [dependencies]
//! 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:
//!
//! ```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 -> "/"
//! ├── hello/
//! │ └── route.rs -> "/hello"
//! ├── users/
//! │ ├── route.rs -> "/users"
//! │ └── [id]/
//! │ └── route.rs -> "/users/{id}"
//! └── files/
//! └── [...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:
//! - ```get```
//! - ```post```
//! - ```put```
//! - ```delete```
//! - ```patch```
//! - ```head```
//! - ```options```
//!
//! ### 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,
//! response::IntoResponse
//! };
//!
//! pub async fn get(Path(id): Path<String>) -> impl IntoResponse {
//! 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,
//! response::IntoResponse
//! };
//!
//! pub async fn get(Path(path): Path<String>) -> impl IntoResponse {
//! format!("Requested file path: {}", path)
//! }
//! ```
//!
//! ### State Extraction
//!
//! The state type provided to the macro is available in all route handlers:
//!
//! ```rust
//! use axum::{
//! extract::State,
//! response::IntoResponse
//! };
//!
//! # #[derive(Debug, Clone)]
//! # struct AppState ();
//!
//! pub async fn get(State(state): State<AppState>) -> impl IntoResponse {
//! format!("State: {:?}", state)
//! }
//! ```
//!
//! ## Limitations
//!
//! - **Compile-time Only**: The routing is determined at compile time, so dynamic route registration isn't supported.
//! - **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::{format_ident, quote};
use std::fs;
use std::path::{Path, PathBuf};
use syn::{parse::Parse, parse::ParseStream, parse_macro_input, Ident, LitStr, Result, Token};
struct FolderRouterArgs {
path: String,
state_type: Ident,
}
impl Parse for FolderRouterArgs {
fn parse(input: ParseStream) -> Result<Self> {
let path_lit = input.parse::<LitStr>()?;
input.parse::<Token![,]>()?;
let state_type = input.parse::<Ident>()?;
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 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;
// 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);
// Collect route files
let mut routes = Vec::new();
collect_route_files(&base_dir, &base_dir, &mut routes);
// Generate module definitions and route registrations
let mut module_defs = Vec::new();
let mut route_registrations = Vec::new();
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);
}
}
// Generate the final code
let expanded = quote! {
{
#(#module_defs)*
let mut router = axum::Router::<#state_type>::new();
#(#route_registrations)*
router
}
};
expanded.into()
}
// 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 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()));
}
}
}
}
}
}
// 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())
}