Skip to main content

rich_data/
serialize.rs

1//! Rows from any `serde::Serialize` values: a struct's fields (or a map's
2//! keys) are columns, and any other value is one `value` column.
3//!
4//! Values go through `serde_json`'s data model, so a field is typed as JSON
5//! would type it (see [`RecordSource`]): numbers stay numbers, `Option`s are
6//! nullable, nested structs and sequences are described in the schema and
7//! shown as compact JSON. [`read`] samples every value; [`source`] streams,
8//! sampling the first [`DEFAULT_SAMPLE`].
9//!
10//! ```
11//! use rich_data::{serialize, Value};
12//!
13//! #[derive(serde::Serialize)]
14//! struct Deploy { service: &'static str, replicas: u32, healthy: Option<bool> }
15//!
16//! let rows = serialize::read([
17//!     Deploy { service: "web", replicas: 3, healthy: Some(true) },
18//!     Deploy { service: "db", replicas: 1, healthy: None },
19//! ])
20//! .unwrap();
21//! assert_eq!(rows.columns(), ["service", "replicas", "healthy"]);
22//! assert_eq!(rows.rows()[1], [Value::from("db"), Value::Int(1), Value::Null]);
23//! let healthy = rows.schema().unwrap().field("healthy").unwrap();
24//! assert!(healthy.is_nullable());
25//! ```
26
27use serde::Serialize;
28use serde_json::Value as Json;
29
30use crate::{DataError, RecordSource, RowSource, Rows};
31
32pub use crate::record::DEFAULT_SAMPLE;
33
34/// Read `items`, sampling every one for the columns and schema.
35pub fn read<T: Serialize>(items: impl IntoIterator<Item = T>) -> Result<Rows, DataError> {
36    RecordSource::new(Records::new(items.into_iter()), usize::MAX)?.collect_rows()
37}
38
39/// Stream `items`, sampling the first [`DEFAULT_SAMPLE`] for the columns and
40/// schema.
41pub fn source<T, I>(items: I) -> Result<SerializeSource<I::IntoIter>, DataError>
42where
43    T: Serialize,
44    I: IntoIterator<Item = T>,
45{
46    source_with_sample(items, DEFAULT_SAMPLE)
47}
48
49/// Stream `items`, sampling the first `sample`.
50pub fn source_with_sample<T, I>(
51    items: I,
52    sample: usize,
53) -> Result<SerializeSource<I::IntoIter>, DataError>
54where
55    T: Serialize,
56    I: IntoIterator<Item = T>,
57{
58    RecordSource::new(Records::new(items.into_iter()), sample)
59}
60
61/// A streaming [`RowSource`] over `Serialize` values.
62pub type SerializeSource<I> = RecordSource<Records<I>>;
63
64/// Values as JSON records, numbered from 1 in errors.
65#[derive(Debug)]
66pub struct Records<I> {
67    items: I,
68    index: usize,
69}
70
71impl<I> Records<I> {
72    /// Records from `items`.
73    pub fn new(items: I) -> Self {
74        Records { items, index: 0 }
75    }
76}
77
78impl<T: Serialize, I: Iterator<Item = T>> Iterator for Records<I> {
79    type Item = Result<Json, DataError>;
80
81    fn next(&mut self) -> Option<Self::Item> {
82        let item = self.items.next()?;
83        self.index += 1;
84        Some(serde_json::to_value(item).map_err(|e| DataError::at(self.index, e.to_string())))
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use std::collections::BTreeMap;
91
92    use super::*;
93    use crate::Value;
94
95    #[test]
96    fn maps_and_scalars_become_rows() {
97        let rows = read([BTreeMap::from([("b", 2), ("a", 1)])]).unwrap();
98        assert_eq!(rows.columns(), ["a", "b"]);
99        let rows = read([1.5, 2.0]).unwrap();
100        assert_eq!(rows.columns(), ["value"]);
101        assert_eq!(rows.rows()[0], [Value::Float(1.5)]);
102    }
103
104    #[test]
105    fn errors_name_the_record() {
106        // A map with non-string keys cannot be a JSON object.
107        let bad = BTreeMap::from([((1, 2), 3)]);
108        let error = read([bad]).unwrap_err();
109        assert_eq!(error.line(), Some(1));
110        let mut source = source(Vec::<u8>::new()).unwrap();
111        assert!(source.next_row().is_none());
112    }
113}