use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use vantage_types::{EmptyEntity, Entity, Record};
use crate::table::Table;
use crate::traits::table_source::TableSource;
use crate::write_context::WriteContext;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Phase {
Normalize,
Populate,
Validate,
}
pub enum HookReturn {
Proceed,
Handled,
}
pub type BoxFuture<'r, X> = Pin<Box<dyn Future<Output = X> + Send + 'r>>;
#[non_exhaustive]
pub struct BeforeCx<'r, T: TableSource> {
pub record: &'r mut Record<T::Value>,
pub id: Option<&'r T::Id>,
pub stored: Option<&'r Record<T::Value>>,
pub patch: bool,
pub context: Option<&'r WriteContext>,
pub table: &'r Table<T, EmptyEntity>,
}
#[non_exhaustive]
pub struct AfterCx<'r, T: TableSource> {
pub id: &'r T::Id,
pub record: &'r Record<T::Value>,
pub before: Option<&'r Record<T::Value>>,
pub context: Option<&'r WriteContext>,
pub table: &'r Table<T, EmptyEntity>,
}
#[non_exhaustive]
pub struct DeleteCx<'r, T: TableSource> {
pub id: &'r T::Id,
pub record: &'r Record<T::Value>,
pub context: Option<&'r WriteContext>,
pub table: &'r Table<T, EmptyEntity>,
}
#[non_exhaustive]
pub struct DeleteAllCx<'r, T: TableSource> {
pub context: Option<&'r WriteContext>,
pub table: &'r Table<T, EmptyEntity>,
}
pub type DeleteAllFn<T> = Arc<
dyn for<'r> Fn(DeleteAllCx<'r, T>) -> BoxFuture<'r, vantage_core::Result<()>> + Send + Sync,
>;
pub fn delete_all_hook<T, F>(f: F) -> DeleteAllFn<T>
where
T: TableSource,
F: for<'r> Fn(DeleteAllCx<'r, T>) -> BoxFuture<'r, vantage_core::Result<()>>
+ Send
+ Sync
+ 'static,
{
Arc::new(f)
}
pub type BeforeFn<T> =
Arc<dyn for<'r> Fn(BeforeCx<'r, T>) -> BoxFuture<'r, vantage_core::Result<()>> + Send + Sync>;
pub type BeforeDeleteFn<T> = Arc<
dyn for<'r> Fn(DeleteCx<'r, T>) -> BoxFuture<'r, vantage_core::Result<HookReturn>>
+ Send
+ Sync,
>;
pub type AfterFn<T> =
Arc<dyn for<'r> Fn(AfterCx<'r, T>) -> BoxFuture<'r, vantage_core::Result<()>> + Send + Sync>;
pub fn before_hook<T, F>(f: F) -> BeforeFn<T>
where
T: TableSource,
F: for<'r> Fn(BeforeCx<'r, T>) -> BoxFuture<'r, vantage_core::Result<()>>
+ Send
+ Sync
+ 'static,
{
Arc::new(f)
}
pub fn before_delete_hook<T, F>(f: F) -> BeforeDeleteFn<T>
where
T: TableSource,
F: for<'r> Fn(DeleteCx<'r, T>) -> BoxFuture<'r, vantage_core::Result<HookReturn>>
+ Send
+ Sync
+ 'static,
{
Arc::new(f)
}
pub fn before_hook_sync<T: TableSource>(
f: impl Fn(&mut Record<T::Value>, Option<&WriteContext>) -> vantage_core::Result<()>
+ Send
+ Sync
+ 'static,
) -> BeforeFn<T> {
Arc::new(move |cx: BeforeCx<'_, T>| {
let outcome = f(cx.record, cx.context);
Box::pin(std::future::ready(outcome))
})
}
pub fn before_hook_sync_cx<T: TableSource>(
f: impl for<'r> Fn(BeforeCx<'r, T>) -> vantage_core::Result<()> + Send + Sync + 'static,
) -> BeforeFn<T> {
Arc::new(move |cx: BeforeCx<'_, T>| Box::pin(std::future::ready(f(cx))))
}
pub fn before_delete_hook_sync<T: TableSource>(
f: impl Fn(&T::Id, Option<&WriteContext>) -> vantage_core::Result<()> + Send + Sync + 'static,
) -> BeforeDeleteFn<T> {
Arc::new(move |cx: DeleteCx<'_, T>| {
let outcome = f(cx.id, cx.context).map(|()| HookReturn::Proceed);
Box::pin(std::future::ready(outcome))
})
}
pub fn after_hook<T, F>(f: F) -> AfterFn<T>
where
T: TableSource,
F: for<'r> Fn(AfterCx<'r, T>) -> BoxFuture<'r, vantage_core::Result<()>>
+ Send
+ Sync
+ 'static,
{
Arc::new(f)
}
#[non_exhaustive]
pub struct SelectCx<'r, T: TableSource> {
pub context: Option<&'r WriteContext>,
pub table: &'r Table<T, EmptyEntity>,
}
pub type SelectFn<T> =
Arc<dyn for<'r> Fn(SelectCx<'r, T>) -> BoxFuture<'r, vantage_core::Result<()>> + Send + Sync>;
pub fn select_hook<T, F>(f: F) -> SelectFn<T>
where
T: TableSource,
F: for<'r> Fn(SelectCx<'r, T>) -> BoxFuture<'r, vantage_core::Result<()>>
+ Send
+ Sync
+ 'static,
{
Arc::new(f)
}
#[non_exhaustive]
pub enum Hook<T: TableSource> {
BeforeSelect(SelectFn<T>),
BeforeInsert(Phase, BeforeFn<T>),
BeforeUpdate(Phase, BeforeFn<T>),
BeforeSave(Phase, BeforeFn<T>),
BeforeDelete(BeforeDeleteFn<T>),
AfterInsert(AfterFn<T>),
AfterUpdate(AfterFn<T>),
AfterSave(AfterFn<T>),
AfterDelete(AfterFn<T>),
BeforeDeleteAll(DeleteAllFn<T>),
AfterDeleteAll(DeleteAllFn<T>),
}
#[derive(Clone)]
pub struct Hooks<T: TableSource> {
pub(crate) before_select: Vec<SelectFn<T>>,
pub(crate) before_insert: Vec<(Phase, BeforeFn<T>)>,
pub(crate) before_update: Vec<(Phase, BeforeFn<T>)>,
pub(crate) before_delete: Vec<BeforeDeleteFn<T>>,
pub(crate) after_insert: Vec<AfterFn<T>>,
pub(crate) after_update: Vec<AfterFn<T>>,
pub(crate) after_delete: Vec<AfterFn<T>>,
pub(crate) before_delete_all: Vec<DeleteAllFn<T>>,
pub(crate) after_delete_all: Vec<DeleteAllFn<T>>,
}
impl<T: TableSource> Default for Hooks<T> {
fn default() -> Self {
Self {
before_select: Vec::new(),
before_insert: Vec::new(),
before_update: Vec::new(),
before_delete: Vec::new(),
after_insert: Vec::new(),
after_update: Vec::new(),
after_delete: Vec::new(),
before_delete_all: Vec::new(),
after_delete_all: Vec::new(),
}
}
}
impl<T: TableSource, E: Entity<T::Value>> Table<T, E> {
pub(crate) fn before_insert_hooks(&self) -> &[(Phase, BeforeFn<T>)] {
&self.hooks.before_insert
}
pub(crate) fn before_update_hooks(&self) -> &[(Phase, BeforeFn<T>)] {
&self.hooks.before_update
}
pub(crate) fn before_delete_hooks(&self) -> &[BeforeDeleteFn<T>] {
&self.hooks.before_delete
}
pub(crate) fn after_insert_hooks(&self) -> &[AfterFn<T>] {
&self.hooks.after_insert
}
pub(crate) fn after_update_hooks(&self) -> &[AfterFn<T>] {
&self.hooks.after_update
}
pub(crate) fn after_delete_hooks(&self) -> &[AfterFn<T>] {
&self.hooks.after_delete
}
pub(crate) fn before_delete_all_hooks(&self) -> &[DeleteAllFn<T>] {
&self.hooks.before_delete_all
}
pub(crate) fn after_delete_all_hooks(&self) -> &[DeleteAllFn<T>] {
&self.hooks.after_delete_all
}
pub fn add_hook(&mut self, hook: Hook<T>) {
let hooks = &mut self.hooks;
match hook {
Hook::BeforeSelect(f) => hooks.before_select.push(f),
Hook::BeforeInsert(phase, f) => push_phased(&mut hooks.before_insert, phase, f),
Hook::BeforeUpdate(phase, f) => push_phased(&mut hooks.before_update, phase, f),
Hook::BeforeSave(phase, f) => {
push_phased(&mut hooks.before_insert, phase, f.clone());
push_phased(&mut hooks.before_update, phase, f);
}
Hook::BeforeDelete(f) => hooks.before_delete.push(f),
Hook::AfterInsert(f) => hooks.after_insert.push(f),
Hook::AfterUpdate(f) => hooks.after_update.push(f),
Hook::AfterSave(f) => {
hooks.after_insert.push(f.clone());
hooks.after_update.push(f);
}
Hook::AfterDelete(f) => hooks.after_delete.push(f),
Hook::BeforeDeleteAll(f) => hooks.before_delete_all.push(f),
Hook::AfterDeleteAll(f) => hooks.after_delete_all.push(f),
}
}
pub fn with_hook(mut self, hook: Hook<T>) -> Self {
self.add_hook(hook);
self
}
pub async fn check_read(&self) -> vantage_core::Result<()> {
if self.hooks.before_select.is_empty() {
return Ok(());
}
let context = WriteContext::current();
let table = self.as_entity_erased();
for hook in &self.hooks.before_select {
hook(SelectCx {
context: context.as_ref(),
table,
})
.await?;
}
Ok(())
}
}
fn push_phased<T: TableSource>(
hooks: &mut Vec<(Phase, BeforeFn<T>)>,
phase: Phase,
f: BeforeFn<T>,
) {
hooks.push((phase, f));
hooks.sort_by_key(|(p, _)| *p);
}