Data Engine
Übersicht
Dieser Artikel dokumentiert ein Rust-Datenmodell, das die zentralen
Entity-Konzepte von Drupal (Node, User, Taxonomy Term, Media, Paragraph)
als struct/enum-Typen abbildet. Das Modell enthält bewusst keine
Logik (keine impl-Methoden) – es dient als reine Datenrepräsentation.
Eine (De-)Serialisierung (z. B. mit serde für
Drupal-JSON:API-Antworten) ist bewusst noch nicht Teil dieses Modells
und folgt in einem späteren Schritt.
Motivation
Drupal modelliert Inhalte über ein generisches Entity-System:
- Entity-Typen (
node,user,taxonomy_term,media, …) definieren die grundlegende Art eines Objekts. - Bundles (bei Nodes „Content Types" genannt, z. B.
article,page) spezialisieren einen Entity-Typ. - Felder (
field_*) sind pro Bundle konfigurierbar, typisiert und können einfach- oder mehrwertig (Kardinalität) sein.
Um mit einem Drupal-Backend aus Rust heraus zu arbeiten, braucht man typisierte Gegenstücke zu diesen Konzepten. Das hier beschriebene Modell bildet diese Struktur 1:1 in Rust ab.
Grundtypen
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EntityType {
Node,
User,
TaxonomyTerm,
Media,
Paragraph,
File,
Menu,
Block,
Custom(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PublishStatus {
Published,
Unpublished,
}
pub type LangCode = String;
EntityType bildet die von Drupal vordefinierten Entity-Typen ab; der
Custom-Zweig deckt eigene, per Modul definierte Entity-Typen ab, ohne
das Enum ändern zu müssen.
Feldwerte: FieldValue
Drupal-Felder sind stark typisiert (Text, Zahl, Datum, Referenz, Bild, …). Dies wird über ein Enum mit Payload pro Variante abgebildet:
#[derive(Debug, Clone)]
pub enum FieldValue {
Text(String),
TextLong { value: String, format: Option<String> },
Integer(i64),
Decimal(f64),
Boolean(bool),
DateTime(DateTime<Utc>),
Email(String),
Link { uri: String, title: Option<String> },
EntityReference { target_id: u64, target_type: EntityType },
Image {
target_id: u64,
alt: Option<String>,
title: Option<String>,
width: Option<u32>,
height: Option<u32>,
},
List(Vec<FieldValue>),
}
pub type FieldMap = HashMap<String, FieldValue>;
List(Vec<FieldValue>)bildet mehrwertige Felder ab (Kardinalität > 1).FieldMapist die generische Ablage für allefield_*-Werte eines Bundles, adressiert über den Feldmaschinennamen (z. B.field_tags).
Entity-Typen im Detail
Gemeinsame Basis
#[derive(Debug, Clone)]
pub struct EntityBase {
pub id: u64,
pub uuid: Uuid,
pub entity_type: EntityType,
pub bundle: String,
pub langcode: LangCode,
pub created: DateTime<Utc>,
pub changed: DateTime<Utc>,
}
EntityBase fasst Eigenschaften zusammen, die praktisch jede
Content-Entity in Drupal besitzt, und wird per Komposition in konkrete
Entity-Structs eingebettet (siehe Node, Media). Beim späteren
Ergänzen von serde bietet sich dafür #[serde(flatten)] an, damit die
Felder von EntityBase beim (De-)Serialisieren auf derselben Ebene
wie die restlichen Felder erscheinen.
Node
#[derive(Debug, Clone)]
pub struct Node {
pub base: EntityBase,
pub title: String,
pub status: PublishStatus,
pub author_id: u64,
pub fields: FieldMap,
}
User
#[derive(Debug, Clone)]
pub struct User {
pub id: u64,
pub uuid: Uuid,
pub name: String,
pub mail: String,
pub status: bool,
pub roles: Vec<String>,
pub created: DateTime<Utc>,
pub fields: FieldMap,
}
User nutzt bewusst kein EntityBase, da User-Entities in Drupal kein
bundle/langcode im klassischen Sinn besitzen und stattdessen
roles und einen einfachen status: bool (aktiv/blockiert) haben.
TaxonomyTerm
#[derive(Debug, Clone)]
pub struct TaxonomyTerm {
pub id: u64,
pub uuid: Uuid,
pub vocabulary: String,
pub name: String,
pub description: Option<String>,
pub parent_id: Option<u64>,
pub weight: i32,
pub fields: FieldMap,
}
Bildet Taxonomiebegriffe inkl. hierarchischer Eltern-Referenz
(parent_id) und Sortiergewicht (weight) ab.
Media & Paragraph
#[derive(Debug, Clone)]
pub struct Media {
pub base: EntityBase,
pub name: String,
pub fields: FieldMap,
}
#[derive(Debug, Clone)]
pub struct Paragraph {
pub id: u64,
pub uuid: Uuid,
pub bundle: String,
pub fields: FieldMap,
}
Paragraph ist bewusst schlank gehalten, da Paragraphs in Drupal keine
eigenständigen, direkt aufrufbaren Entities mit URL/Alias sind, sondern
stets über eine Host-Entity referenziert werden.
Schema-Introspektion (optional)
Für Anwendungsfälle, in denen Feld- und Bundle-Definitionen selbst (nicht nur Werte) abgebildet werden müssen – etwa um ein Drupal-Schema zu spiegeln oder Formulare dynamisch zu generieren:
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Cardinality {
Single,
Multiple,
Unlimited,
}
#[derive(Debug, Clone)]
pub struct FieldDefinition {
pub field_name: String,
pub field_type: String,
pub cardinality: Cardinality,
pub required: bool,
}
#[derive(Debug, Clone)]
pub struct BundleDefinition {
pub entity_type: EntityType,
pub bundle: String,
pub label: String,
pub fields: Vec<FieldDefinition>,
}
Design-Entscheidungen
| Entscheidung | Begründung |
|---|---|
FieldValue als Enum mit Payload pro Variante |
Bildet Drupals starke Feldtypisierung typsicher ab, statt alles als String zu behandeln.
|
FieldMap = HashMap<String, FieldValue> |
Bundles sind zur Compile-Zeit nicht bekannt; generische Map bleibt flexibel für beliebige field_*-Namen.
|
EntityBase als Kompositionsfeld |
Vermeidet Duplikation gemeinsamer Properties zwischen Node/Media, ohne Vererbung (die es in Rust nicht gibt). |
Kein impl-Block |
Modell bleibt reine Datenschicht; Geschäftslogik (Validierung, API-Calls) gehört in separate Module. |
EntityType::Custom(String) |
Deckt durch Contrib-/Custom-Module definierte Entity-Typen ab, ohne das Enum erweitern zu müssen. |
Offene Erweiterungspunkte
- Weitere
FieldValue-Varianten je nach genutzten Feldtypen (z. B.Geolocation,Address,Filelosgelöst vonImage). - Übersetzungen (
translations: HashMap<LangCode, FieldMap>), falls Drupal-Mehrsprachigkeit (Content Translation) abgebildet werden soll. - Konkretes Mapping auf die JSON:API-Hüllstruktur (
data,attributes,relationships,included), falls direkt gegen die Drupal-JSON:API deserialisiert werden soll, statt ein eigenes flaches Modell zu pflegen.
Verwendete Crates
Hinweis: serde (für die (De-)Serialisierung) ist bewusst noch nicht Teil dieses Modells und wird in einem späteren Schritt ergänzt.
Siehe auch
- Drupal-Dokumentation: Entity API
- Drupal-Dokumentation: JSON:API-Modul