SystemPrompt is a world-class Rust programming brand. Every Rust file in extensions and MCP servers must be instantly recognizable as on-brand, world-class idiomatic Rust as Steve Klabnik would write it. No exceptions. No shortcuts. No compromise.

Checkable, actionable patterns. Run cargo clippy --workspace -- -D warnings and cargo fmt --all after changes.


0. Idiomatic Rust

Write code that would pass Steve Klabnik's review. Prefer iterator chains, combinators, and pattern matching over imperative control flow.

Option/Result Combinators

let name = request.name.as_deref().map(str::trim);

let value = opt.unwrap_or_else(|| compute_default());

let result = input.ok_or_else(|| Error::Missing)?;

Pattern Matching

match request.name.as_deref().map(str::trim) {
    Some("") => return Err(ApiError::bad_request("Name cannot be empty")),
    Some(name) => name.to_owned(),
    None => generate_default(),
}

Iterator Chains

let valid_items: Vec<_> = items
    .iter()
    .filter(|item| item.is_active())
    .map(|item| item.to_dto())
    .collect();

Avoid

Anti-Pattern Idiomatic
if let Some(x) = opt { x } else { default } opt.unwrap_or(default)
match opt { Some(x) => Some(f(x)), None => None } opt.map(f)
if condition { Some(x) } else { None } condition.then(|| x)
Nested if let / match Combine with and_then, map, ok_or
Manual loops building Vec Iterator chains with collect()
match with guards when combinators suffice filter, map, and_then

1. Limits

Metric Limit
Source file length 300 lines
Cognitive complexity 15
Function length 75 lines
Parameters 5

2. Forbidden Constructs

Construct Resolution
unsafe Remove - forbidden in this codebase
unwrap() Use ?, ok_or_else(), or expect() with descriptive message
panic!() / todo!() / unimplemented!() Return Result or implement
Inline comments (//) ZERO TOLERANCE - delete all. Code documents itself through naming and structure
Doc comments (///, //!) ZERO TOLERANCE - no API docs, no rustdoc. Only exception: rare //! module docs at file top when absolutely necessary
TODO/FIXME/HACK comments Fix immediately or don't write
Tests in source files (#[cfg(test)]) Move to separate test crate

3. Mandatory Patterns

Typed Identifiers

All identifier fields use wrappers from systemprompt_identifiers:

// WRONG
pub struct Content { pub id: String, pub user_id: String }

// RIGHT
use systemprompt_identifiers::{ContentId, UserId};
pub struct Content { pub id: ContentId, pub user_id: UserId }

Available: SessionId, UserId, AgentId, TaskId, ContextId, TraceId, ClientId, AgentName, AiToolCallId, McpExecutionId, SkillId, SourceId, CategoryId, ContentId.

Logging

All logging via tracing. No println! in library code.

use tracing::{info, error, debug, warn};

// Structured fields (preferred over format strings)
tracing::info!(user_id = %user.id, "Created user");
tracing::error!(error = %e, "Operation failed");

// In handlers/services
tracing::info!(item_id = %id, "Item created successfully");
Forbidden Resolution
println! in library code Use tracing::info!()
Format strings with interpolation Use structured fields

Repository Pattern

Services NEVER execute queries directly. All SQL in repositories using SQLX macros:

// Repository - uses sqlx::query_as!
pub async fn find_by_email(&self, email: &str) -> Result<Option<User>, sqlx::Error> {
    sqlx::query_as!(User, "SELECT id, email, name FROM users WHERE email = $1", email)
        .fetch_optional(&**self.pool)
        .await
}

// Service - calls repository
let user = self.user_repository.find_by_email(email).await?;

SQLX Macros Only

Allowed Forbidden
sqlx::query!() sqlx::query()
sqlx::query_as!() sqlx::query_as()
sqlx::query_scalar!() sqlx::query_scalar()

The ! suffix enables compile-time verification. Zero tolerance for runtime query strings.

Repository Constructor

pub struct ContentRepository {
    pool: Arc<PgPool>,
}

impl ContentRepository {
    pub fn new(pool: Arc<PgPool>) -> Self {
        Self { pool }
    }
}

Error Handling

Use domain-specific errors with thiserror. anyhow only at application boundaries:

#[derive(Error, Debug)]
pub enum ServiceError {
    #[error("Item not found: {0}")]
    NotFound(String),
    #[error("Database error: {0}")]
    Database(#[from] sqlx::Error),
}

Log errors once at handling boundary, not at every propagation point.

DateTime

Layer Type
Rust DateTime<Utc>
PostgreSQL TIMESTAMPTZ

Never use NaiveDateTime or TIMESTAMP. Never format as strings for DB operations.

Option

Only valid when absence is a meaningful domain state. Invalid uses:

  • "I don't have it yet"
  • Avoiding validation
  • Default values that should be explicit

Fail Fast

Never return Ok for failed paths. Propagate errors immediately with ?.

Builder Pattern (MANDATORY for Complex Types)

Required for types with 3+ fields OR any type that mixes required and optional fields.

Structure:

pub struct CreateContentParams {
    pub slug: String,
    pub title: String,
    pub body: String,
    pub description: Option<String>,
    pub image: Option<String>,
}

pub struct CreateContentParamsBuilder {
    slug: String,
    title: String,
    body: String,
    description: Option<String>,
    image: Option<String>,
}

impl CreateContentParamsBuilder {
    pub fn new(slug: impl Into<String>, title: impl Into<String>, body: impl Into<String>) -> Self {
        Self {
            slug: slug.into(),
            title: title.into(),
            body: body.into(),
            description: None,
            image: None,
        }
    }

    pub fn with_description(mut self, description: impl Into<String>) -> Self {
        self.description = Some(description.into());
        self
    }

    pub fn with_image(mut self, image: impl Into<String>) -> Self {
        self.image = Some(image.into());
        self
    }

    pub fn build(self) -> CreateContentParams {
        CreateContentParams {
            slug: self.slug,
            title: self.title,
            body: self.body,
            description: self.description,
            image: self.image,
        }
    }
}

impl CreateContentParams {
    pub fn builder(slug: impl Into<String>, title: impl Into<String>, body: impl Into<String>) -> CreateContentParamsBuilder {
        CreateContentParamsBuilder::new(slug, title, body)
    }
}

Rules:

Rule Description
Required fields in new() All non-optional fields MUST be constructor parameters
Optional fields via with_*() Each optional field gets a with_* method
build() returns owned type Builder is consumed, returns final struct
No Default for complex types Explicit construction prevents invalid states
Static builder() on target type Entry point: CreateContentParams::builder(...)

4. Naming

Functions

Prefix Returns
get_ Result<T> - fails if missing
find_ Result<Option<T>> - may not exist
list_ Result<Vec<T>>
create_ Result<T> or Result<Id>
update_ Result<T> or Result<()>
delete_ Result<()>
is_ / has_ bool

Variables

Type Name
Database pool pool
Repository repo or {noun}_repo
Service service or {noun}_service

Abbreviations

Allowed: id, uuid, url, jwt, mcp, a2a, api, http, json, sql, ctx, req, res, msg, err, cfg


5. Anti-Patterns

Pattern Resolution
Raw string identifiers Use typed identifiers
Magic numbers/strings Use constants or enums
Direct SQL in services Move to repository
Option<Id> for required fields Use non-optional
Fuzzy strings / hardcoded fallbacks Use typed constants, enums, or fail explicitly
Unused code / dead code Delete immediately
Tech debt / TODO comments Fix now or don't write it
Commented-out code Delete - git has history

6. Derive Ordering

When deriving traits, use this order:

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct MyType { ... }

Order: Debug, Clone, Copy (if applicable), PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize


7. Extension-Specific Patterns

Schema Embedding

pub const SCHEMA_MY_TABLE: &str = include_str!("../schema/001_my_table.sql");

impl MyExtension {
    pub fn schemas() -> Vec<(&'static str, &'static str)> {
        vec![("my_table", SCHEMA_MY_TABLE)]
    }
}

Job Registration

use systemprompt_traits::{Job, JobContext, JobResult, submit_job};

#[derive(Debug, Clone, Copy, Default)]
pub struct MyJob;

#[async_trait::async_trait]
impl Job for MyJob {
    fn name(&self) -> &'static str { "my_job" }
    fn description(&self) -> &'static str { "Does something" }
    fn schedule(&self) -> &'static str { "0 0 * * * *" }

    async fn execute(&self, ctx: &JobContext) -> anyhow::Result<JobResult> {
        let pool = ctx.db_pool::<PgPool>()?;
        Ok(JobResult::success())
    }
}

submit_job!(&MyJob);

Router Factory

impl MyExtension {
    pub fn router(&self, pool: Arc<PgPool>) -> Router {
        let state = MyState { pool };
        Router::new()
            .route("/items", get(list_items).post(create_item))
            .route("/items/:id", get(get_item).delete(delete_item))
            .with_state(state)
    }
}

Quick Reference

Task Command
Lint all cargo clippy --workspace -- -D warnings
Format all cargo fmt --all
Check format cargo fmt --all -- --check
Build all cargo build --workspace
Test all cargo test --workspace