use toasty::stmt::Expr;
use topcoat::{Result, context::Cx};
use super::super::super::relationship::{
OptionLoadError, OptionSource, RelatedCheck, RelationshipChecker, RelationshipLoader,
RelationshipSearchLoader, related_record_check, related_records, related_records_search,
viewable_keys,
};
use crate::BoxFuture;
type ViewableKeys =
for<'a> fn(
&'a Cx,
&'a [String],
) -> BoxFuture<'a, Result<std::collections::HashSet<String>, OptionLoadError>>;
#[derive(Clone)]
pub(crate) struct Relationship {
pub(super) load: RelationshipLoader,
pub(super) search: RelationshipSearchLoader,
pub(super) check: RelationshipChecker,
pub(super) viewable: ViewableKeys,
pub(super) source: &'static str,
source_type: std::any::TypeId,
pub(super) model: std::any::TypeId,
pub(super) model_id: crate::toasty_compat::model::ModelId,
pub(super) available: fn(&Cx) -> bool,
pub(super) tenant_scoped_model: fn(&Cx) -> Option<crate::toasty_compat::model::ModelId>,
pub(super) composite: bool,
custom_label: bool,
model_name: &'static str,
}
impl Relationship {
pub(crate) fn new<R>(label: impl Fn(&Cx, &R::Model) -> String + Send + Sync + 'static) -> Self
where
R: OptionSource + 'static,
{
let project = std::sync::Arc::new(move |cx: &Cx, record: &R::Model| {
(crate::toasty_compat::pk::pk_text(record), label(cx, record))
});
let search_project = project.clone();
let load = std::sync::Arc::new(move |cx: &Cx| {
let project = project.clone();
let cx = cx.clone();
Box::pin(async move {
let records = related_records::<R>(&cx, crate::tenancy::tenant_id(&cx))
.await
.map_err(|error| error.clone())?;
Ok(records.iter().map(|record| project(&cx, record)).collect())
}) as BoxFuture<'static, _>
}) as RelationshipLoader;
let search = std::sync::Arc::new(move |cx: &Cx, q: String, scope: Option<Expr<bool>>| {
let project = search_project.clone();
let cx = cx.clone();
Box::pin(async move {
let records = related_records_search::<R>(&cx, q, scope)
.await
.map_err(|error| error.clone())?;
Ok(records.iter().map(|record| project(&cx, record)).collect())
}) as BoxFuture<'static, _>
}) as RelationshipSearchLoader;
let check = std::sync::Arc::new(check_record::<R>) as RelationshipChecker;
Self {
load,
search,
check,
viewable: |cx, keys| {
Box::pin(async move {
viewable_keys::<R>(cx, crate::tenancy::tenant_id(cx), keys)
.await
.cloned()
.map_err(Clone::clone)
})
},
source: std::any::type_name::<R>(),
source_type: std::any::TypeId::of::<R>(),
model: std::any::TypeId::of::<R::Model>(),
model_id: <R::Model as toasty::schema::Model>::id(),
available: R::available,
tenant_scoped_model: |cx| {
R::requires_tenant(cx).then(<R::Model as toasty::schema::Model>::id)
},
composite: crate::toasty_compat::pk::pk_is_composite::<R::Model>(),
custom_label: false,
model_name: crate::naming::type_short_name::<R::Model>(),
}
}
pub(crate) fn load(&self) -> RelationshipLoader {
self.load.clone()
}
pub(crate) async fn admits(&self, cx: &Cx, keys: &[String]) -> Result<bool> {
let shown: std::collections::HashSet<String> = match (self.load)(cx).await {
Ok(options) => options.into_iter().map(|(value, _)| value).collect(),
Err(OptionLoadError::Overflow) => match (self.viewable)(cx, keys).await {
Ok(keys) => keys,
Err(error) => return refusal(error),
},
Err(error) => return refusal(error),
};
Ok(keys.iter().all(|key| shown.contains(key)))
}
pub(crate) fn has_composite_source(&self) -> bool {
self.composite
}
pub(crate) fn with_custom_label(mut self) -> Self {
self.custom_label = true;
self
}
pub(crate) fn unmasked_model(
&self,
is_mounted: impl Fn(std::any::TypeId) -> bool,
restricted: impl Fn(crate::toasty_compat::model::ModelId) -> bool,
) -> Option<&'static str> {
let unmasked = self.custom_label || !is_mounted(self.source_type);
(unmasked && restricted(self.model_id)).then_some(self.model_name)
}
pub(crate) fn unavailable_source(&self, cx: &Cx) -> Option<&'static str> {
(!(self.available)(cx)).then_some(self.source)
}
}
fn refusal(error: OptionLoadError) -> Result<bool> {
match error {
OptionLoadError::LoadFailed => Err(crate::error::unavailable(
"relationship filter check failed",
)),
_ => Ok(false),
}
}
fn check_record<'a, R>(
cx: &'a Cx,
value: String,
scope: Option<Expr<bool>>,
ex: &'a mut dyn toasty::Executor,
) -> BoxFuture<'a, Result<RelatedCheck, OptionLoadError>>
where
R: OptionSource,
{
Box::pin(related_record_check::<R>(cx, value, scope, ex))
}