knut-bifrost 0.3.0

Bifröst — watch lakehouse tables (Iceberg via Spark Connect) and materialise them as a FalkorDB property graph
Documentation
//! Functional-status emitters for the `nornir-testmatrix` matrix.
//!
//! A component reports whether it *actually works* from the inside: during a
//! self-test / headless path it calls [`emit`]`(component, check, ok, detail)`,
//! which forwards to `nornir_testmatrix::functional_status`. With the
//! `testmatrix` cargo feature ON the record lands in the matrix's process-global
//! buffer (drained into a RED/GREEN `functional` row by `Aspect::Functional`);
//! with the feature OFF (the release default) [`emit`] is an inlined no-op and
//! `nornir-testmatrix` is not even pulled into the build.
//!
//! The component name reported by bifröst's checks.
pub const COMPONENT: &str = "knut-bifrost";

/// Forward one functional-status check to the matrix. Feature ON → records;
/// feature OFF → inlined no-op (zero release cost). Call sites that build an
/// *expensive* `detail` string should additionally wrap the whole block in
/// `#[cfg(feature = "testmatrix")]` so the formatting is stripped too.
#[cfg(feature = "testmatrix")]
#[inline]
pub fn emit(component: &str, check: &str, ok: bool, detail: &str) {
    nornir_testmatrix::functional_status(component, check, ok, detail);
}

/// No-op when the `testmatrix` feature is OFF (release builds).
#[cfg(not(feature = "testmatrix"))]
#[inline]
pub fn emit(_component: &str, _check: &str, _ok: bool, _detail: &str) {}

/// Headless self-test of bifröst's core transform→mutation path — no FalkorDB,
/// no Spark, no files. Builds a known two-row table, runs the real
/// [`crate::VertexTransform`] over it, and reports each key invariant as a
/// functional-status check the matrix reads. Returns `true` iff every check
/// passed. Safe to call from the CLI's `--self-test` and from a test.
pub fn self_test() -> bool {
    use crate::transform::{TableContext, Transform, VertexTransform};
    use arrow_array::{Int64Array, RecordBatch, StringArray};
    use arrow_schema::{DataType, Field, Schema};
    use std::sync::Arc;

    let schema = Arc::new(Schema::new(vec![
        Field::new("user_id", DataType::Int64, false),
        Field::new("name", DataType::Utf8, false),
    ]));
    let batch = RecordBatch::try_new(
        schema,
        vec![
            Arc::new(Int64Array::from(vec![1_i64, 2])),
            Arc::new(StringArray::from(vec!["alice", "bob"])),
        ],
    )
    .expect("self_test batch");

    let t = VertexTransform {
        label: "Person".into(),
        id_column: "user_id".into(),
        properties: vec!["user_id".into(), "name".into()],
        rename: Default::default(),
    };
    let ctx = TableContext {
        table: "users".into(),
    };
    let mut all_ok = true;

    match t.apply(&ctx, &[batch]) {
        Ok(m) => {
            let one_vertex_set = m.vertices.len() == 1;
            emit(
                COMPONENT,
                "self_test_vertex_set_built",
                one_vertex_set,
                &format!("{} vertex set(s)", m.vertices.len()),
            );
            all_ok &= one_vertex_set;

            if let Some(set) = m.vertices.first() {
                let label_ok = set.label == "Person";
                emit(
                    COMPONENT,
                    "self_test_label_mapped",
                    label_ok,
                    &format!("label={}", set.label),
                );
                all_ok &= label_ok;

                let rows = set.batch.num_rows();
                let rows_ok = rows == 2;
                emit(
                    COMPONENT,
                    "self_test_row_count",
                    rows_ok,
                    &format!("{rows} vertex rows"),
                );
                all_ok &= rows_ok;
            } else {
                all_ok = false;
            }
        }
        Err(e) => {
            emit(
                COMPONENT,
                "self_test_vertex_set_built",
                false,
                &format!("transform errored: {e}"),
            );
            all_ok = false;
        }
    }
    emit(COMPONENT, "self_test", all_ok, "core transform path");
    all_ok
}

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

    #[test]
    fn self_test_passes_and_is_idempotent() {
        // INJECT-ASSERT: the headless self-test runs the real transform path and
        // every invariant must hold. (It also emits functional-status rows when
        // built `--features testmatrix`.)
        assert!(self_test(), "core transform self-test must pass");
        assert!(self_test(), "self-test is idempotent");
        emit(
            COMPONENT,
            "self_test_test_ran",
            true,
            "self_test() returned true twice",
        );
    }
}