1use std::fmt;
9
10use crate::shared::datum::Datum;
11use crate::shared::error::Fail;
12use crate::shared::selector::{Segment, Selector};
13use crate::shared::sink::Flow;
14
15#[derive(Clone, Debug, PartialEq)]
17pub enum Cell {
18 Null,
19 Bool(bool),
20 Number {
21 value: f64,
22 lexeme: Option<Box<str>>,
23 },
24 String(Box<str>),
25 Missing,
28}
29
30impl Cell {
31 pub fn from_datum(d: &Datum) -> Cell {
35 match d {
36 Datum::Null => Cell::Null,
37 Datum::Bool(b) => Cell::Bool(*b),
38 Datum::Number { value, lexeme } => Cell::Number {
39 value: *value,
40 lexeme: lexeme.clone(),
41 },
42 Datum::String(s) => Cell::String(s.clone()),
43 Datum::Array(_) | Datum::Object(_) => Cell::String(d.to_string().into()),
44 }
45 }
46
47 pub fn from_owned(d: Datum) -> Cell {
50 match d {
51 Datum::Null => Cell::Null,
52 Datum::Bool(b) => Cell::Bool(b),
53 Datum::Number { value, lexeme } => Cell::Number { value, lexeme },
54 Datum::String(s) => Cell::String(s),
55 Datum::Array(_) | Datum::Object(_) => Cell::String(d.to_string().into()),
56 }
57 }
58
59 pub fn is_missing(&self) -> bool {
60 matches!(self, Cell::Missing)
61 }
62
63 pub fn byte_size(&self) -> usize {
65 match self {
66 Cell::Null | Cell::Bool(_) | Cell::Missing => crate::shared::limits::NODE_BYTES,
67 Cell::Number { lexeme, .. } => {
68 crate::shared::limits::NODE_BYTES + lexeme.as_ref().map_or(8, |l| l.len())
69 }
70 Cell::String(s) => crate::shared::limits::NODE_BYTES + s.len(),
71 }
72 }
73}
74
75impl fmt::Display for Cell {
76 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78 match self {
79 Cell::Null => f.write_str("null"),
80 Cell::Bool(b) => write!(f, "{b}"),
81 Cell::Number { value, lexeme } => match lexeme {
82 Some(l) => f.write_str(l),
83 None => write!(f, "{value}"),
84 },
85 Cell::String(s) => {
86 let mut out = String::new();
87 crate::shared::datum::write_json_string(s, &mut out);
88 f.write_str(&out)
89 }
90 Cell::Missing => f.write_str("missing"),
91 }
92 }
93}
94
95#[derive(Clone, Debug, PartialEq, Eq)]
97pub struct PublicColumn {
98 pub label: Box<str>,
99}
100
101impl PublicColumn {
102 pub fn new(label: impl Into<Box<str>>) -> PublicColumn {
103 PublicColumn {
104 label: label.into(),
105 }
106 }
107}
108
109#[derive(Clone, Copy, Debug, PartialEq)]
111pub enum TableEvent<'a> {
112 Schema(&'a [PublicColumn]),
114 Row(&'a [Cell]),
116 End,
118}
119
120pub trait TableSink {
122 fn table_event(&mut self, ev: TableEvent<'_>) -> Result<Flow, Fail>;
123}
124
125impl<S: TableSink + ?Sized> TableSink for &mut S {
126 fn table_event(&mut self, ev: TableEvent<'_>) -> Result<Flow, Fail> {
127 (**self).table_event(ev)
128 }
129}
130
131impl<S: TableSink + ?Sized> TableSink for Box<S> {
132 fn table_event(&mut self, ev: TableEvent<'_>) -> Result<Flow, Fail> {
133 (**self).table_event(ev)
134 }
135}
136
137#[derive(Clone, Debug, Default, PartialEq)]
139pub struct Table {
140 pub columns: Vec<PublicColumn>,
141 pub rows: Vec<Vec<Cell>>,
142 pub ended: bool,
143}
144
145impl TableSink for Table {
146 fn table_event(&mut self, ev: TableEvent<'_>) -> Result<Flow, Fail> {
147 match ev {
148 TableEvent::Schema(c) => self.columns = c.to_vec(),
149 TableEvent::Row(r) => self.rows.push(r.to_vec()),
150 TableEvent::End => self.ended = true,
151 }
152 Ok(Flow::Continue)
153 }
154}
155
156#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
158pub enum MissingPolicy {
159 #[default]
161 Missing,
162 Null,
164 Error,
166}
167
168#[derive(Clone, Debug, PartialEq, Eq)]
171pub struct BoundColumn {
172 pub label: Box<str>,
173 pub source: Vec<Segment>,
174 pub missing: MissingPolicy,
175}
176
177impl BoundColumn {
178 pub fn new(label: impl Into<Box<str>>, source: Vec<Segment>) -> BoundColumn {
179 BoundColumn {
180 label: label.into(),
181 source,
182 missing: MissingPolicy::Missing,
183 }
184 }
185
186 pub fn public(&self) -> PublicColumn {
187 PublicColumn {
188 label: self.label.clone(),
189 }
190 }
191}
192
193pub type ColumnMapper = Box<dyn Fn(&Datum) -> Result<BoundColumn, Fail> + Send + Sync>;
195
196pub enum Schema {
198 Static(Vec<BoundColumn>),
200 FromMetadata {
203 columns: Selector,
204 column: ColumnMapper,
205 },
206 Infer,
210}
211
212impl fmt::Debug for Schema {
213 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214 match self {
215 Schema::Static(c) => f.debug_tuple("Static").field(c).finish(),
216 Schema::FromMetadata { columns, .. } => f
217 .debug_struct("FromMetadata")
218 .field("columns", columns)
219 .finish_non_exhaustive(),
220 Schema::Infer => f.write_str("Infer"),
221 }
222 }
223}
224
225#[derive(Debug)]
227pub struct TableBinding {
228 pub schema: Schema,
229 pub rows: Selector,
231}
232
233pub fn column_from_meta(meta: &Datum) -> Result<BoundColumn, Fail> {
236 let obj = meta
237 .as_object()
238 .ok_or_else(|| Fail::input("a column descriptor is not an object"))?;
239 let label = obj
240 .get("title")
241 .and_then(Datum::as_str)
242 .ok_or_else(|| Fail::input("a column descriptor has no string \"title\""))?;
243 let path = obj
244 .get("path")
245 .and_then(Datum::as_array)
246 .ok_or_else(|| Fail::input(format!("column {label:?} has no \"path\" array")))?;
247 let source = path
248 .iter()
249 .map(|seg| match seg {
250 Datum::String(s) => Ok(Segment::Key(s.clone())),
251 Datum::Number { value, .. }
252 if *value >= 0.0 && value.fract() == 0.0 && *value <= u32::MAX as f64 =>
253 {
254 Ok(Segment::Index(*value as usize))
255 }
256 other => Err(Fail::input(format!(
257 "column {label:?} has a path segment that is neither a string nor a non-negative integer: {other}"
258 ))),
259 })
260 .collect::<Result<Vec<_>, _>>()?;
261 Ok(BoundColumn::new(label, source))
262}
263
264#[cfg(test)]
265mod tests {
266 use super::*;
267
268 #[test]
269 fn column_from_meta_reads_title_and_path() {
270 let meta = Datum::from_json(
271 &serde_json::json!({"title": "Balance", "path": ["account", "balance"]}),
272 );
273 let c = column_from_meta(&meta).unwrap();
274 assert_eq!(&*c.label, "Balance");
275 assert_eq!(
276 c.source,
277 vec![Segment::key("account"), Segment::key("balance")]
278 );
279 let meta = Datum::from_json(&serde_json::json!({"title": "First", "path": ["tags", 0]}));
280 assert_eq!(
281 column_from_meta(&meta).unwrap().source[1],
282 Segment::Index(0)
283 );
284 let bad = Datum::from_json(&serde_json::json!({"title": "x", "path": ["a", -1]}));
285 assert_eq!(
286 column_from_meta(&bad).unwrap_err().code,
287 crate::shared::Code::InputInvalid
288 );
289 let bad = Datum::from_json(&serde_json::json!({"path": ["a"]}));
290 assert!(column_from_meta(&bad).is_err());
291 }
292
293 #[test]
294 fn cells_print_as_json() {
295 assert_eq!(
296 Cell::from_datum(&Datum::from_json(&serde_json::json!(50.25))).to_string(),
297 "50.25"
298 );
299 assert_eq!(
300 Cell::from_datum(&Datum::from_json(&serde_json::json!([1, 2]))).to_string(),
301 "\"[1,2]\""
302 );
303 assert_eq!(Cell::Missing.to_string(), "missing");
304 assert_eq!(Cell::from_datum(&Datum::Null), Cell::Null);
305 }
306
307 #[test]
308 fn a_table_records() {
309 let mut t = Table::default();
310 let cols = [PublicColumn::new("a")];
311 t.table_event(TableEvent::Schema(&cols)).unwrap();
312 t.table_event(TableEvent::Row(&[Cell::Bool(true)])).unwrap();
313 t.table_event(TableEvent::End).unwrap();
314 assert_eq!(t.columns.len(), 1);
315 assert_eq!(t.rows, vec![vec![Cell::Bool(true)]]);
316 assert!(t.ended);
317 }
318}