use std::{any::TypeId, sync::Arc};
use toasty::stmt::{Expr, IntoExpr, Path};
use super::Resource;
use crate::{form::FormScalar, schema::ResolvedLens, toasty_compat::pk};
pub struct Relation<P> {
pub(crate) child: TypeId,
pub(crate) child_name: &'static str,
pub(crate) label: Option<String>,
pub(crate) foreign_key: String,
bind: BindFn<P>,
pub(crate) scope_of: ScopeFn,
pub(crate) misdeclared: Option<crate::DeclarationErrorKind>,
}
type BindFn<P> = Arc<dyn Fn(&P) -> (Expr<bool>, String) + Send + Sync>;
pub(crate) type ScopeFn = Arc<dyn Fn(&str) -> Option<Expr<bool>> + Send + Sync>;
pub trait ForeignKey<K>:
sealed::Sealed<K> + IntoExpr<Self> + FormScalar + Send + Sync + 'static
{
}
impl<K> ForeignKey<K> for K where K: IntoExpr<K> + FormScalar + Send + Sync + 'static {}
impl<K> ForeignKey<K> for Option<K> where
Option<K>: IntoExpr<Self> + FormScalar + Send + Sync + 'static
{
}
mod sealed {
pub trait Sealed<K> {}
impl<K> Sealed<K> for K {}
impl<K> Sealed<K> for Option<K> {}
}
impl<P> Relation<P>
where
P: toasty::schema::Model + IntoExpr<P> + 'static,
{
pub fn has_many<C>(foreign_key: Path<C::Model, impl ForeignKey<P::PrimaryKey>>) -> Self
where
C: Resource,
{
Self::bound_by::<C, _>(foreign_key)
}
fn bound_by<C, T>(foreign_key: Path<C::Model, T>) -> Self
where
C: Resource,
T: ForeignKey<P::PrimaryKey>,
{
let binding = ResolvedLens::of(foreign_key.clone());
let scope_key = foreign_key.clone();
Self {
child: TypeId::of::<C>(),
child_name: std::any::type_name::<C>(),
label: None,
foreign_key: binding.name,
misdeclared: binding.misdeclared,
bind: Arc::new(move |owner| {
let scope = foreign_key.clone().eq(pk::pk_expr::<P, T>(owner));
(scope, pk::pk_text(owner))
}),
scope_of: Arc::new(move |owner| {
let owner = T::parse_form(owner.trim()).ok()?;
Some(scope_key.clone().eq(owner))
}),
}
}
}
impl<P> Relation<P> {
#[must_use]
pub fn label(mut self, label: impl Into<String>) -> Self {
self.label = Some(label.into());
self
}
pub(crate) fn bind(&self, owner: &P) -> (Expr<bool>, String) {
(self.bind)(owner)
}
}
impl<P> std::fmt::Debug for Relation<P> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Relation")
.field("child", &self.child_name)
.field("label", &self.label)
.field("foreign_key", &self.foreign_key)
.finish_non_exhaustive()
}
}