Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(expr): enhance expr with JQ functionality #3191

Closed
wants to merge 34 commits into from
Closed
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
847a7da
feat(mustache): enhance mustache with JQ funtionality
karatakis Dec 3, 2024
f35dc2a
wip: implement missing functions
karatakis Dec 4, 2024
a502e71
feat: extend DynamicValue with jq templates * The resulting value is …
karatakis Dec 4, 2024
1fd76f0
fix: jq template to work with current implementation
karatakis Dec 5, 2024
ffa4e9c
fix: lint
karatakis Dec 5, 2024
9cb72ee
fix: add tests
karatakis Dec 5, 2024
c8087c1
Merge branch 'main' into feat/jq-template-hybrid
karatakis Dec 5, 2024
ef00eb8
fix: lint
karatakis Dec 5, 2024
1406a8d
fix: add more tests
karatakis Dec 5, 2024
f3de14d
feat: jq with mustache
karatakis Dec 5, 2024
ae9ebb0
wip: impl ValT
karatakis Dec 5, 2024
531d524
fix: compile errors
karatakis Dec 6, 2024
533b766
fix: missing implementations
karatakis Dec 6, 2024
d60d170
fix: compile jq only once
karatakis Dec 9, 2024
1c082be
refactor: JqTemplate to JqTransform
karatakis Dec 9, 2024
551c091
feat: add jq_template
karatakis Dec 9, 2024
06b37b4
fix: lint
karatakis Dec 9, 2024
dd7a9be
fix: all tests
karatakis Dec 9, 2024
74ee689
fix: error handling
karatakis Dec 10, 2024
98cc24d
fix: allow referencing .env for jq templates
karatakis Dec 10, 2024
c13656d
fix: add more tests
karatakis Dec 10, 2024
85abf92
fix: add args test
karatakis Dec 10, 2024
d74853f
fix: add jq select tests
karatakis Dec 10, 2024
4cc06e1
Merge branch 'main' into feat/jq-template-hybrid
karatakis Dec 10, 2024
ef0b699
fix: update cargo lock
karatakis Dec 10, 2024
5b04d26
Merge branch 'main' into feat/jq-template-hybrid
karatakis Dec 11, 2024
4676300
refactor: review changes
karatakis Dec 12, 2024
7571da7
fix: remove headers and vars
karatakis Dec 12, 2024
2e3c8f9
fix: allow getting env and header from context
karatakis Dec 12, 2024
84cf742
fix: change priority of parsing
karatakis Dec 12, 2024
a1e6b83
Merge branch 'main' into feat/jq-template-hybrid
karatakis Dec 12, 2024
ed65705
fix: lint
karatakis Dec 12, 2024
b4853be
Merge branch 'main' into feat/jq-template-hybrid
karatakis Dec 18, 2024
1c55d91
Merge branch 'main' into feat/jq-template-hybrid
tusharmath Dec 24, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 72 additions & 4 deletions Cargo.lock

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

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,9 @@ tailcall-valid = { workspace = true }
dashmap = "6.1.0"
urlencoding = "2.1.3"
tailcall-chunk = "0.3.0"
jaq-core = "2.0.0"
jaq-std = "2.0.0"
jaq-json = { version = "1.0.0", features = ["serde_json"]}

# to build rquickjs bindings on systems without builtin bindings
[target.'cfg(all(target_os = "windows", target_arch = "x86"))'.dependencies]
Expand Down
4 changes: 4 additions & 0 deletions benches/data_loader_bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ impl EnvIO for Env {
fn get(&self, _: &str) -> Option<Cow<'_, str>> {
unimplemented!("Not needed for this bench")
}

fn get_raw(&self) -> Vec<(String, String)> {
unimplemented!("Not needed for this bench")
}
}

struct File;
Expand Down
4 changes: 4 additions & 0 deletions benches/impl_path_string_for_evaluation_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ impl EnvIO for Env {
fn get(&self, _: &str) -> Option<Cow<'_, str>> {
unimplemented!("Not needed for this bench")
}

fn get_raw(&self) -> Vec<(String, String)> {
unimplemented!("Not needed for this bench")
}
}

struct File;
Expand Down
7 changes: 7 additions & 0 deletions src/cli/runtime/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ impl EnvIO for EnvNative {
fn get(&self, key: &str) -> Option<Cow<'_, str>> {
self.vars.get(key).map(Cow::from)
}

fn get_raw(&self) -> Vec<(String, String)> {
self.vars
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect()
}
}

impl EnvNative {
Expand Down
73 changes: 42 additions & 31 deletions src/core/blueprint/dynamic_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ use async_graphql_value::{ConstValue, Name};
use indexmap::IndexMap;
use serde_json::Value;

use crate::core::mustache::Mustache;
use crate::core::mustache::JqTemplate;

#[derive(Debug, Clone, PartialEq)]
/// This is used to express dynamic value resolver engine.
pub enum DynamicValue<A> {
Value(A),
Mustache(Mustache),
JqTemplate(JqTemplate),
karatakis marked this conversation as resolved.
Show resolved Hide resolved
Object(IndexMap<Name, DynamicValue<A>>),
Array(Vec<DynamicValue<A>>),
}
Expand All @@ -25,19 +26,25 @@ impl<A> DynamicValue<A> {
pub fn prepend(self, name: &str) -> Self {
match self {
DynamicValue::Value(value) => DynamicValue::Value(value),
DynamicValue::Mustache(mut mustache) => {
if mustache.is_const() {
DynamicValue::Mustache(mustache)
} else {
let segments = mustache.segments_mut();
if let Some(crate::core::mustache::Segment::Expression(vec)) =
segments.get_mut(0)
{
vec.insert(0, name.to_string());
}
DynamicValue::Mustache(mustache)
}
}
DynamicValue::JqTemplate(jqt) => DynamicValue::JqTemplate(JqTemplate(
jqt.0
.into_iter()
.map(|mut f| match &mut f {
crate::core::mustache::JqTemplateIR::JqTransform(_) => f,
crate::core::mustache::JqTemplateIR::Literal(_) => f,
// this function can prepend a custom prefix to mustache only
crate::core::mustache::JqTemplateIR::Mustache(mustache) => {
let segments = mustache.segments_mut();
if let Some(crate::core::mustache::Segment::Expression(vec)) =
segments.get_mut(0)
{
vec.insert(0, name.to_string());
}
f
}
})
.collect(),
)),
DynamicValue::Object(index_map) => {
let index_map = index_map
.into_iter()
Expand All @@ -59,8 +66,8 @@ impl TryFrom<&DynamicValue<ConstValue>> for ConstValue {
fn try_from(value: &DynamicValue<ConstValue>) -> Result<Self, Self::Error> {
match value {
DynamicValue::Value(v) => Ok(v.to_owned()),
DynamicValue::Mustache(_) => Err(anyhow::anyhow!(
"mustache cannot be converted to const value"
DynamicValue::JqTemplate(_) => Err(anyhow::anyhow!(
"jq template cannot be converted to const value"
)),
DynamicValue::Object(obj) => {
let out: Result<IndexMap<Name, ConstValue>, anyhow::Error> = obj
Expand All @@ -82,7 +89,7 @@ impl<A> DynamicValue<A> {
// Helper method to determine if the value is constant (non-mustache).
pub fn is_const(&self) -> bool {
match self {
DynamicValue::Mustache(m) => m.is_const(),
DynamicValue::JqTemplate(t) => t.is_const(),
DynamicValue::Object(obj) => obj.values().all(|v| v.is_const()),
DynamicValue::Array(arr) => arr.iter().all(|v| v.is_const()),
_ => true,
Expand All @@ -93,6 +100,7 @@ impl<A> DynamicValue<A> {
impl TryFrom<&Value> for DynamicValue<ConstValue> {
type Error = anyhow::Error;

/// Used to convert json notation to dynamic value
fn try_from(value: &Value) -> Result<Self, Self::Error> {
match value {
Value::Object(obj) => {
Expand All @@ -109,11 +117,12 @@ impl TryFrom<&Value> for DynamicValue<ConstValue> {
Ok(DynamicValue::Array(out?))
}
Value::String(s) => {
let m = Mustache::parse(s.as_str());
if m.is_const() {
let jqt = JqTemplate::parse(s);

if jqt.is_const() {
Ok(DynamicValue::Value(ConstValue::from_json(value.clone())?))
} else {
Ok(DynamicValue::Mustache(m))
Ok(DynamicValue::JqTemplate(jqt))
}
}
_ => Ok(DynamicValue::Value(ConstValue::from_json(value.clone())?)),
Expand All @@ -128,31 +137,33 @@ mod test {
#[test]
fn test_dynamic_value_inject() {
let value: DynamicValue<ConstValue> =
DynamicValue::Mustache(Mustache::parse("{{.foo}}")).prepend("args");
DynamicValue::JqTemplate(JqTemplate::parse("{{.foo}}")).prepend("args");
let expected: DynamicValue<ConstValue> =
DynamicValue::Mustache(Mustache::parse("{{.args.foo}}"));
DynamicValue::JqTemplate(JqTemplate::parse("{{.args.foo}}"));
assert_eq!(value, expected);

let mut value_map = IndexMap::new();
value_map.insert(
Name::new("foo"),
DynamicValue::Mustache(Mustache::parse("{{.foo}}")),
DynamicValue::JqTemplate(JqTemplate::parse("{{.foo}}")),
);
let value: DynamicValue<ConstValue> = DynamicValue::Object(value_map).prepend("args");
let mut expected_map = IndexMap::new();
expected_map.insert(
Name::new("foo"),
DynamicValue::Mustache(Mustache::parse("{{.args.foo}}")),
DynamicValue::JqTemplate(JqTemplate::parse("{{.args.foo}}")),
);
let expected: DynamicValue<ConstValue> = DynamicValue::Object(expected_map);
assert_eq!(value, expected);

let value: DynamicValue<ConstValue> =
DynamicValue::Array(vec![DynamicValue::Mustache(Mustache::parse("{{.foo}}"))])
.prepend("args");
let expected: DynamicValue<ConstValue> = DynamicValue::Array(vec![DynamicValue::Mustache(
Mustache::parse("{{.args.foo}}"),
)]);
let value: DynamicValue<ConstValue> = DynamicValue::Array(vec![DynamicValue::JqTemplate(
JqTemplate::parse("{{.foo}}"),
)])
.prepend("args");
let expected: DynamicValue<ConstValue> =
DynamicValue::Array(vec![DynamicValue::JqTemplate(JqTemplate::parse(
"{{.args.foo}}",
))]);
assert_eq!(value, expected);

let value: DynamicValue<ConstValue> = DynamicValue::Value(ConstValue::Null).prepend("args");
Expand Down
6 changes: 5 additions & 1 deletion src/core/ir/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use derive_more::From;
use thiserror::Error;

use crate::core::jit::graphql_error::{Error as ExtensionError, ErrorExtensions};
use crate::core::mustache::JqRuntimeError;
use crate::core::{auth, cache, worker, Errata};

#[derive(From, Debug, Error, Clone)]
Expand Down Expand Up @@ -33,6 +34,8 @@ pub enum Error {

Cache(cache::Error),

DynamicValue(JqRuntimeError),

#[from(ignore)]
Entity(String),
}
Expand Down Expand Up @@ -67,7 +70,8 @@ impl From<Error> for Errata {
}
Error::Worker(err) => Errata::new("Worker Error").description(err.to_string()),
Error::Cache(err) => Errata::new("Cache Error").description(err.to_string()),
Error::Entity(message) => Errata::new("Entity Resolver Error").description(message)
Error::Entity(message) => Errata::new("Entity Resolver Error").description(message),
Error::DynamicValue(err) => Errata::new("Dynamic Value Rendering Error").description(err.to_string())
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/core/ir/eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ impl IR {
.unwrap_or(&async_graphql::Value::Null)
.clone())
}
IR::Dynamic(value) => Ok(value.render_value(ctx)),
IR::Dynamic(value) => Ok(value.render_value(ctx)?),
IR::Protect(auth, expr) => {
let verifier = AuthVerifier::from(auth.clone());
verifier.verify(ctx.request_ctx).await.to_result()?;
Expand Down
Loading
Loading