use derive_where::derive_where;
use toasty::stmt::Path;
use crate::{DeclarationErrorKind, column::Includes, toasty_compat::model};
#[derive_where(Clone, Debug)]
pub struct RelationLens<M, R> {
includes: Includes<M>,
field: Result<(String, String), DeclarationErrorKind>,
reads: Vec<String>,
#[derive_where(skip(Debug))]
pub(super) read: fn(&M) -> &R,
}
impl<M, R> RelationLens<M, R>
where
M: toasty::schema::Model,
{
#[doc(hidden)]
pub fn new<T>(path: impl Into<Path<M, T>>, read: fn(&M) -> &R) -> Self {
let path = path.into();
let info = model::field::<M>(&model::ModelPath::of(&path));
let reads = match &info {
Ok(field) => std::iter::once(field.name.clone())
.chain(
model::belongs_to::<M>(field.index)
.map(|relation| relation.foreign_keys)
.unwrap_or_default(),
)
.collect(),
Err(_) => Vec::new(),
};
Self {
includes: Includes::new().with(path),
field: info.map(|f| (f.name, f.label)),
reads,
read,
}
}
}
impl<M, R> RelationLens<M, R> {
pub fn includes(&self) -> &Includes<M> {
&self.includes
}
pub fn read<'a>(&self, record: &'a M) -> &'a R {
(self.read)(record)
}
pub(crate) fn reads(&self) -> crate::lens::Reads<M> {
self.reads
.iter()
.fold(crate::lens::Reads::nothing(), |reads, field| {
reads.named(field.clone())
})
}
pub(crate) fn field(&self) -> Result<(&str, &str), DeclarationErrorKind> {
self.field
.as_ref()
.map(|(name, label)| (name.as_str(), label.as_str()))
.map_err(Clone::clone)
}
}
#[macro_export]
macro_rules! relation {
($($model:ident)::+ . $field:ident) => {
$crate::RelationLens::new(
<$($model)::+>::fields().$field(),
|record: &$($model)::+| &record.$field,
)
};
}