use topcoat::{context::Cx, router::Body, view::BoxView};
use super::{InputPage, Pending, Posted, read_post, refusal};
use crate::{
BoxFuture,
db::{WriteTx, db},
form::FormValues,
panel::write::commit_then,
resource::{Change, HeaderEntry, Resource, WriteError, run_after_commit},
};
#[derive(Clone, Copy)]
pub(crate) struct HeaderHooks {
before: for<'c> fn(
&'c Cx,
&'static str,
&'c mut dyn toasty::Executor,
) -> BoxFuture<'c, topcoat::Result<()>>,
after: for<'c> fn(&'c Cx, &'static str) -> BoxFuture<'c, ()>,
}
impl HeaderHooks {
pub(super) fn of<R: Resource>() -> Self {
Self {
before: |cx, name, ex| {
Box::pin(async move {
crate::panel::commit_hook::before_commit::<R>(
cx,
&Change::HeaderAction { name },
ex,
)
.await
})
},
after: |cx, name| Box::pin(run_after_commit::<R>(cx, Change::HeaderAction { name })),
}
}
}
pub(crate) fn run_header<'f, 'a: 'f>(
cx: &'a Cx,
body: Body,
action: HeaderEntry,
home: &'f str,
hooks: Option<HeaderHooks>,
) -> BoxFuture<'f, Result<BoxView<'a>, topcoat::Error>> {
Box::pin(header_run(cx, body, action, home, hooks))
}
async fn header_run<'a>(
cx: &'a Cx,
body: Body,
action: HeaderEntry,
home: &str,
hooks: Option<HeaderHooks>,
) -> Result<BoxView<'a>, topcoat::Error> {
let Posted { pending, .. } =
read_post(cx, body, action.name, action.confirm, &action.input).await?;
let render = |page: InputPage| {
let label = (action.label)().localize(cx);
let chrome = page.chrome(
action.name,
label.clone(),
label,
action.confirm,
Vec::new(),
home,
);
(page, chrome)
};
let (input, values) = match pending {
Pending::Ready(input) => (input, FormValues::new()),
Pending::Submitted { input, values } => (input, values),
Pending::Page(page) => {
let (page, chrome) = render(page);
return page.render(cx, &action.input, chrome).await;
}
};
let mut db = db(cx)?;
let mut tx = WriteTx::begin(cx, &mut db)
.await
.map_err(crate::error::unavailable)?;
let errors = (action.input.schema)()
.recheck_relationships(cx, &values, &FormValues::new(), &mut *tx)
.await;
if !errors.is_empty() {
drop(tx);
let (page, chrome) = render(InputPage { values, errors });
return page.render(cx, &action.input, chrome).await;
}
let written = match ((action.run)(cx, input, &mut *tx).await, hooks) {
(Err(WriteError::Refused(errors)), _) => {
drop(tx);
let errors = refusal(action.name, &action.input, errors)?;
let (page, chrome) = render(InputPage { values, errors });
return page.render(cx, &action.input, chrome).await;
}
(Err(WriteError::Declined(message)), _) => {
drop(tx);
return Err(crate::panel::write::declined_write(cx, &message, home));
}
(Err(WriteError::Failed(error)), _) => Err(error),
(Ok(outcome), Some(hooks)) => (hooks.before)(cx, action.name, &mut *tx)
.await
.map(|()| outcome),
(Ok(outcome), None) => Ok(outcome),
};
let note = (action.success)().localize(cx);
commit_then(
cx,
tx,
written.map(|outcome| ((), outcome)),
|()| async move {
if let Some(hooks) = hooks {
(hooks.after)(cx, action.name).await;
}
},
note,
"tablo-failed-run",
home,
)
.await
}