use crate::store::model::FieldKind; use crate::store::{model, Indexes, Store}; use serde_json::Value; use std::borrow::Cow; use std::collections::BTreeSet; use lazy_static::lazy_static; use indexmap::map::IndexMap; use rocket::request::{FormItems, FromForm}; lazy_static! { /// This is an example for using doc comment attributes static ref EMPTY_SET: BTreeSet = Default::default(); } #[derive(Serialize, Debug, Default)] pub struct RenderedField<'a> { pub key: Cow<'a, str>, pub label: Cow<'a, str>, pub kind: &'static str, pub step: &'static str, pub min: String, pub max: String, pub all_tags_json: String, pub tags_json: String, pub options: Option>, pub value: Cow<'a, str>, pub checked: bool, } impl<'a> RenderedField<'a> { pub fn from_template_field<'i>( key: &'i String, field: &'i model::Field, value: Option<&'i Value>, index: &'i Indexes, ) -> RenderedField<'i> { let mut rendered = RenderedField::default(); rendered.key = key.as_str().into(); rendered.label = if field.label.is_empty() { titlecase::titlecase(&key.replace('_', " ")).into() } else { field.label.as_str().into() }; match &field.kind { FieldKind::String => { rendered.kind = "string"; } FieldKind::Text => { rendered.kind = "text"; } FieldKind::Bool { default } => { rendered.kind = "bool"; rendered.checked = if let Some(Value::Bool(v)) = value { *v } else { *default } } FieldKind::Int { min, max, default } => { rendered.kind = "number"; let num = if let Some(Value::Number(n)) = value { n.as_i64().expect("Error parsing number") } else { *default }; if let Some(n) = min { rendered.min = n.to_string(); } if let Some(n) = max { rendered.max = n.to_string(); } rendered.value = Cow::Owned(num.to_string()); rendered.step = "1"; } FieldKind::Float { min, max, default } => { rendered.kind = "number"; let num = if let Some(Value::Number(n)) = value { n.as_f64().expect("Error parsing number") } else { *default }; if let Some(n) = min { rendered.min = n.to_string(); } if let Some(n) = max { rendered.max = n.to_string(); } rendered.value = Cow::Owned(num.to_string()); rendered.step = "any"; } FieldKind::Enum { options, default } => { rendered.kind = "select"; rendered.options = Some(options.iter().collect()); if let Some(Value::String(s)) = value { rendered.value = Cow::Borrowed(&s.as_str()); } else if let Some(def) = default { rendered.value = def.to_owned().into(); } } FieldKind::FreeEnum { enum_group } => { rendered.kind = "free_select"; let group = enum_group.as_ref().unwrap_or(key); let options = index.free_enums.get(group).unwrap_or(&EMPTY_SET); rendered.options = Some(options.iter().collect()); } FieldKind::Tags { options } => { rendered.kind = "tags"; rendered.options = Some(options.iter().collect()); rendered.all_tags_json = serde_json::to_string(options).unwrap(); } FieldKind::FreeTags { tag_group } => { rendered.kind = "free_tags"; let group = tag_group.as_ref().unwrap_or(key); let options = index.free_tags.get(group).unwrap_or(&EMPTY_SET); rendered.options = Some(options.iter().collect()); rendered.all_tags_json = "[]".into(); } } // Shared code by multiple variants match field.kind { FieldKind::Text | FieldKind::String => { if let Some(Value::String(s)) = value { rendered.value = Cow::Borrowed(&s.as_str()); } } FieldKind::Enum { .. } | FieldKind::FreeEnum { .. } => { if let Some(Value::String(s)) = value { rendered.value = Cow::Borrowed(&s.as_str()); } } FieldKind::Tags { .. } | FieldKind::FreeTags { .. } => { if let Some(v) = value { rendered.value = serde_json::from_value::>(v.clone()) .unwrap() .join(" ") .into(); rendered.tags_json = serde_json::to_string(v).unwrap(); } else { rendered.tags_json = "[]".into(); } } _ => {} } rendered } } #[derive(Serialize, Debug)] pub struct RenderedCard<'a> { pub fields: Vec>, pub id: usize, } pub fn render_empty_fields(store: &Store) -> Vec { let indexes = &store.index; store .model .fields .iter() .map(|(key, field)| RenderedField::from_template_field(key, field, None, indexes)) .collect() } pub fn render_card_fields<'a>( store: &'a Store, values: &'a serde_json::Map, ) -> Vec> { let indexes = &store.index; store .model .fields .iter() .map(|(key, field)| { RenderedField::from_template_field(key, field, values.get(key), indexes) }) .collect() } #[derive(Default)] pub struct MapFromForm { pub data: IndexMap, } impl<'a> FromForm<'a> for MapFromForm { type Error = (); fn from_form(items: &mut FormItems, _strict: bool) -> Result { let mut new = MapFromForm::default(); items.for_each(|item| { let (k, v) = item.key_value_decoded(); new.data.insert(k, v); }); Ok(new) } } pub fn collect_card_form(store: &Store, mut form: MapFromForm) -> IndexMap { let mut card = IndexMap::new(); for (k, field) in &store.model.fields { let mut value: Option = None; if let Some(input) = form.data.remove(k) { value = Some(match &field.kind { FieldKind::Text | FieldKind::String | FieldKind::FreeEnum { .. } => { Value::String(input) } FieldKind::Bool { .. } => serde_json::to_value(true).unwrap(), FieldKind::Int { min, max, default } => { let mut val: i64 = if input.is_empty() { *default } else { input.parse().expect("Error parse number") }; if let Some(min) = min { val = val.max(*min); } if let Some(max) = max { val = val.min(*max); } serde_json::to_value(val).unwrap() } FieldKind::Float { min, max, default } => { let mut val: f64 = if input.is_empty() { *default } else { input.parse().expect("Error parse number") }; if let Some(min) = min { val = val.min(*min); } if let Some(max) = max { val = val.max(*max); } serde_json::to_value(val).unwrap() } FieldKind::Enum { options, default } => { if options.contains(&input) { Value::String(input) } else { let val = default.as_ref().map(ToOwned::to_owned).unwrap_or_else(|| { options .first() .expect("fixed enum must have values") .to_owned() }); Value::String(val) } } FieldKind::Tags { options } => { let mut tags: Vec = input .split(' ') .map(ToOwned::to_owned) .filter_map(|tag| { if options.contains(&tag) { Some(tag) } else { None } }) .collect(); tags.sort(); serde_json::to_value(tags).unwrap() } FieldKind::FreeTags { .. } => { let mut tags: Vec = input .split(' ') .map(str::trim) .filter(|s| !s.is_empty()) .map(ToOwned::to_owned) .collect(); tags.sort(); serde_json::to_value(tags).unwrap() } }); } else { if let FieldKind::Bool { .. } = field.kind { value = Some(Value::Bool(false)); } } if let Some(v) = value { card.insert(k.to_owned(), v); } } card }