modelvault-core 0.14.0

Core engine for ModelVault — application-focused embedded storage with model schemas, validation, and migrations.
Documentation
use std::collections::BTreeMap;

use crate::error::{DbError, SchemaError};
use crate::record::RowValue;
use crate::schema::FieldDef;
use crate::validation;

pub(crate) fn row_value_at_path(
    row: &BTreeMap<String, RowValue>,
    path: &[std::borrow::Cow<'static, str>],
) -> Option<RowValue> {
    let mut cur = row.get(
        path.first()
            .expect("catalog field paths are validated as non-empty")
            .as_ref(),
    )?;
    for seg in path.iter().skip(1) {
        cur = cur.as_object_map()?.get(seg.as_ref())?;
    }
    Some(cur.clone())
}

pub(crate) fn build_non_pk_values_in_schema_order(
    row: &BTreeMap<String, RowValue>,
    non_pk_defs: &[&FieldDef],
) -> Result<Vec<(FieldDef, RowValue)>, DbError> {
    let mut non_pk: Vec<(FieldDef, RowValue)> = Vec::with_capacity(non_pk_defs.len());
    for def in non_pk_defs {
        let v = match row_value_at_path(row, &def.path.0) {
            Some(x) => x,
            None if validation::allows_absent_root(&def.ty) => RowValue::None,
            None => {
                return Err(DbError::Schema(SchemaError::RowMissingField {
                    name: def
                        .path
                        .0
                        .iter()
                        .map(|s| s.as_ref())
                        .collect::<Vec<_>>()
                        .join("."),
                }));
            }
        };
        non_pk.push(((*def).clone(), v));
    }
    Ok(non_pk)
}

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

    use crate::error::DbError;
    use crate::schema::{FieldDef, FieldPath, Type};

    #[test]
    fn build_non_pk_values_errors_when_required_path_missing() {
        let row = BTreeMap::from([("id".to_string(), RowValue::Int64(1))]);
        let defs = [FieldDef {
            path: FieldPath(vec![Cow::Borrowed("meta"), Cow::Borrowed("tag")]),
            ty: Type::String,
            constraints: vec![],
        }];
        let refs: Vec<&FieldDef> = defs.iter().collect();
        let err = build_non_pk_values_in_schema_order(&row, &refs).unwrap_err();
        assert!(matches!(
            err,
            DbError::Schema(crate::error::SchemaError::RowMissingField { name }) if name == "meta.tag"
        ));
    }
}