generic_relation_helpers
Package Rust de traits et helpers génériques pour construire des objets enrichis à partir de relations entre tables — avec ou sans base de données.
Problème résolu
Dans la plupart des applications, on charge des données depuis plusieurs tables et on veut les présenter sous forme d'objets imbriqués :
// Ce qu'on veut retourner à l'API ou utiliser dans le code
-- Ce qu'on a en base : 3 tables plates
SELECT * FROM Hotel;
SELECT * FROM HotelResponsable; -- table de jointure
SELECT * FROM Responsable; -- données enrichies
Sans ce package, cette transformation se réécrit à chaque fois pour chaque entité.
Avec ce package, une seule fonction générique couvre tous les cas.
Installation
[]
= { = "../generic_relation_helpers" }
# ou depuis crates.io
= "0.1.0"
Couverture des cas de relations entre tables
✅ Cas supportés
1. Relation N:N via table de jointure
Hotel ──── HotelResponsable ──── Responsable
(table de jointure) (données enrichies)
Cas typiques : hôtels/responsables, étudiants/cours, utilisateurs/rôles, auteurs/livres.
get_parents_with_their_childrens
2. Relation 1:N avec FK directe dans l'enfant
Categorie ──── Produit
(FK: code_categorie dans Produit)
Pas de table de jointure — Produit joue les deux rôles :
get_parents_with_their_childrens
Note : passer la même
Vecdeux fois nécessite un.clone()pour éviter le double emprunt Rust.
3. N:N avec attributs sur la jointure
Commande ──── LigneCommande ──── Produit
quantite: 3 nom: "Smartphone"
prix_unitaire: 899
Les attributs de la jointure (quantite, prix_unitaire) sont fusionnés automatiquement dans TEnfant via HasFields — sans code supplémentaire dans le projet.
// LigneCommande expose ses attributs via HasFields
// Produit les reçoit via BuildFromSon
4. N:N — gestion des droits et rôles
Utilisateur ──── UtilisateurRole ──── Role
(jointure) niveau_acces: 100
Cas typiques : permissions, profils, groupes d'accès.
5. Filtrage intégré — insensible à la casse
Cinq modes disponibles sans code supplémentaire :
| Mode | Comportement | Exemple |
|---|---|---|
None |
Retourne tout | — |
"equal" |
Égalité exacte | "HiveHotel" |
"begin" |
Commence par | "Hive" → HiveHotel |
"finished" |
Termine par | "Hotel" → tous |
"contains" |
Contient | "tem" → ArtemHotel |
Tous les filtres sont insensibles à la casse ("bhotel" trouve "BHotel").
⚠️ Cas partiellement supportés
| Cas | Statut | Contournement |
|---|---|---|
| Attributs de jointure | ⚠️ Fusionnés automatiquement via HasFields |
Déclarer les champs dans HasFields de la jointure |
| FK directe (1:N) | ✅ Supporté avec .clone() |
Passer la même Vec deux fois |
❌ Cas non supportés
| Cas | Raison |
|---|---|
Relation récursive (Employe → Manager) |
Même table en parent et enfant |
| Relation ternaire (3 tables liées simultanément) | Architecture non prévue |
| Relation 1:1 simple | Pas de Vec d'enfants — inutile d'utiliser le package |
Utilisation sans base de données
Le package est totalement indépendant de tout ORM ou base de données.
Les Vec peuvent venir de n'importe quelle source : JSON, CSV, API externe, fichiers, mémoire.
// ── Données depuis une API externe ──
let hotels: = get
.await?.json.await?;
let responsables: = get
.await?.json.await?;
let jointure: = get
.await?.json.await?;
// ── Même appel que avec Diesel / PostgreSQL ──
let result: = get_parents_with_their_childrens;
// ── Données en mémoire pour les tests ──
let hotels = vec!;
let responsables = vec!;
let jointure = vec!;
let result: = get_parents_with_their_childrens;
Représentation des objets obtenus
Avant — objets plats, relations invisibles
// Ce qu'on a : 3 Vec non liées
let hotels: = vec!;
let responsables: = vec!;
let jointure: = vec!;
// ❌ Relations invisibles — il faut croiser manuellement les 3 Vec
Après — objets imbriqués, relations explicites
// Ce qu'on obtient : Vec<HotelAndResponsable> prêt à l'emploi
let result: = get_parents_with_their_childrens;
// result[0] → HotelAndResponsable
// .nom = "HiveHotel"
// .adresse = Some("Paris")
// .responsables = [
// Responsable { nom_complet: "Martin Dupont", telephone: "06 10 11 12 13" },
// Responsable { nom_complet: "Claire Fontaine", telephone: "06 30 31 32 33" },
// ]
// result[1] → HotelAndResponsable
// .nom = "StarHotel"
// .adresse = Some("Lyon")
// .responsables = [
// Responsable { nom_complet: "Sophie Lefebvre", telephone: "06 40 41 42 43" },
// ]
Sérialisé en JSON (retour d'API REST) :
Traits à implémenter
Pour chaque entité, le projet déclare uniquement les traits nécessaires :
Struct parent → GetValuePropertyOfKeyParent (expose sa clé PK)
GetValueFieldForSourceParent (expose ses champs + reçoit les enfants)
HasFields (liste les champs)
Struct jointure → HasNomHotelAndFieldOfSourceSecondary (FK parent + FK enfant)
GetValueFieldForSourceSecondary (expose ses champs pour fusion)
HasFields (liste les champs)
Struct enfant → HasMatchKey (clé de correspondance avec la jointure)
BuildFromSon (construit l'enfant depuis une HashMap)
HasFields (liste les champs)
Clone (requis pour la copie)
Serialize (requis pour la sérialisation JSON)
Struct destination → BuildFromParent (construit l'objet final depuis une HashMap)
Fonctions exposées
// ── Construit Vec<SourceDestination> depuis 3 sources ──
Helpers de conversion inclus
get_string // → String
get_string_opt // → Option<String>
get_bool // → bool
get_i64 // → i64
get_f64 // → f64
get_time // → Option<NaiveTime>
get_date // → Option<NaiveDate>
get_datetime // → Option<NaiveDateTime>
Compatibilité testée
| ORM / Source | Statut |
|---|---|
| Diesel + PostgreSQL | ✅ Testé |
| Données en mémoire (tests unitaires) | ✅ Testé |
| API externe (Vec désérialisée depuis JSON) | ✅ Compatible |
| SQLx | ✅ Compatible (Vec standard) |
| Fichiers CSV / JSON | ✅ Compatible |
Licence
MIT