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(())
}