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(
86 &self,
87 cx: &Cx,
88 posted: &[(String, String)],
89 ) -> std::result::Result<FormValues, String> {
90 self.read_in(posted, crate::time_zone(cx))
91 }
92
93 pub(super) fn read_in(
95 &self,
96 posted: &[(String, String)],
97 zone: TimeZone,
98 ) -> std::result::Result<FormValues, String> {
99 self.read_claiming(&mut Pairs::new(posted, zone))
100 }
101
102 pub(crate) fn read_unclaimed(
104 &self,
105 cx: &Cx,
106 posted: &[(String, String)],
107 ) -> std::result::Result<(FormValues, Vec<String>), String> {
108 let mut pairs = Pairs::new(posted, crate::time_zone(cx));
109 let values = self.read_claiming(&mut pairs)?;
110 Ok((values, pairs.unclaimed()))
111 }
112
113 fn read_claiming(&self, pairs: &mut Pairs<'_>) -> std::result::Result<FormValues, String> {
114 let mut values = FormValues::new();
115 for field in &self.fields {
116 let name = field.name();
117 if let Some(repeater) = field.as_repeater() {
118 if let Some(rows) = repeater.read(name, pairs)? {
119 values.insert(name, rows);
120 }
121 continue;
122 }
123 let posted = pairs.claim(name);
124 if field.as_choice().is_some_and(ChoiceControl::is_multiple) {
125 if !posted.is_empty() {
126 values.insert(name, distinct(&posted));
127 }
128 } else if let Some(last) = posted.last() {
129 values.insert(name, field.read_posted(last, &pairs.zone));
130 }
131 }
132 Ok(values)
133 }
134
135 pub(crate) fn read_input(
143 &self,
144 cx: &Cx,
145 posted: &[(String, String)],
146 reserved: &[&str],
147 ) -> Result<FormValues> {
148 use topcoat::router::error::bad_request;
149
150 let (mut input, unclaimed) = self.read_unclaimed(cx, posted).map_err(bad_request)?;
151 let unknown: Vec<String> = unclaimed
152 .into_iter()
153 .filter(|key| !reserved.contains(&key.as_str()))
154 .collect();
155 if !unknown.is_empty() {
156 return Err(bad_request(format!("unknown field(s): {}", unknown.join(", "))).into());
157 }
158 self.drop_disabled(&mut input, false);
160 for key in self.condition_hidden(&input) {
161 input.remove(&key);
162 }
163 Ok(input)
164 }
165}
166
167fn distinct(posted: &[&str]) -> FormValue {
170 let mut seen = HashSet::new();
171 FormValue::List(
172 posted
173 .iter()
174 .map(|value| value.trim())
175 .filter(|value| !value.is_empty() && seen.insert(*value))
176 .map(str::to_string)
177 .collect(),
178 )
179}
180
181#[cfg(test)]
182mod tests;