skills/rust-feature-slice/SKILL.md
Feature-based vertical slice architecture for Rust using Axum routers, handler functions, and service traits. No DI framework — dependencies are wired manually via Arc<dyn Trait> in Axum State. CQRS via trait separation: reader traits for queries, writer traits for commands. Use when scaffolding Rust features, creating Axum feature modules, implementing vertical slice architecture in Rust, adding Rust service layers, or organizing Rust code by feature rather than technical layer. Triggers on: "scaffold rust feature", "axum feature folder", "rust vertical slice", "add rust endpoint", "rust service layer", "rust feature module", "rust cqrs". Do NOT use when designing microservice boundaries or inter-process architecture — this skill scopes to module organization within a single Rust binary.
npx skillsauth add michaelalber/ai-toolkit rust-feature-sliceInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
"Organize code by what it does, not by what it is. A feature folder contains everything needed to deliver a capability — not everything that happens to be a handler." -- Adapted from Jimmy Bogard
"In Rust, there is no DI framework. There is ownership. Design your dependencies so that ownership is clear, and wiring becomes mechanical." -- Rust Architecture Principle
Vertical slice architecture in Rust organizes code by feature (domain capability) rather than technical layer (handlers, services, repositories). Each feature is a Rust module containing everything needed to deliver that capability: router, handlers, service trait, service implementation, request/response types, and tests.
Rust has no dependency injection framework. Dependencies are wired manually using Arc<dyn Trait> stored in Axum's State<AppState>. This is not a limitation — it is explicit, compile-time-verified dependency wiring. The AppState struct is the composition root.
CQRS in Rust is a naming convention enforced by trait design: OrderReader (query methods) and OrderWriter (command methods) are separate traits, potentially implemented by the same struct. This separation makes the read/write boundary explicit without requiring a library.
Non-Negotiable Constraints:
use crate::features::other_feature:: in handlers. Features communicate through shared types in a common or domain module, not through direct imports.Arc<dyn OrderService> in AppState, not Arc<OrderServiceImpl>. This enables testing with mock implementations.Arc<dyn Trait> for shared state — not Mutex<ConcreteType>. The trait defines the contract; the Arc provides shared ownership.Result<T, AppError> on all handlers — handlers return Result<impl IntoResponse, AppError>. No panics, no unwrap in handler code.| # | Principle | Description | Applied As |
|---|-----------|-------------|------------|
| 1 | Feature Isolation | Each feature module is a self-contained unit. It owns its router, handlers, service trait, and types. It does not reach into other feature modules. Cross-feature dependencies go through shared domain types. | Enforce with pub(crate) on internal types; use use crate::domain:: for shared types, never use crate::features::other::. |
| 2 | Service Trait Autonomy | The service trait defines the feature's contract. The implementation is an internal detail. Tests use mock implementations. Production uses the real implementation. The handler never knows which it is talking to. | Define pub trait OrderService: Send + Sync in the feature module; implement in a separate struct; inject via Arc<dyn OrderService>. |
| 3 | Minimal Trait Surface | Service traits should expose only the methods that handlers actually call. A trait with 20 methods is a god object. A trait with 3 methods is a focused contract. | Start with the minimum methods needed by the handlers. Add methods only when a new handler requires them. |
| 4 | State Injection via Arc | Axum's State<AppState> is the composition root. AppState holds Arc<dyn Trait> for each service. Handlers extract the service they need via State(state): State<AppState>. | Define AppState with Arc<dyn Trait> fields; use FromRef for sub-state extraction when handlers need only one service. |
| 5 | Read/Write Trait Separation | Query methods (read-only, no side effects) and command methods (mutate state) are defined in separate traits. This makes the CQRS boundary explicit and enables separate implementations if needed. | Define OrderReader and OrderWriter traits; implement both on OrderRepository; inject separately if the handler only needs one. |
| 6 | Immutable Request Types | Request types are plain structs with #[derive(Deserialize)]. They are immutable value objects. Validation happens in the handler or a dedicated validator, not in the request type itself. | Define CreateOrderRequest with #[derive(Deserialize, ToSchema)]; validate in handler before calling service. |
| 7 | Explicit Error Types | Each feature defines its own error type using thiserror. The AppError type in the router layer converts feature errors to HTTP responses. This keeps domain errors separate from HTTP concerns. | Define OrderError with thiserror; implement From<OrderError> for AppError; handlers use ? to propagate. |
| 8 | Validator Co-Location | Input validation logic lives in the feature module, not in a global validator. The feature knows what valid input looks like; the framework does not. | Define validation functions or use the validator crate in the feature module; call from the handler before the service call. |
| 9 | Handler Thinness | A handler that is longer than 20 lines is doing too much. Extract business logic to the service. Extract validation to a validator. The handler's job is: extract → validate → call service → return response. | If a handler exceeds 20 lines, identify what can move to the service or validator. |
| 10 | Test Organization | Unit tests for the service use mock implementations of dependencies. Integration tests use axum::test to test the full handler stack. Both live in the feature module. | #[cfg(test)] module in service.rs for unit tests; tests/features/<name>/ for integration tests. |
| Query | When to Call |
|-------|--------------|
| search_knowledge("Axum router handler state Arc dependency injection") | During scaffold — verify Axum state injection patterns |
| search_knowledge("Rust async trait service layer tokio") | During service trait design — verify async trait patterns |
| search_knowledge("Rust thiserror error handling Result") | During error type design — verify thiserror patterns |
| search_knowledge("Axum testing TestClient integration test") | During test scaffold — verify Axum test patterns |
| search_knowledge("Rust CQRS command query separation trait") | During CQRS design — verify trait separation patterns |
DETECT (before scaffolding)
[ ] Check for existing AppState struct (src/state.rs or src/app.rs)
[ ] Check for existing features/ directory structure
[ ] Identify Rust edition and async runtime (Cargo.toml)
[ ] Check for existing error types (AppError)
[ ] Identify database access pattern (SQLx, Diesel, none)
|
v
SCAFFOLD (create feature module)
[ ] Create src/features/<name>/mod.rs (re-exports)
[ ] Create src/features/<name>/router.rs (Axum Router with State)
[ ] Create src/features/<name>/service.rs (trait + impl)
[ ] Create src/features/<name>/models.rs (request/response types)
[ ] Create src/features/<name>/errors.rs (feature error type)
[ ] Create tests/features/<name>/integration_test.rs
|
v
REGISTER (wire into app)
[ ] Add service to AppState struct
[ ] Add Arc<dyn Trait> initialization in app startup
[ ] Merge feature Router into main app Router
[ ] Add From<FeatureError> for AppError
|
v
VERIFY
[ ] cargo build (no errors)
[ ] cargo test (all tests pass)
[ ] cargo clippy -- -D warnings (no warnings)
Exit criteria: Feature module created, registered in app, tests pass, Clippy clean.
<rust-feature-slice-state>
phase: DETECT | SCAFFOLD | REGISTER | VERIFY | COMPLETE
feature_name: [name]
edition: [2015 | 2018 | 2021]
async_runtime: [tokio | async-std | none]
app_state_exists: [true | false]
features_dir_exists: [true | false]
app_error_exists: [true | false]
files_created: [comma-separated list]
build_status: [pass | fail | not-run]
test_status: [pass | fail | not-run]
last_action: [description]
next_action: [description]
</rust-feature-slice-state>
## Rust Feature Slice Scaffold: [feature-name]
### Files to Create
- [ ] `src/features/[name]/mod.rs`
- [ ] `src/features/[name]/router.rs`
- [ ] `src/features/[name]/service.rs`
- [ ] `src/features/[name]/models.rs`
- [ ] `src/features/[name]/errors.rs`
- [ ] `tests/features/[name]/integration_test.rs`
### Files to Modify
- [ ] `src/state.rs` — add `Arc<dyn [Name]Service>` field
- [ ] `src/app.rs` or `src/main.rs` — merge feature Router, initialize service
- [ ] `src/errors.rs` — add `From<[Name]Error> for AppError`
### Verification
- [ ] `cargo build` passes
- [ ] `cargo test` passes
- [ ] `cargo clippy -- -D warnings` passes
// src/features/orders/mod.rs
// <AI-Generated START>
pub mod errors;
pub mod models;
pub mod router;
pub mod service;
pub use router::orders_router;
pub use service::OrderService;
// <AI-Generated END>
WRONG:
pub struct AppState {
pub order_service: Arc<OrderServiceImpl>,
}
RIGHT:
pub struct AppState {
pub order_service: Arc<dyn OrderService>,
}
The trait in AppState enables mock injection in tests. The concrete type is an implementation detail.
WRONG:
async fn create_order(
State(state): State<AppState>,
Json(req): Json<CreateOrderRequest>,
) -> Result<impl IntoResponse, AppError> {
// 50 lines of business logic here
let validated = if req.items.is_empty() { ... };
let total = req.items.iter().map(|i| i.price * i.qty).sum();
// ... more logic
}
RIGHT:
async fn create_order(
State(state): State<AppState>,
Json(req): Json<CreateOrderRequest>,
) -> Result<impl IntoResponse, AppError> {
let order = state.order_service.create_order(req).await?;
Ok((StatusCode::CREATED, Json(order)))
}
For Rust < 1.75, use async_trait:
#[async_trait::async_trait]
pub trait OrderService: Send + Sync {
async fn create_order(&self, req: CreateOrderRequest) -> Result<Order, OrderError>;
}
For Rust 1.75+, use native async traits:
pub trait OrderService: Send + Sync {
fn create_order(&self, req: CreateOrderRequest)
-> impl Future<Output = Result<Order, OrderError>> + Send;
}
Note both patterns in the scaffold and recommend native async traits for Rust 1.75+.
| # | Anti-Pattern | Why It Fails | Correct Approach |
|---|-------------|-------------|-----------------|
| 1 | Cross-Feature Imports | use crate::features::users::User in the orders feature creates coupling. Changes to the users feature break the orders feature. | Shared types belong in crate::domain or crate::common. Features import from there, not from each other. |
| 2 | Concrete Types in AppState | Arc<OrderServiceImpl> in AppState makes testing impossible without the real implementation. | Use Arc<dyn OrderService>. Inject a mock in tests, the real impl in production. |
| 3 | Fat Handlers | Business logic in handlers cannot be unit-tested without an HTTP request. | Move business logic to the service. Handlers are thin: extract → validate → call → respond. |
| 4 | Mutex<ConcreteType> for State | Mutex<OrderRepository> in AppState serializes all requests through a single lock. | Use Arc<dyn OrderService> where the service manages its own concurrency (e.g., a connection pool). |
| 5 | God Service Trait | A service trait with 20 methods is a god object. Every handler depends on the full trait even if it uses 2 methods. | Split into focused traits: OrderReader (queries) and OrderWriter (commands). |
| 6 | Validation in Request Types | Putting validation logic in impl CreateOrderRequest mixes data and behavior. | Validate in the handler or a dedicated validator function. Request types are pure data. |
| 7 | Panic in Handlers | .unwrap() in a handler panics the entire request, potentially crashing the server. | Return Result<impl IntoResponse, AppError>. Use ? to propagate errors. |
| 8 | String Errors in Service Traits | Result<T, String> in service traits loses type information and makes error handling impossible. | Use thiserror error enums. Implement From<ServiceError> for AppError. |
| 9 | Missing Router Registration | Creating a feature module but forgetting to merge its Router into the app means the routes are never served. | Always verify with cargo test that the routes are reachable. |
| 10 | No Integration Tests | Unit tests for the service don't verify that the handler correctly wires the service call. | Write at least one integration test per handler using axum::test. |
Symptoms: src/state.rs exists but uses a different pattern (e.g., concrete types)
Recovery:
1. Read the existing AppState structure
2. Report the current pattern to the user
3. Ask: "The existing AppState uses [pattern]. Should I adapt the new feature
to match the existing pattern, or refactor AppState to use Arc<dyn Trait>?"
4. Do NOT silently change the existing AppState pattern
5. If user wants to keep existing pattern: scaffold the feature to match
Symptoms: src/features/ does not exist
Recovery:
1. Create src/features/mod.rs with the new feature module declaration
2. Note in the scaffold output: "Created src/features/ directory structure"
3. Add `pub mod features;` to src/lib.rs or src/main.rs
4. Proceed with the feature scaffold
Symptoms: Cargo.toml has async_trait but at an incompatible version
Recovery:
1. Check the Rust edition and version
2. If Rust 1.75+: recommend removing async_trait and using native async traits
3. If Rust < 1.75: check async_trait version compatibility
4. Report the conflict and recommended resolution before scaffolding
| Skill | Relationship |
|-------|-------------|
| axum-scaffolder | Provides the HTTP layer patterns (middleware, OpenAPI, auth). Use rust-feature-slice for module organization; use axum-scaffolder for HTTP infrastructure. |
| sqlx-migration-manager | When the feature requires database access, sqlx-migration-manager provides the safe migration and query patterns for the service implementation. |
| rust-architecture-checklist | After scaffolding, run rust-architecture-checklist to verify the feature follows ownership, trait, and error handling conventions. |
| rust-security-review | After scaffolding, run rust-security-review to verify the feature's auth middleware, input validation, and error handling are secure. |
| dotnet-vertical-slice | Parallel skill for .NET/Blazor codebases. Same vertical slice philosophy; different ecosystem. |
development
Federal / government security overlay applied ON TOP OF a base language security review (dotnet/python/php/rust/react). Language-agnostic: adds NIST SP 800-53 control mapping, FIPS 140-2/3 cryptographic compliance (with a per-language crypto table), CUI handling, EO 14028 supply-chain requirements, and DOE Order 205.1B, and emits POA&M-ready findings with FIPS 199 impact levels. Use for federal/DOE/DOD/national-laboratory systems. Triggers on "federal security review", "NIST compliance", "NIST 800-53", "FISMA", "CUI", "FIPS audit", "DOE security", "POA&M", "ATO review". Do NOT use alone — run the matching <lang>-security-review FIRST; this overlay maps and extends it.
tools
OWASP-based security review of React / TypeScript front-end applications. Detects the framework (Vite/CRA/Next), entry points, and data flows, scans against the OWASP Top 10 (2025) mapped to React client-side patterns (XSS via raw HTML, URL/protocol injection, secrets in the bundle, insecure token storage, dependency CVEs, missing CSP, open redirects), and produces a manager-friendly executive summary plus a graded technical findings table. Use to audit React code for vulnerabilities. Triggers on "react security review", "frontend security audit", "audit react for vulnerabilities", "owasp react", "react xss", "react security posture", "npm audit review". For federal / gov / DOE / NIST / FIPS / CUI context, run security-review-federal after this base review. Do NOT use to grade architecture/structure — use react-architecture-checklist.
tools
Analyzes legacy React codebases and produces actionable modernization plans. Primary migration paths include class components to function components + hooks, Create React App to Vite, React 16/17 to 18 to 19, JavaScript to TypeScript, Enzyme to React Testing Library, legacy Redux to Redux Toolkit / Zustand / Context, and deprecated lifecycle/API removal. Does NOT perform the migration — assesses, quantifies risk, and plans. Triggers on phrases like "modernize react", "class to hooks", "upgrade react", "migrate CRA to vite", "react legacy migration", "react 17 to 18", "react js to typescript", "react technical debt", "enzyme to RTL".
development
Scaffolds feature-based React / TypeScript architecture using feature folders, presentational + container components, custom hooks, a typed data layer, and structural CQRS (query hooks vs mutation hooks). React analog of dotnet-vertical-slice and python-feature-slice — no DI framework; uses props/context for dependency injection and a query cache for server state. Use when creating feature-based React projects, adding React features, organizing components by feature rather than by technical type, or scaffolding a feature's data layer. Triggers on phrases like "scaffold react feature", "create react slice", "react feature folder", "react vertical slice", "add react feature", "react feature architecture", "organize react by feature".