haematite-cli 0.21.3

The haematite command: serve a store, its door and Studio, answer MCP, query with jq, and host the store's assistant seat
Documentation
#[path = "shape_support.rs"]
mod support;

#[path = "shape_roots_tests.rs"]
mod roots;

use std::num::NonZeroU64;

use serde_json::{Value, json};

use crate::serve::query::{Continuation, Rows};
use support::{Fixture, TestResult, request};

const FOUR: NonZeroU64 = NonZeroU64::MIN.saturating_add(3);
const ONE: NonZeroU64 = NonZeroU64::MIN;

fn rows(answer: &Value) -> Result<Vec<Value>, Box<dyn std::error::Error>> {
    answer
        .get("rows")
        .and_then(Value::as_array)
        .ok_or_else(|| "missing rows".into())
        .cloned()
}

fn values(answer: &Value) -> Result<Vec<Value>, Box<dyn std::error::Error>> {
    rows(answer)?
        .iter()
        .map(|row| {
            row.get("value")
                .cloned()
                .ok_or_else(|| "missing value".into())
        })
        .collect()
}

fn documents() -> Vec<Value> {
    vec![
        json!({"name":"Ada", "ward":{"code":"4B", "beds":12}, "private":"hidden"}),
        json!({"name":"Bea", "ward":{"code":"3A", "beds":8}, "private":"hidden"}),
        json!({"name":"Cy", "private":"hidden"}),
    ]
}

#[test]
fn later_shape_cuts_fields_and_preserves_input_order_and_origins() -> TestResult {
    let documents = documents();
    let fixture = Fixture::new(&documents)?;
    let before = fixture.run(&request("patients | '.'", FOUR), &[])?;
    let after = fixture.run(&request("patients | '.' | { name ward }", FOUR), &[])?;
    assert_eq!(
        values(&after)?,
        vec![
            json!({"name":"Ada", "ward":{"code":"4B", "beds":12}}),
            json!({"name":"Bea", "ward":{"code":"3A", "beds":8}}),
            json!({"name":"Cy"}),
        ]
    );
    for (old, cut) in rows(&before)?.iter().zip(rows(&after)?) {
        assert_eq!(
            old.get("origin").ok_or("missing old origin")?,
            cut.get("origin").ok_or("missing cut origin")?
        );
    }
    assert_eq!(
        after.get("stages").ok_or("missing stages")?,
        &json!(["jq", "{"])
    );
    assert_eq!(
        fixture.inputs()?,
        documents
            .iter()
            .chain(&documents)
            .cloned()
            .collect::<Vec<_>>()
    );
    Ok(())
}

#[test]
fn nested_shape_cuts_inside_objects_and_never_invents_absent_fields() -> TestResult {
    let fixture = Fixture::new(&documents())?;
    let answer = fixture.run(
        &request("patients | { name ward { code missing } absent }", FOUR),
        &[],
    )?;
    assert_eq!(
        values(&answer)?,
        vec![
            json!({"name":"Ada", "ward":{"code":"4B"}}),
            json!({"name":"Bea", "ward":{"code":"3A"}}),
            json!({"name":"Cy"}),
        ]
    );
    assert_eq!(
        answer.get("records_read").ok_or("missing count")?,
        &json!(3)
    );
    Ok(())
}

#[test]
fn first_shape_reads_one_collection_and_worker_input_is_already_cut() -> TestResult {
    let fixture = Fixture::new(&documents())?;
    let answer = fixture.run(
        &request("{ patients { name ward { code } } } | '.'", FOUR),
        &[],
    )?;
    let expected = vec![
        json!({"name":"Ada", "ward":{"code":"4B"}}),
        json!({"name":"Bea", "ward":{"code":"3A"}}),
        json!({"name":"Cy"}),
    ];
    assert_eq!(fixture.inputs()?, expected);
    assert_eq!(values(&answer)?, expected);
    let frames = fixture.frames()?;
    assert_eq!(frames.len(), 3);
    for (index, frame) in frames.iter().enumerate() {
        let bindings = frame.get("bindings").ok_or("missing Input bindings")?;
        assert_eq!(
            bindings
                .get("collection")
                .ok_or("missing Input collection")?,
            &json!("patients")
        );
        assert_eq!(
            bindings.get("id").ok_or("missing Input id")?,
            &json!(format!("p{index}"))
        );
        let written = fixture
            .written
            .get(&format!("patients/p{index}"))
            .ok_or("Input record was never written")?;
        let written = serde_json::to_value(written)?;
        assert_ne!(
            &written,
            answer
                .get("selection")
                .and_then(|value| value.get("version"))
                .ok_or("missing selected version")?
        );
        assert_eq!(
            bindings.get("version").ok_or("missing Input version")?,
            &written
        );
        assert_eq!(
            bindings.get("score").ok_or("missing Input score")?,
            &Value::Null
        );
    }
    let origins = rows(&answer)?;
    assert_eq!(origins.len(), 3);
    for (index, row) in origins.iter().enumerate() {
        let origin = row.get("origin").ok_or("missing origin")?;
        assert_eq!(
            origin.get("collection").ok_or("missing collection")?,
            &json!("patients")
        );
        assert_eq!(
            origin.get("id").ok_or("missing id")?,
            &json!(format!("p{index}"))
        );
        let written = fixture
            .written
            .get(&format!("patients/p{index}"))
            .ok_or("origin record was never written")?;
        assert_eq!(
            origin.get("version").ok_or("missing version")?,
            &serde_json::to_value(written)?
        );
    }
    Ok(())
}

#[test]
fn shapes_before_and_after_jq_replay_without_skipping_or_repeating_rows() -> TestResult {
    let fixture = Fixture::new(&documents())?;
    let mut ask = request(
        "{ patients { name ward } } | '.' | { name } | limit(3)",
        ONE,
    );
    let mut names = Vec::new();
    for index in 0..3 {
        let answer = fixture.run(&ask, &[])?;
        names.extend(values(&answer)?);
        let next = answer.get("next").ok_or("missing next")?;
        if index < 2 {
            let next = next.as_str().ok_or("missing continuation")?;
            let token = Continuation::decode(next, &fixture.seal)?;
            assert_eq!(token.resume.pending.id, format!("p{}", index + 1));
            ask.continuation = Some(next.to_owned());
        } else {
            assert!(next.is_null());
        }
    }
    assert_eq!(
        names,
        vec![
            json!({"name":"Ada"}),
            json!({"name":"Bea"}),
            json!({"name":"Cy"})
        ]
    );
    assert!(
        fixture
            .inputs()?
            .iter()
            .all(|value| value.get("private").is_none())
    );
    Ok(())
}

#[test]
fn shape_keeps_access_denials_and_origin_only_queries() -> TestResult {
    let fixture = Fixture::new(&documents())?;
    let mut ask = request("{ patients { name } } | '.'", FOUR);
    ask.rows = Rows::Origins;
    let answer = fixture.run(&ask, &[("patients", "p1")])?;
    let rows = rows(&answer)?;
    assert_eq!(rows.len(), 2);
    assert!(rows.iter().all(|row| row.get("value").is_none()));
    assert_eq!(
        fixture.inputs()?,
        vec![json!({"name":"Ada"}), json!({"name":"Cy"})]
    );
    Ok(())
}

#[test]
fn first_shape_without_jq_projects_and_preserves_the_collection_source() -> TestResult {
    let fixture = Fixture::new(&documents())?;
    let answer = fixture.run(&request("{ patients { name } }", FOUR), &[])?;
    assert_eq!(
        values(&answer)?,
        vec![
            json!({"name":"Ada"}),
            json!({"name":"Bea"}),
            json!({"name":"Cy"})
        ]
    );
    assert_eq!(answer.get("stages").ok_or("missing stages")?, &json!(["{"]));
    assert!(fixture.inputs()?.is_empty());
    Ok(())
}

#[test]
fn shape_after_expanding_jq_replays_the_pending_output_with_its_original_origin() -> TestResult {
    let fixture = Fixture::new(&[json!({"items":[
        {"name":"Ada", "private":1}, {"name":"Bea", "private":2},
        {"name":"Cy", "private":3}
    ], "private":4})])?;
    let mut ask = request(
        "{ patients { items } } | '.items[]' | { name } | limit(3)",
        ONE,
    );
    let mut names = Vec::new();
    let mut origin = None;
    for index in 0..3 {
        let answer = fixture.run(&ask, &[])?;
        names.extend(values(&answer)?);
        let rows = rows(&answer)?;
        assert_eq!(rows.len(), 1);
        let current = rows[0].get("origin").ok_or("missing origin")?;
        if let Some(previous) = &origin {
            assert_eq!(previous, current);
        }
        origin = Some(current.clone());
        let next = answer.get("next").ok_or("missing next")?;
        if index < 2 {
            let next = next.as_str().ok_or("missing continuation")?;
            let token = Continuation::decode(next, &fixture.seal)?;
            assert_eq!(token.resume.pending.id, "p0");
            assert_eq!(token.resume.discard_outputs, index + 1);
            assert_eq!(token.resume.passed_counts, vec![index + 1]);
            ask.continuation = Some(next.to_owned());
        } else {
            assert!(next.is_null());
        }
    }
    assert_eq!(
        names,
        vec![
            json!({"name":"Ada"}),
            json!({"name":"Bea"}),
            json!({"name":"Cy"})
        ]
    );
    let inputs = fixture.inputs()?;
    assert_eq!(inputs.len(), 3);
    assert!(inputs.iter().all(|value| value.get("private").is_none()));
    Ok(())
}

#[test]
fn shape_before_count_keeps_one_result_for_each_incoming_value() -> TestResult {
    let fixture = Fixture::new(&documents())?;
    for query in [
        "patients | { absent } | count",
        "patients | '.' | { absent } | count",
    ] {
        let answer = fixture.run(&request(query, FOUR), &[])?;
        assert_eq!(values(&answer)?, vec![json!(3)]);
    }
    Ok(())
}

#[test]
fn stage_braces_fix_the_pipeline_reading_even_if_the_collection_is_a_jq_word() -> TestResult {
    let query = "empty | { name }";
    let parsed = super::super::shape::parse(query)?;
    assert_eq!(super::super::shape::program(query, &parsed), ".");
    for query in ["{ name }", "{ name: .name }", ". | { name }"] {
        let error = super::super::shape::parse(query)
            .err()
            .ok_or("shape became jq")?;
        assert_eq!(error.code, "query_parse");
    }
    for query in ["'{name}'", "patients(field: {\"x\":1}) | '.'"] {
        let parsed = super::super::shape::parse(query)?;
        assert_eq!(super::super::shape::program(query, &parsed), query);
    }
    Ok(())
}