Rust-Datenmodell für Drupal-Entities

Aus Dokument
Zur Navigation springen Zur Suche springen

Hinweis: Diese Inhalte wurden mit Unterstützung von Künstlicher Intelligenz erstellt und redaktionell überprüft (Transparenzhinweis gemäß Art. 50 EU AI Act).

Ü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 alle
    field_*
    -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
    ,
    File
    losgelöst von
    Image
    ).
  • Ü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

  • chrono – Zeitstempel (
    created
    ,
    changed
    )
  • uuid – Entity-UUIDs

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

Hinweis

Hinweis: Diese Inhalte wurden mit Unterstützung von Künstlicher Intelligenz erstellt und redaktionell überprüft (Transparenzhinweis gemäß Art. 50 EU AI Act).