---
description: Rust Axum web API conventions
globs: ["src/**/*.rs","Cargo.toml"]
alwaysApply: false
---

# Rust + Axum 0.8

- Router built in `app.rs` (`fn router(state: AppState) -> Router`) so tests can call it; `main.rs` only loads config, sets up tracing, and serves with `tokio::net::TcpListener` + `axum::serve`.
- Axum 0.8 path syntax: `/users/{id}` and `/files/{*path}` (not `:id`).
- Shared state in a `#[derive(Clone)] struct AppState` (pool, config) passed with `.with_state()`; extract via `State<AppState>`.
- Handlers take extractors (`Path`, `Query`, `Json`) and return `Result<impl IntoResponse, AppError>`. `AppError` is a `thiserror` enum implementing `IntoResponse` that maps variants to status codes and hides internal details.
- No `unwrap()`/`expect()` in request paths; use `?`.
- Database with `sqlx` (`query_as!` macros checked at compile time, migrations in `migrations/` run with `sqlx migrate run`). Keep `.sqlx/` offline data updated with `cargo sqlx prepare`.
- Validate input (e.g. `validator` crate or custom `TryFrom` newtypes) before use.
- Observability: `tracing` + `tower_http::trace::TraceLayer`; add timeouts and a body size limit via `tower_http` layers.
- Don't hold a lock (`Mutex`) across `.await`; use `tokio::sync` types where needed. CPU-heavy work goes in `spawn_blocking`.

## Done when
`cargo fmt --check`, `cargo clippy --all-targets -- -D warnings`, and `cargo test` pass. Handler tests use `tower::ServiceExt::oneshot` on the router.
