use std::{
any::{Any, TypeId},
collections::HashMap,
sync::Arc,
};
use topcoat::context::{Cx, try_app_context};
use super::{Actions, Relation, Resource, ResourceDef};
use crate::{
DeclarationError, DeclarationErrorKind, Site,
form::{FormField, RecordForm},
naming::{kebab_case, pluralize, type_short_name, type_stem},
navigation::NavigationItem,
policy::{Ability, Policy},
schema::{Schema, declare_with, schema_of},
table::Table,
tenancy::Tenancy,
};
pub(crate) struct Mounted<R: Resource> {
pub(crate) slug: String,
pub(crate) url: String,
pub(crate) label: String,
pub(crate) plural_label: String,
pub(crate) navigation: NavigationItem,
pub(crate) policy: Arc<dyn Policy<R::Model>>,
pub(crate) tenancy: Tenancy<R::Model>,
pub(crate) table: Table<R::Model>,
pub(crate) form: Arc<Schema>,
view: Option<Schema>,
pub(crate) relations: Vec<Relation<R::Model>>,
pub(crate) actions: Actions<R>,
pub(crate) fields: Vec<FormField<<R::Form as RecordForm>::Field>>,
pub(crate) create_columns: Vec<&'static str>,
}
impl<R: Resource> Mounted<R> {
pub(crate) fn new(def: ResourceDef<R>, prefix: &str) -> Self {
let slug = def
.slug
.unwrap_or_else(|| kebab_case(&pluralize(type_stem::<R>("Resource"))));
let url = format!("{prefix}/{slug}");
let label = def
.label
.unwrap_or_else(|| type_short_name::<R::Model>().to_string());
let plural_label = def.plural_label.unwrap_or_else(|| pluralize(&label));
let navigation = def
.navigation
.unwrap_or_else(|| NavigationItem {
label: plural_label.clone(),
order: def.navigation_order,
icon: def.icon,
..NavigationItem::default()
})
.resolved(&url);
Self {
slug,
url,
label,
plural_label,
navigation,
policy: def.policy,
tenancy: def.tenancy,
table: def.table.unwrap_or_else(<R::Form as RecordForm>::table),
form: Arc::new(def.form.unwrap_or_else(<R::Form as RecordForm>::schema)),
view: def.view,
relations: def.relations,
actions: def.actions,
fields: <R::Form as RecordForm>::fields(),
create_columns: def.create_columns,
}
}
pub(crate) fn view(&self) -> &Schema {
self.view.as_ref().unwrap_or(&self.form)
}
pub(crate) fn has_own_view(&self) -> bool {
self.view.is_some()
}
pub(crate) fn viewed(&self) -> bool {
!self.view().is_empty()
}
pub(crate) fn can(&self, cx: &Cx, ability: Ability<'_, R::Model>) -> bool {
self.policy.allows(cx, ability)
}
}
#[derive(Default)]
pub(crate) struct Mounts(HashMap<TypeId, Arc<dyn Any + Send + Sync>>);
impl Mounts {
pub(crate) fn insert<R: Resource>(&mut self, mounted: Arc<Mounted<R>>) {
self.0.insert(TypeId::of::<R>(), mounted);
}
pub(crate) fn get<R: Resource>(&self) -> Option<Arc<Mounted<R>>> {
let mounted = self.0.get(&TypeId::of::<R>())?;
Arc::clone(mounted).downcast().ok()
}
pub(crate) fn contains(&self, resource: TypeId) -> bool {
self.0.contains_key(&resource)
}
}
pub(crate) struct MountScope(pub(crate) fn(&Cx) -> Option<&Mounts>);
pub(crate) fn mounted<R: Resource>(cx: &Cx) -> Option<Arc<Mounted<R>>> {
match try_app_context::<MountScope>(cx) {
Some(MountScope(mounts)) => mounts(cx)?.get::<R>(),
None => Some(Arc::new(declare_with(schema_of(cx), || {
Mounted::new(R::declare(), "")
}))),
}
}
pub(crate) fn require_mounted<R: Resource>(cx: &Cx) -> topcoat::Result<Arc<Mounted<R>>> {
mounted::<R>(cx).ok_or_else(|| {
crate::error::declaration(
DeclarationError::of::<R>(Site::Registration, DeclarationErrorKind::NotMounted)
.to_string(),
)
})
}