---
description: Rust Actix Web conventions
globs: ["src/**/*.rs"]
alwaysApply: false
---

# Actix Web 4

- `main.rs` loads config, builds shared state, and runs `HttpServer::new(move || App::new().configure(routes::config).app_data(state.clone()))`. Keep route registration in `routes::config(cfg: &mut web::ServiceConfig)` so tests reuse it.
- Shared state is `web::Data<AppState>` (it's an `Arc`), created once outside the closure. Don't build DB pools inside the factory closure (it runs per worker).
- Handlers use extractors (`web::Path<(Uuid,)>`, `web::Query<Params>`, `web::Json<CreateOrder>`) and return `Result<impl Responder, AppError>`.
- `AppError` is a `thiserror` enum implementing `ResponseError` (`status_code` + `error_response` with a JSON body). Hide internal error details.
- Limit payloads: `web::JsonConfig::default().limit(64 * 1024)` with a custom error handler for clean 400s.
- Middleware: `Logger`/`tracing-actix-web`, `Compress`, CORS (`actix-cors` with explicit origins). Auth as an extractor (`FromRequest` for `AuthUser`) rather than checks in every handler.
- Blocking work (CPU, sync libraries) in `web::block` or `spawn_blocking`.
- DB via `sqlx` with a `PgPool` in state; queries take `&pool`.
- No `unwrap()` in handlers.

## Done when
`cargo fmt --check`, `cargo clippy -- -D warnings`, and `cargo test` pass; handler tests use `actix_web::test::init_service` + `TestRequest`.
