vantage_table/table/sets/
hooks.rs1use std::future::Future;
11
12use vantage_core::Result;
13use vantage_types::{EmptyEntity, Entity, InvariantValue, Record};
14use vantage_vista::Transaction;
15
16use super::invariants::enforce_invariants;
17use crate::table::{
18 AfterCx, AfterFn, BeforeCx, BeforeDeleteFn, BeforeFn, DeleteAllCx, DeleteAllFn, DeleteCx,
19 HookReturn, Phase, Table,
20};
21use crate::traits::table_source::TableSource;
22use crate::write_context::WriteContext;
23
24impl<T: TableSource, E: Entity<T::Value>> Table<T, E>
28where
29 T::Value: InvariantValue,
30{
31 pub async fn prepare_insert(&self, record: &Record<T::Value>) -> Result<Record<T::Value>> {
36 self.require_confined_writes("insert")?;
37 let mut record = record.clone();
38 run_before(
39 self.before_insert_hooks(),
40 &mut record,
41 None,
42 self.as_entity_erased(),
43 )
44 .await?;
45 self.strip_imported_columns(&mut record);
46 enforce_invariants(&mut record, self.invariants())?;
47 Ok(record)
48 }
49
50 pub async fn finish_insert(&self, id: &T::Id, record: &Record<T::Value>) -> Result<()> {
54 run_after(
55 self.after_insert_hooks(),
56 id,
57 record,
58 None,
59 self.as_entity_erased(),
60 )
61 .await
62 }
63}
64
65pub(crate) struct UpdateOf<'a, T: TableSource> {
68 pub id: &'a T::Id,
69 pub stored: Option<&'a Record<T::Value>>,
70 pub patch: bool,
71}
72
73pub(crate) async fn run_before<T: TableSource>(
75 hooks: &[(Phase, BeforeFn<T>)],
76 record: &mut Record<T::Value>,
77 update: Option<UpdateOf<'_, T>>,
78 table: &Table<T, EmptyEntity>,
79) -> Result<()> {
80 if hooks.is_empty() {
81 return Ok(());
82 }
83 let context = WriteContext::current();
84 for (_phase, hook) in hooks {
85 hook(BeforeCx {
86 record: &mut *record,
87 id: update.as_ref().map(|u| u.id),
88 stored: update.as_ref().and_then(|u| u.stored),
89 patch: update.as_ref().is_some_and(|u| u.patch),
90 context: context.as_ref(),
91 table,
92 })
93 .await?;
94 }
95 Ok(())
96}
97
98pub(crate) async fn run_after<T: TableSource>(
100 hooks: &[AfterFn<T>],
101 id: &T::Id,
102 record: &Record<T::Value>,
103 before: Option<&Record<T::Value>>,
104 table: &Table<T, EmptyEntity>,
105) -> Result<()> {
106 if hooks.is_empty() {
107 return Ok(());
108 }
109 let Some(tx) = table.data_source().transaction() else {
110 return run_after_now(hooks, id, record, before, table).await;
111 };
112 let hooks = hooks.to_vec();
113 let (id, record, before) = (id.clone(), record.clone(), before.cloned());
114 after_commit(&tx, table, move |table| async move {
115 run_after_now(&hooks, &id, &record, before.as_ref(), &table).await
116 });
117 Ok(())
118}
119
120pub(crate) async fn run_after_delete_all<T: TableSource>(
122 hooks: &[DeleteAllFn<T>],
123 table: &Table<T, EmptyEntity>,
124) -> Result<()> {
125 if hooks.is_empty() {
126 return Ok(());
127 }
128 let Some(tx) = table.data_source().transaction() else {
129 return run_delete_all(hooks, table).await;
130 };
131 let hooks = hooks.to_vec();
132 after_commit(&tx, table, move |table| async move {
133 run_delete_all(&hooks, &table).await
134 });
135 Ok(())
136}
137
138fn after_commit<T, F, Fut>(tx: &Transaction, table: &Table<T, EmptyEntity>, work: F)
142where
143 T: TableSource,
144 F: FnOnce(Table<T, EmptyEntity>) -> Fut + Send + 'static,
145 Fut: Future<Output = Result<()>> + Send + 'static,
146{
147 let mut outside = table.with_data_source(table.data_source().without_transaction());
148 outside.lock = false;
150 let context = WriteContext::current();
151 tx.defer(Box::new(move || {
152 Box::pin(async move {
153 match context {
154 Some(context) => context.scope(work(outside)).await,
155 None => work(outside).await,
156 }
157 })
158 }));
159}
160
161async fn run_after_now<T: TableSource>(
162 hooks: &[AfterFn<T>],
163 id: &T::Id,
164 record: &Record<T::Value>,
165 before: Option<&Record<T::Value>>,
166 table: &Table<T, EmptyEntity>,
167) -> Result<()> {
168 let context = WriteContext::current();
169 for hook in hooks {
170 hook(AfterCx {
171 id,
172 record,
173 before,
174 context: context.as_ref(),
175 table,
176 })
177 .await?;
178 }
179 Ok(())
180}
181
182pub(crate) async fn run_delete_all<T: TableSource>(
183 hooks: &[DeleteAllFn<T>],
184 table: &Table<T, EmptyEntity>,
185) -> Result<()> {
186 if hooks.is_empty() {
187 return Ok(());
188 }
189 let context = WriteContext::current();
190 for hook in hooks {
191 hook(DeleteAllCx {
192 context: context.as_ref(),
193 table,
194 })
195 .await?;
196 }
197 Ok(())
198}
199
200pub(crate) async fn run_before_delete<T: TableSource>(
203 hooks: &[BeforeDeleteFn<T>],
204 id: &T::Id,
205 record: &Record<T::Value>,
206 table: &Table<T, EmptyEntity>,
207) -> Result<HookReturn> {
208 let context = WriteContext::current();
209 for hook in hooks {
210 let cx = DeleteCx {
211 id,
212 record,
213 context: context.as_ref(),
214 table,
215 };
216 if let HookReturn::Handled = hook(cx).await? {
217 return Ok(HookReturn::Handled);
218 }
219 }
220 Ok(HookReturn::Proceed)
221}