Use when creating or modifying Rust files under src/backend/ in an Axum service — handlers, the router (app.rs), extractors/DTOs, the central AppError IntoResponse error shaper, tower middleware layers, Arc<AppState> shared state, and their tokio tests. Triggers on any .rs change under src/backend/. Covers thin async handlers returning Result<T, AppError>, ?-propagation over unwrap, fail-closed extractor validation, the single IntoResponse error shaper, and router testing via tower oneshot. G...
Scanned 9/4/2026
Install to Claude Code
npx -y skills add kouroshez/coding-os --skill rust --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Rust?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/kouroshez-rust)More formats (shields.io, HTML) on the badges page.
---
name: rust
tier: stack
domain: [backend]
description: Use when creating or modifying Rust files under src/backend/ in an Axum service — handlers, the router (app.rs), extractors/DTOs, the central AppError IntoResponse error shaper, tower middleware layers, Arc<AppState> shared state, and their tokio tests. Triggers on any .rs change under src/backend/. Covers thin async handlers returning Result<T, AppError>, ?-propagation over unwrap, fail-closed extractor validation, the single IntoResponse error shaper, and router testing via tower oneshot. Generic backend design lives in backend-fundamentals.
globs: "src/backend/**/*.rs"
depends_on:
- clean-code
- backend-fundamentals
- api-design
last_reviewed: "2026-06-14"
---
REQUIRED BACKGROUND: You MUST also follow the core `backend-fundamentals` skill (request lifecycle, layering, idempotency) and `clean-code` (fail-closed errors, self-documenting code, error-path tests). This skill adds ONLY Rust + Axum-specific patterns on top; `api-design` governs the public contract.
# rust
## Layer contract (matches `structure.tree`) — the SSOT for layering
| Layer | May import | Never |
|---|---|---|
| `main.rs` | `app.rs` (the router builder), tokio | handler/business logic |
| `app.rs` | `routes::*`, tower layers, `AppState` | request-specific logic |
| `routes/<domain>.rs` (handlers) | `AppState`, extractors, services, `AppError` | another domain's privates, raw response bodies |
| `error.rs` (`AppError`) | domain error types | handlers, route wiring |
Handlers are thin async fns returning `Result<T, AppError>` — they parse via an
extractor, call one service method, and return the value (Axum serializes). The
business logic survives a transport swap because nothing below the handler imports
an Axum type.
## Handlers (thin) & routing
- A handler signature is `async fn(State<Arc<AppState>>, <Extractor>) -> Result<Json<T>, AppError>`.
Parse/validate via the extractor → call ONE service method → return `Ok(Json(..))`.
- Never hand-build the response envelope or `match` on errors to build JSON —
`?`-propagate into `AppError` and let its `IntoResponse` shape the body.
- Routes are assembled in `app.rs` (`Router::new().route(...).with_state(state)`);
one module per domain under `routes/`, re-exported through `routes/mod.rs`.
## Error handling (the one shaper)
- ONE `AppError` enum in `error.rs` implements `IntoResponse` — it is the ONLY
place that writes an error body (RFC 9457 problem shape per
`docs/api-contracts/error-format.md`). Map domain errors into it with
`thiserror` + `From` so handlers stay `?`-only.
- Unknown/internal errors → 500 with a generic body; full detail goes to the
`tracing` logger only — never a `Debug`/backtrace or DB message to the client.
## `?` over `.unwrap()` / `.expect()`
- Request paths NEVER `.unwrap()` / `.expect()` — convert with `?` into `AppError`.
A panic in a handler aborts the task and leaks nothing useful to the client.
- `.unwrap()`/`.expect()` is allowed ONLY at startup (binding the listener, reading
required config) where a failure SHOULD abort the process before serving traffic.
## Extractors, state & middleware
- Input validation is fail-closed at the extractor boundary: `Json<Dto>` /
`Query<Dto>` reject malformed input (400) before the handler body runs; a handler
never reads the raw request.
- Shared state is `Arc<AppState>` injected via `State<Arc<AppState>>` — no global
mutable statics, no `lazy_static!` request state.
- Tower middleware (`TraceLayer`, timeout, CORS, auth) lives in `app.rs` as
`.layer(...)` on the router — never inside an individual handler.
## Testing
- `#[tokio::test]` driving the router via `tower::ServiceExt::oneshot(request)` —
no real port is bound. One happy + one error path per route minimum.
- Services: pure unit tests with fakes, no router. Assert the `AppError` variant on
the error path, not just the status, so a remap is caught.
- `cargo clippy -- -D warnings` is the lint gate; warnings are errors.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!