use std::{collections::HashSet, fmt::Debug, hash::Hash};
use toasty::{Executor, schema::Model};
use topcoat::context::Cx;
#[doc(hidden)]
pub use crate::schema::input::required_input;
use crate::{
column::Includes,
schema::{OptionSource, Schema},
};
pub use crate::{
schema::input::FormInput,
values::{
FieldError, FieldErrors, FormField, FormScalar, FormValue, FormValues, NullableScalar,
assert_form_scalar, parse_list, parse_password, parse_scalar,
},
};
impl<F: RecordForm> crate::schema::EditsRecords for F {}
pub(crate) const VERSION_FIELD: &str = "record_version";
pub(crate) const ANOTHER_FIELD: &str = "create_another";
#[doc(hidden)]
pub fn links_key<M, R, K>(
path: impl Into<toasty::stmt::Path<M, toasty::stmt::List<R::Model>>>,
) -> String
where
M: Model,
R: OptionSource,
R::Model: Model<PrimaryKey = K>,
K: FormScalar,
{
crate::schema::form_key(path.into())
}
#[doc(hidden)]
pub fn links_include<M, R>(
includes: Includes<M>,
path: impl Into<toasty::stmt::Path<M, toasty::stmt::List<R::Model>>>,
) -> Includes<M>
where
M: Model,
R: OptionSource,
{
includes.with(path.into())
}
#[doc(hidden)]
pub fn write_links<T>(records: &toasty::Deferred<Vec<T>>) -> Option<FormValue>
where
T: Model + toasty::stmt::IntoExpr<T>,
{
(!records.is_unloaded()).then(|| {
FormValue::List(
records
.get()
.iter()
.map(crate::toasty_compat::pk::pk_text)
.collect(),
)
})
}
pub trait RecordForm: Sized + Send + 'static {
type Model: Model + toasty::stmt::IntoExpr<Self::Model> + Send + Sync + 'static;
type Field: Copy + Eq + Hash + Debug + Send + Sync + 'static;
fn fields() -> Vec<FormField<Self::Field>>;
fn control(field: Self::Field) -> Schema<Self>;
fn includes() -> Includes<Self::Model> {
Includes::new()
}
fn hydrate(cx: &Cx, record: &Self::Model) -> FormValues;
fn links(&self) -> Vec<(Self::Field, Vec<String>)> {
Vec::new()
}
fn parse(cx: &Cx, values: &FormValues) -> std::result::Result<Self, Vec<FieldError>>;
fn into_create(self) -> <Self::Model as Model>::Create;
fn into_update<'a>(
self,
record: &'a mut Self::Model,
named: &HashSet<Self::Field>,
) -> Option<<Self::Model as Model>::Update<'a>>;
fn exec_update<'a>(
update: <Self::Model as Model>::Update<'a>,
ex: &'a mut dyn Executor,
) -> impl Future<Output = toasty::Result<()>> + Send + 'a;
const HAS_FORM: bool = true;
}
pub struct NoForm<M>(std::marker::PhantomData<fn() -> M>);
impl<M: Model + toasty::stmt::IntoExpr<M> + Send + Sync + 'static> NoForm<M> {
fn unreachable() -> ! {
panic!("`NoForm<{}>` has no form", std::any::type_name::<M>())
}
}
impl<M: Model + toasty::stmt::IntoExpr<M> + Send + Sync + 'static> RecordForm for NoForm<M> {
type Model = M;
type Field = std::convert::Infallible;
const HAS_FORM: bool = false;
fn fields() -> Vec<FormField<Self::Field>> {
Vec::new()
}
fn control(field: Self::Field) -> Schema<Self> {
match field {}
}
fn hydrate(_cx: &Cx, _record: &M) -> FormValues {
FormValues::new()
}
fn parse(_cx: &Cx, _values: &FormValues) -> std::result::Result<Self, Vec<FieldError>> {
Self::unreachable()
}
fn into_create(self) -> M::Create {
Self::unreachable()
}
fn into_update<'a>(
self,
_record: &'a mut M,
_named: &HashSet<Self::Field>,
) -> Option<M::Update<'a>> {
None
}
fn exec_update<'a>(
update: M::Update<'a>,
ex: &'a mut dyn Executor,
) -> impl Future<Output = toasty::Result<()>> + Send + 'a {
drop((update, ex));
async { Self::unreachable() }
}
}
pub struct Posted<F: RecordForm> {
form: F,
named: HashSet<F::Field>,
}
impl<F: RecordForm> Posted<F> {
pub fn new(form: F, named: impl IntoIterator<Item = F::Field>) -> Self {
Self {
form,
named: named.into_iter().collect(),
}
}
pub fn named(&self, field: F::Field) -> bool {
self.named.contains(&field)
}
pub fn into_update(self, record: &mut F::Model) -> Option<<F::Model as Model>::Update<'_>> {
self.form.into_update(record, &self.named)
}
}
impl<F: RecordForm> std::ops::Deref for Posted<F> {
type Target = F;
fn deref(&self) -> &F {
&self.form
}
}