cartulary 0.3.0-alpha.1

The knowledge layer of your project — decisions, issues, docs, all in one place.
Documentation
/// Tabular result returned by a [`QueryRunner`]. Headers and rows
/// are aligned: every row has exactly `headers.len()` cells.
///
/// Cells are typed via [`QueryValue`] so adapters can surface the
/// primitive shape (null / bool / number / string) without forcing
/// the domain to depend on a query engine's value enum. Anything the
/// adapter cannot map cleanly is folded into [`QueryValue::Other`],
/// already serialised, so the domain never re-formats engine
/// internals.
///
/// [`QueryRunner`]: crate::domain::usecases::query::QueryRunner
#[derive(Debug, Clone, Default, PartialEq)]
pub struct QueryResult {
    pub headers: Vec<String>,
    pub rows: Vec<Vec<QueryValue>>,
}

#[derive(Debug, Clone, PartialEq)]
pub enum QueryValue {
    Null,
    Bool(bool),
    Int(i64),
    Float(f64),
    Str(String),
    Other(String),
}

impl QueryResult {
    pub fn new(headers: Vec<String>, rows: Vec<Vec<QueryValue>>) -> Self {
        Self { headers, rows }
    }

    pub fn is_empty(&self) -> bool {
        self.rows.is_empty()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn empty_result_is_empty() {
        assert!(QueryResult::default().is_empty());
    }

    #[test]
    fn non_empty_result_carries_its_rows() {
        let r = QueryResult::new(
            vec!["n".to_owned()],
            vec![vec![QueryValue::Int(1)], vec![QueryValue::Int(2)]],
        );
        assert_eq!(r.headers, vec!["n".to_owned()]);
        assert_eq!(r.rows.len(), 2);
        assert!(!r.is_empty());
    }
}