use std::collections::{HashMap, HashSet};
use topcoat::{Result, context::Cx, view::*};
use super::{
Schema,
embedded::Embedded,
fields::Field,
layouts::{Grid, Group, Repeater, Section},
validation::required_error,
};
use crate::form::FieldErrors;
#[derive(Debug)]
pub(crate) enum Node {
Field(usize),
Repeater(Box<Repeater>),
Section(Box<Section>),
Group(Box<Group>),
Grid(Box<Grid>),
Embedded(Box<Embedded>),
}
#[derive(Clone, Copy, Debug)]
pub(crate) enum LeafPlace {
Rendered,
Payload,
Discriminant,
}
pub struct Source<'a> {
values: &'a HashMap<String, String>,
errors: Option<&'a FieldErrors>,
}
impl<'a> Source<'a> {
pub fn form(values: &'a HashMap<String, String>, errors: &'a FieldErrors) -> Self {
Self {
values,
errors: Some(errors),
}
}
pub fn view(values: &'a HashMap<String, String>) -> Self {
Self {
values,
errors: None,
}
}
pub(crate) fn mode(&self) -> Mode {
if self.errors.is_some() {
Mode::Form
} else {
Mode::View
}
}
pub(crate) fn value(&self, name: &str) -> Option<&str> {
self.values.get(name).map(String::as_str)
}
pub(crate) fn errors_for(&self, name: &str) -> Option<&str> {
self.errors
.and_then(|errors| errors.first(name))
.map(|error| error.message.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Mode {
Form,
View,
}
impl Node {
pub(crate) async fn render<'a>(
&self,
cx: &'a Cx,
fields: &[Field],
source: &Source<'_>,
) -> Result<BoxView<'a>> {
match self {
Node::Field(index) => {
let field = &fields[*index];
Box::pin(field.render(
cx,
source.value(field.name()),
source.errors_for(field.name()),
source.mode(),
))
.await
}
Node::Repeater(r) => Box::pin(r.render(cx, fields, source)).await,
Node::Section(s) => Box::pin(s.render(cx, fields, source)).await,
Node::Group(g) => Box::pin(g.render(cx, fields, source)).await,
Node::Grid(g) => Box::pin(g.render(cx, fields, source)).await,
Node::Embedded(e) => Box::pin(e.render(cx, fields, source)).await,
}
}
pub(crate) fn children(&self) -> Option<&[Node]> {
match self {
Node::Repeater(r) => Some(&r.children.nodes),
Node::Section(s) => Some(&s.children.nodes),
Node::Group(g) => Some(&g.children.nodes),
Node::Grid(g) => Some(&g.children.nodes),
Node::Field(_) | Node::Embedded(_) => None,
}
}
fn children_mut(&mut self) -> Option<&mut [Node]> {
match self {
Node::Repeater(r) => Some(&mut r.children.nodes),
Node::Section(s) => Some(&mut s.children.nodes),
Node::Group(g) => Some(&mut g.children.nodes),
Node::Grid(g) => Some(&mut g.children.nodes),
Node::Field(_) | Node::Embedded(_) => None,
}
}
pub(crate) fn offset(&mut self, by: usize) {
match self {
Node::Field(index) => *index += by,
Node::Embedded(e) => e.offset(by),
_ => {
for child in self.children_mut().unwrap_or_default() {
child.offset(by);
}
}
}
}
pub(crate) fn visit_fields(&self, f: &mut impl FnMut(usize, LeafPlace)) {
match self {
Node::Field(index) => f(*index, LeafPlace::Rendered),
Node::Embedded(e) => e.visit_fields(LeafPlace::Rendered, f),
_ => {
for child in self.children().unwrap_or_default() {
child.visit_fields(f);
}
}
}
}
}
pub(crate) async fn render_nodes<'a>(
cx: &'a Cx,
nodes: &[Node],
fields: &[Field],
source: &Source<'_>,
) -> Result<BoxView<'a>> {
let mut views = Vec::with_capacity(nodes.len());
for node in nodes {
views.push(node.render(cx, fields, source).await?);
}
Ok(view! {
cx =>
for v in views {
(v)
}
}
.boxed())
}
pub(crate) fn walk_absent_groups(
nodes: &[Node],
fields: &[Field],
values: &HashMap<String, String>,
skip: &mut HashSet<String>,
errors: &mut FieldErrors,
inside_absent: bool,
) {
let name = |index: usize| fields[index].name().to_string();
for node in nodes {
match node {
Node::Embedded(e) => {
let mut hidden = Vec::new();
e.hidden_fields(values, &mut hidden);
skip.extend(hidden.into_iter().map(name));
}
Node::Repeater(r) => {
let mut inner = Vec::new();
node.visit_fields(&mut |index, _| inner.push(name(index)));
let all_empty = inner
.iter()
.all(|n| values.get(n).map(|v| v.trim().is_empty()).unwrap_or(true));
if all_empty {
skip.extend(inner);
if r.required && !inside_absent && !errors.contains_key(&r.label) {
errors.add_required(&r.label, required_error(&r.label));
}
}
walk_absent_groups(
&r.children.nodes,
fields,
values,
skip,
errors,
inside_absent || all_empty,
);
}
_ => {
if let Some(children) = node.children() {
walk_absent_groups(children, fields, values, skip, errors, inside_absent);
}
}
}
}
}
pub trait IntoSchema {
fn into_schema(self) -> Schema;
}
impl IntoSchema for Schema {
fn into_schema(self) -> Schema {
self
}
}
impl IntoSchema for Field {
fn into_schema(self) -> Schema {
Schema {
nodes: vec![Node::Field(0)],
fields: vec![self],
}
}
}
macro_rules! container_nodes {
($($ty:ident),+ $(,)?) => {
$(
impl IntoSchema for $ty {
fn into_schema(mut self) -> Schema {
let fields = std::mem::take(&mut self.children.fields);
Schema {
nodes: vec![Node::$ty(Box::new(self))],
fields,
}
}
}
)+
};
}
container_nodes!(Section, Group, Grid, Repeater);
macro_rules! into_schema_tuples {
($($T:ident => $v:ident),+ $(,)?) => {
impl<$($T),+> IntoSchema for ($($T,)+)
where
$($T: IntoSchema,)+
{
fn into_schema(self) -> Schema {
let ($($v,)+) = self;
let mut schema = Schema::empty();
$( schema.append($v.into_schema()); )+
schema
}
}
};
}
into_schema_tuples!(A => a, B => b);
into_schema_tuples!(A => a, B => b, C => c);
into_schema_tuples!(A => a, B => b, C => c, D => d);
into_schema_tuples!(A => a, B => b, C => c, D => d, E => e);
into_schema_tuples!(A => a, B => b, C => c, D => d, E => e, F => f);
into_schema_tuples!(A => a, B => b, C => c, D => d, E => e, F => f, G => g);
into_schema_tuples!(
A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h
);
#[cfg(test)]
mod tests;