use std::sync::OnceLock;
use toasty::stmt::Path;
use topcoat::context::Cx;
pub(crate) use crate::toasty_compat::model::{EnumShape, LeafField};
use crate::{
DeclarationErrorKind,
toasty_compat::model::{self, AppSchema, ModelPath},
};
#[derive(Debug, Clone)]
pub(crate) struct Binding {
path: Option<ModelPath>,
model: &'static str,
placeholder: String,
leaf: OnceLock<Result<LeafField, DeclarationErrorKind>>,
}
impl Binding {
pub(crate) fn of<M, T>(path: &Path<M, T>) -> Self
where
M: toasty::schema::Model,
{
let path = ModelPath::of(path);
let leaf = OnceLock::new();
if !path.is_embedded() {
let _ = leaf.set(model::single_field::<M>(&path));
}
Self {
placeholder: format!("{:?}", path.steps()),
path: Some(path),
model: std::any::type_name::<M>(),
leaf,
}
}
pub(crate) fn named(name: String, label: String) -> Self {
Self {
path: None,
model: "",
placeholder: String::new(),
leaf: OnceLock::from(Ok(LeafField {
name,
label,
nullable: true,
unique: false,
})),
}
}
pub(crate) fn bind(&self, resolver: &FieldResolver) {
if let Some(path) = &self.path {
self.leaf
.get_or_init(|| resolver.resolve_embedded(path, self.model));
}
}
fn leaf(&self) -> Option<&LeafField> {
self.leaf.get().and_then(|leaf| leaf.as_ref().ok())
}
pub(crate) fn name(&self) -> &str {
self.leaf().map_or(&self.placeholder, |leaf| &leaf.name)
}
pub(crate) fn label(&self) -> &str {
self.leaf().map_or("", |leaf| &leaf.label)
}
pub(crate) fn nullable(&self) -> bool {
self.leaf().is_none_or(|leaf| leaf.nullable)
}
pub(crate) fn unique(&self) -> bool {
self.leaf().is_some_and(|leaf| leaf.unique)
}
pub(crate) fn misdeclared(&self) -> Option<DeclarationErrorKind> {
match self.leaf.get() {
Some(Ok(_)) => None,
Some(Err(error)) => Some(error.clone()),
None => Some(DeclarationErrorKind::Unbound { item: self.model }),
}
}
}
#[doc(hidden)]
pub fn form_key<M, T>(path: Path<M, T>) -> String
where
M: toasty::schema::Model,
{
Binding::of(&path).name().to_string()
}
#[derive(Clone, Default)]
pub struct FieldResolver {
schema: Option<AppSchema>,
}
impl FieldResolver {
pub(crate) fn new(schema: Option<AppSchema>) -> Self {
Self { schema }
}
pub(crate) fn of_db(db: &toasty::Db) -> Self {
Self::new(Some(AppSchema::of_db(db)))
}
pub(crate) fn of(cx: &Cx) -> Self {
Self::new(AppSchema::of(cx))
}
pub(crate) fn has_schema(&self) -> bool {
self.schema.is_some()
}
#[cfg(test)]
pub(crate) fn resolve<M, T>(&self, path: Path<M, T>) -> Result<LeafField, DeclarationErrorKind>
where
M: toasty::schema::Model,
{
let path = ModelPath::of(&path);
if path.is_embedded() {
self.resolve_embedded(&path, std::any::type_name::<M>())
} else {
model::single_field::<M>(&path)
}
}
fn resolve_embedded(
&self,
path: &ModelPath,
model: &'static str,
) -> Result<LeafField, DeclarationErrorKind> {
model::embedded_leaf(self.schema.as_ref(), path, model)
}
pub(crate) fn resolve_enum<M, T>(&self, path: Path<M, T>) -> Option<EnumShape> {
model::embedded_enum(self.schema.as_ref()?, &ModelPath::of(&path))
}
}
#[cfg(test)]
mod tests;