use topcoat::{Result, context::Cx};
use crate::{
error::TabloError,
policy::Ability,
resource::{Mounted, Resource},
};
pub(crate) async fn find_by_key<R: Resource>(
cx: &Cx,
resource: &Mounted<R>,
id: &str,
ex: &mut dyn toasty::Executor,
) -> Result<R::Model> {
find_by_key_in(resource, id, ex, || resource.scoped_query(cx)).await
}
pub(super) fn composite_pk_error<R: Resource>(resource: &Mounted<R>) -> Option<topcoat::Error> {
if !crate::toasty_compat::pk::pk_is_composite::<R::Model>() {
return None;
}
tracing::error!(
resource = resource.slug,
"composite primary key has no URL representation"
);
Some(
TabloError::Declaration(format!(
"resource '{}' has a composite primary key, which has no URL representation",
resource.slug
))
.into(),
)
}
async fn find_by_key_in<R: Resource>(
resource: &Mounted<R>,
id: &str,
ex: &mut dyn toasty::Executor,
seed: impl FnOnce() -> Result<toasty::stmt::Query<toasty::stmt::List<R::Model>>>,
) -> Result<R::Model> {
let Some(expr) = crate::toasty_compat::pk::pk_eq_expr::<R::Model>(id) else {
if let Some(error) = composite_pk_error(resource) {
return Err(error);
}
return Err(topcoat::router::error::not_found().into());
};
seed()?
.filter(expr)
.first()
.exec(&mut *ex)
.await
.map_err(crate::error::unavailable)?
.ok_or_else(topcoat::router::error::not_found)
.map_err(Into::into)
}
pub(crate) async fn load_viewable<R: Resource>(
cx: &Cx,
resource: &Mounted<R>,
ex: &mut dyn toasty::Executor,
) -> Result<R::Model> {
let id = topcoat::router::path_param_segment(cx, "id").to_string();
check_viewable(cx, resource, find_by_key(cx, resource, &id, ex).await?)
}
pub(crate) async fn load_detail<R: Resource>(
cx: &Cx,
resource: &Mounted<R>,
ex: &mut dyn toasty::Executor,
) -> Result<R::Model> {
let id = topcoat::router::path_param_segment(cx, "id").to_string();
let record = find_by_key_in(resource, &id, ex, || resource.scoped_view_query(cx)).await?;
check_viewable(cx, resource, record)
}
fn check_viewable<R: Resource>(
cx: &Cx,
resource: &Mounted<R>,
record: R::Model,
) -> Result<R::Model> {
if !resource.can(cx, Ability::View(&record)) {
return Err(topcoat::router::error::forbidden().into());
}
Ok(record)
}
#[cfg(test)]
mod tests;