Skip to content

Latest commit

 

History

History
178 lines (153 loc) · 9.79 KB

File metadata and controls

178 lines (153 loc) · 9.79 KB

Project Overview

This is a Rust web service using Actix-web with async/await and SQLx for database access. All code must compile without warnings. Clippy must pass with no exceptions. Production code must never panic. Test code may panic only in test assertions.

Code Style

Naming Conventions

  • File names: snake_case.rs only. No camelCase or PascalCase files.
  • Module names: snake_case matching file names exactly.
  • Public struct names: PascalCase. Private struct names: snake_case with leading underscore if unused.
  • Function names: snake_case for all functions, including async handlers.
  • Constant names: SCREAMING_SNAKE_CASE for module-level constants only.
  • Type parameters: Single uppercase letter (T, E) or descriptive PascalCase (Handler, Request).
  • Lifetime parameters: 'a, 'b, 'static only. Never 'lifetime_name.
  • Error enum variants: PascalCase with Error suffix on enum name (e.g., ValidationError::InvalidEmail).

Type Annotations

  • Never use as type assertions in non-test code. Use TryFrom, TryInto, or type guards instead.
  • Never use _ as a catch-all type. Explicitly name all types in function signatures.
  • Generic bounds must be on the function signature, never in the function body.
  • All public function parameters must have explicit types. No type inference for public APIs.
  • Return types must be explicit for all public functions. Use -> Result<T, E> not -> impl Future.

Formatting and Warnings

  • Run cargo fmt before every commit. Non-formatted code is rejected.
  • Run cargo clippy -- -D warnings and fix all warnings. No #[allow(...)] without justification in a comment.
  • Line length: 100 characters maximum. Break long lines at logical boundaries.
  • Imports: Group in three sections (std, external crates, internal modules) separated by blank lines.
  • No trailing whitespace. No multiple blank lines in sequence.

File Structure

Directory Layout

src/
  main.rs              # Entry point only. Calls run() from lib.rs
  lib.rs               # Public API exports and run() function
  config.rs            # Configuration loading and validation
  error.rs             # AppError enum and error handling utilities
  handlers/
    mod.rs             # Handler module exports
    health.rs          # Health check endpoints
    users.rs           # User-related endpoints
    items.rs           # Item-related endpoints
  models/
    mod.rs             # Model exports
    user.rs            # User struct and validation
    item.rs            # Item struct and validation
  db/
    mod.rs             # Database module exports
    pool.rs            # Connection pool initialization
    queries.rs         # Raw SQL query functions
    migrations.rs      # Migration runner
  middleware/
    mod.rs             # Middleware exports
    auth.rs            # Authentication middleware
    logging.rs         # Request logging middleware
  utils/
    mod.rs             # Utility exports
    validation.rs      # Input validation functions
    crypto.rs          # Hashing and encryption utilities
tests/
  integration/
    mod.rs
    health_test.rs
    users_test.rs

Module Exports

  • Every mod.rs must explicitly re-export public items: pub use self::submodule::*;
  • Never use glob imports in public APIs. Always name specific imports.
  • Private modules use mod submodule; without pub.
  • Public modules use pub mod submodule; only if re-exporting from mod.rs.

Configuration Files

  • Cargo.toml: Pinned versions for all dependencies. No wildcard versions (*).
  • .env.example: Template with all required variables. No actual secrets.
  • CLAUDE.md: This ruleset. Must be kept in sync with actual rules.

Patterns

Error Handling

  • All fallible operations return Result<T, AppError>.
  • AppError enum must have variants for: Database, Validation, NotFound, Unauthorized, Internal.
  • Each variant must include a String message field and optional source: Box<dyn std::error::Error>.
  • Never use .unwrap(), .expect(), or .panic!() in non-test code. Use ? operator instead.
  • Every Result from external crates must be mapped to AppError at the boundary: .map_err(|e| AppError::Database(e.to_string()))?
  • Error responses must use actix_web::error::ErrorResponse with appropriate HTTP status codes.
  • Validation errors return 400 Bad Request. Database errors return 500 Internal Server Error. Missing resources return 404 Not Found.

Async and Concurrency

  • All I/O operations must use async/await. No blocking calls in async context.
  • Database queries must use sqlx::query! (compile-time checked) or sqlx::query_as!, never sqlx::query().
  • Connection pool must be created once in main() and passed via web::Data<PgPool>.
  • Tokio tasks spawned with tokio::spawn() must have explicit Send + Sync bounds if capturing variables.
  • Never use std::thread::spawn(). Use tokio::task::spawn_blocking() for CPU-bound work only.
  • Timeouts must be explicit: wrap all external calls with tokio::time::timeout(Duration::from_secs(N), future).

Handler Functions

  • All handlers must be async fn with signature: async fn handler_name(req: web::Json<Request>, pool: web::Data<PgPool>) -> Result<web::Json<Response>, AppError>
  • Handlers must validate input immediately: call validate_request(&req)? before any business logic.
  • Handlers must return web::Json<T> for success, never raw T.
  • Handlers must never directly access database. Call functions from db::queries module.
  • Handlers must log all errors before returning: log::error!("Handler failed: {}", err);

Database Access

  • All SQL queries must be in db/queries.rs as standalone functions.
  • Query functions must accept &PgPool as first parameter.
  • Query functions must return Result<T, AppError> with AppError::Database on failure.
  • Use sqlx::query! for all queries. Compile-time checking is mandatory.
  • Migrations must be in db/migrations/ directory as numbered SQL files: 001_initial_schema.sql.
  • Run migrations on startup in main() before starting the server.
  • No raw SQL strings in handler code. All queries must be abstracted to db/queries.rs.

Validation

  • All user input must be validated in utils/validation.rs functions.
  • Validation functions must return Result<(), String> with descriptive error messages.
  • Call validation functions in handlers before any database operations.
  • Validation must check: type correctness, length bounds, format (email, URL, etc.), and business rules.
  • Never trust Content-Type headers. Validate actual data structure.

Configuration

  • Load configuration from environment variables in config.rs using std::env::var().
  • Configuration struct must implement Debug but never print secrets.
  • Secrets (database passwords, API keys) must never be logged or included in error messages.
  • Configuration must be validated on startup. Missing required variables must cause immediate panic in main() only.
  • Use .env file for local development only. Never commit .env to version control.

Middleware

  • Authentication middleware must extract and validate JWT tokens from Authorization: Bearer <token> header.
  • Middleware must return 401 Unauthorized for missing or invalid tokens.
  • Logging middleware must log: method, path, status code, and duration. Never log request/response bodies.
  • Middleware must not modify request/response bodies. Use extractors for data access.

Security

  • All password hashing must use argon2 crate with default parameters.
  • Never store plaintext passwords. Hash on creation and verify on login.
  • All external API calls must validate SSL certificates. Never use danger_accept_invalid_certs().
  • SQL injection prevention: use parameterized queries only. sqlx::query! enforces this.
  • CORS headers must be explicitly configured. Never use Access-Control-Allow-Origin: * in production.
  • Rate limiting must be implemented for authentication endpoints: max 5 attempts per minute per IP.

Testing

Test File Organization

  • Integration tests in tests/integration/ directory only.
  • Unit tests in same file as code with #[cfg(test)] module at end of file.
  • Test file names: {module}_test.rs for integration tests, tests module for unit tests.
  • Test function names: test_{scenario}_{expected_outcome} (e.g., test_create_user_returns_201).

Test Requirements

  • Every public function must have at least one happy-path test and one error-case test.
  • Database tests must use a test database with migrations run before each test.
  • Handlers must be tested with actix_web::test::TestServer or actix_web::test::call_service().
  • Mock external services using mockito crate. Never make real HTTP calls in tests.
  • Tests must be deterministic. No random data without seeding. No time-dependent assertions.
  • All tests must complete in under 5 seconds. Slow tests must be marked #[ignore].

Test Assertions

  • Use assert_eq!() for equality checks, never assert!(a == b).
  • Use assert!(condition, "message") for boolean checks with descriptive messages.
  • Use Result::expect() in tests only: result.expect("test setup failed").
  • Test error cases explicitly: assert!(result.is_err(), "expected error").
  • Never use .unwrap() in test setup. Use .expect() with descriptive message instead.

Test Data

  • Create fixtures in tests/fixtures/ directory as JSON or SQL files.
  • Use builder pattern for complex test objects: UserBuilder::new().with_email("test@example.com").build().
  • Test database must be isolated per test. Use transactions that rollback after each test.
  • Never commit test data with real user information. Use placeholder values only.

Source: Codelibrium — the marketplace for AI behaviour files. Browse multiple rulesets at codelibrium.com or install via CLI: npx codelibrium-cli install <ruleset-name>