Skip to main content

teaql_core/
entity_graph.rs

1use crate::{Entity, Record, TeaqlEntity};
2
3/// Operation hint for an entity graph node.
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum EntityGraphOperation {
6    /// Upsert: insert if new, update if exists (default).
7    Save,
8    /// Delete: soft-delete the entity.
9    Delete,
10}
11
12/// A single node in an annotated entity graph.
13///
14/// Carries the entity's record data, an optional business-intent comment,
15/// and child nodes keyed by relation name.
16#[derive(Debug, Clone)]
17pub struct EntityGraphNode {
18    pub entity_type: String,
19    pub record: Record,
20    pub comment: Option<String>,
21    pub operation: EntityGraphOperation,
22    pub children: Vec<(String, EntityGraphNode)>,
23}
24
25/// Builder for constructing an annotated entity graph that preserves
26/// comment trace chains through the save pipeline.
27///
28/// # Example
29///
30/// ```ignore
31/// let graph = EntityGraph::new(task)
32///     .comment("Create task 'Deploy v2'")
33///     .child("task_execution_log_list",
34///         EntityGraph::new(log)
35///             .comment("Create task 'Deploy v2'"))
36///     .build();
37/// ```
38pub struct EntityGraphBuilder {
39    node: EntityGraphNode,
40}
41
42impl EntityGraphBuilder {
43    /// Set a business-intent comment on this node.
44    /// The comment will appear in SQL debug logs and audit trails
45    /// as part of the hierarchical trace chain.
46    pub fn comment(mut self, comment: impl Into<String>) -> Self {
47        self.node.comment = Some(comment.into());
48        self
49    }
50
51    /// Mark this node for deletion instead of save.
52    pub fn delete(mut self) -> Self {
53        self.node.operation = EntityGraphOperation::Delete;
54        self
55    }
56
57    /// Attach a child entity under the given relation name.
58    pub fn child(mut self, relation: impl Into<String>, child: EntityGraphBuilder) -> Self {
59        self.node.children.push((relation.into(), child.node));
60        self
61    }
62
63    /// Finalize and produce the `EntityGraph`.
64    pub fn build(self) -> EntityGraph {
65        EntityGraph { root: self.node }
66    }
67}
68
69/// An annotated entity graph ready for saving.
70///
71/// Unlike raw `entity.save()`, this structure preserves business-intent
72/// comments at every hop in the graph, producing proper trace chains
73/// in SQL logs and audit trails.
74pub struct EntityGraph {
75    pub root: EntityGraphNode,
76}
77
78impl EntityGraph {
79    /// Start building from an entity.
80    pub fn new<T: Entity + TeaqlEntity>(entity: T) -> EntityGraphBuilder {
81        EntityGraphBuilder {
82            node: EntityGraphNode {
83                entity_type: T::entity_descriptor().name.clone(),
84                record: entity.into_record(),
85                comment: None,
86                operation: EntityGraphOperation::Save,
87                children: Vec::new(),
88            },
89        }
90    }
91}