Architecture d'un projet Runique
Documentation / Architecture d'un projet Runique / Architecture d'un projet Runique

Architecture d'un projet Runique

Un projet Runique est une crate binaire Rust standard. runique new génère la structure de base, que tu fais évoluer selon tes besoins.

Structure type

mon_projet/
├── src/
│   ├── main.rs            # Point d'entrée — builder RuniqueApp
│   ├── url.rs             # urlpatterns! — table de routage
│   ├── views.rs           # Handlers (fonctions async)
│   ├── forms/             # Formulaires (#[form] ou déclarés à la main)
│   ├── entities/          # Modèles model!{} / extend!{}, lus par makemigrations
│   └── admin.rs           # ModelAdmin + AdminSite (feature `admin` seulement)
│
├── templates/             # Templates Tera (.html)
├── static/                # Fichiers statiques (CSS, JS, images)
├── media/                 # Fichiers envoyés par les utilisateurs (FileField) ;
│                          # MEDIA_ROOT peut pointer ailleurs
├── migration/             # Crate des migrations (workspace)
│   ├── Cargo.toml
│   └── src/               # main.rs, lib.rs, migrations, snapshots/
├── .env                   # Variables d'environnement
└── Cargo.toml             # Workspace : l'application + migration

src/entities/ ne doit contenir que des model!{} et extend!{} : makemigrations lit ce dossier et génère une migration pour chaque modèle qu'il y trouve. Un formulaire déclaré à la main va dans src/forms/.


Rôle de chaque fichier

main.rs — Configure et lance l'application via le builder :

use runique::prelude::*;

mod entities;
mod forms;
mod url;
mod views;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    password_init(PasswordConfig::auto_with(Manual::Argon2));

    let config: RuniqueConfig = RuniqueConfig::from_env();
    let db_config = DatabaseConfig::from_env()?.build();
    let db: DatabaseConnection = db_config.connect().await?;

    RuniqueApp::builder(config)
        .routes(url::routes())
        .with_database(db)
        .statics()
        .build()
        .await?
        .run()
        .await?;

    Ok(())
}

url.rs — Déclare les routes via urlpatterns! :

use crate::views;
use runique::prelude::*;

pub fn routes() -> Routes {
    urlpatterns! {
        "/"       => view!{ views::index },  name = "index",
        "/signup" => view!{ views::signup }, name = "signup",
    }
}

views.rs — Handlers de requêtes :

use runique::prelude::*;

pub async fn index(mut request: Request) -> AppResult<Response> {
    context_update!(request => { "title" => "Accueil" });
    request.render("index.html")
}

forms/ — Formulaires typés. Un formulaire déclaré à la main :

use runique::prelude::*;
use serde::Serialize;

#[derive(Serialize, Debug, Clone)]
#[serde(transparent)]
pub struct ContactForm {
    pub form: Forms,
}

impl FormAccess for ContactForm {
    fn register_fields(form: &mut Forms) {
        form.field(&TextField::text("name").label("Nom").required());
        form.field(&TextField::email("email").label("Email").required());
    }
    impl_form_access!();
}

impl RuniqueForm for ContactForm {}

Un formulaire tiré d'un modèle s'écrit en une ligne : #[form(schema = blog, fields = [title, content])]. Voir Formulaires.

admin.rs — Déclaration typée de l'admin (ModelAdmin + AdminSite) :

use crate::entities::demo_category;
use runique::admin::{AdminSite, ModelAdmin};
use runique::app::staging::AdminStaging;
use sea_orm::Order;

pub fn config(admin: AdminStaging) -> AdminStaging {
    admin
        .site_title("Administration")
        .path("/admin/")
        .site(site)
}

fn site(site: AdminSite) -> AdminSite {
    site.register(demo_category())
}

fn demo_category() -> ModelAdmin<demo_category::Entity> {
    use demo_category::Column::*;
    ModelAdmin::new().list(|l| {
        l.display([Title, BackLinkLabel, BackLinkUrl, SortOrder])
            .links([Title])
            .ordering([(SortOrder, Order::Asc)])
            .search([Title])
            .empty_value("—")
    })
}

Câblé dans main.rs par .with_admin(admin::config).

L'admin est une feature à activer. runique::admin, AdminStaging et .with_admin(...) n'existent que si runique est compilé avec la feature admin :

runique = { version = "4.0.0", features = ["postgres", "admin"] }

Sans elle, le compilateur signale unresolved import runique::admin ou no method named with_admin. Tout model!{} du projet doit être enregistré dans l'AdminSite : les droits d'un modèle absent sont supprimés au démarrage.


Démarrer un nouveau projet

runique new mon_projet
cd mon_projet
runique migration up
cargo run

Ce que chaque partie du projet généré vous appartient de modifier :

Fichier / dossierÀ modifierRôle
src/main.rsOuiConfigure le builder et déclare les modules
src/url.rsOuiTable de routage
src/views.rsOuiHandlers de requêtes
src/entities/OuiModèles (model!{}, extend!{}), lus par makemigrations
src/forms/OuiFormulaires et validation
src/admin.rsOui (feature admin)ModelAdmin + enregistrement AdminSite
templates/OuiTemplates Tera
static/OuiCSS, JS, images
migration/NonTenu par runique makemigrations (lib.rs, migrations, snapshots). À la main : seulement le up() / down() d'un changement de type de colonne, que makemigrations ne sait pas encore écrire
.envOuiVariables d'environnement

SectionContenu
Concepts clésRuniqueEngine, Request, request.form("nom")
Fonctionnement interne de MakemigrationsPipeline en trois phases : extraction AST, diff, génération SeaQuery
MacrosMacros de contexte, flash, routage, erreur
Tags & filtres TeraTags Django-like, filtres, fonctions
Stack middlewareOrdre des slots, injection de dépendances
Lifecycle d'une requêteCycle de vie, bonnes pratiques
Testsrunique_test : logique métier testée contre la vraie base, transaction annulée ; runique test ; contrôles au démarrage