use topcoat::context::Cx;
use super::{Action, HeaderAction, Resource};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Change<M> {
Create(M),
Update { before: M, after: M },
Delete(Vec<M>),
Restore(Vec<M>),
ForceDelete(Vec<M>),
Action { name: &'static str, records: Vec<M> },
HeaderAction { name: &'static str },
Attach {
owner: M,
relation: String,
key: String,
},
Detach {
owner: M,
relation: String,
keys: Vec<String>,
},
}
impl<M> Change<M> {
pub fn records(&self) -> &[M] {
match self {
Self::Create(record)
| Self::Update { after: record, .. }
| Self::Attach { owner: record, .. }
| Self::Detach { owner: record, .. } => std::slice::from_ref(record),
Self::Delete(records)
| Self::Restore(records)
| Self::ForceDelete(records)
| Self::Action { records, .. } => records,
Self::HeaderAction { .. } => &[],
}
}
pub(crate) fn map<N>(&self, f: impl Fn(&M) -> N) -> Change<N> {
match self {
Self::Create(record) => Change::Create(f(record)),
Self::Update { before, after } => Change::Update {
before: f(before),
after: f(after),
},
Self::Delete(records) => Change::Delete(records.iter().map(f).collect()),
Self::Restore(records) => Change::Restore(records.iter().map(f).collect()),
Self::ForceDelete(records) => Change::ForceDelete(records.iter().map(f).collect()),
Self::Action { name, records } => Change::Action {
name,
records: records.iter().map(f).collect(),
},
Self::HeaderAction { name } => Change::HeaderAction { name },
Self::Attach {
owner,
relation,
key,
} => Change::Attach {
owner: f(owner),
relation: relation.clone(),
key: key.clone(),
},
Self::Detach {
owner,
relation,
keys,
} => Change::Detach {
owner: f(owner),
relation: relation.clone(),
keys: keys.clone(),
},
}
}
pub fn is_action<A, R>(&self) -> bool
where
A: Action<R>,
R: Resource<Model = M>,
{
matches!(self, Self::Action { name, .. } if *name == A::NAME)
}
pub fn is_header_action<A: HeaderAction>(&self) -> bool {
matches!(self, Self::HeaderAction { name } if *name == A::NAME)
}
}
pub(crate) fn acted<R: Resource, A: Action<R>>(records: Vec<R::Model>) -> Change<R::Model> {
Change::Action {
name: A::NAME,
records,
}
}
pub(crate) async fn run_after_commit<R: Resource>(cx: &Cx, change: Change<R::Model>) {
if let Err(error) = R::after_commit(cx, change).await {
tracing::error!(
error = %error,
resource = std::any::type_name::<R>(),
"after_commit failed; the write stays committed"
);
}
}