use std::collections::HashSet;
use topcoat::{Result, context::Cx};
use crate::{
form::{FieldError, FieldErrors, FormValues, RecordForm},
resource::{Mounted, Resource},
schema::Schema,
};
pub(in crate::panel::forms) fn complete(
schema: &Schema,
values: &mut FormValues,
named: &HashSet<String>,
stored: &FormValues,
) {
for field in schema.fields() {
let name = field.name();
if named.contains(name) {
continue;
}
match stored.get(name) {
Some(value) => values.insert(name, value.clone()),
None => values.remove(name),
};
}
}
pub(in crate::panel::forms) fn parse_form<R: Resource>(
cx: &Cx,
resource: &Mounted<R>,
values: &FormValues,
errors: &mut FieldErrors,
) -> Result<Option<R::Form>, topcoat::Error> {
match <R::Form as RecordForm>::parse(cx, values) {
Ok(form) => {
errors.extend(form_keyed(
resource,
"validate_record",
R::validate_record(cx, &form),
)?);
Ok(Some(form))
}
Err(failures) => {
for failure in failures {
if !resource.fields.iter().any(|field| {
field
.keys
.iter()
.any(|key| renders_under(&resource.form, key, &failure.key))
}) {
return Err(unbound::<R>("the parse", &failure.key));
}
if !errors.contains_key(&failure.key) {
errors.push(failure);
}
}
Ok(None)
}
}
}
fn form_keyed<R: Resource>(
resource: &Mounted<R>,
source: &str,
errors: FieldErrors<<R::Form as RecordForm>::Field>,
) -> Result<FieldErrors, topcoat::Error> {
let mut keyed = FieldErrors::new();
for FieldError { key, kind } in errors {
let Some(first) = resource
.fields
.iter()
.find(|field| field.field == key)
.and_then(|field| field.keys.first())
else {
return Err(unbound::<R>(source, &key));
};
keyed.push(FieldError {
key: first.clone(),
kind,
});
}
Ok(keyed)
}
pub(in crate::panel::forms) fn refusal<R: Resource>(
resource: &Mounted<R>,
source: &str,
errors: FieldErrors<<R::Form as RecordForm>::Field>,
) -> Result<FieldErrors, topcoat::Error> {
if errors.is_empty() {
return Err(crate::error::declaration(format!(
"{source} of `{}` refused the submission with no field error",
std::any::type_name::<R>()
)));
}
form_keyed(resource, source, errors)
}
fn unbound<R: Resource>(source: &str, key: &dyn std::fmt::Debug) -> topcoat::Error {
crate::error::declaration(format!(
"{source} refused {key:?}, which `{}` binds to no form key",
std::any::type_name::<R::Form>()
))
}
fn renders_under(form: &Schema, key: &str, failed: &str) -> bool {
match failed.strip_prefix(key) {
Some("") => true,
Some(rest) => {
rest.starts_with('.')
&& form
.fields()
.any(|field| field.name() == key && field.as_repeater().is_some())
}
None => false,
}
}
pub(in crate::panel::forms) async fn recheck_relationships(
cx: &Cx,
schema: &Schema,
values: &FormValues,
stored: &FormValues,
errors: &mut FieldErrors,
ex: &mut dyn toasty::Executor,
) {
if errors.is_empty() {
errors.extend(schema.recheck_relationships(cx, values, stored, ex).await);
}
}
pub(in crate::panel::forms) fn named_fields<R: Resource>(
resource: &Mounted<R>,
named: &HashSet<String>,
) -> Vec<<R::Form as RecordForm>::Field> {
resource
.fields
.iter()
.filter(|field| field.keys.iter().any(|key| named.contains(key)))
.map(|field| field.field)
.collect()
}
#[cfg(test)]
mod tests;