use indexmap::{IndexMap, IndexSet};
use serde::Serialize;
use crate::form::prelude::*;
use crate::http::Status;
#[derive(Debug)]
pub struct Contextual<'v, T> {
pub value: Option<T>,
pub context: Context<'v>,
}
#[derive(Debug, Default, Serialize)]
pub struct Context<'v> {
errors: IndexMap<NameBuf<'v>, Errors<'v>>,
values: IndexMap<&'v Name, Vec<&'v str>>,
data_fields: IndexSet<&'v Name>,
form_errors: Errors<'v>,
#[serde(skip)]
status: Status,
}
impl<'v> Context<'v> {
pub fn fields(&self) -> impl Iterator<Item = &'v Name> + '_ {
self.values
.iter()
.map(|(name, _)| *name)
.chain(self.data_fields.iter().copied())
}
pub fn field_value<N: AsRef<Name>>(&self, name: N) -> Option<&'v str> {
self.values.get(name.as_ref())?.first().cloned()
}
pub fn field_values<N>(&self, name: N) -> impl Iterator<Item = &'v str> + '_
where
N: AsRef<Name>,
{
self.values
.get(name.as_ref())
.map(|e| e.iter().cloned())
.into_iter()
.flatten()
}
pub fn errors(&self) -> impl Iterator<Item = &Error<'v>> {
self.errors
.values()
.flat_map(|e| e.iter())
.chain(self.form_errors.iter())
}
pub fn field_errors<'a, N>(&'a self, name: N) -> impl Iterator<Item = &'a Error<'v>> + 'a
where
N: AsRef<Name> + 'a,
{
self.errors
.values()
.flat_map(|e| e.iter())
.filter(move |e| e.is_for(&name))
}
pub fn exact_field_errors<'a, N>(&'a self, name: N) -> impl Iterator<Item = &'a Error<'v>> + 'a
where
N: AsRef<Name> + 'a,
{
self.errors
.values()
.flat_map(|e| e.iter())
.filter(move |e| e.is_for_exactly(&name))
}
pub fn status(&self) -> Status {
self.status
}
pub fn push_error(&mut self, error: Error<'v>) {
self.status = std::cmp::max(self.status, error.status());
match error.name {
Some(ref name) => match self.errors.get_mut(name) {
Some(errors) => errors.push(error),
None => {
self.errors.insert(name.clone(), error.into());
}
},
None => self.form_errors.push(error),
}
}
pub fn push_errors<E: Into<Errors<'v>>>(&mut self, errors: E) {
errors.into().into_iter().for_each(|e| self.push_error(e))
}
}
impl<'f> From<Errors<'f>> for Context<'f> {
fn from(errors: Errors<'f>) -> Self {
let mut context = Context::default();
context.push_errors(errors);
context
}
}
#[crate::async_trait]
impl<'v, T: FromForm<'v>> FromForm<'v> for Contextual<'v, T> {
type Context = (<T as FromForm<'v>>::Context, Context<'v>);
fn init(opts: Options) -> Self::Context {
(T::init(opts), Context::default())
}
fn push_value((ref mut val_ctxt, ctxt): &mut Self::Context, field: ValueField<'v>) {
ctxt.values
.entry(field.name.source())
.or_default()
.push(field.value);
T::push_value(val_ctxt, field);
}
async fn push_data((ref mut val_ctxt, ctxt): &mut Self::Context, field: DataField<'v, '_>) {
ctxt.data_fields.insert(field.name.source());
T::push_data(val_ctxt, field).await;
}
fn push_error((_, ref mut ctxt): &mut Self::Context, e: Error<'v>) {
ctxt.push_error(e);
}
fn finalize((val_ctxt, mut context): Self::Context) -> Result<'v, Self> {
let value = match T::finalize(val_ctxt) {
Ok(value) => Some(value),
Err(errors) => {
context.push_errors(errors);
None
}
};
Ok(Contextual { value, context })
}
}