use crate::data::doc::Node;
use color_eyre::{eyre::eyre, Result};
const MAX_OUTPUTS: usize = 100_000;
pub fn run_jq(root: &Node, program: &str) -> Result<Node> {
use jaq_core::load::{Arena, File, Loader};
use jaq_core::{data, unwrap_valr, Compiler, Ctx, Vars};
use jaq_json::Val;
let program = program.trim();
if program.is_empty() {
return Err(eyre!("empty query"));
}
let input_text = crate::data::doc::serialize(
root,
crate::data::doc::Format::Json,
false,
&crate::data::doc::SaveOpts {
indent: false,
sort_keys: false,
},
)?;
let input = jaq_json::read::parse_single(input_text.as_bytes())
.map_err(|e| eyre!("could not read the document as JSON: {e}"))?;
let defs = jaq_core::defs()
.chain(jaq_std::defs())
.chain(jaq_json::defs());
let funs = jaq_core::funs()
.chain(jaq_std::funs())
.chain(jaq_json::funs());
let loader = Loader::new(defs);
let arena = Arena::default();
let modules = loader
.load(
&arena,
File {
code: program,
path: (),
},
)
.map_err(|_| eyre!("could not parse `{program}`"))?;
let filter = Compiler::default()
.with_funs(funs)
.compile(modules)
.map_err(|_| eyre!("could not compile `{program}`"))?;
let ctx = Ctx::<data::JustLut<Val>>::new(&filter.lut, Vars::new([]));
let mut outputs = Vec::new();
for result in filter.id.run((ctx, input)).map(unwrap_valr) {
let val = result.map_err(|e| eyre!("{e}"))?;
outputs.push(parse_val(&val)?);
if outputs.len() >= MAX_OUTPUTS {
break;
}
}
match outputs.len() {
0 => Ok(Node::Arr(Vec::new())),
1 => Ok(outputs.pop().expect("length checked")),
_ => Ok(Node::Arr(outputs)),
}
}
fn parse_val(val: &jaq_json::Val) -> Result<Node> {
let doc = crate::data::doc::Doc::from_str(&val.to_string(), crate::data::doc::Format::Json)?;
Ok(doc.root)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::data::doc::{Doc, Format, Seg};
fn root(src: &str) -> Node {
Doc::from_str(src, Format::Json).unwrap().root
}
#[test]
fn a_query_selecting_records_comes_back_as_an_array() {
let r = root(r#"[{"n":1,"ok":true},{"n":2,"ok":false},{"n":3,"ok":true}]"#);
let out = run_jq(&r, ".[] | select(.ok)").unwrap();
let Node::Arr(items) = &out else {
panic!("expected an array, got {:?}", out)
};
assert_eq!(items.len(), 2);
assert_eq!(
out.get(&[Seg::Idx(1), Seg::Key("n".into())]),
Some(&Node::Int(3))
);
}
#[test]
fn a_single_output_is_returned_as_itself() {
let r = root(r#"{"a":{"b":[1,2]}}"#);
assert_eq!(run_jq(&r, ".a.b").unwrap(), root("[1,2]"));
assert_eq!(run_jq(&r, ".a.b | length").unwrap(), Node::Int(2));
}
#[test]
fn queries_can_reshape_into_something_tabular() {
let r = root(r#"{"users":{"alice":{"age":30},"bob":{"age":40}}}"#);
let out = run_jq(
&r,
".users | to_entries | map({name: .key, age: .value.age})",
)
.unwrap();
assert_eq!(
out.get(&[Seg::Idx(0), Seg::Key("name".into())]),
Some(&Node::Str("alice".into()))
);
assert_eq!(
out.get(&[Seg::Idx(1), Seg::Key("age".into())]),
Some(&Node::Int(40))
);
}
#[test]
fn a_bad_program_is_reported_rather_than_panicking() {
let r = root("[1,2]");
assert!(run_jq(&r, ".[ | broken").is_err());
assert!(run_jq(&r, "").is_err());
}
#[test]
fn a_program_matching_nothing_yields_an_empty_result_not_an_error() {
let r = root("[1,2]");
assert_eq!(
run_jq(&r, ".[] | select(. > 99)").unwrap(),
Node::Arr(vec![])
);
}
#[test]
fn a_runtime_error_is_reported_rather_than_panicking() {
let r = root(r#"{"a":1}"#);
assert!(run_jq(&r, ".a | .[0]").is_err(), "indexing a number");
}
}