Rullst Specification π
βThe Single Source of Truth (SST) for Framework Architecture & Conventionsβ
This document is the Single Source of Truth (SST) for the Rullst Framework. It specifies the exact conventions, API structures, naming rules, and directory standards of Rullst.
Important
AI Alignment Instruction: Whenever updating, refactoring, or generating documentation and code for Rullst, always refer to this specification as the baseline. Do not invent or assume conventions outside of this document.
π 1. Directory Structure Conventions
A standard Rullst application scaffold must strictly follow this folder hierarchy:
my-app/
βββ src/
β βββ controllers/ # Route controllers (async modules)
β β βββ mod.rs
β βββ models/ # Active Record Models (rullst-orm entities)
β β βββ mod.rs
β βββ pages/ # Shared static HTML elements or full page layouts
β β βββ mod.rs
β βββ main.rs # Entrypoint, DB initialization, and Central routing
βββ Cargo.toml # Project cargo dependencies
βββ Rullst.toml # Framework configuration (databases, environment, etc.)
π οΈ 2. Naming Conventions
To guarantee consistency, both humans and AI coders must adhere to the following name normalization rules handled by the cargo-rullst generator:
- File Names: Standard Rust
snake_case(e.g.users_controller.rs,post_model.rs). - Struct / Model / Documentation Names: Standard
PascalCase(e.g.UsersController,PostModel). - URL Paths: Lowercase kebab-case (e.g.
/users,/user-profiles).
β‘ 3. Core API Specifications
3.1. Server & Routing (rullst::routing)
- Routing Macro: central routing declared via the
routes!macro, wrapping Axum routing handlers.#![allow(unused)] fn main() { let router = routes![ get("/" => home), post("/posts" => posts_controller::store), ]; } - Server Lifecycle:
#![allow(unused)] fn main() { Server::new(router: Router) .run(port: u16) -> Result<(), Box<dyn std::error::Error>> }
3.2. Server-Side Rendering (rullst::macros)
- Macro:
html!procedural macro compiles HTML trees directly into static memory string concat builders. - XSS Protection: Automatic HTML escaping on all dynamic variables wrapped in
{expr}. - Raw Unescaped HTML: Explicitly bypassed using the wrapper
rullst::html::RawHtml(String). - Lists/Iterators:
#![allow(unused)] fn main() { let mut list_builder = String::new(); for item in items { list_builder.push_str(&html! { <li>{item}</li> }); } html! { <ul>{ rullst::html::RawHtml(list_builder) }</ul> } }
3.3. Active Record ORM (rullst-orm)
- Model definition:
#![allow(unused)] fn main() { #[derive(Debug, Clone, FromRow, rullst_orm::Orm)] #[orm(table = "table_name")] pub struct Model { pub id: i32, // ... fields } } - Static queries:
Model::all().await->Result<Vec<Model>, sqlx::Error>Model::find(id).await->Result<Model, sqlx::Error>
- Instance Operations:
let mut instance = Model { ... };instance.save().await->Result<(), sqlx::Error>(handles auto-incrementing inserts or updates).instance.delete().await->Result<(), sqlx::Error>
π» 4. CLI Specifications (cargo-rullst)
- Project Creation:
cargo rullst new <name>- Convention: Automatically extracts the package name from path expressions (e.g.,
..\dummy_test->dummy_test).
- Convention: Automatically extracts the package name from path expressions (e.g.,
- Controller/Island Scaffolding:
cargo rullst make:controller <Name>cargo rullst make:island <Name>- Behavior: Generates
src/controllers/<snake_name>_controller.rswithindexandshowactions. Appends declaration tosrc/controllers/mod.rs. Addspub mod controllers;to the top ofsrc/main.rs.
- Behavior: Generates
- Documentation SSG (RullstPress):
cargo rullst docs buildandcargo rullst docs dev- Behavior: Compiles markdown files in
docs/into a static site insidedocs/dist/.
- Behavior: Compiles markdown files in
π§± 5. Controller Architecture
Controllers handle business logic and HTTP responses.
- Module Structure: Each controller is a separate module inside
src/controllers/(e.g.,users_controller.rs). - Function Signatures: Functions must be asynchronous and return a type that implements
axum::response::IntoResponse(orResult<impl IntoResponse, AppError>). - Database Access: Controllers must never contain raw
sqlx::query!macros inline. Database logic must be delegated to the Active Record ORM methods (.save(),.all(), etc.) or encapsulated within specificimpl Modelfunctions. - Standard Actions:
pub async fn index(): List all resources.pub async fn show(Path(id): Path<i32>): Show a specific resource.pub async fn store(Form(payload): Form<CreateDto>): Create a new resource.pub async fn update(Path(id): Path<i32>, Form(payload): Form<UpdateDto>): Update a resource.pub async fn delete(Path(id): Path<i32>): Delete a resource.
π 6. HTML Pages & Components
Rullst uses a functional approach for HTML rendering, relying on the html! macro.
- Organization: Pages and components reside in
src/pages/. - Functional Components: Pages and components are simply Rust functions. They are not structs or classes.
- Props/Data: Pass data into pages and components as regular function arguments.
- Return Type: Components should return a
String(orrullst::html::RawHtml) so they can be embedded in otherhtml!calls. Route-level pages should returnaxum::response::Html<String>to be served directly. - Example:
#![allow(unused)] fn main() { pub fn button_component(label: &str, url: &str) -> String { html! { <a href={url} class="btn">{label}</a> } } pub fn home_page(user_name: &str) -> axum::response::Html<String> { let content = html! { <div> <h1>"Welcome, "{user_name}</h1> { rullst::html::RawHtml(button_component("Click Me", "/click")) } </div> }; axum::response::Html(content) } }
π¨ 7. Error Handling
Consistent error handling ensures safety and predictable API responses.
- Default Error Type: The framework expects a standard error enum, typically
AppError, located insrc/error.rsor similar. - Implementation:
AppErrormust implementaxum::response::IntoResponse. - Controller Usage: Controllers that can fail should return
Result<impl IntoResponse, AppError>. - HTTP Codes: The
IntoResponseimplementation maps internal errors to appropriate HTTP status codes (e.g.,404 Not Found,500 Internal Server Error).
π‘οΈ 8. Middlewares
Middlewares intercept requests for authentication, logging, etc.
- Location: Middlewares are placed in
src/middlewares/. - Standard Signature: Following Axumβs
from_fnpattern, a middleware function looks like:#![allow(unused)] fn main() { use axum::{extract::Request, middleware::Next, response::Response}; pub async fn my_middleware(req: Request, next: Next) -> Response { // Pre-request logic here let response = next.run(req).await; // Post-request logic here response } } - Registration: Middlewares are registered on the router using Axumβs
.layer()or through Rullstβs server configuration wrapper.
π‘οΈ 9. Architectural Guidelines for Backward Compatibility
To guarantee stress-free and self-healing updates for Rullst users, all framework code must strictly adhere to the following backward compatibility rules:
9.1. The Builder Pattern and #[non_exhaustive]
Any public configuration struct or extensible enum exposed by the framework must use the #[non_exhaustive] attribute. This prevents developers from instantiating the struct directly, ensuring that adding new fields in future minor versions will not break user code.
- Mandatory Usage: All instantiation must be done via a constructor (
new()) and the Builder Pattern (with_...()).
#![allow(unused)]
fn main() {
#[non_exhaustive]
pub struct RullstConfig {
pub port: u16,
}
impl RullstConfig {
pub fn new(port: u16) -> Self {
Self { port }
}
}
}
9.2. Deprecation Lifecycle (#[deprecated])
The framework will never abruptly remove or rename a public function, struct, or method. If a breaking change to an API is required, the old API must be kept alive for at least one minor version using the #[deprecated] attribute.
- Mandatory Usage: The
notefield must explicitly tell the user what to use instead, enablingcargo fixto potentially automate the migration.
#![allow(unused)]
fn main() {
#[deprecated(since = "0.2.0", note = "Please use `Router::new()` instead")]
pub fn old_initializer() {
Router::new();
}
}
9.3. Sealed Traits
If the framework exposes a Trait that is meant to be used by the user but not implemented by the user (e.g., core framework behavior), it must use the βSealed Traitβ pattern. This ensures that adding new methods to the trait in the future will not break downstream implementations.
#![allow(unused)]
fn main() {
mod private {
pub trait Sealed {}
}
pub trait RullstTrait: private::Sealed {
fn execute(&self);
}
}
ποΈ 10. CLI Modular Architecture (cargo-rullst)
Important
The
cargo-rullstCLI must never be allowed to grow into a monolithicmain.rs. Once the file exceeds ~1000 lines, refactoring into the module structure below is mandatory. Mixing template strings, HTML, SQL schemas, spinner logic, and argument parsing in a single file is classified as a critical architecture violation.
10.1. Required Module Structure
The cargo-rullst source directory must be organized as follows:
cargo-rullst/
βββ src/
β βββ main.rs # β€ 80 lines: Entry point only. Dispatches to cli or ui.
β βββ cli.rs # Clap structs, Commands enum, argument definitions.
β βββ ui/ # Everything visual: banners, spinners, menus, boxes.
β β βββ mod.rs
β β βββ banner.rs # ASCII art, version display, neon color palette.
β β βββ components.rs # with_spinner(), prompt_select(), boxed_output().
β βββ generators/ # Scaffold logic: writes files to disk on user's project.
β β βββ mod.rs
β β βββ controller.rs # create_new_controller()
β β βββ model.rs # create_new_model()
β β βββ migration.rs # create_new_migration(), regenerate_migrations_mod()
β β βββ project.rs # create_new_project() β main wizard
β β βββ auth.rs # scaffold_auth_system()
β β βββ billing.rs # scaffold_billing_system()
β β βββ desktop.rs # scaffold_omni_system() / run_omni_app()
β β βββ foundry.rs # scaffold_foundry_config() / run_foundry_deploy()
β βββ blueprints/ # Blueprint template definitions (NOT inline strings).
β βββ mod.rs
β βββ blank.rs # Blank Starter template files
β βββ lms.rs # LMS / Course Platform template files
β βββ saas.rs # SaaS Starter template files
β βββ blog.rs # Blog / Content System template files
10.2. The main.rs Purity Rule
main.rs is the maestro, not a musician. It must contain only:
- Crate-level
#![allow(...)]attributes. - Module declarations (
pub mod cli; pub mod ui; pub mod generators; pub mod blueprints;). - The
fn main()function itself β which reads arguments and dispatches. - Zero business logic, zero template strings, zero file I/O.
// β
CORRECT main.rs pattern
fn main() -> Result<(), Box<dyn std::error::Error>> {
trigger_background_update_check();
if std::env::args_trimmed().has_no_subcommand() {
ui::show_interactive_dashboard()?;
} else {
cli::parse_and_run()?;
}
Ok(())
}
10.3. Template String Rules
Never embed large multi-line HTML, Rust, or SQL strings directly inside scaffold generator functions. Instead:
- Option A (Preferred): Use
include_str!()to load.rs.tmpl,.html, or.sqlfiles from atemplates/directory compiled into the binary. - Option B: Define typed constants or functions inside the
blueprints/module (e.g.,blueprints::lms::course_model_rs()) that return&'static strorString. These can be updated independently without touching generator logic. - Option C (Last Resort): Use
r###"..."###raw string literals (triple-hash delimiters) to prevent early termination when templates contain"#sequences (common in HTML attributes likehx-target="#player-panel").
Warning
Using
r#"..."#(single-hash) raw strings for HTML templates will cause a compiler error whenever the template contains"#(which is extremely common in HTMLidattributes used with HTMX). Always user###"..."###or move the template to theblueprints/module.
π¨ 11. Rullst Blueprints Engine β Design Rules
Blueprints are pre-configured project archetypes generated by the cargo rullst new wizard. They must follow strict rules to guarantee quality, safety, and ease of maintenance.
11.1. Available Blueprints
| Blueprint ID | Name | Description |
|---|---|---|
0 | π Blank Starter | Minimal HTMX reactive counter. Clean baseline. |
1 | π LMS / Course Platform | Courses + Lessons models, migrations with seed data, glassmorphic video player via HTMX. |
2 | ποΈ SaaS Starter | Auth system + Stripe pricing panels + user dashboard. |
3 | π° Blog / Content System | Post model, auto-CMS via Nexus, glassmorphic press feed. |
11.2. Blueprint Scaffolding Rules
- Only during
new: Blueprint file injection (route wiring,lib.rs,main.rs) may only happen duringcargo rullst new, when the project directory is freshly created and fully controlled. Never attempt to inject code into an existing user project. - Isolated file generation: Each blueprint generates a fully self-contained set of files. No cross-file regex or AST manipulation is permitted.
- Template sourcing: All blueprint templates must live in
src/blueprints/<name>.rsas typed functions, not insidegenerators/project.rs. - Additive, not destructive: Blueprints append to or create new files. They never delete or overwrite files created by a previous step.
11.3. Blueprint File Manifest
Every blueprint module (e.g., blueprints::lms) must expose a FileManifest β a list of (relative_path, content) pairs β via a public function:
#![allow(unused)]
fn main() {
// src/blueprints/lms.rs
pub fn file_manifest() -> Vec<(&'static str, String)> {
vec![
("src/models/course.rs", course_model()),
("src/models/lesson.rs", lesson_model()),
("src/models/mod.rs", "pub mod course;\npub mod lesson;\n".to_string()),
("src/controllers/lms_controller.rs", lms_controller()),
("src/pages/lms.rs", lms_page()),
// ...migrations, etc.
]
}
}
The generator iterates over this manifest and writes each file β no HTML or Rust templates inline.
π 12. Environment Variables & Third-Party Secrets
Any blueprint or scaffold that integrates a paid third-party service (Stripe, LemonSqueezy, Cloudinary, etc.) must generate a .env file with clear, commented placeholder values.
12.1. Required .env Template
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Rullst Application Environment Configuration
# Generated automatically by cargo rullst new
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# β οΈ SECURITY: This file must NEVER be committed to git.
# It is automatically added to .gitignore by the Rullst CLI.
# ββ Database ββββββββββββββββββββββββββββββββββββββββββββββββββ
DATABASE_URL=sqlite://db.sqlite3
# ββ Application βββββββββββββββββββββββββββββββββββββββββββββββ
APP_KEY=GENERATE_A_RANDOM_32_CHAR_SECRET_HERE
APP_ENV=development
# ββ Stripe Billing (replace with your real keys from stripe.com/dashboard) ββ
# STRIPE_SECRET_KEY=sk_test_REPLACE_WITH_YOUR_SECRET_KEY
# STRIPE_WEBHOOK_SECRET=whsec_REPLACE_WITH_YOUR_WEBHOOK_SECRET
# STRIPE_PRICE_ID_MONTHLY=price_REPLACE_WITH_YOUR_PRICE_ID
12.2. .gitignore Auto-Protection Rules
The CLI must automatically append the following entries to .gitignore during project creation:
# Rullst: Environment & Secrets
.env
.env.*
!.env.example
# Rullst: Foundry Deployment Manifest (contains SSH keys and cloud credentials)
Foundry.toml
12.3. .env.example Companion File
Every project generated by cargo rullst new must also produce an .env.example file with all the same keys but with the placeholder values clearly documented. This file is committed to version control and serves as the public onboarding guide for new contributors.