use std::fmt;
use crate::shared::datum::Datum;
use crate::shared::error::Fail;
use crate::shared::selector::{Segment, Selector};
use crate::shared::sink::Flow;
#[derive(Clone, Debug, PartialEq)]
pub enum Cell {
Null,
Bool(bool),
Number {
value: f64,
lexeme: Option<Box<str>>,
},
String(Box<str>),
Missing,
}
impl Cell {
pub fn from_datum(d: &Datum) -> Cell {
match d {
Datum::Null => Cell::Null,
Datum::Bool(b) => Cell::Bool(*b),
Datum::Number { value, lexeme } => Cell::Number {
value: *value,
lexeme: lexeme.clone(),
},
Datum::String(s) => Cell::String(s.clone()),
Datum::Array(_) | Datum::Object(_) => Cell::String(d.to_string().into()),
}
}
pub fn from_owned(d: Datum) -> Cell {
match d {
Datum::Null => Cell::Null,
Datum::Bool(b) => Cell::Bool(b),
Datum::Number { value, lexeme } => Cell::Number { value, lexeme },
Datum::String(s) => Cell::String(s),
Datum::Array(_) | Datum::Object(_) => Cell::String(d.to_string().into()),
}
}
pub fn is_missing(&self) -> bool {
matches!(self, Cell::Missing)
}
pub fn byte_size(&self) -> usize {
match self {
Cell::Null | Cell::Bool(_) | Cell::Missing => crate::shared::limits::NODE_BYTES,
Cell::Number { lexeme, .. } => {
crate::shared::limits::NODE_BYTES + lexeme.as_ref().map_or(8, |l| l.len())
}
Cell::String(s) => crate::shared::limits::NODE_BYTES + s.len(),
}
}
}
impl fmt::Display for Cell {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Cell::Null => f.write_str("null"),
Cell::Bool(b) => write!(f, "{b}"),
Cell::Number { value, lexeme } => match lexeme {
Some(l) => f.write_str(l),
None => write!(f, "{value}"),
},
Cell::String(s) => {
let mut out = String::new();
crate::shared::datum::write_json_string(s, &mut out);
f.write_str(&out)
}
Cell::Missing => f.write_str("missing"),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PublicColumn {
pub label: Box<str>,
}
impl PublicColumn {
pub fn new(label: impl Into<Box<str>>) -> PublicColumn {
PublicColumn {
label: label.into(),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum TableEvent<'a> {
Schema(&'a [PublicColumn]),
Row(&'a [Cell]),
End,
}
pub trait TableSink {
fn table_event(&mut self, ev: TableEvent<'_>) -> Result<Flow, Fail>;
}
impl<S: TableSink + ?Sized> TableSink for &mut S {
fn table_event(&mut self, ev: TableEvent<'_>) -> Result<Flow, Fail> {
(**self).table_event(ev)
}
}
impl<S: TableSink + ?Sized> TableSink for Box<S> {
fn table_event(&mut self, ev: TableEvent<'_>) -> Result<Flow, Fail> {
(**self).table_event(ev)
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Table {
pub columns: Vec<PublicColumn>,
pub rows: Vec<Vec<Cell>>,
pub ended: bool,
}
impl TableSink for Table {
fn table_event(&mut self, ev: TableEvent<'_>) -> Result<Flow, Fail> {
match ev {
TableEvent::Schema(c) => self.columns = c.to_vec(),
TableEvent::Row(r) => self.rows.push(r.to_vec()),
TableEvent::End => self.ended = true,
}
Ok(Flow::Continue)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum MissingPolicy {
#[default]
Missing,
Null,
Error,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BoundColumn {
pub label: Box<str>,
pub source: Vec<Segment>,
pub missing: MissingPolicy,
}
impl BoundColumn {
pub fn new(label: impl Into<Box<str>>, source: Vec<Segment>) -> BoundColumn {
BoundColumn {
label: label.into(),
source,
missing: MissingPolicy::Missing,
}
}
pub fn public(&self) -> PublicColumn {
PublicColumn {
label: self.label.clone(),
}
}
}
pub type ColumnMapper = Box<dyn Fn(&Datum) -> Result<BoundColumn, Fail> + Send + Sync>;
pub enum Schema {
Static(Vec<BoundColumn>),
FromMetadata {
columns: Selector,
column: ColumnMapper,
},
Infer,
}
impl fmt::Debug for Schema {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Schema::Static(c) => f.debug_tuple("Static").field(c).finish(),
Schema::FromMetadata { columns, .. } => f
.debug_struct("FromMetadata")
.field("columns", columns)
.finish_non_exhaustive(),
Schema::Infer => f.write_str("Infer"),
}
}
}
#[derive(Debug)]
pub struct TableBinding {
pub schema: Schema,
pub rows: Selector,
}
pub fn column_from_meta(meta: &Datum) -> Result<BoundColumn, Fail> {
let obj = meta
.as_object()
.ok_or_else(|| Fail::input("a column descriptor is not an object"))?;
let label = obj
.get("title")
.and_then(Datum::as_str)
.ok_or_else(|| Fail::input("a column descriptor has no string \"title\""))?;
let path = obj
.get("path")
.and_then(Datum::as_array)
.ok_or_else(|| Fail::input(format!("column {label:?} has no \"path\" array")))?;
let source = path
.iter()
.map(|seg| match seg {
Datum::String(s) => Ok(Segment::Key(s.clone())),
Datum::Number { value, .. }
if *value >= 0.0 && value.fract() == 0.0 && *value <= u32::MAX as f64 =>
{
Ok(Segment::Index(*value as usize))
}
other => Err(Fail::input(format!(
"column {label:?} has a path segment that is neither a string nor a non-negative integer: {other}"
))),
})
.collect::<Result<Vec<_>, _>>()?;
Ok(BoundColumn::new(label, source))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn column_from_meta_reads_title_and_path() {
let meta = Datum::from_json(
&serde_json::json!({"title": "Balance", "path": ["account", "balance"]}),
);
let c = column_from_meta(&meta).unwrap();
assert_eq!(&*c.label, "Balance");
assert_eq!(
c.source,
vec![Segment::key("account"), Segment::key("balance")]
);
let meta = Datum::from_json(&serde_json::json!({"title": "First", "path": ["tags", 0]}));
assert_eq!(
column_from_meta(&meta).unwrap().source[1],
Segment::Index(0)
);
let bad = Datum::from_json(&serde_json::json!({"title": "x", "path": ["a", -1]}));
assert_eq!(
column_from_meta(&bad).unwrap_err().code,
crate::shared::Code::InputInvalid
);
let bad = Datum::from_json(&serde_json::json!({"path": ["a"]}));
assert!(column_from_meta(&bad).is_err());
}
#[test]
fn cells_print_as_json() {
assert_eq!(
Cell::from_datum(&Datum::from_json(&serde_json::json!(50.25))).to_string(),
"50.25"
);
assert_eq!(
Cell::from_datum(&Datum::from_json(&serde_json::json!([1, 2]))).to_string(),
"\"[1,2]\""
);
assert_eq!(Cell::Missing.to_string(), "missing");
assert_eq!(Cell::from_datum(&Datum::Null), Cell::Null);
}
#[test]
fn a_table_records() {
let mut t = Table::default();
let cols = [PublicColumn::new("a")];
t.table_event(TableEvent::Schema(&cols)).unwrap();
t.table_event(TableEvent::Row(&[Cell::Bool(true)])).unwrap();
t.table_event(TableEvent::End).unwrap();
assert_eq!(t.columns.len(), 1);
assert_eq!(t.rows, vec![vec![Cell::Bool(true)]]);
assert!(t.ended);
}
}