tablo_core/schema/
read.rs1use std::collections::{BTreeSet, HashMap, HashSet};
9
10use jiff::tz::TimeZone;
11use topcoat::{Result, context::Cx};
12
13use super::{Schema, fields::ChoiceControl};
14use crate::values::{FormValue, FormValues};
15
16pub(super) struct Pairs<'p> {
19 pairs: &'p [(String, String)],
20 at: HashMap<&'p str, Vec<usize>>,
21 claimed: Vec<bool>,
22 pub(super) zone: TimeZone,
23}
24
25impl<'p> Pairs<'p> {
26 fn new(pairs: &'p [(String, String)], zone: TimeZone) -> Self {
27 let mut at: HashMap<&str, Vec<usize>> = HashMap::new();
28 for (index, (key, _)) in pairs.iter().enumerate() {
29 at.entry(key.as_str()).or_default().push(index);
30 }
31 Self {
32 pairs,
33 at,
34 claimed: vec![false; pairs.len()],
35 zone,
36 }
37 }
38
39 pub(super) fn claim(&mut self, key: &str) -> Vec<&'p str> {
41 let Some(indices) = self.at.get(key) else {
42 return Vec::new();
43 };
44 indices
45 .iter()
46 .map(|&index| {
47 self.claimed[index] = true;
48 self.pairs[index].1.as_str()
49 })
50 .collect()
51 }
52
53 fn unclaimed(&self) -> Vec<String> {
55 self.pairs
56 .iter()
57 .zip(&self.claimed)
58 .filter(|(_, claimed)| !**claimed)
59 .map(|((key, _), _)| key.clone())
60 .collect::<BTreeSet<_>>()
61 .into_iter()
62 .collect()
63 }
64}
65
66impl<F> Schema<F> {
67 pub fn read(
87 &self,
88 cx: &Cx,
89 posted: &[(String, String)],
90 ) -> std::result::Result<FormValues, String> {
91 self.read_in(posted, crate::time_zone(cx))
92 }
93
94 pub(super) fn read_in(
96 &self,
97 posted: &[(String, String)],
98 zone: TimeZone,
99 ) -> std::result::Result<FormValues, String> {
100 self.read_claiming(&mut Pairs::new(posted, zone))
101 }
102
103 pub(crate) fn read_unclaimed(
105 &self,
106 cx: &Cx,
107 posted: &[(String, String)],
108 ) -> std::result::Result<(FormValues, Vec<String>), String> {
109 let mut pairs = Pairs::new(posted, crate::time_zone(cx));
110 let values = self.read_claiming(&mut pairs)?;
111 Ok((values, pairs.unclaimed()))
112 }
113
114 fn read_claiming(&self, pairs: &mut Pairs<'_>) -> std::result::Result<FormValues, String> {
115 let mut values = FormValues::new();
116 for field in &self.fields {
117 let name = field.name();
118 if let Some(repeater) = field.as_repeater() {
119 if let Some(rows) = repeater.read(name, pairs)? {
120 values.insert(name, rows);
121 }
122 continue;
123 }
124 let mut posted = pairs.claim(name);
125 if field.as_choice().is_some_and(ChoiceControl::is_tags) {
126 posted = posted.iter().flat_map(|value| value.split(',')).collect();
128 }
129 if field.as_choice().is_some_and(ChoiceControl::is_multiple) || field.is_file_list() {
130 if !posted.is_empty() {
131 values.insert(name, distinct(&posted));
132 }
133 } else if let Some(last) = posted.last() {
134 values.insert(name, field.read_posted(last, &pairs.zone));
135 }
136 }
137 Ok(values)
138 }
139
140 pub(crate) fn read_input(
149 &self,
150 cx: &Cx,
151 posted: &[(String, String)],
152 reserved: &[&str],
153 ) -> Result<FormValues> {
154 use topcoat::router::error::bad_request;
155
156 let (mut input, unclaimed) = self.read_unclaimed(cx, posted).map_err(bad_request)?;
157 let unknown: Vec<String> = unclaimed
158 .into_iter()
159 .filter(|key| !reserved.contains(&key.as_str()))
160 .collect();
161 if !unknown.is_empty() {
162 return Err(bad_request(format!("unknown field(s): {}", unknown.join(", "))).into());
163 }
164 self.drop_disabled(&mut input, false);
166 for key in self.condition_hidden(&input) {
167 input.remove(&key);
168 }
169 let disabled = self.condition_disabled(&input);
170 for key in &disabled {
171 input.remove(key);
172 }
173 input.extend(self.defaults_of(disabled.iter().map(String::as_str)));
174 Ok(input)
175 }
176}
177
178fn distinct(posted: &[&str]) -> FormValue {
181 let mut seen = HashSet::new();
182 FormValue::List(
183 posted
184 .iter()
185 .map(|value| value.trim())
186 .filter(|value| !value.is_empty() && seen.insert(*value))
187 .map(str::to_string)
188 .collect(),
189 )
190}
191
192#[cfg(test)]
193mod tests;