Http actix axum
List of custom agent skills
npx -y skills add pedromneto97/custom-skills --skill http-actix-axumAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
HTTP best practices for actix-web 4 and axum 0.7+ Rust backends. Use when: naming REST resources, choosing HTTP status codes, implementing RFC 9457 Problem Details error responses, configuring OWASP security headers, setting up CORS, enabling response compression, versioning APIs, implementing custom request extractors, wiring OpenAPI docs, or structuring the HTTP layer. Covers both actix-web and axum.
SKILL.md
7.5 KB, as published. Nobody here has run it
HTTP Best Practices — actix-web / axum
1. Resource Naming
| Rule | Good | Bad |
|---|---|---|
| Plural nouns | /orders, /users | /order, /getOrders |
| Lowercase + hyphens | /order-items | /orderItems, /Order_Items |
| Hierarchical nesting | /orders/{id}/items | /order-items?orderId={id} |
| No verbs in path | POST /orders | POST /createOrder |
| Filter / sort in query | /orders?status=pending&sort=created_at | /pending-orders |
Max nesting depth: 2 levels (/resource/{id}/sub-resource). Avoid deeper hierarchies.
2. API Versioning
Prefix at the router level. Handlers are version-agnostic.
actix-web
// inbound/src/http/router.rs
pub fn configure(cfg: &mut web::ServiceConfig) {
cfg.service(
web::scope("/api/v1")
.service(web::scope("/orders")
.route("", web::get().to(list))
.route("", web::post().to(create))
.route("/{id}", web::get().to(get_one))
.route("/{id}", web::put().to(update))
.route("/{id}", web::delete().to(delete)),
),
);
}
axum
// inbound/src/http/router.rs
pub fn build_router() -> Router {
Router::new()
.nest("/api/v1", Router::new()
.nest("/orders", Router::new()
.route("/", get(list).post(create))
.route("/:id", get(get_one).put(update).delete(delete)),
),
)
}
3. HTTP Status Codes
| Operation | Method | Success | Error cases |
|---|---|---|---|
| Fetch one | GET | 200 | 404 if not found |
| Fetch list | GET | 200 | Empty list → 200 [], never 404 |
| Create | POST | 201 + Location header | 400, 422 |
| Full replace | PUT | 200 | 404, 422 |
| Partial update | PATCH | 200 | 404, 422 |
| Delete | DELETE | 204 No Content | 404 |
| Async action | POST | 202 Accepted | — |
| Bad input | — | 400 Bad Request | — |
| Unauthenticated | — | 401 Unauthorized | — |
| Forbidden | — | 403 Forbidden | — |
| Conflict (duplicate) | — | 409 Conflict | — |
| Business rule violated | — | 422 Unprocessable Entity | — |
| Server fault | — | 500 Internal Server Error | Never leak stack traces |
// 201 + Location (actix-web)
HttpResponse::Created()
.insert_header(("Location", format!("/api/v1/orders/{}", order.id)))
.json(OrderResponse::from(order))
// 201 + Location (axum)
(StatusCode::CREATED, [("Location", format!("/api/v1/orders/{}", order.id))], Json(body))
4. Error Responses — Problem Details (RFC 9457)
→ Read references/problem-details.md for the full struct,
ResponseError / IntoResponse impl, From<DomainError>, and validation error mapping.
Quick rules:
Content-Type: application/problem+jsontypeis a URI; use"about:blank"when no dedicated error page exists- Never expose stack traces, internal IDs, or DB details in
detail - Convert framework extractor errors (JSON/query/path) into sanitized Problem Details
5. Security Headers (OWASP)
→ Read references/security-headers.md for middleware
implementation (actix-web Transform + axum tower-http layer).
Mandatory headers on every response:
| Header | Value |
|---|---|
X-Content-Type-Options | nosniff |
X-Frame-Options | DENY |
Referrer-Policy | strict-origin-when-cross-origin |
Content-Security-Policy | default-src 'self' (tune per app) |
Permissions-Policy | geolocation=(), microphone=(), camera=() |
Strict-Transport-Security | max-age=31536000; includeSubDomains (HTTPS only) |
Remove Server and X-Powered-By response headers.
6. CORS
→ Read references/cors.md for full configuration.
Decision guide:
| Scenario | Strategy |
|---|---|
| Public API, no cookies | allow_any_origin() |
| Cookie-auth / credentialed | Explicit origin allowlist + allow_credentials(true) |
Never combine
allow_any_origin()withallow_credentials(true)— browsers reject it.
For cookie/session auth, combine CORS policy with cookie security (Secure, HttpOnly,
SameSite) and an explicit origin allowlist.
7. Response Compression
→ Read references/compression.md for middleware setup.
- Priority order: Brotli → gzip → deflate (auto-negotiated from
Accept-Encoding) - Skip already-compressed content: images (jpeg/png/gif/webp), video,
application/zip,application/pdf - Skip small responses: < 1 KB gains nothing
8. Input Validation
Use the validator crate to annotate request structs. Call .validate() at the top of each handler; convert ValidationErrors to a 400 Problem Detail via From<ValidationErrors> for ApiError.
→ Read references/problem-details.md for the From<ValidationErrors> impl and the domain-validation bridge pattern.
actix-web
#[derive(Deserialize, Validate)]
#[serde(rename_all = "camelCase")]
pub struct CreateOrderRequest {
#[validate(length(min = 1, max = 100))]
pub customer_name: String,
#[validate(email)]
pub email: String,
}
pub async fn create_order<R: AppRepository>(
state: web::Data<AppState<R>>,
body: web::Json<CreateOrderRequest>,
) -> Result<impl Responder, ApiError> {
body.validate()?; // ValidationErrors → ApiError via From impl → 400 Problem Detail
let order = orders::create_order(&state.repo, body.into_inner().into()).await?;
Ok(HttpResponse::Created()
.insert_header(("Location", format!("/api/v1/orders/{}", order.id)))
.json(OrderResponse::from(order)))
}
9. Custom Extractors
Use typed request extractors (FromRequest in actix-web, FromRequestParts in axum) for
cross-cutting concerns like auth claims, tenant context, and request-scoped metadata.
→ Read references/extractors.md.
10. OpenAPI / Swagger
Keep handlers transport-focused and annotate route contracts where possible. Expose docs routes conditionally (often only in non-production/debug builds).
→ Read references/openapi.md.
Domain-validation bridge (when domain owns the rules):
use validator::ValidationError;
use domain::use_cases::validate_customer_name; // pure domain fn → Vec<String>
fn customer_name_valid(val: &str) -> Result<(), ValidationError> {
match validate_customer_name(val) {
Ok(_) => Ok(()),
Err(errors) => {
let mut e = ValidationError::new("customer_name")
.with_message("Invalid customer name".into());
e.add_param("errors".into(), &errors);
Err(e)
}
}
}
#[derive(Deserialize, Validate)]
pub struct CreateOrderRequest {
#[validate(custom(function = "customer_name_valid"))]
pub customer_name: String,
}