Data Engine: Unterschied zwischen den Versionen
Keine Bearbeitungszusammenfassung |
Keine Bearbeitungszusammenfassung |
||
| Zeile 1: | Zeile 1: | ||
* [[Rust-Datenmodell für Drupal-Entities]] | |||
= Rust-Datenmodell für Drupal-Entities = | = Rust-Datenmodell für Drupal-Entities = | ||
Version vom 12. August 2026, 01:19 Uhr
Rust-Datenmodell für Drupal-Entities
Ü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).FieldMap
ist 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 |
|---|---|
FieldValueals Enum mit Payload pro Variante |
Bildet Drupals starke Feldtypisierung typsicher ab, statt alles als Stringzu behandeln. |
FieldMap = HashMap<String, FieldValue> |
Bundles sind zur Compile-Zeit nicht bekannt; generische Map bleibt flexibel für beliebige field_*-Namen. |
EntityBaseals 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
,File
losgelö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