1use std::collections::BTreeMap;
2
3use anyhow::Context;
4use jaq_core::load::{Arena, File, Loader};
5use jaq_core::{Ctx, Vars, data, unwrap_valr};
6use jaq_json::{Val, write};
7
8use crate::Result;
9
10fn to_jaq_val(value: &serde_json::Value) -> Result<Val> {
11 let v: Val = serde_json::from_value(value.clone())?;
12 Ok(v)
13}
14
15fn from_jaq_val(value: &Val) -> Result<serde_json::Value> {
16 let mut buf = Vec::new();
17 write::write(&mut buf, &write::Pp::default(), 0, value).context("write jaq value as JSON")?;
18 let v: serde_json::Value = serde_json::from_slice(&buf).context("parse jaq output as JSON")?;
19 Ok(v)
20}
21
22fn compile_filter<'s>(
23 expr: &'s str,
24 global_vars: impl IntoIterator<Item = &'s str>,
25) -> Result<jaq_core::compile::Filter<jaq_core::Native<data::JustLut<Val>>>> {
26 let loader = Loader::new(jaq_std::defs().chain(jaq_json::defs()));
27 let arena = Arena::default();
28 let modules = loader
29 .load(
30 &arena,
31 File {
32 code: expr,
33 path: (),
34 },
35 )
36 .map_err(|errs| anyhow::anyhow!("{errs:?}"))
37 .with_context(|| format!("jq parse failed: {expr:?}"))?;
38
39 let compiler = jaq_core::Compiler::default()
40 .with_funs(jaq_std::funs().chain(jaq_json::funs()))
41 .with_global_vars(global_vars);
42
43 compiler
44 .compile(modules)
45 .map_err(|errs| anyhow::anyhow!("{errs:?}"))
46 .with_context(|| format!("jq compile failed: {expr:?}"))
47}
48
49pub fn query(value: &serde_json::Value, expr: &str) -> Result<Vec<serde_json::Value>> {
50 query_with_vars(value, expr, &BTreeMap::new())
51}
52
53pub fn query_with_vars(
54 value: &serde_json::Value,
55 expr: &str,
56 vars: &BTreeMap<String, serde_json::Value>,
57) -> Result<Vec<serde_json::Value>> {
58 let input = to_jaq_val(value).context("convert input JSON to jq value")?;
59
60 let global_var_names: Vec<String> = vars.keys().map(|k| format!("${k}")).collect();
61 let global_var_slices: Vec<&str> = global_var_names.iter().map(String::as_str).collect();
62 let filter = compile_filter(expr, global_var_slices)?;
63
64 let mut global_var_values: Vec<Val> = Vec::with_capacity(vars.len());
65 for v in vars.values() {
66 global_var_values.push(to_jaq_val(v)?);
67 }
68
69 let ctx = Ctx::<data::JustLut<Val>>::new(&filter.lut, Vars::new(global_var_values));
70
71 let mut out = Vec::new();
72 for y in filter
73 .id
74 .run((ctx, input))
75 .map(unwrap_valr)
76 .collect::<Vec<_>>()
77 {
78 let y = y
79 .map_err(|e| anyhow::anyhow!("{e:?}"))
80 .context("jq runtime error")?;
81 out.push(from_jaq_val(&y).context("convert jq output to JSON")?);
82 }
83
84 Ok(out)
85}
86
87pub fn eval_exit_status(value: &serde_json::Value, expr: &str) -> Result<bool> {
94 let out = query(value, expr)?;
95 let Some(last) = out.last() else {
96 return Ok(false);
97 };
98 Ok(!matches!(
99 last,
100 serde_json::Value::Null | serde_json::Value::Bool(false)
101 ))
102}
103
104pub fn query_raw(value: &serde_json::Value, expr: &str) -> Result<Vec<String>> {
109 let out = query(value, expr)?;
110 let mut lines = Vec::with_capacity(out.len());
111 for v in out {
112 match v {
113 serde_json::Value::String(s) => lines.push(s),
114 other => lines.push(serde_json::to_string(&other)?),
115 }
116 }
117 Ok(lines)
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123 use pretty_assertions::assert_eq;
124
125 #[test]
126 fn jq_basic_query_outputs_multiple_values() {
127 let input = serde_json::json!(["a", "b"]);
128 let out = query(&input, ".[]").unwrap();
129 assert_eq!(out, vec![serde_json::json!("a"), serde_json::json!("b")]);
130 }
131
132 #[test]
133 fn jq_eval_exit_status_matches_truthiness() {
134 let input = serde_json::json!({"a": 1});
135 assert!(eval_exit_status(&input, ".a == 1").unwrap());
136 assert!(!eval_exit_status(&input, ".a == 2").unwrap());
137 assert!(!eval_exit_status(&input, "empty").unwrap());
138 }
139
140 #[test]
141 fn jq_vars_support_arg_like_usage() {
142 let input = serde_json::json!({"data": {"login": {"accessToken": "t"}}});
143 let mut vars = BTreeMap::new();
144 vars.insert("field".to_string(), serde_json::json!("login"));
145
146 let out = query_with_vars(&input, ".data[$field].accessToken", &vars).unwrap();
147 assert_eq!(out, vec![serde_json::json!("t")]);
148 }
149
150 #[test]
151 fn jq_query_raw_unwraps_strings() {
152 let input = serde_json::json!({"token": "abc"});
153 let out = query_raw(&input, ".token").unwrap();
154 assert_eq!(out, vec!["abc".to_string()]);
155 }
156
157 #[test]
158 fn jq_parse_errors_include_expression() {
159 let input = serde_json::json!({});
160 let err = query(&input, ".[").unwrap_err();
161 assert!(format!("{err:#}").contains(".["));
162 }
163}