

Also from Kynth Studios


Also from Kynth Studios


Also from Kynth Studios
1# Rust + Actix Web — Cursor Rules2# Comprehensive rules for building web APIs with Rust and Actix Web34## Project Context5You are working on a Rust web API built with the Actix Web framework. The codebase6follows Rust idioms, emphasizes type safety and zero-cost abstractions, and uses the7Rust ownership model for memory safety without a garbage collector. The project uses8Cargo for dependency management and follows the Rust 2021 edition.910## Tech Stack11- Rust 2021 edition (stable toolchain)12- Actix Web 4.x for HTTP framework13- SQLx for async database access (compile-time checked queries)14- Serde for serialization/deserialization15- Tokio as async runtime (via Actix)16- Tracing + tracing-subscriber for structured logging17- Cargo for build and dependency management18- Docker for deployment1920## Coding Style2122### Naming Conventions23- Types/structs/enums: PascalCase (e.g., `UserService`, `OrderStatus`)24- Functions/methods: snake_case (e.g., `create_user`, `get_order_by_id`)25- Modules: snake_case (e.g., `user_handler`, `order_service`)26- Constants: UPPER_SNAKE_CASE (e.g., `MAX_PAGE_SIZE`, `DEFAULT_TIMEOUT`)27- Traits: PascalCase, descriptive of behavior (e.g., `UserRepository`, `Authenticator`)28- Crate names: kebab-case in Cargo.toml, snake_case in code29- Error types: PascalCase with `Error` suffix (e.g., `AppError`, `AuthError`)30- Lifetimes: short, descriptive if multiple (`'a`, `'req`, `'conn`)3132### Project Structure33```34src/35 main.rs # Entry point, server setup36 config.rs # Configuration loading (env vars, files)37 lib.rs # Library root (re-exports)38 errors.rs # Error types and conversions39 handlers/ # HTTP handlers (thin — extract, delegate, respond)40 mod.rs41 user.rs42 order.rs43 services/ # Business logic44 mod.rs45 user.rs46 order.rs47 models/ # Domain types and database models48 mod.rs49 user.rs50 order.rs51 repositories/ # Data access layer52 mod.rs53 user.rs54 order.rs55 middleware/ # Actix middleware56 mod.rs57 auth.rs58 logging.rs59 extractors/ # Custom Actix extractors60 mod.rs61 auth.rs62migrations/ # SQLx migrations63tests/ # Integration tests64 common/mod.rs # Shared test utilities65 api/ # API integration tests66```6768## Rust/Actix Patterns6970### Handler Pattern71```rust72use actix_web::{web, HttpResponse, Result};7374pub async fn get_user(75 path: web::Path<i64>,76 service: web::Data<UserService>,77) -> Result<HttpResponse, AppError> {78 let user_id = path.into_inner();79 let user = service.find_by_id(user_id).await?;8081 match user {82 Some(user) => Ok(HttpResponse::Ok().json(UserResponse::from(user))),83 None => Err(AppError::NotFound(format!("User {} not found", user_id))),84 }85}8687pub async fn create_user(88 body: web::Json<CreateUserRequest>,89 service: web::Data<UserService>,90) -> Result<HttpResponse, AppError> {91 let input = body.into_inner();92 let user = service.create(input).await?;93 Ok(HttpResponse::Created().json(UserResponse::from(user)))94}9596pub fn configure(cfg: &mut web::ServiceConfig) {97 cfg.service(98 web::scope("/users")99 .route("", web::post().to(create_user))100 .route("/{id}", web::get().to(get_user))101 .route("/{id}", web::put().to(update_user))102 .route("/{id}", web::delete().to(delete_user)),103 );104}105```106107### Error Handling108```rust109use actix_web::{HttpResponse, ResponseError};110use std::fmt;111112#[derive(Debug)]113pub enum AppError {114 NotFound(String),115 BadRequest(String),116 Unauthorized(String),117 Internal(String),118 Database(sqlx::Error),119}120121impl fmt::Display for AppError {122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {123 match self {124 Self::NotFound(msg) => write!(f, "Not found: {msg}"),125 Self::BadRequest(msg) => write!(f, "Bad request: {msg}"),126 Self::Unauthorized(msg) => write!(f, "Unauthorized: {msg}"),127 Self::Internal(msg) => write!(f, "Internal error: {msg}"),128 Self::Database(e) => write!(f, "Database error: {e}"),129 }130 }131}132133impl ResponseError for AppError {134 fn error_response(&self) -> HttpResponse {135 let (status, message) = match self {136 Self::NotFound(msg) => (StatusCode::NOT_FOUND, msg.clone()),137 Self::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg.clone()),138 Self::Unauthorized(msg) => (StatusCode::UNAUTHORIZED, msg.clone()),139 Self::Internal(_) | Self::Database(_) => {140 tracing::error!("Internal error: {self}");141 (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error".into())142 }143 };144 HttpResponse::build(status).json(serde_json::json!({"error": message}))145 }146}147148impl From<sqlx::Error> for AppError {149 fn from(e: sqlx::Error) -> Self {150 Self::Database(e)151 }152}153```154155### Serde Models156```rust157use serde::{Deserialize, Serialize};158use chrono::{DateTime, Utc};159160#[derive(Debug, Serialize, sqlx::FromRow)]161pub struct User {162 pub id: i64,163 pub email: String,164 pub name: String,165 pub created_at: DateTime<Utc>,166 pub updated_at: DateTime<Utc>,167}168169#[derive(Debug, Deserialize)]170pub struct CreateUserRequest {171 #[serde(deserialize_with = "validate_email")]172 pub email: String,173 pub name: String,174 pub password: String,175}176177#[derive(Debug, Serialize)]178pub struct UserResponse {179 pub id: i64,180 pub email: String,181 pub name: String,182 pub created_at: DateTime<Utc>,183}184185impl From<User> for UserResponse {186 fn from(u: User) -> Self {187 Self {188 id: u.id,189 email: u.email,190 name: u.name,191 created_at: u.created_at,192 }193 }194}195```196197### Service Pattern198```rust199pub struct UserService {200 pool: PgPool,201}202203impl UserService {204 pub fn new(pool: PgPool) -> Self {205 Self { pool }206 }207208 pub async fn find_by_id(&self, id: i64) -> Result<Option<User>, AppError> {209 let user = sqlx::query_as!(User, "SELECT * FROM users WHERE id = $1", id)210 .fetch_optional(&self.pool)211 .await?;212 Ok(user)213 }214215 pub async fn create(&self, input: CreateUserRequest) -> Result<User, AppError> {216 let password_hash = hash_password(&input.password)?;217 let user = sqlx::query_as!(218 User,219 "INSERT INTO users (email, name, password_hash) VALUES ($1, $2, $3) RETURNING *",220 input.email, input.name, password_hash,221 )222 .fetch_one(&self.pool)223 .await224 .map_err(|e| match e {225 sqlx::Error::Database(ref db_err) if db_err.constraint() == Some("users_email_key") => {226 AppError::BadRequest("Email already registered".into())227 }228 e => AppError::Database(e),229 })?;230 Ok(user)231 }232}233```234235## Ownership and Borrowing236- Use `&str` for function parameters when you only need to read a string237- Use `String` in structs that own their data238- Use `Arc<T>` for shared ownership across threads/tasks (via `web::Data`)239- Prefer cloning over complex lifetime annotations in web handlers240- Use `Cow<'_, str>` when a function might or might not allocate241242## Testing243```rust244#[cfg(test)]245mod tests {246 use super::*;247 use actix_web::{test, App};248249 #[actix_web::test]250 async fn test_get_user_not_found() {251 let pool = setup_test_db().await;252 let service = web::Data::new(UserService::new(pool));253 let app = test::init_service(254 App::new().app_data(service.clone()).configure(configure),255 ).await;256257 let req = test::TestRequest::get().uri("/users/999").to_request();258 let resp = test::call_service(&app, req).await;259 assert_eq!(resp.status(), StatusCode::NOT_FOUND);260 }261}262```263264## Performance Guidelines265- Use `web::Data<T>` (wraps `Arc`) for shared application state266- Use connection pooling with SQLx `PgPool`267- Prefer `query_as!` macro for compile-time checked SQL268- Use streaming responses for large payloads (`HttpResponse::Ok().streaming(...)`)269- Avoid unnecessary allocations — use references and slices where possible270- Use `tokio::spawn` for CPU-intensive work to avoid blocking the runtime271- Profile with `cargo flamegraph` or `perf`272273## Security274- Validate all input with serde and custom validators275- Use parameterized queries (SQLx prevents SQL injection by default)276- Hash passwords with `argon2` or `bcrypt` crate277- Set security headers in middleware278- Use `actix-cors` with explicit origins279- Never expose internal errors to clients280- Rate limit with `actix-governor`281282## Common Pitfalls283- Forgetting to call `.await` on async functions (Rust won't warn if result is unused)284- Holding a `MutexGuard` across `.await` points (causes deadlocks)285- Not implementing `From` conversions for error types (verbose `map_err` everywhere)286- Using `unwrap()` in production code — use `?` operator or handle explicitly287- Blocking the async runtime with synchronous I/O (use `tokio::task::spawn_blocking`)288- Circular module dependencies — restructure with traits289- Forgetting `#[derive(Serialize)]` or `#[derive(Deserialize)]` on types used in JSON290- Not running `cargo clippy` — it catches many subtle issues291
One repository carrying more than one format is the comparison this product exists for: does anyone actually write different content in each file, or is one a copy of the other?
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| survivorforge/cursor-rulesrules/ai-ml-python/.cursorrules · 17 | .cursorrules | teststylearchdeployment+2 | 81/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/api-microservices/.cursorrules · 17 | .cursorrules | buildteststylearch+5 | 92/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/database-sql/.cursorrules · 17 | .cursorrules | styletypessecuritydatabase+3 | 65/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/devops-docker/.cursorrules · 17 | .cursorrules | setupbuildteststyle+4 | 93/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/devops-infrastructure/.cursorrules · 17 | .cursorrules | buildteststylesecurity+3 | 93/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/django-rest/.cursorrules · 17 | .cursorrules | buildteststylearch+5 | 84/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/docker-devops/.cursorrules · 17 | .cursorrules | setupteststylearch+6 | 85/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/flutter-dart/.cursorrules · 17 | .cursorrules | teststylearchtypes+5 | 89/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/go-gin/.cursorrules · 17 | .cursorrules | testlint-formatstylearch+5 | 84/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/langchain-ai/.cursorrules · 17 | .cursorrules | testlint-formatstylearch+4 | 84/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/mobile-react-native/.cursorrules · 17 | .cursorrules | teststylearchtypes+7 | 89/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/nextjs-14-app-router/.cursorrules · 17 | .cursorrules | teststyletypestesting-strategy+3 | 71/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/nextjs-app-router/.cursorrules · 17 | .cursorrules | teststylearchtypes+5 | 81/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/nextjs-typescript/.cursorrules · 17 | .cursorrules | teststylearchtypes+5 | 81/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/nextjs/.cursorrules · 17 | .cursorrules | teststylearchtypes+5 | 81/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/nodejs-express-typescript/.cursorrules · 17 | .cursorrules | setupteststylearch+7 | 81/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/nodejs-express/.cursorrules · 17 | .cursorrules | testlint-formatstylearch+7 | 92/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/performance-optimization/.cursorrules · 17 | .cursorrules | styledatabaseapiperformance+2 | 65/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/python-django/.cursorrules · 17 | .cursorrules | testlint-formatstylearch+6 | 99/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/python-fastapi/.cursorrules · 17 | .cursorrules | testlint-formatstylearch+6 | 99/100 | 13 days ago |
A badge carrying the measured quality of the strongest agent config file in this repository, out of 100. It reads from this index every time somebody loads your page, so it changes when the measurement changes and there is nothing to keep up to date. Free, no account, and the value is not something you or we can set by hand.
[](https://rulestack.kynth.studio/configs/survivorforge-cursor-rules-rules-rust-actix-cursorrules)Would rather not hotlink us? Every badge is also served in shields.io’s endpoint schema, so shields renders the image and your readers never talk to our domain:
Published by Toolproof, the masthead over this index and eight others. The method behind the number is at toolproof.kynth.studio/methodology, and the whole thing is readable as JSON with no key at /api.