Skip to content

Commit

Permalink
Initial cut at moving to JAQ 2.0.
Browse files Browse the repository at this point in the history
- Update Filter to only remember strings, as we can't safely precompile with new lifetime shenanigans.
- Create new jq file to hide all details of calling JQ
- Update filter/engine to start passing serde-JSON structures by default, instead of JAQ internals.
- Update tests.

Note: error messages are horrendous with this update.
  • Loading branch information
jsuereth committed Dec 14, 2024
1 parent 4ee6970 commit 6b03c69
Show file tree
Hide file tree
Showing 8 changed files with 218 additions and 201 deletions.
86 changes: 34 additions & 52 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 2 additions & 5 deletions crates/weaver_codegen_test/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use weaver_common::{in_memory, Logger};
use weaver_forge::config::{Params, WeaverConfig};
use weaver_forge::file_loader::FileSystemFileLoader;
use weaver_forge::registry::ResolvedRegistry;
use weaver_forge::{OutputDirective, TemplateEngine, SEMCONV_JQ};
use weaver_forge::{OutputDirective, TemplateEngine};
use weaver_resolver::SchemaResolver;
use weaver_semconv::registry::SemConvRegistry;

Expand Down Expand Up @@ -58,10 +58,7 @@ fn main() {
.unwrap_or_else(|e| process_error(&logger, e));
let config = WeaverConfig::try_from_path("./templates/registry/rust")
.unwrap_or_else(|e| process_error(&logger, e));
let mut engine = TemplateEngine::new(config, loader, Params::default());
engine
.import_jq_package(SEMCONV_JQ)
.unwrap_or_else(|e| process_error(&logger, e));
let engine = TemplateEngine::new(config, loader, Params::default());
let template_registry = ResolvedRegistry::try_from_resolved_registry(
schema
.registry(REGISTRY_ID)
Expand Down
8 changes: 3 additions & 5 deletions crates/weaver_forge/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,9 @@ weaver_semconv = { path = "../weaver_semconv" }
minijinja = { version = "2.5.0", features = ["loader", "custom_syntax", "debug", "json", "urlencode", "macros"] }
minijinja-contrib = { version="2.5.0", features = ["pycompat"] }
convert_case = "0.6.0"
jaq-core = "1.5.1"
jaq-std = "1.6.0"
jaq-interpret = "1.5.0"
jaq-parse = "1.0.3"
jaq-syn = "1.6.0"
jaq-core = "2.0.0"
jaq-std = "2.0.0"
jaq-json = { version="1.0.0", features=["serde_json"] }
indexmap = "2.7.0"
regex = "1.11.1"
markdown = "=1.0.0-alpha.21"
Expand Down
119 changes: 35 additions & 84 deletions crates/weaver_forge/src/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,87 +4,31 @@
use crate::error::Error;
use core::fmt;
use jaq_interpret::{Ctx, FilterT, RcIter, Val};
use jaq_syn::Def;
use std::fmt::Debug;
use std::{collections::BTreeMap, fmt::Debug};

/// A filter that can be applied to a JSON value.
pub struct Filter {
filter_expr: String,
filter: jaq_interpret::Filter,
}

impl Filter {
/// Create a new filter from a string expression or return an error if the
/// expression is invalid.
/// The vars parameter is a list of variable names that can be used in the
/// filter expression.
pub fn try_new(filter_expr: &str, vars: Vec<String>, defs: Vec<Def>) -> Result<Self, Error> {
let mut ctx = jaq_interpret::ParseCtx::new(vars);
ctx.insert_natives(jaq_core::core());
ctx.insert_defs(jaq_std::std());
ctx.insert_defs(defs);

let (parsed_expr, errs) = jaq_parse::parse(filter_expr, jaq_parse::main());

// If there are any errors, return them
if !errs.is_empty() {
return Err(Error::CompoundError(
errs.into_iter()
.map(|e| Error::FilterError {
filter: filter_expr.to_owned(),
error: e.to_string(),
})
.collect(),
));
}

let parsed_expr = parsed_expr.ok_or_else(|| Error::FilterError {
filter: filter_expr.to_owned(),
error: "No parsed expression".to_owned(),
})?;
let filter = ctx.compile(parsed_expr);
if !ctx.errs.is_empty() {
return Err(Error::CompoundError(
ctx.errs
.iter()
.map(|e| Error::FilterError {
filter: filter_expr.to_owned(),
error: e.0.to_string(),
})
.collect(),
));
}

Ok(Self {
pub fn new(filter_expr: &str) -> Self {
Self {
filter_expr: filter_expr.to_owned(),
filter,
})
}
}

/// Apply the filter to a JSON value and return the result as a JSON value.
pub fn apply(
&self,
ctx: serde_json::Value,
jq_ctx: Vec<Val>,
values: &BTreeMap<String, serde_json::Value>,
) -> Result<serde_json::Value, Error> {
let inputs = RcIter::new(core::iter::empty());
let filter_result = self.filter.run((Ctx::new(jq_ctx, &inputs), Val::from(ctx)));
let mut errs = Vec::new();
let mut values = Vec::new();

for r in filter_result {
match r {
Ok(v) => values.push(serde_json::Value::from(v)),
Err(e) => errs.push(e),
}
}

if values.len() == 1 {
return Ok(values.pop().expect("values.len() == 1, should not happen"));
}

Ok(serde_json::Value::Array(values))
crate::jq::execute_jq(&ctx, &self.filter_expr, values)
}
}

Expand All @@ -96,26 +40,30 @@ impl Debug for Filter {

#[cfg(test)]
mod tests {
use jaq_interpret::Val;
use std::collections::BTreeMap;

#[test]
fn test_filter() {
let filter = super::Filter::try_new("true", Vec::new(), Vec::new()).unwrap();
let result = filter.apply(serde_json::json!({}), Vec::new()).unwrap();
let filter = super::Filter::new("true");
let result = filter
.apply(serde_json::json!({}), &BTreeMap::new())
.unwrap();
assert_eq!(result, serde_json::json!(true));

let filter = super::Filter::try_new(".", Vec::new(), Vec::new()).unwrap();
let result = filter.apply(serde_json::json!({}), Vec::new()).unwrap();
let filter = super::Filter::new(".");
let result = filter
.apply(serde_json::json!({}), &BTreeMap::new())
.unwrap();
assert_eq!(result, serde_json::Value::Object(serde_json::Map::new()));

let filter = super::Filter::try_new(".", Vec::new(), Vec::new()).unwrap();
let filter = super::Filter::new(".");
let result = filter
.apply(
serde_json::json!({
"a": 1,
"b": 2,
}),
Vec::new(),
&BTreeMap::new(),
)
.unwrap();
assert_eq!(
Expand All @@ -126,40 +74,43 @@ mod tests {
})
);

let filter = super::Filter::try_new(".key1", Vec::new(), Vec::new()).unwrap();
let filter = super::Filter::new(".key1");
let result = filter
.apply(
serde_json::json!({
"key1": 1,
"key2": 2,
}),
Vec::new(),
&BTreeMap::new(),
)
.unwrap();
assert_eq!(result, serde_json::json!(1));

let filter = super::Filter::try_new(".[\"key1\"]", Vec::new(), Vec::new()).unwrap();
let filter = super::Filter::new(".[\"key1\"]");
let result = filter
.apply(
serde_json::json!({
"key1": 1,
"key2": 2,
}),
Vec::new(),
&BTreeMap::new(),
)
.unwrap();
assert_eq!(result, serde_json::json!(1));

let vars = vec!["key".to_owned()];
let ctx = vec![Val::from(serde_json::Value::String("key1".to_owned()))];
let filter = super::Filter::try_new(".[$key]", vars, Vec::new()).unwrap();
let mut vars = BTreeMap::new();
let _ = vars.insert(
"key".to_owned(),
serde_json::Value::String("key1".to_owned()),
);
let filter = super::Filter::new(".[$key]");
let result = filter
.apply(
serde_json::json!({
"key1": 1,
"key2": 2,
}),
ctx,
&vars,
)
.unwrap();
assert_eq!(result, serde_json::json!(1));
Expand All @@ -174,17 +125,17 @@ end"#;
"key1": 1,
"key2": 2,
});
let vars = vec!["incubating".to_owned()];
// When incubating is true, the entire input is returned.
let ctx = vec![Val::from(serde_json::Value::Bool(true))];
let filter = super::Filter::try_new(jq_filter, vars.clone(), Vec::new()).unwrap();
let result = filter.apply(input.clone(), ctx).unwrap();
let mut ctx = BTreeMap::new();
let _ = ctx.insert("incubating".to_owned(), serde_json::Value::Bool(true));
let filter = super::Filter::new(jq_filter);
let result = filter.apply(input.clone(), &ctx).unwrap();
assert_eq!(result, input);

// When incubating = false the filter should return an empty array
let ctx = vec![Val::from(serde_json::Value::Bool(false))];
let filter = super::Filter::try_new(jq_filter, vars, Vec::new()).unwrap();
let result = filter.apply(input.clone(), ctx).unwrap();
let _ = ctx.insert("incubating".to_owned(), serde_json::Value::Bool(false));
let filter = super::Filter::new(jq_filter);
let result = filter.apply(input.clone(), &ctx).unwrap();
assert_eq!(result, serde_json::Value::Null);
}
}
Loading

0 comments on commit 6b03c69

Please sign in to comment.