Install
$ agentstack add skill-pedromneto97-custom-skills-http-actix-axum ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
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](./references/problem-details.md) for the full struct, ResponseError / IntoResponse impl, From, 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](./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](./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() with allow_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](./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: for ApiError`.
→ Read [references/problem-details.md](./references/problem-details.md) for the From 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(
state: web::Data>,
body: web::Json,
) -> Result {
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](./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](./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
fn customer_name_valid(val: &str) -> Result {
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,
}
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: pedromneto97
- Source: pedromneto97/custom-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.