add example

This commit is contained in:
2026-05-26 02:52:27 -04:00
parent 513cbc19a3
commit 386eb59f84
4 changed files with 161 additions and 197 deletions

103
README.md
View File

@@ -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<S>` 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<Config>,
metrics: Arc<Metrics>,
}
#[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<AppState>) -> String {
format!("{}:ok", state.config.service_name)
}
async fn metrics_handler(Extension(metrics): Extension<Arc<Metrics>>) -> &'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::<AppState>::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::<AppState>::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::<AppState>::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::<AppState>::new()`. That gives Rust enough context to infer the `state` parameter:
@@ -161,6 +67,3 @@ impl AppPlugin<AppState> for ConfigPlugin {
}
}
```
Shutdown hooks run like stack unwinding: the last registered plugin shuts down first, and each hook
finishes before the next one starts.