From 386eb59f84e119aeb8d161741eb177e9af1a40db Mon Sep 17 00:00:00 2001 From: fa-sharp Date: Tue, 26 May 2026 02:52:27 -0400 Subject: [PATCH] add example --- Cargo.lock | 145 ++++++++++++++++++------------------------------ Cargo.toml | 18 ++++-- README.md | 103 +--------------------------------- examples/app.rs | 92 ++++++++++++++++++++++++++++++ 4 files changed, 161 insertions(+), 197 deletions(-) create mode 100644 examples/app.rs diff --git a/Cargo.lock b/Cargo.lock index 3310244..51d74c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -55,6 +55,8 @@ dependencies = [ "axum", "axum-app-wrapper-macros", "futures", + "tokio", + "tracing", "type-map", ] @@ -92,6 +94,16 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + [[package]] name = "fnv" version = "1.0.7" @@ -287,9 +299,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "libc" -version = "0.2.177" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "log" @@ -317,13 +329,13 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mio" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "wasi", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -445,6 +457,16 @@ dependencies = [ "serde", ] +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + [[package]] name = "slab" version = "0.4.11" @@ -459,12 +481,12 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys", ] [[package]] @@ -486,23 +508,24 @@ checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" [[package]] name = "tokio" -version = "1.48.0" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "libc", "mio", "pin-project-lite", + "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] name = "tokio-macros" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", @@ -539,20 +562,32 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", + "tracing-attributes", "tracing-core", ] [[package]] -name = "tracing-core" -version = "0.1.34" +name = "tracing-attributes" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", ] @@ -584,15 +619,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets", -] - [[package]] name = "windows-sys" version = "0.61.2" @@ -601,68 +627,3 @@ checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", ] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "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.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" diff --git a/Cargo.toml b/Cargo.toml index 448265e..664a13d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,15 +1,23 @@ -[workspace] -members = [".", "crates/*"] -resolver = "2" - [package] name = "axum-app-wrapper" version = "0.1.1" edition = "2024" +[workspace] +resolver = "2" +members = [".", "crates/*"] + [dependencies] anyhow = "1.0" axum = "0.8" +axum-app-wrapper-macros = { path = "crates/axum-app-wrapper-macros" } futures = "0.3" type-map = "0.5" -axum-app-wrapper-macros = { path = "crates/axum-app-wrapper-macros" } + +[dev-dependencies] +tokio = { + version = "1.52.3", + default-features = false, + features = ["net", "rt", "rt-multi-thread", "signal"] +} +tracing = "0.1.44" diff --git a/README.md b/README.md index 2b476f0..c7735a5 100644 --- a/README.md +++ b/README.md @@ -17,106 +17,12 @@ This crate is intentionally thin. It does not replace axum's router, extractors, 1. `on_init` in registration order, passing a `TypeMap` to build state. 2. `S::try_from(TypeMap)` to build the final typed app state. 3. `on_setup` in registration order, passing `Router` and `&S`. -4. `on_shutdown` consecutively in reverse registration order +4. `on_shutdown` consecutively in reverse registration order: the last registered plugin shuts down first, and each hook finishes before the next one starts. + ## Example +See the `examples` folder for full examples. -```rust -use std::{net::SocketAddr, sync::Arc}; - -use axum::{ - extract::State, - routing::get, - Extension, -}; -use axum_app_wrapper::{AdHocPlugin, App, AppState}; - -#[derive(Clone, AppState)] -struct AppState { - config: Arc, - metrics: Arc, -} - -#[derive(Clone)] -struct Config { - service_name: String, -} - -struct Metrics; - -impl Metrics { - fn new() -> Self { - Self - } - - async fn flush(&self) { - tracing::info!("flushed metrics"); - } -} - -async fn health(State(state): State) -> String { - format!("{}:ok", state.config.service_name) -} - -async fn metrics_handler(Extension(metrics): Extension>) -> &'static str { - let _metrics = metrics; - "metrics:ok" -} - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - let config = Arc::new(Config { - service_name: "api".to_owned(), - }); - let metrics_registry = Arc::new(Metrics::new()); - - let config_plugin = AdHocPlugin::::new() - .on_init(async move |mut state| { - state.insert(config); - Ok(state) - }) - .on_setup(|router, state| { - tracing::info!(service = %state.config.service_name, "configuring routes"); - Ok(router.route("/health", get(health))) - }); - - let metrics_plugin = AdHocPlugin::::new() - .on_init(async move |mut state| { - state.insert(metrics_registry); - Ok(state) - }) - .on_setup(|router, state| { - Ok(router - .route("/metrics", get(metrics_handler)) - .layer(Extension(Arc::clone(&state.metrics)))) - }) - .on_shutdown(|state| { - let metrics = Arc::clone(&state.metrics); - async move { - metrics.flush().await; - } - }); - - let app = App::::new() - .register(config_plugin) - .register(metrics_plugin); - - let (router, state, on_shutdown) = app.init().await?; - let router = router.with_state(state); - - let addr: SocketAddr = "127.0.0.1:3000".parse()?; - let listener = tokio::net::TcpListener::bind(addr).await?; - - axum::serve(listener, router) - .with_graceful_shutdown(async { - tokio::signal::ctrl_c().await.expect("failed to listen for ctrl-c"); - on_shutdown.await; - }) - .await?; - - Ok(()) -} -``` For `on_setup` closures that access typed state, construct the plugin as `AdHocPlugin::::new()`. That gives Rust enough context to infer the `state` parameter: @@ -161,6 +67,3 @@ impl AppPlugin for ConfigPlugin { } } ``` - -Shutdown hooks run like stack unwinding: the last registered plugin shuts down first, and each hook -finishes before the next one starts. diff --git a/examples/app.rs b/examples/app.rs new file mode 100644 index 0000000..3e59dc0 --- /dev/null +++ b/examples/app.rs @@ -0,0 +1,92 @@ +use std::{net::SocketAddr, sync::Arc}; + +use axum::{Extension, extract::State, routing::get}; +use axum_app_wrapper::{AdHocPlugin, App, AppState}; + +#[derive(Clone, AppState)] +struct AppState { + config: Arc, + metrics: Arc, +} + +#[derive(Clone)] +struct Config { + service_name: String, +} + +struct Metrics; + +impl Metrics { + fn new() -> Self { + Self + } + + async fn flush(&self) { + tracing::info!("flushed metrics"); + } +} + +async fn health(State(state): State) -> String { + format!("{}:ok", state.config.service_name) +} + +async fn metrics_handler(Extension(metrics): Extension>) -> &'static str { + let _metrics = metrics; + "metrics:ok" +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let config = Arc::new(Config { + service_name: "api".to_owned(), + }); + let metrics_registry = Arc::new(Metrics::new()); + + let config_plugin = AdHocPlugin::::new() + .on_init(async move |mut state| { + state.insert(config); + Ok(state) + }) + .on_setup(|router, state| { + tracing::info!(service = %state.config.service_name, "configuring routes"); + Ok(router.route("/health", get(health))) + }); + + let metrics_plugin = AdHocPlugin::::new() + .on_init(async move |mut state| { + state.insert(metrics_registry); + Ok(state) + }) + .on_setup(|router, state| { + Ok(router + .route("/metrics", get(metrics_handler)) + .layer(Extension(Arc::clone(&state.metrics)))) + }) + .on_shutdown(|state| { + let metrics = Arc::clone(&state.metrics); + async move { + metrics.flush().await; + } + }); + + let app = App::::new() + .register(config_plugin) + .register(metrics_plugin); + + let (router, state, on_shutdown) = app.init().await?; + let router = router.with_state(state); + + let addr: SocketAddr = "127.0.0.1:3000".parse()?; + let listener = tokio::net::TcpListener::bind(addr).await?; + + axum::serve(listener, router) + .with_graceful_shutdown(async { + tokio::signal::ctrl_c() + .await + .expect("failed to listen for ctrl-c"); + on_shutdown.await; + }) + .await?; + + Ok(()) +}