Skip to main content

vantage_table/table/sets/
hooks.rs

1//! Fire a table's lifecycle hooks around a write.
2//!
3//! Before-write hooks run on the record about to be written, in phase order,
4//! just ahead of set-invariant enforcement; an error cancels the operation.
5//! `before_delete` hooks may instead return [`HookReturn::Handled`] to take over
6//! (soft-delete). After-commit hooks run for their side-effects. Hooks receive
7//! the entity-erased table for relation/datasource access.
8
9use vantage_core::Result;
10use vantage_types::{EmptyEntity, Entity, InvariantValue, Record};
11
12use super::invariants::enforce_invariants;
13use crate::table::{AfterFn, BeforeDeleteFn, BeforeFn, HookReturn, Phase, Table};
14use crate::traits::table_source::TableSource;
15
16/// The two halves of a single-record insert that surround the driver's write.
17/// A driver's bulk path calls them around its batch so that it stays
18/// equivalent to inserting the records one by one.
19impl<T: TableSource, E: Entity<T::Value>> Table<T, E>
20where
21    T::Value: InvariantValue,
22{
23    /// Everything a single insert does to `record` before the driver write:
24    /// the confined-writes check, before-insert hooks, dropping read-only
25    /// imported columns, and conforming to the set's invariants. Returns the
26    /// record to store.
27    pub async fn prepare_insert(&self, record: &Record<T::Value>) -> Result<Record<T::Value>> {
28        self.require_confined_writes("insert")?;
29        let mut record = record.clone();
30        run_before(
31            self.before_insert_hooks(),
32            &mut record,
33            self.as_entity_erased(),
34        )
35        .await?;
36        self.strip_imported_columns(&mut record);
37        enforce_invariants(&mut record, self.invariants())?;
38        Ok(record)
39    }
40
41    /// Run the after-insert hooks for a record stored under `id`, as a single
42    /// insert does once the driver write has succeeded.
43    pub async fn finish_insert(&self, id: &T::Id, record: &Record<T::Value>) -> Result<()> {
44        run_after(
45            self.after_insert_hooks(),
46            id,
47            record,
48            self.as_entity_erased(),
49        )
50        .await
51    }
52}
53
54pub(crate) async fn run_before<T: TableSource>(
55    hooks: &[(Phase, BeforeFn<T>)],
56    record: &mut Record<T::Value>,
57    table: &Table<T, EmptyEntity>,
58) -> Result<()> {
59    for (_phase, hook) in hooks {
60        hook(record, table).await?;
61    }
62    Ok(())
63}
64
65pub(crate) async fn run_after<T: TableSource>(
66    hooks: &[AfterFn<T>],
67    id: &T::Id,
68    record: &Record<T::Value>,
69    table: &Table<T, EmptyEntity>,
70) -> Result<()> {
71    for hook in hooks {
72        hook(id, record, table).await?;
73    }
74    Ok(())
75}
76
77/// Run before-delete hooks; the first `Handled` short-circuits and signals the
78/// caller to skip the underlying delete.
79pub(crate) async fn run_before_delete<T: TableSource>(
80    hooks: &[BeforeDeleteFn<T>],
81    id: &T::Id,
82    record: &Record<T::Value>,
83    table: &Table<T, EmptyEntity>,
84) -> Result<HookReturn> {
85    for hook in hooks {
86        if let HookReturn::Handled = hook(id, record, table).await? {
87            return Ok(HookReturn::Handled);
88        }
89    }
90    Ok(HookReturn::Proceed)
91}