haematite-cli 0.21.1

The haematite command: serve a store, its door and Studio, answer MCP, query with jq, and host the store's assistant seat
Documentation
use super::read;
use crate::serve::query::parser::{Span, Stage, pipeline};

type TestResult = Result<(), Box<dyn std::error::Error>>;

#[test]
fn rg_grammar_returns_stage_and_original_byte_span() -> TestResult {
    let parsed = read(r#"rg(.notes; "chest"; "-i -w") | count"#, 11)?;
    assert_eq!(parsed.span, Span { start: 11, end: 39 });
    assert_eq!(
        parsed.value,
        Stage::Rg {
            field: Some("notes".to_owned()),
            pattern: "chest".to_owned(),
            flags: "-i -w".to_owned(),
        }
    );
    let plan = pipeline(r#"patients | rg("4B") | count"#)?;
    assert_eq!(plan.stages[0].span, Span { start: 11, end: 19 });
    assert!(matches!(plan.stages[1].value, Stage::Count));
    Ok(())
}

#[test]
fn rg_grammar_decodes_json_escapes_and_keeps_inner_pipes() -> TestResult {
    let parsed = read(r#"rg("chest|\\bward\\b"; "-i") | limit(2)"#, 4)?;
    assert_eq!(
        parsed.value,
        Stage::Rg {
            field: None,
            pattern: r"chest|\bward\b".to_owned(),
            flags: "-i".to_owned(),
        }
    );
    assert_eq!(parsed.span, Span { start: 4, end: 32 });
    Ok(())
}

#[test]
fn rg_grammar_names_unknown_flag_and_marks_its_string() -> TestResult {
    let error = read(r#"rg("ward"; "--mystery")"#, 11)
        .err()
        .ok_or("unknown flag accepted")?;
    assert_eq!(error.code(), "query_flag");
    assert!(error.to_string().contains("--mystery"));
    assert_eq!(error.span(), Span { start: 23, end: 32 });
    Ok(())
}

#[test]
fn rg_grammar_refuses_missing_field_pattern_and_extra_arguments() -> TestResult {
    for text in [
        r#"rg(.; "x")"#,
        "rg(4)",
        r#"rg("x"; "-i"; "-w")"#,
        r#"rg("x)"#,
    ] {
        let error = read(text, 7).err().ok_or("invalid rg grammar accepted")?;
        assert_eq!(error.code(), "query_parse", "{text}");
        assert!(error.span().start >= 7);
    }
    Ok(())
}

#[test]
fn rg_grammar_preserves_all_previous_prefix_cases_and_byte_positions() -> TestResult {
    for prefix in ["\u{2003}patients | ", "patients | '.a' | "] {
        let text = format!("{prefix}rg(\"x\")");
        let plan = pipeline(&text)?;
        let stage = plan.stages.last().ok_or("rg stage missing")?;
        assert_eq!(
            stage.span,
            Span {
                start: prefix.len(),
                end: text.len()
            }
        );
        assert!(
            matches!(&stage.value, Stage::Rg { field: None, pattern, flags }
            if pattern == "x" && flags.is_empty())
        );
    }
    let error = pipeline(r#"rg("x")"#)
        .err()
        .ok_or("rg accepted as a source")?;
    assert_eq!(error.code(), "query_parse");
    assert!(error.to_string().contains("rg"));
    assert_eq!(error.span(), Span { start: 0, end: 2 });
    Ok(())
}

#[test]
fn grep_alias_parses_the_same_stage_and_original_span() -> TestResult {
    for arguments in [r#"("4B")"#, r#"(.ward; "4B"; "-i -w")"#] {
        let rg_text = format!("patients | rg{arguments}");
        let grep_text = format!("patients | grep{arguments}");
        let rg = pipeline(&rg_text)?;
        let grep = pipeline(&grep_text)?;
        assert_eq!(grep.source, rg.source);
        assert_eq!(grep.stages.len(), 1);
        assert_eq!(grep.stages[0].value, rg.stages[0].value);
        assert_eq!(
            rg.stages[0].span,
            Span {
                start: 11,
                end: rg_text.len()
            }
        );
        assert_eq!(
            grep.stages[0].span,
            Span {
                start: 11,
                end: grep_text.len()
            }
        );
    }
    Ok(())
}

#[test]
fn rg_value_taking_switch_is_refused_before_its_value_with_its_span() -> TestResult {
    for stage in ["rg", "grep"] {
        for (flags, switch, guidance) in [
            ("-m 5", "-m", "use `limit(n)`"),
            ("-i -m 5", "-m", "use `limit(n)`"),
            (
                "-e foo",
                "-e",
                "one pattern per stage; use regex alternation `a|b`",
            ),
            (
                "-f path",
                "-f",
                "one pattern per stage; use regex alternation `a|b`",
            ),
            ("-c", "-c", "use the `count` stage"),
        ] {
            let query = format!("patients | {stage}(\"ward\"; \"{flags}\")");
            let error = pipeline(&query)
                .err()
                .ok_or("unsupported switch accepted")?;
            let start = query.find(switch).ok_or("test switch missing")?;
            assert_eq!(error.code(), "query_flag");
            assert_eq!(
                error.span(),
                Span {
                    start,
                    end: start + switch.len()
                }
            );
            assert_eq!(
                error.to_string(),
                format!(
                    "rg flag {switch:?} is not supported; {guidance} at bytes {start}..{}",
                    start + switch.len()
                )
            );
        }
    }
    Ok(())
}

#[test]
fn rg_refusal_span_maps_json_escapes_and_utf8_to_original_bytes() -> TestResult {
    for flags in [r"\t-i \u002dm 5", "\u{2003}-i -m 5", r"\uD83D\uDE00 -m 5"] {
        let query = format!("patients | rg(\"ward\"; \"{flags}\")");
        let error = pipeline(&query)
            .err()
            .ok_or("unsupported switch accepted")?;
        assert_eq!(error.code(), "query_flag");
        let switch = if flags.starts_with(r"\uD83D") {
            r"\uD83D\uDE00"
        } else if flags.contains(r"\u002d") {
            r"\u002dm"
        } else {
            "-m"
        };
        let start = query.find(switch).ok_or("encoded test switch missing")?;
        assert_eq!(
            error.span(),
            Span {
                start,
                end: start + switch.len()
            }
        );
    }
    Ok(())
}

#[test]
fn quoted_grep_is_jq_text_and_never_read_as_an_alias() -> TestResult {
    let query = r#"patients | 'grep("x")'"#;
    let plan = pipeline(query)?;
    assert_eq!(plan.stages.len(), 1);
    assert_eq!(plan.stages[0].value, Stage::Jq(r#"grep("x")"#.to_owned()));
    assert_eq!(
        plan.stages[0].span,
        Span {
            start: 11,
            end: query.len()
        }
    );
    Ok(())
}

#[test]
fn grep_alias_is_refused_as_a_source_and_keeps_rg_argument_refusals() -> TestResult {
    let error = pipeline(r#"grep("4B")"#)
        .err()
        .ok_or("grep accepted as source")?;
    assert_eq!(error.code(), "query_parse");
    assert_eq!(error.span(), Span { start: 0, end: 4 });
    assert!(error.to_string().contains("`rg`"));
    for arguments in [r#"(.; "x")"#, "(4)", r#"("x"; "-i"; "-w")"#] {
        let rg = read(&format!("rg{arguments}"), 0)
            .err()
            .ok_or("invalid rg accepted")?;
        let grep = read(&format!("grep{arguments}"), 0)
            .err()
            .ok_or("invalid grep accepted")?;
        assert_eq!(grep.code(), rg.code());
        assert_eq!(grep.span().start, rg.span().start + 2);
        assert_eq!(grep.span().end, rg.span().end + 2);
        let super::super::Refused::Parse(rg_message, _) = rg else {
            return Err("rg argument refusal is not a parse refusal".into());
        };
        let super::super::Refused::Parse(grep_message, _) = grep else {
            return Err("grep argument refusal is not a parse refusal".into());
        };
        assert_eq!(grep_message, rg_message);
    }
    Ok(())
}