From 8b40336525839860b4f17ae3ba94f31581d8f93f Mon Sep 17 00:00:00 2001 From: laststylebender Date: Wed, 13 Nov 2024 19:03:01 +0530 Subject: [PATCH 01/35] - render the selection set directly. --- src/core/ir/eval_context.rs | 20 ++---- tests/core/snapshots/graphql-nested.md_0.snap | 23 ++++++ .../snapshots/graphql-nested.md_client.snap | 30 ++++++++ .../snapshots/graphql-nested.md_merged.snap | 30 ++++++++ tests/execution/graphql-nested.md | 71 +++++++++++++++++++ 5 files changed, 161 insertions(+), 13 deletions(-) create mode 100644 tests/core/snapshots/graphql-nested.md_0.snap create mode 100644 tests/core/snapshots/graphql-nested.md_client.snap create mode 100644 tests/core/snapshots/graphql-nested.md_merged.snap create mode 100644 tests/execution/graphql-nested.md diff --git a/src/core/ir/eval_context.rs b/src/core/ir/eval_context.rs index 89c03d80c6..5675494893 100644 --- a/src/core/ir/eval_context.rs +++ b/src/core/ir/eval_context.rs @@ -117,22 +117,20 @@ impl GraphQLOperationContext for EvalContext<'_, Ctx> .map(|directives| print_directives(directives.iter())) } - fn selection_set(&self, related_fields: &RelatedFields) -> Option { + fn selection_set(&self, _related_fields: &RelatedFields) -> Option { + // ignore selection set and directly use selection set. let selection_field = self.graphql_ctx.field()?; - format_selection_set(selection_field.selection_set(), related_fields) + format_selection_set(selection_field.selection_set()) } } fn format_selection_set<'a>( selection_set: impl Iterator, - related_fields: &RelatedFields, ) -> Option { let set = selection_set - .filter_map(|field| { + .map(|field| { // add to set only related fields that should be resolved with current resolver - related_fields.get(field.name()).map(|related_fields| { - format_selection_field(field, &related_fields.0, &related_fields.1) - }) + format_selection_field(field, field.name()) }) .collect::>(); @@ -143,13 +141,9 @@ fn format_selection_set<'a>( Some(format!("{{ {} }}", set.join(" "))) } -fn format_selection_field( - field: &SelectionField, - name: &str, - related_fields: &RelatedFields, -) -> String { +fn format_selection_field(field: &SelectionField, name: &str) -> String { let arguments = format_selection_field_arguments(field); - let selection_set = format_selection_set(field.selection_set(), related_fields); + let selection_set = format_selection_set(field.selection_set()); let mut output = format!("{}{}", name, arguments); diff --git a/tests/core/snapshots/graphql-nested.md_0.snap b/tests/core/snapshots/graphql-nested.md_0.snap new file mode 100644 index 0000000000..0b3e1b3e22 --- /dev/null +++ b/tests/core/snapshots/graphql-nested.md_0.snap @@ -0,0 +1,23 @@ +--- +source: tests/core/spec.rs +expression: response +--- +{ + "status": 200, + "headers": { + "content-type": "application/json" + }, + "body": { + "data": { + "queryNodeA": { + "name": "nodeA", + "nodeB": { + "name": "nodeB" + }, + "nodeC": { + "name": "nodeC" + } + } + } + } +} diff --git a/tests/core/snapshots/graphql-nested.md_client.snap b/tests/core/snapshots/graphql-nested.md_client.snap new file mode 100644 index 0000000000..b804ac9539 --- /dev/null +++ b/tests/core/snapshots/graphql-nested.md_client.snap @@ -0,0 +1,30 @@ +--- +source: tests/core/spec.rs +expression: formatted +--- +type NodeA { + child: NodeA + name: String + nodeB: NodeB + nodeC: NodeC +} + +type NodeB { + name: String + nodeA: NodeA + nodeC: NodeC +} + +type NodeC { + name: String + nodeA: NodeA + nodeB: NodeB +} + +type Query { + queryNodeA: NodeA +} + +schema { + query: Query +} diff --git a/tests/core/snapshots/graphql-nested.md_merged.snap b/tests/core/snapshots/graphql-nested.md_merged.snap new file mode 100644 index 0000000000..8accbbde47 --- /dev/null +++ b/tests/core/snapshots/graphql-nested.md_merged.snap @@ -0,0 +1,30 @@ +--- +source: tests/core/spec.rs +expression: formatter +--- +schema @server(hostname: "0.0.0.0", port: 8000) @upstream { + query: Query +} + +type NodeA { + name: String + nodeA: NodeA @modify(name: "child") + nodeB: NodeB + nodeC: NodeC +} + +type NodeB { + name: String + nodeA: NodeA + nodeC: NodeC +} + +type NodeC { + name: String + nodeA: NodeA + nodeB: NodeB +} + +type Query { + queryNodeA: NodeA @graphQL(url: "http://upstream/graphql", name: "nodeA") +} diff --git a/tests/execution/graphql-nested.md b/tests/execution/graphql-nested.md new file mode 100644 index 0000000000..cdb41835bd --- /dev/null +++ b/tests/execution/graphql-nested.md @@ -0,0 +1,71 @@ +# Complicated queries + +```graphql @config +schema @server(port: 8000, hostname: "0.0.0.0") { + query: Query +} + +type Query { + queryNodeA: NodeA @graphQL(url: "http://upstream/graphql", name: "nodeA") +} + +type NodeA { + name: String + nodeB: NodeB + nodeC: NodeC + nodeA: NodeA @modify(name: "child") +} + +type NodeB { + name: String + nodeA: NodeA + nodeC: NodeC +} + +type NodeC { + name: String + nodeA: NodeA + nodeB: NodeB +} +``` + +```yml @mock +- request: + method: POST + url: http://upstream/graphql + textBody: { "query": "query { nodeA { name nodeB { name } nodeC { name } } }" } + expectedHits: 1 + response: + status: 200 + body: + data: + nodeA: + name: nodeA + nodeB: + name: nodeB + nodeC: + name: nodeC + nodeA: + name: nodeA +``` + +```yml @test +- method: POST + url: http://localhost:8080/graphql + body: + query: | + query queryNodeA { + queryNodeA { + name + nodeA { + name + } + nodeB { + name + } + nodeC { + name + } + } + } +``` \ No newline at end of file From 11b7733ee796e1f1766a424d844ee6ad1604cf99 Mon Sep 17 00:00:00 2001 From: laststylebender Date: Thu, 14 Nov 2024 11:05:29 +0530 Subject: [PATCH 02/35] - store selection at graphql IO level. --- src/core/app_context.rs | 1 + src/core/blueprint/operators/graphql.rs | 2 +- src/core/ir/model.rs | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/app_context.rs b/src/core/app_context.rs index af1963868e..bbe36d3fbb 100644 --- a/src/core/app_context.rs +++ b/src/core/app_context.rs @@ -94,6 +94,7 @@ impl AppContext { req_template: req_template.clone(), field_name: field_name.clone(), batch: *batch, + selection: None, dl_id: Some(DataLoaderId::new(gql_data_loaders.len())), dedupe, })); diff --git a/src/core/blueprint/operators/graphql.rs b/src/core/blueprint/operators/graphql.rs index ee25eb5fd1..00a3631c0f 100644 --- a/src/core/blueprint/operators/graphql.rs +++ b/src/core/blueprint/operators/graphql.rs @@ -82,7 +82,7 @@ pub fn compile_graphql( let field_name = graphql.name.clone(); let batch = graphql.batch; let dedupe = graphql.dedupe.unwrap_or_default(); - IR::IO(IO::GraphQL { req_template, field_name, batch, dl_id: None, dedupe }) + IR::IO(IO::GraphQL { req_template, field_name, batch, dl_id: None, dedupe, selection: None }) }) } diff --git a/src/core/ir/model.rs b/src/core/ir/model.rs index ecab192987..03ed163475 100644 --- a/src/core/ir/model.rs +++ b/src/core/ir/model.rs @@ -51,6 +51,7 @@ pub enum IO { }, GraphQL { req_template: graphql::RequestTemplate, + selection: Option, field_name: String, batch: bool, dl_id: Option, From aa521de65e5bbc849ac2ef0ff067ba47a3c71f7c Mon Sep 17 00:00:00 2001 From: laststylebender Date: Thu, 14 Nov 2024 11:48:21 +0530 Subject: [PATCH 03/35] - revert: selection from IO. --- src/core/app_context.rs | 1 - src/core/blueprint/operators/graphql.rs | 2 +- src/core/ir/model.rs | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/core/app_context.rs b/src/core/app_context.rs index bbe36d3fbb..af1963868e 100644 --- a/src/core/app_context.rs +++ b/src/core/app_context.rs @@ -94,7 +94,6 @@ impl AppContext { req_template: req_template.clone(), field_name: field_name.clone(), batch: *batch, - selection: None, dl_id: Some(DataLoaderId::new(gql_data_loaders.len())), dedupe, })); diff --git a/src/core/blueprint/operators/graphql.rs b/src/core/blueprint/operators/graphql.rs index 00a3631c0f..ee25eb5fd1 100644 --- a/src/core/blueprint/operators/graphql.rs +++ b/src/core/blueprint/operators/graphql.rs @@ -82,7 +82,7 @@ pub fn compile_graphql( let field_name = graphql.name.clone(); let batch = graphql.batch; let dedupe = graphql.dedupe.unwrap_or_default(); - IR::IO(IO::GraphQL { req_template, field_name, batch, dl_id: None, dedupe, selection: None }) + IR::IO(IO::GraphQL { req_template, field_name, batch, dl_id: None, dedupe }) }) } diff --git a/src/core/ir/model.rs b/src/core/ir/model.rs index 03ed163475..ecab192987 100644 --- a/src/core/ir/model.rs +++ b/src/core/ir/model.rs @@ -51,7 +51,6 @@ pub enum IO { }, GraphQL { req_template: graphql::RequestTemplate, - selection: Option, field_name: String, batch: bool, dl_id: Option, From a775b68045fc0ac9c3763e4c33c8afceb111c188 Mon Sep 17 00:00:00 2001 From: laststylebender Date: Thu, 14 Nov 2024 11:49:27 +0530 Subject: [PATCH 04/35] - store selection in request_template while generating the plan. --- src/core/graphql/request_template.rs | 10 ++- src/core/jit/model.rs | 11 ++++ src/core/jit/request.rs | 1 + src/core/jit/transform/graphql.rs | 97 ++++++++++++++++++++++++++++ src/core/jit/transform/mod.rs | 2 + 5 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 src/core/jit/transform/graphql.rs diff --git a/src/core/graphql/request_template.rs b/src/core/graphql/request_template.rs index 1eb87f1cc8..d7bebd4b9b 100644 --- a/src/core/graphql/request_template.rs +++ b/src/core/graphql/request_template.rs @@ -26,6 +26,7 @@ pub struct RequestTemplate { pub operation_arguments: Option>, pub headers: MustacheHeaders, pub related_fields: RelatedFields, + pub selection: Option, } impl RequestTemplate { @@ -85,7 +86,13 @@ impl RequestTemplate { ctx: &C, ) -> String { let operation_type = &self.operation_type; - let selection_set = ctx.selection_set(&self.related_fields).unwrap_or_default(); + let selection_set = self + .selection + .as_ref() + .map(|s| Cow::Borrowed(s)) + .unwrap_or_else(|| { + Cow::Owned(ctx.selection_set(&self.related_fields).unwrap_or_default()) + }); let mut operation = Cow::Borrowed(&self.operation_name); @@ -149,6 +156,7 @@ impl RequestTemplate { operation_arguments, headers, related_fields, + selection: None, }) } } diff --git a/src/core/jit/model.rs b/src/core/jit/model.rs index f0b30ce21c..0e95ae0030 100644 --- a/src/core/jit/model.rs +++ b/src/core/jit/model.rs @@ -96,6 +96,17 @@ pub struct Arg { pub default_value: Option, } +impl ToString for Arg { + fn to_string(&self) -> String { + if let Some(val) = self.value.as_ref() { + format!("{}:{}", self.name, val.to_string()) + } else { + let value = self.default_value.as_ref().map(|v| v.to_string()); + format!("{}:{}", self.name, value.unwrap_or_default()) + } + } +} + impl Arg { pub fn try_map( self, diff --git a/src/core/jit/request.rs b/src/core/jit/request.rs index 459ccf7162..add7b194e5 100644 --- a/src/core/jit/request.rs +++ b/src/core/jit/request.rs @@ -49,6 +49,7 @@ impl Request { .pipe(transform::CheckDedupe::new()) .pipe(transform::CheckProtected::new()) .pipe(transform::CheckCache::new()) + .pipe(transform::GraphQL::new()) .transform(plan) .to_result() // both transformers are infallible right now diff --git a/src/core/jit/transform/graphql.rs b/src/core/jit/transform/graphql.rs new file mode 100644 index 0000000000..46bd863de7 --- /dev/null +++ b/src/core/jit/transform/graphql.rs @@ -0,0 +1,97 @@ +use std::{borrow::Cow, convert::Infallible, marker::PhantomData}; + +use tailcall_valid::Valid; + +use crate::core::{ + ir::model::{IO, IR}, + jit::{Field, OperationPlan}, + Transform, +}; + +#[derive(Default)] +pub struct GraphQL(PhantomData); + +impl GraphQL { + pub fn new() -> Self { + Self(PhantomData) + } +} + +impl Transform for GraphQL { + type Value = OperationPlan; + type Error = Infallible; + + fn transform(&self, mut plan: Self::Value) -> Valid { + for field in plan.selection.iter_mut() { + let rendered = field.render_graphql(); + if let Some(IR::IO(IO::GraphQL { req_template, .. })) = field.ir.as_mut() { + req_template.selection = rendered; + } + } + + Valid::succeed(plan) + } +} + +impl Field { + pub fn render_graphql(&self) -> Option { + format_selection_set(self.selection.iter()) + } +} + +fn format_selection_set<'a, A: 'a + ToString>( + selection_set: impl Iterator>, +) -> Option { + let set = selection_set + .map(|field| format_selection_field(field, &field.name)) + .collect::>(); + + if set.is_empty() { + return None; + } + + Some(format!("{{ {} }}", set.join(" "))) +} + +fn format_selection_field(field: &Field, name: &str) -> String { + let arguments = format_selection_field_arguments(field); + let selection_set = format_selection_set(field.selection.iter()); + + let mut output = format!("{}{}", name, arguments); + + // if let Some(directives) = field.directives() { + // let directives = print_directives(directives.iter()); + + // if !directives.is_empty() { + // output.push(' '); + // output.push_str(&directives.escape_default().to_string()); + // } + // } + + if let Some(selection_set) = selection_set { + output.push(' '); + output.push_str(&selection_set); + } + + output +} + +fn format_selection_field_arguments(field: &Field) -> Cow<'static, str> { + let arguments = field + .args + .iter() + .filter_map(|a| a.value.as_ref()) + .collect::>(); + + if arguments.is_empty() { + return Cow::Borrowed(""); + } + + let args = arguments + .iter() + .map(|arg| arg.to_string()) + .collect::>() + .join(","); + + Cow::Owned(format!("({})", args.escape_default())) +} diff --git a/src/core/jit/transform/mod.rs b/src/core/jit/transform/mod.rs index 986e5c695c..5d27b587fe 100644 --- a/src/core/jit/transform/mod.rs +++ b/src/core/jit/transform/mod.rs @@ -4,6 +4,7 @@ mod check_dedupe; mod check_protected; mod input_resolver; mod skip; +mod graphql; pub use check_cache::*; pub use check_const::*; @@ -11,3 +12,4 @@ pub use check_dedupe::*; pub use check_protected::*; pub use input_resolver::*; pub use skip::*; +pub use graphql::*; From e502f3e2ad6f6890a7cee2bcf9f4ccccefd3c5c9 Mon Sep 17 00:00:00 2001 From: laststylebender Date: Thu, 14 Nov 2024 12:05:56 +0530 Subject: [PATCH 05/35] - fix indentation --- src/core/jit/model.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/jit/model.rs b/src/core/jit/model.rs index 0e95ae0030..bf4a8cc521 100644 --- a/src/core/jit/model.rs +++ b/src/core/jit/model.rs @@ -99,10 +99,10 @@ pub struct Arg { impl ToString for Arg { fn to_string(&self) -> String { if let Some(val) = self.value.as_ref() { - format!("{}:{}", self.name, val.to_string()) + format!("{}: {}", self.name, val.to_string()) } else { let value = self.default_value.as_ref().map(|v| v.to_string()); - format!("{}:{}", self.name, value.unwrap_or_default()) + format!("{}: {}", self.name, value.unwrap_or_default()) } } } From 2171fd7ccc226da5a2d6dec9ae5479a8235a0264 Mon Sep 17 00:00:00 2001 From: laststylebender Date: Thu, 14 Nov 2024 12:06:48 +0530 Subject: [PATCH 06/35] - filter out args that has values. --- src/core/jit/transform/graphql.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/jit/transform/graphql.rs b/src/core/jit/transform/graphql.rs index 46bd863de7..72670534fe 100644 --- a/src/core/jit/transform/graphql.rs +++ b/src/core/jit/transform/graphql.rs @@ -80,7 +80,7 @@ fn format_selection_field_arguments(field: &Field) -> Cow<'stati let arguments = field .args .iter() - .filter_map(|a| a.value.as_ref()) + .filter(|a| a.value.is_some()) .collect::>(); if arguments.is_empty() { From 4ce9e4e5f89475f37da0c89a46d7984129982772 Mon Sep 17 00:00:00 2001 From: laststylebender Date: Thu, 14 Nov 2024 12:39:24 +0530 Subject: [PATCH 07/35] - skip IR fields. --- src/core/jit/transform/graphql.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/core/jit/transform/graphql.rs b/src/core/jit/transform/graphql.rs index 72670534fe..c8745f2c7f 100644 --- a/src/core/jit/transform/graphql.rs +++ b/src/core/jit/transform/graphql.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, convert::Infallible, marker::PhantomData}; +use std::{borrow::Cow, convert::Infallible, fmt::Debug, marker::PhantomData}; use tailcall_valid::Valid; @@ -17,7 +17,7 @@ impl GraphQL { } } -impl Transform for GraphQL { +impl Transform for GraphQL { type Value = OperationPlan; type Error = Infallible; @@ -42,7 +42,9 @@ impl Field { fn format_selection_set<'a, A: 'a + ToString>( selection_set: impl Iterator>, ) -> Option { + // TODO: skip fields that has resolver. let set = selection_set + .filter(|field| field.ir.is_none()) .map(|field| format_selection_field(field, &field.name)) .collect::>(); From d66a424a9d45a973ff81d9fc53ea34cb2b00e20d Mon Sep 17 00:00:00 2001 From: laststylebender Date: Thu, 14 Nov 2024 13:56:57 +0530 Subject: [PATCH 08/35] impl JsonLike for Value. --- src/core/json/graphql.rs | 132 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 131 insertions(+), 1 deletion(-) diff --git a/src/core/json/graphql.rs b/src/core/json/graphql.rs index 3a0870360f..706359d400 100644 --- a/src/core/json/graphql.rs +++ b/src/core/json/graphql.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use std::collections::HashMap; use async_graphql::Name; -use async_graphql_value::ConstValue; +use async_graphql_value::{ConstValue, Value}; use indexmap::IndexMap; use super::*; @@ -154,3 +154,133 @@ impl<'json> JsonLike<'json> for ConstValue { ConstValue::String(s.to_string()) } } + + + +impl<'json> JsonLike<'json> for Value { + type JsonObject = IndexMap; + + fn as_array(&self) -> Option<&Vec> { + match self { + Value::List(seq) => Some(seq), + _ => None, + } + } + + fn as_array_mut(&mut self) -> Option<&mut Vec> { + match self { + Value::List(seq) => Some(seq), + _ => None, + } + } + + fn into_array(self) -> Option> { + match self { + Value::List(seq) => Some(seq), + _ => None, + } + } + + fn as_str(&self) -> Option<&str> { + match self { + Value::String(s) => Some(s), + _ => None, + } + } + + fn as_i64(&self) -> Option { + match self { + Value::Number(n) => n.as_i64(), + _ => None, + } + } + + fn as_u64(&self) -> Option { + match self { + Value::Number(n) => n.as_u64(), + _ => None, + } + } + + fn as_f64(&self) -> Option { + match self { + Value::Number(n) => n.as_f64(), + _ => None, + } + } + + fn as_bool(&self) -> Option { + match self { + Value::Boolean(b) => Some(*b), + _ => None, + } + } + + fn is_null(&self) -> bool { + matches!(self, Value::Null) + } + + fn get_path>(&self, path: &[T]) -> Option<&Self> { + let mut val = self; + for token in path { + val = match val { + Value::List(seq) => { + let index = token.as_ref().parse::().ok()?; + seq.get(index)? + } + Value::Object(map) => map.get(token.as_ref())?, + _ => return None, + }; + } + Some(val) + } + + fn get_key(&self, path: &str) -> Option<&Self> { + match self { + Value::Object(map) => map.get(&async_graphql::Name::new(path)), + _ => None, + } + } + + fn group_by(&self, path: &[String]) -> HashMap> { + let src = gather_path_matches(self, path, vec![]); + group_by_key(src) + } + + fn null() -> Self { + Default::default() + } + + fn as_object(&self) -> Option<&Self::JsonObject> { + match self { + Value::Object(map) => Some(map), + _ => None, + } + } + + fn as_object_mut(&mut self) -> Option<&mut Self::JsonObject> { + match self { + Value::Object(map) => Some(map), + _ => None, + } + } + + fn into_object(self) -> Option { + match self { + Value::Object(map) => Some(map), + _ => None, + } + } + + fn object(obj: Self::JsonObject) -> Self { + Value::Object(obj) + } + + fn array(arr: Vec) -> Self { + Value::List(arr) + } + + fn string(s: Cow<'json, str>) -> Self { + Value::String(s.to_string()) + } +} From cea0573ea306de01649786ae55db2312ddb850ae Mon Sep 17 00:00:00 2001 From: laststylebender Date: Thu, 14 Nov 2024 13:57:10 +0530 Subject: [PATCH 09/35] - add support for directives. --- src/core/jit/transform/graphql.rs | 91 +++++++++++++++++++++++++++---- 1 file changed, 79 insertions(+), 12 deletions(-) diff --git a/src/core/jit/transform/graphql.rs b/src/core/jit/transform/graphql.rs index c8745f2c7f..647cc7d627 100644 --- a/src/core/jit/transform/graphql.rs +++ b/src/core/jit/transform/graphql.rs @@ -1,10 +1,15 @@ use std::{borrow::Cow, convert::Infallible, fmt::Debug, marker::PhantomData}; +use async_graphql::{ + parser::types::{DirectiveDefinition, InputValueDefinition, Type}, + Name, Pos, Positioned, +}; use tailcall_valid::Valid; use crate::core::{ ir::model::{IO, IR}, - jit::{Field, OperationPlan}, + jit::{Directive, Field, OperationPlan}, + json::{JsonLike, JsonLikeOwned}, Transform, }; @@ -17,7 +22,7 @@ impl GraphQL { } } -impl Transform for GraphQL { +impl Transform for GraphQL { type Value = OperationPlan; type Error = Infallible; @@ -33,13 +38,13 @@ impl Transform for GraphQL { } } -impl Field { +impl Field { pub fn render_graphql(&self) -> Option { format_selection_set(self.selection.iter()) } } -fn format_selection_set<'a, A: 'a + ToString>( +fn format_selection_set<'a, A: 'a + ToString + JsonLikeOwned>( selection_set: impl Iterator>, ) -> Option { // TODO: skip fields that has resolver. @@ -55,20 +60,20 @@ fn format_selection_set<'a, A: 'a + ToString>( Some(format!("{{ {} }}", set.join(" "))) } -fn format_selection_field(field: &Field, name: &str) -> String { +fn format_selection_field(field: &Field, name: &str) -> String { let arguments = format_selection_field_arguments(field); let selection_set = format_selection_set(field.selection.iter()); let mut output = format!("{}{}", name, arguments); - // if let Some(directives) = field.directives() { - // let directives = print_directives(directives.iter()); + if !field.directives.is_empty() { + let directives = print_directives(field.directives.iter()); - // if !directives.is_empty() { - // output.push(' '); - // output.push_str(&directives.escape_default().to_string()); - // } - // } + if !directives.is_empty() { + output.push(' '); + output.push_str(&directives.escape_default().to_string()); + } + } if let Some(selection_set) = selection_set { output.push(' '); @@ -97,3 +102,65 @@ fn format_selection_field_arguments(field: &Field) -> Cow<'stati Cow::Owned(format!("({})", args.escape_default())) } + +// TODO: refactor this. +pub fn print_directives<'a, A: 'a + JsonLikeOwned>( + directives: impl Iterator>, +) -> String { + directives + .map(|d| print_directive(&const_directive_to_sdl(d))) + .collect::>() + .join(" ") +} + +fn print_directive(directive: &DirectiveDefinition) -> String { + let args = directive + .arguments + .iter() + .map(|arg| format!("{}: {}", arg.node.name.node, arg.node.ty.node)) + .collect::>() + .join(", "); + + if args.is_empty() { + format!("@{}", directive.name.node) + } else { + format!("@{}({})", directive.name.node, args) + } +} + +fn pos(a: A) -> Positioned { + Positioned::new(a, Pos::default()) +} + +fn const_directive_to_sdl( + directive: &Directive, +) -> DirectiveDefinition { + DirectiveDefinition { + description: None, + name: pos(Name::new(directive.name.as_str())), + arguments: directive + .arguments + .iter() + .filter_map(|(k, v)| { + if let Some(v) = v.as_str().map(|v| v.to_string()) { + Some(pos(InputValueDefinition { + description: None, + name: pos(Name::new(k)), + ty: pos(Type { + nullable: true, + base: async_graphql::parser::types::BaseType::Named(Name::new( + v.clone(), + )), + }), + default_value: Some(pos(JsonLike::string(Cow::Owned(v)))), + directives: Vec::new(), + })) + } else { + None + } + }) + .collect(), + is_repeatable: true, + locations: vec![], + } +} From 64e3dc29b330e79f5ec4892c4efa911cc43df912 Mon Sep 17 00:00:00 2001 From: laststylebender Date: Thu, 14 Nov 2024 14:47:38 +0530 Subject: [PATCH 10/35] - impl to_string_value for JsonLike --- src/core/json/borrow.rs | 4 ++++ src/core/json/graphql.rs | 9 +++++++++ src/core/json/json_like.rs | 1 + src/core/json/serde.rs | 4 ++++ 4 files changed, 18 insertions(+) diff --git a/src/core/json/borrow.rs b/src/core/json/borrow.rs index 235d5a0475..d8fced86a8 100644 --- a/src/core/json/borrow.rs +++ b/src/core/json/borrow.rs @@ -134,4 +134,8 @@ impl<'ctx> JsonLike<'ctx> for Value<'ctx> { let src = gather_path_matches(self, path, vec![]); group_by_key(src) } + + fn to_string_value(&'ctx self) -> String { + format!("{}", self) + } } diff --git a/src/core/json/graphql.rs b/src/core/json/graphql.rs index 706359d400..3dc85acdd6 100644 --- a/src/core/json/graphql.rs +++ b/src/core/json/graphql.rs @@ -153,6 +153,10 @@ impl<'json> JsonLike<'json> for ConstValue { fn string(s: Cow<'json, str>) -> Self { ConstValue::String(s.to_string()) } + + fn to_string_value(&'json self) -> String { + format!("{}", self) + } } @@ -184,6 +188,7 @@ impl<'json> JsonLike<'json> for Value { fn as_str(&self) -> Option<&str> { match self { Value::String(s) => Some(s), + Value::Variable(map) => Some(map.as_str()), _ => None, } } @@ -283,4 +288,8 @@ impl<'json> JsonLike<'json> for Value { fn string(s: Cow<'json, str>) -> Self { Value::String(s.to_string()) } + + fn to_string_value(&'json self) -> String { + format!("{}", self) + } } diff --git a/src/core/json/json_like.rs b/src/core/json/json_like.rs index 19d9568e8f..da905c5b95 100644 --- a/src/core/json/json_like.rs +++ b/src/core/json/json_like.rs @@ -30,6 +30,7 @@ pub trait JsonLike<'json>: Sized { fn get_path>(&'json self, path: &[T]) -> Option<&'json Self>; fn get_key(&'json self, path: &str) -> Option<&'json Self>; fn group_by(&'json self, path: &[String]) -> HashMap>; + fn to_string_value(&'json self) -> String; } /// A trait for objects that can be used as JSON objects diff --git a/src/core/json/serde.rs b/src/core/json/serde.rs index 115b7e64e3..cbe677d866 100644 --- a/src/core/json/serde.rs +++ b/src/core/json/serde.rs @@ -124,4 +124,8 @@ impl<'json> JsonLike<'json> for serde_json::Value { fn string(s: Cow<'json, str>) -> Self { serde_json::Value::String(s.to_string()) } + + fn to_string_value(&'json self) -> String { + format!("{}", self) + } } From fee9ea25848a74fe5cf18b6d809700ba2c3beaf5 Mon Sep 17 00:00:00 2001 From: laststylebender Date: Thu, 14 Nov 2024 14:48:16 +0530 Subject: [PATCH 11/35] - we can apply GraphQL transformation only when selection set is enriched with variables. --- src/core/jit/request.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/jit/request.rs b/src/core/jit/request.rs index add7b194e5..459ccf7162 100644 --- a/src/core/jit/request.rs +++ b/src/core/jit/request.rs @@ -49,7 +49,6 @@ impl Request { .pipe(transform::CheckDedupe::new()) .pipe(transform::CheckProtected::new()) .pipe(transform::CheckCache::new()) - .pipe(transform::GraphQL::new()) .transform(plan) .to_result() // both transformers are infallible right now From ccb918a910766f77c6dd3f05cf234e0142551382 Mon Sep 17 00:00:00 2001 From: laststylebender Date: Thu, 14 Nov 2024 14:49:31 +0530 Subject: [PATCH 12/35] - add transformation to plan. --- src/core/jit/exec_const.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/core/jit/exec_const.rs b/src/core/jit/exec_const.rs index 7f26bce003..fe1a4b8f01 100644 --- a/src/core/jit/exec_const.rs +++ b/src/core/jit/exec_const.rs @@ -59,7 +59,13 @@ impl ConstValueExecutor { let result = InputResolver::new(plan).resolve_input(variables); let plan = match result { - Ok(plan) => plan, + Ok(plan) => { + let plan = transform::GraphQL::new() + .transform(plan) + .to_result() + .unwrap(); + plan + } Err(err) => { return Response::default().with_errors(vec![Positioned::new( BuildError::from(err).into(), From 44ddda1f0548f6947c041f5be83c7f1fe68e299d Mon Sep 17 00:00:00 2001 From: laststylebender Date: Thu, 14 Nov 2024 14:49:43 +0530 Subject: [PATCH 13/35] - refactor: const_directive_to_sdl --- src/core/jit/transform/graphql.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/core/jit/transform/graphql.rs b/src/core/jit/transform/graphql.rs index 647cc7d627..d80f2087a9 100644 --- a/src/core/jit/transform/graphql.rs +++ b/src/core/jit/transform/graphql.rs @@ -142,17 +142,16 @@ fn const_directive_to_sdl( .arguments .iter() .filter_map(|(k, v)| { - if let Some(v) = v.as_str().map(|v| v.to_string()) { + if !v.is_null() { + let v_str = v.to_string_value(); Some(pos(InputValueDefinition { description: None, name: pos(Name::new(k)), + default_value: Some(pos(JsonLike::string(Cow::Borrowed(&v_str)))), ty: pos(Type { nullable: true, - base: async_graphql::parser::types::BaseType::Named(Name::new( - v.clone(), - )), + base: async_graphql::parser::types::BaseType::Named(Name::new(v_str)), }), - default_value: Some(pos(JsonLike::string(Cow::Owned(v)))), directives: Vec::new(), })) } else { From 96df89ebefc17e6d54c878006bca9b8170262775 Mon Sep 17 00:00:00 2001 From: laststylebender Date: Thu, 14 Nov 2024 14:53:25 +0530 Subject: [PATCH 14/35] - there's no point re-creating selection set during each request. --- src/core/jit/exec_const.rs | 8 +------- src/core/jit/request.rs | 1 + 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/core/jit/exec_const.rs b/src/core/jit/exec_const.rs index fe1a4b8f01..7f26bce003 100644 --- a/src/core/jit/exec_const.rs +++ b/src/core/jit/exec_const.rs @@ -59,13 +59,7 @@ impl ConstValueExecutor { let result = InputResolver::new(plan).resolve_input(variables); let plan = match result { - Ok(plan) => { - let plan = transform::GraphQL::new() - .transform(plan) - .to_result() - .unwrap(); - plan - } + Ok(plan) => plan, Err(err) => { return Response::default().with_errors(vec![Positioned::new( BuildError::from(err).into(), diff --git a/src/core/jit/request.rs b/src/core/jit/request.rs index 459ccf7162..add7b194e5 100644 --- a/src/core/jit/request.rs +++ b/src/core/jit/request.rs @@ -49,6 +49,7 @@ impl Request { .pipe(transform::CheckDedupe::new()) .pipe(transform::CheckProtected::new()) .pipe(transform::CheckCache::new()) + .pipe(transform::GraphQL::new()) .transform(plan) .to_result() // both transformers are infallible right now From 186ab9f9043f54b509bd78da230791a61aa03ab6 Mon Sep 17 00:00:00 2001 From: laststylebender Date: Thu, 14 Nov 2024 16:19:59 +0530 Subject: [PATCH 15/35] - impl pathstring for Variables --- src/core/jit/model.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/core/jit/model.rs b/src/core/jit/model.rs index bf4a8cc521..f5131ba1ae 100644 --- a/src/core/jit/model.rs +++ b/src/core/jit/model.rs @@ -13,12 +13,20 @@ use super::Error; use crate::core::blueprint::Index; use crate::core::ir::model::IR; use crate::core::ir::TypedValue; -use crate::core::json::JsonLike; +use crate::core::json::{JsonLike, JsonLikeOwned}; +use crate::core::path::PathString; use crate::core::scalar::Scalar; #[derive(Debug, Deserialize, Clone)] pub struct Variables(HashMap); +impl PathString for Variables { + fn path_string<'a, T: AsRef>(&'a self, path: &'a [T]) -> Option> { + self.get(path[0].as_ref()) + .map(|v| Cow::Owned(v.to_string_value())) + } +} + impl Default for Variables { fn default() -> Self { Self::new() From f8734dd08374969e6b81b5924802300f5a58aeeb Mon Sep 17 00:00:00 2001 From: laststylebender Date: Thu, 14 Nov 2024 16:23:46 +0530 Subject: [PATCH 16/35] - compute the variables at runtime query with mustache --- src/core/jit/transform/graphql.rs | 8 +++++++- src/core/jit/transform/input_resolver.rs | 14 ++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/core/jit/transform/graphql.rs b/src/core/jit/transform/graphql.rs index d80f2087a9..5226e59214 100644 --- a/src/core/jit/transform/graphql.rs +++ b/src/core/jit/transform/graphql.rs @@ -135,6 +135,12 @@ fn pos(a: A) -> Positioned { fn const_directive_to_sdl( directive: &Directive, ) -> DirectiveDefinition { + let to_mustache = |s: &str| -> String { + s.strip_prefix('$') + .map(|v| format!("{{{{{}}}}}", v)) + .unwrap_or_else(|| s.to_string()) + }; + DirectiveDefinition { description: None, name: pos(Name::new(directive.name.as_str())), @@ -143,7 +149,7 @@ fn const_directive_to_sdl( .iter() .filter_map(|(k, v)| { if !v.is_null() { - let v_str = v.to_string_value(); + let v_str = to_mustache(&v.to_string_value()); Some(pos(InputValueDefinition { description: None, name: pos(Name::new(k)), diff --git a/src/core/jit/transform/input_resolver.rs b/src/core/jit/transform/input_resolver.rs index b83d524238..8d173675f2 100644 --- a/src/core/jit/transform/input_resolver.rs +++ b/src/core/jit/transform/input_resolver.rs @@ -2,8 +2,9 @@ use async_graphql_value::{ConstValue, Value}; use super::super::{Arg, Field, OperationPlan, ResolveInputError, Variables}; use crate::core::blueprint::Index; +use crate::core::ir::model::{IO, IR}; use crate::core::json::{JsonLikeOwned, JsonObjectLike}; -use crate::core::Type; +use crate::core::{Mustache, Type}; /// Trait to represent conversion from some dynamic type (with variables) /// to the resolved variant based on the additional provided info. @@ -55,7 +56,7 @@ where variables: &Variables, ) -> Result, ResolveInputError> { let index = self.plan.index; - let selection = self + let mut selection = self .plan .selection .into_iter() @@ -68,6 +69,15 @@ where .map(|field| Self::resolve_field(&index, field?)) .collect::, _>>()?; + // adjust the pre-computed values in selection set like graphql query for @graphql directive. + for field in selection.iter_mut() { + if let Some(IR::IO(IO::GraphQL { req_template, .. })) = field.ir.as_mut() { + if let Some(sel) = req_template.selection.as_mut() { + *sel = Mustache::parse(sel).render(variables); + } + } + } + Ok(OperationPlan { root_name: self.plan.root_name.to_string(), operation_type: self.plan.operation_type, From 8327582a1027b7439f0510779157c24872605045 Mon Sep 17 00:00:00 2001 From: laststylebender Date: Thu, 14 Nov 2024 17:12:55 +0530 Subject: [PATCH 17/35] - show query in info logger --- src/core/graphql/request_template.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/core/graphql/request_template.rs b/src/core/graphql/request_template.rs index d7bebd4b9b..6a60271d58 100644 --- a/src/core/graphql/request_template.rs +++ b/src/core/graphql/request_template.rs @@ -6,6 +6,7 @@ use std::hash::{Hash, Hasher}; use derive_setters::Setters; use http::header::{HeaderMap, HeaderValue}; use tailcall_hasher::TailcallHasher; +use tracing::info; use crate::core::config::{GraphQLOperationType, KeyValue}; use crate::core::has_headers::HasHeaders; @@ -128,7 +129,9 @@ impl RequestTemplate { } } - format!(r#"{{ "query": "{operation_type} {{ {operation} {selection_set} }}" }}"#) + let query = format!(r#"{{ "query": "{operation_type} {{ {operation} {selection_set} }}" }}"#); + info!("Query {} ", query); + query } pub fn new( From 91db9efaeec796c85e4af4a9d2ba74b33aa2462b Mon Sep 17 00:00:00 2001 From: laststylebender Date: Thu, 14 Nov 2024 17:13:32 +0530 Subject: [PATCH 18/35] - handle modify directive scenario --- src/core/jit/transform/graphql.rs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/core/jit/transform/graphql.rs b/src/core/jit/transform/graphql.rs index 5226e59214..dfbcdf7c81 100644 --- a/src/core/jit/transform/graphql.rs +++ b/src/core/jit/transform/graphql.rs @@ -28,6 +28,7 @@ impl Transform for GraphQL { fn transform(&self, mut plan: Self::Value) -> Valid { for field in plan.selection.iter_mut() { + // TODO: fix this. let rendered = field.render_graphql(); if let Some(IR::IO(IO::GraphQL { req_template, .. })) = field.ir.as_mut() { req_template.selection = rendered; @@ -47,10 +48,20 @@ impl Field { fn format_selection_set<'a, A: 'a + ToString + JsonLikeOwned>( selection_set: impl Iterator>, ) -> Option { - // TODO: skip fields that has resolver. let set = selection_set - .filter(|field| field.ir.is_none()) - .map(|field| format_selection_field(field, &field.name)) + .filter(|field| match &field.ir { + Some(IR::IO(_)) | Some(IR::Dynamic(_)) => false, + _ => true, + }) + .map(|field| { + // handle @modify directive scenario. + let field_name = if let Some(IR::ContextPath(data)) = &field.ir { + data.get(0).cloned().unwrap_or(field.name.to_string()) + } else { + field.name.to_string() + }; + format_selection_field(field, &field_name) + }) .collect::>(); if set.is_empty() { From 0b092e3f101e79d382e57ebc59612b07f820e4aa Mon Sep 17 00:00:00 2001 From: laststylebender Date: Thu, 14 Nov 2024 17:26:35 +0530 Subject: [PATCH 19/35] - lint changes --- src/core/graphql/request_template.rs | 5 ++-- src/core/jit/model.rs | 23 ++++++++------ src/core/jit/transform/graphql.rs | 38 +++++++++++------------- src/core/jit/transform/input_resolver.rs | 3 +- src/core/jit/transform/mod.rs | 4 +-- src/core/json/graphql.rs | 6 ++-- 6 files changed, 40 insertions(+), 39 deletions(-) diff --git a/src/core/graphql/request_template.rs b/src/core/graphql/request_template.rs index 6a60271d58..831016ad2b 100644 --- a/src/core/graphql/request_template.rs +++ b/src/core/graphql/request_template.rs @@ -90,7 +90,7 @@ impl RequestTemplate { let selection_set = self .selection .as_ref() - .map(|s| Cow::Borrowed(s)) + .map(Cow::Borrowed) .unwrap_or_else(|| { Cow::Owned(ctx.selection_set(&self.related_fields).unwrap_or_default()) }); @@ -129,7 +129,8 @@ impl RequestTemplate { } } - let query = format!(r#"{{ "query": "{operation_type} {{ {operation} {selection_set} }}" }}"#); + let query = + format!(r#"{{ "query": "{operation_type} {{ {operation} {selection_set} }}" }}"#); info!("Query {} ", query); query } diff --git a/src/core/jit/model.rs b/src/core/jit/model.rs index f5131ba1ae..5615c6c510 100644 --- a/src/core/jit/model.rs +++ b/src/core/jit/model.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; use std::collections::HashMap; -use std::fmt::{Debug, Formatter}; +use std::fmt::{Debug, Display, Formatter}; use std::num::NonZeroU64; use std::sync::Arc; @@ -104,14 +104,19 @@ pub struct Arg { pub default_value: Option, } -impl ToString for Arg { - fn to_string(&self) -> String { - if let Some(val) = self.value.as_ref() { - format!("{}: {}", self.name, val.to_string()) - } else { - let value = self.default_value.as_ref().map(|v| v.to_string()); - format!("{}: {}", self.name, value.unwrap_or_default()) - } +impl Display for Arg { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let v = self + .value + .as_ref() + .map(|v| format!("{}", v)) + .unwrap_or_else(|| { + self.default_value + .as_ref() + .map(|v| format!("{}", v)) + .unwrap_or_default() + }); + write!(f, "{}: {}", self.name, v) } } diff --git a/src/core/jit/transform/graphql.rs b/src/core/jit/transform/graphql.rs index dfbcdf7c81..24e12cad1c 100644 --- a/src/core/jit/transform/graphql.rs +++ b/src/core/jit/transform/graphql.rs @@ -1,17 +1,16 @@ -use std::{borrow::Cow, convert::Infallible, fmt::Debug, marker::PhantomData}; +use std::borrow::Cow; +use std::convert::Infallible; +use std::fmt::{Debug, Display}; +use std::marker::PhantomData; -use async_graphql::{ - parser::types::{DirectiveDefinition, InputValueDefinition, Type}, - Name, Pos, Positioned, -}; +use async_graphql::parser::types::{DirectiveDefinition, InputValueDefinition, Type}; +use async_graphql::{Name, Pos, Positioned}; use tailcall_valid::Valid; -use crate::core::{ - ir::model::{IO, IR}, - jit::{Directive, Field, OperationPlan}, - json::{JsonLike, JsonLikeOwned}, - Transform, -}; +use crate::core::ir::model::{IO, IR}; +use crate::core::jit::{Directive, Field, OperationPlan}; +use crate::core::json::{JsonLike, JsonLikeOwned}; +use crate::core::Transform; #[derive(Default)] pub struct GraphQL(PhantomData); @@ -22,7 +21,7 @@ impl GraphQL { } } -impl Transform for GraphQL { +impl Transform for GraphQL { type Value = OperationPlan; type Error = Infallible; @@ -39,24 +38,21 @@ impl Transform for GraphQL { } } -impl Field { +impl Field { pub fn render_graphql(&self) -> Option { format_selection_set(self.selection.iter()) } } -fn format_selection_set<'a, A: 'a + ToString + JsonLikeOwned>( +fn format_selection_set<'a, A: 'a + Display + JsonLikeOwned>( selection_set: impl Iterator>, ) -> Option { let set = selection_set - .filter(|field| match &field.ir { - Some(IR::IO(_)) | Some(IR::Dynamic(_)) => false, - _ => true, - }) + .filter(|field| !matches!(&field.ir, Some(IR::IO(_)) | Some(IR::Dynamic(_)))) .map(|field| { // handle @modify directive scenario. let field_name = if let Some(IR::ContextPath(data)) = &field.ir { - data.get(0).cloned().unwrap_or(field.name.to_string()) + data.first().cloned().unwrap_or(field.name.to_string()) } else { field.name.to_string() }; @@ -71,7 +67,7 @@ fn format_selection_set<'a, A: 'a + ToString + JsonLikeOwned>( Some(format!("{{ {} }}", set.join(" "))) } -fn format_selection_field(field: &Field, name: &str) -> String { +fn format_selection_field(field: &Field, name: &str) -> String { let arguments = format_selection_field_arguments(field); let selection_set = format_selection_set(field.selection.iter()); @@ -94,7 +90,7 @@ fn format_selection_field(field: &Field, name: & output } -fn format_selection_field_arguments(field: &Field) -> Cow<'static, str> { +fn format_selection_field_arguments(field: &Field) -> Cow<'static, str> { let arguments = field .args .iter() diff --git a/src/core/jit/transform/input_resolver.rs b/src/core/jit/transform/input_resolver.rs index 8d173675f2..56c9a32ee2 100644 --- a/src/core/jit/transform/input_resolver.rs +++ b/src/core/jit/transform/input_resolver.rs @@ -69,7 +69,8 @@ where .map(|field| Self::resolve_field(&index, field?)) .collect::, _>>()?; - // adjust the pre-computed values in selection set like graphql query for @graphql directive. + // adjust the pre-computed values in selection set like graphql query for + // @graphql directive. for field in selection.iter_mut() { if let Some(IR::IO(IO::GraphQL { req_template, .. })) = field.ir.as_mut() { if let Some(sel) = req_template.selection.as_mut() { diff --git a/src/core/jit/transform/mod.rs b/src/core/jit/transform/mod.rs index 5d27b587fe..1f8fb9c835 100644 --- a/src/core/jit/transform/mod.rs +++ b/src/core/jit/transform/mod.rs @@ -2,14 +2,14 @@ mod check_cache; mod check_const; mod check_dedupe; mod check_protected; +mod graphql; mod input_resolver; mod skip; -mod graphql; pub use check_cache::*; pub use check_const::*; pub use check_dedupe::*; pub use check_protected::*; +pub use graphql::*; pub use input_resolver::*; pub use skip::*; -pub use graphql::*; diff --git a/src/core/json/graphql.rs b/src/core/json/graphql.rs index 3dc85acdd6..aa7653b7bb 100644 --- a/src/core/json/graphql.rs +++ b/src/core/json/graphql.rs @@ -153,14 +153,12 @@ impl<'json> JsonLike<'json> for ConstValue { fn string(s: Cow<'json, str>) -> Self { ConstValue::String(s.to_string()) } - + fn to_string_value(&'json self) -> String { format!("{}", self) } } - - impl<'json> JsonLike<'json> for Value { type JsonObject = IndexMap; @@ -288,7 +286,7 @@ impl<'json> JsonLike<'json> for Value { fn string(s: Cow<'json, str>) -> Self { Value::String(s.to_string()) } - + fn to_string_value(&'json self) -> String { format!("{}", self) } From 3cd304cad6ab8384bf3570649b519ad0e283470b Mon Sep 17 00:00:00 2001 From: laststylebender Date: Thu, 14 Nov 2024 17:29:22 +0530 Subject: [PATCH 20/35] - clean up of transform --- src/core/jit/transform/graphql.rs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/core/jit/transform/graphql.rs b/src/core/jit/transform/graphql.rs index 24e12cad1c..61d6c4dcd1 100644 --- a/src/core/jit/transform/graphql.rs +++ b/src/core/jit/transform/graphql.rs @@ -27,10 +27,8 @@ impl Transform for GraphQL { fn transform(&self, mut plan: Self::Value) -> Valid { for field in plan.selection.iter_mut() { - // TODO: fix this. - let rendered = field.render_graphql(); if let Some(IR::IO(IO::GraphQL { req_template, .. })) = field.ir.as_mut() { - req_template.selection = rendered; + req_template.selection = format_selection_set(field.selection.iter()); } } @@ -38,12 +36,6 @@ impl Transform for GraphQL { } } -impl Field { - pub fn render_graphql(&self) -> Option { - format_selection_set(self.selection.iter()) - } -} - fn format_selection_set<'a, A: 'a + Display + JsonLikeOwned>( selection_set: impl Iterator>, ) -> Option { From eae316205010aed005d3f1f4e5e67a22971fc1c7 Mon Sep 17 00:00:00 2001 From: laststylebender Date: Thu, 14 Nov 2024 17:39:17 +0530 Subject: [PATCH 21/35] - minor clean up --- src/core/document.rs | 2 +- src/core/jit/transform/graphql.rs | 30 ++++++------------------------ 2 files changed, 7 insertions(+), 25 deletions(-) diff --git a/src/core/document.rs b/src/core/document.rs index 26f37c0ae5..8c972c03ce 100644 --- a/src/core/document.rs +++ b/src/core/document.rs @@ -310,7 +310,7 @@ fn print_input_value(field: &async_graphql::parser::types::InputValueDefinition) print_default_value(field.default_value.as_ref()) ) } -fn print_directive(directive: &DirectiveDefinition) -> String { +pub fn print_directive(directive: &DirectiveDefinition) -> String { let args = directive .arguments .iter() diff --git a/src/core/jit/transform/graphql.rs b/src/core/jit/transform/graphql.rs index 61d6c4dcd1..67d05c6600 100644 --- a/src/core/jit/transform/graphql.rs +++ b/src/core/jit/transform/graphql.rs @@ -7,6 +7,7 @@ use async_graphql::parser::types::{DirectiveDefinition, InputValueDefinition, Ty use async_graphql::{Name, Pos, Positioned}; use tailcall_valid::Valid; +use crate::core::document::print_directive; use crate::core::ir::model::{IO, IR}; use crate::core::jit::{Directive, Field, OperationPlan}; use crate::core::json::{JsonLike, JsonLikeOwned}; @@ -87,19 +88,15 @@ fn format_selection_field_arguments(field: &Field) -> Cow<'static .args .iter() .filter(|a| a.value.is_some()) - .collect::>(); - - if arguments.is_empty() { - return Cow::Borrowed(""); - } - - let args = arguments - .iter() .map(|arg| arg.to_string()) .collect::>() .join(","); - Cow::Owned(format!("({})", args.escape_default())) + if arguments.is_empty() { + Cow::Borrowed("") + } else { + Cow::Owned(format!("({})", arguments.escape_default())) + } } // TODO: refactor this. @@ -112,21 +109,6 @@ pub fn print_directives<'a, A: 'a + JsonLikeOwned>( .join(" ") } -fn print_directive(directive: &DirectiveDefinition) -> String { - let args = directive - .arguments - .iter() - .map(|arg| format!("{}: {}", arg.node.name.node, arg.node.ty.node)) - .collect::>() - .join(", "); - - if args.is_empty() { - format!("@{}", directive.name.node) - } else { - format!("@{}({})", directive.name.node, args) - } -} - fn pos(a: A) -> Positioned { Positioned::new(a, Pos::default()) } From c12de8bca3fc703c1bb8bb419f6ec5b025227571 Mon Sep 17 00:00:00 2001 From: laststylebender Date: Thu, 14 Nov 2024 17:42:20 +0530 Subject: [PATCH 22/35] - make pos inline --- src/core/jit/transform/graphql.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/jit/transform/graphql.rs b/src/core/jit/transform/graphql.rs index 67d05c6600..40c8552a7b 100644 --- a/src/core/jit/transform/graphql.rs +++ b/src/core/jit/transform/graphql.rs @@ -109,6 +109,7 @@ pub fn print_directives<'a, A: 'a + JsonLikeOwned>( .join(" ") } +#[inline] fn pos(a: A) -> Positioned { Positioned::new(a, Pos::default()) } @@ -139,7 +140,7 @@ fn const_directive_to_sdl( nullable: true, base: async_graphql::parser::types::BaseType::Named(Name::new(v_str)), }), - directives: Vec::new(), + directives: Vec::default(), })) } else { None From 8bcd4df54bfbb21379e4465b0510a253c2419745 Mon Sep 17 00:00:00 2001 From: laststylebender Date: Thu, 14 Nov 2024 18:15:01 +0530 Subject: [PATCH 23/35] - clean up the selection API. --- src/core/graphql/request_template.rs | 45 +++++++++++++++++++----- src/core/jit/transform/graphql.rs | 6 ++-- src/core/jit/transform/input_resolver.rs | 6 ++-- 3 files changed, 43 insertions(+), 14 deletions(-) diff --git a/src/core/graphql/request_template.rs b/src/core/graphql/request_template.rs index 831016ad2b..d1cc42bb1e 100644 --- a/src/core/graphql/request_template.rs +++ b/src/core/graphql/request_template.rs @@ -15,7 +15,35 @@ use crate::core::http::Method::POST; use crate::core::ir::model::{CacheKey, IoId}; use crate::core::ir::{GraphQLOperationContext, RelatedFields}; use crate::core::mustache::Mustache; -use crate::core::path::PathGraphql; +use crate::core::path::{PathGraphql, PathString}; + +/// Represents a GraphQL selection that can either be resolved or unresolved. +#[derive(Debug, Clone)] +pub enum Selection { + /// A selection with a resolved string value. + Resolved(String), + /// A selection that contains a Mustache template to be resolved later. + UnResolved(Mustache), +} + +impl Selection { + /// Resolves the `Unresolved` variant using the provided `PathString`. + pub fn resolve(self, p: &impl PathString) -> Selection { + match self { + Selection::UnResolved(template) => Selection::Resolved(template.render(p)), + resolved @ _ => resolved, + } + } +} + +impl From for Selection { + fn from(value: Mustache) -> Self { + match value.is_const() { + true => Selection::Resolved(value.to_string()), + false => Selection::UnResolved(value), + } + } +} /// RequestTemplate for GraphQL requests (See RequestTemplate documentation) #[derive(Setters, Debug, Clone)] @@ -27,7 +55,7 @@ pub struct RequestTemplate { pub operation_arguments: Option>, pub headers: MustacheHeaders, pub related_fields: RelatedFields, - pub selection: Option, + pub selection: Option, } impl RequestTemplate { @@ -87,13 +115,12 @@ impl RequestTemplate { ctx: &C, ) -> String { let operation_type = &self.operation_type; - let selection_set = self - .selection - .as_ref() - .map(Cow::Borrowed) - .unwrap_or_else(|| { - Cow::Owned(ctx.selection_set(&self.related_fields).unwrap_or_default()) - }); + + let selection_set = match &self.selection { + Some(Selection::Resolved(s)) => Cow::Borrowed(s), + Some(Selection::UnResolved(u)) => Cow::Owned(u.to_string()), + None => Cow::Owned(ctx.selection_set(&self.related_fields).unwrap_or_default()), + }; let mut operation = Cow::Borrowed(&self.operation_name); diff --git a/src/core/jit/transform/graphql.rs b/src/core/jit/transform/graphql.rs index 40c8552a7b..e3a657bc76 100644 --- a/src/core/jit/transform/graphql.rs +++ b/src/core/jit/transform/graphql.rs @@ -11,7 +11,7 @@ use crate::core::document::print_directive; use crate::core::ir::model::{IO, IR}; use crate::core::jit::{Directive, Field, OperationPlan}; use crate::core::json::{JsonLike, JsonLikeOwned}; -use crate::core::Transform; +use crate::core::{Mustache, Transform}; #[derive(Default)] pub struct GraphQL(PhantomData); @@ -29,7 +29,9 @@ impl Transform for GraphQL { fn transform(&self, mut plan: Self::Value) -> Valid { for field in plan.selection.iter_mut() { if let Some(IR::IO(IO::GraphQL { req_template, .. })) = field.ir.as_mut() { - req_template.selection = format_selection_set(field.selection.iter()); + if let Some(v) = format_selection_set(field.selection.iter()) { + req_template.selection = Some(Mustache::parse(&v).into()); + } } } diff --git a/src/core/jit/transform/input_resolver.rs b/src/core/jit/transform/input_resolver.rs index 56c9a32ee2..302a9f4575 100644 --- a/src/core/jit/transform/input_resolver.rs +++ b/src/core/jit/transform/input_resolver.rs @@ -4,7 +4,7 @@ use super::super::{Arg, Field, OperationPlan, ResolveInputError, Variables}; use crate::core::blueprint::Index; use crate::core::ir::model::{IO, IR}; use crate::core::json::{JsonLikeOwned, JsonObjectLike}; -use crate::core::{Mustache, Type}; +use crate::core::Type; /// Trait to represent conversion from some dynamic type (with variables) /// to the resolved variant based on the additional provided info. @@ -73,8 +73,8 @@ where // @graphql directive. for field in selection.iter_mut() { if let Some(IR::IO(IO::GraphQL { req_template, .. })) = field.ir.as_mut() { - if let Some(sel) = req_template.selection.as_mut() { - *sel = Mustache::parse(sel).render(variables); + if let Some(selection) = req_template.selection.take() { + req_template.selection = Some(selection.resolve(variables)); } } } From 57ee895d9466050d7f02d33274f6e37ba03d101e Mon Sep 17 00:00:00 2001 From: laststylebender Date: Thu, 14 Nov 2024 18:19:45 +0530 Subject: [PATCH 24/35] - rename func --- src/core/jit/transform/graphql.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/jit/transform/graphql.rs b/src/core/jit/transform/graphql.rs index e3a657bc76..bbab30675f 100644 --- a/src/core/jit/transform/graphql.rs +++ b/src/core/jit/transform/graphql.rs @@ -106,7 +106,7 @@ pub fn print_directives<'a, A: 'a + JsonLikeOwned>( directives: impl Iterator>, ) -> String { directives - .map(|d| print_directive(&const_directive_to_sdl(d))) + .map(|d| print_directive(&directive_to_sdl(d))) .collect::>() .join(" ") } @@ -116,7 +116,7 @@ fn pos(a: A) -> Positioned { Positioned::new(a, Pos::default()) } -fn const_directive_to_sdl( +fn directive_to_sdl( directive: &Directive, ) -> DirectiveDefinition { let to_mustache = |s: &str| -> String { From afd8ce4cd813423fcffe4903b308b062df2cfd7a Mon Sep 17 00:00:00 2001 From: laststylebender Date: Thu, 14 Nov 2024 18:21:27 +0530 Subject: [PATCH 25/35] - lint changes --- src/core/graphql/request_template.rs | 2 +- src/core/jit/transform/graphql.rs | 4 +--- tests/execution/graphql-nested.md | 4 ++-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/core/graphql/request_template.rs b/src/core/graphql/request_template.rs index d1cc42bb1e..9e849d3033 100644 --- a/src/core/graphql/request_template.rs +++ b/src/core/graphql/request_template.rs @@ -31,7 +31,7 @@ impl Selection { pub fn resolve(self, p: &impl PathString) -> Selection { match self { Selection::UnResolved(template) => Selection::Resolved(template.render(p)), - resolved @ _ => resolved, + resolved => resolved, } } } diff --git a/src/core/jit/transform/graphql.rs b/src/core/jit/transform/graphql.rs index bbab30675f..668e12beb0 100644 --- a/src/core/jit/transform/graphql.rs +++ b/src/core/jit/transform/graphql.rs @@ -116,9 +116,7 @@ fn pos(a: A) -> Positioned { Positioned::new(a, Pos::default()) } -fn directive_to_sdl( - directive: &Directive, -) -> DirectiveDefinition { +fn directive_to_sdl(directive: &Directive) -> DirectiveDefinition { let to_mustache = |s: &str| -> String { s.strip_prefix('$') .map(|v| format!("{{{{{}}}}}", v)) diff --git a/tests/execution/graphql-nested.md b/tests/execution/graphql-nested.md index cdb41835bd..bb9a277611 100644 --- a/tests/execution/graphql-nested.md +++ b/tests/execution/graphql-nested.md @@ -33,7 +33,7 @@ type NodeC { - request: method: POST url: http://upstream/graphql - textBody: { "query": "query { nodeA { name nodeB { name } nodeC { name } } }" } + textBody: {"query": "query { nodeA { name nodeB { name } nodeC { name } } }"} expectedHits: 1 response: status: 200 @@ -68,4 +68,4 @@ type NodeC { } } } -``` \ No newline at end of file +``` From d70ddf711a1f0c65c07d74391cd2b7029211477e Mon Sep 17 00:00:00 2001 From: laststylebender Date: Thu, 14 Nov 2024 18:54:01 +0530 Subject: [PATCH 26/35] - refactor: remove duplicate code. --- src/core/document.rs | 121 +++++++++++++++++++----------- src/core/jit/transform/graphql.rs | 59 +-------------- 2 files changed, 81 insertions(+), 99 deletions(-) diff --git a/src/core/document.rs b/src/core/document.rs index 8c972c03ce..f62e55730c 100644 --- a/src/core/document.rs +++ b/src/core/document.rs @@ -1,10 +1,11 @@ +use std::borrow::Cow; + use async_graphql::parser::types::*; -use async_graphql::{Pos, Positioned}; -use async_graphql_value::{ConstValue, Name}; +use async_graphql::Positioned; +use async_graphql_value::ConstValue; -fn pos(a: A) -> Positioned { - Positioned::new(a, Pos::default()) -} +use super::jit::Directive as JitDirective; +use super::json::JsonLikeOwned; struct LineBreaker<'a> { string: &'a str, @@ -61,9 +62,12 @@ fn get_formatted_docs(docs: Option, indent: usize) -> String { formatted_docs } -pub fn print_directives<'a>(directives: impl Iterator) -> String { +pub fn print_directives<'a, T>(directives: impl Iterator) -> String +where + &'a T: Into> + 'a, +{ directives - .map(|d| print_directive(&const_directive_to_sdl(d))) + .map(|d| print_directive(d)) .collect::>() .join(" ") } @@ -102,37 +106,6 @@ fn print_schema(schema: &SchemaDefinition) -> String { ) } -fn const_directive_to_sdl(directive: &ConstDirective) -> DirectiveDefinition { - DirectiveDefinition { - description: None, - name: pos(Name::new(directive.name.node.as_str())), - arguments: directive - .arguments - .iter() - .filter_map(|(k, v)| { - if v.node != ConstValue::Null { - Some(pos(InputValueDefinition { - description: None, - name: pos(Name::new(k.node.clone())), - ty: pos(Type { - nullable: true, - base: async_graphql::parser::types::BaseType::Named(Name::new( - v.to_string(), - )), - }), - default_value: Some(pos(ConstValue::String(v.to_string()))), - directives: Vec::new(), - })) - } else { - None - } - }) - .collect(), - is_repeatable: true, - locations: vec![], - } -} - fn print_type_def(type_def: &TypeDefinition) -> String { match &type_def.kind { TypeKind::Scalar => { @@ -310,18 +283,23 @@ fn print_input_value(field: &async_graphql::parser::types::InputValueDefinition) print_default_value(field.default_value.as_ref()) ) } -pub fn print_directive(directive: &DirectiveDefinition) -> String { + +pub fn print_directive<'a, T>(directive: &'a T) -> String +where + &'a T: Into>, +{ + let directive: Directive<'a> = directive.into(); let args = directive - .arguments + .args .iter() - .map(|arg| format!("{}: {}", arg.node.name.node, arg.node.ty.node)) + .map(|arg| format!("{}: {}", arg.name, arg.value)) .collect::>() .join(", "); if args.is_empty() { - format!("@{}", directive.name.node) + format!("@{}", directive.name) } else { - format!("@{}({})", directive.name.node, args) + format!("@{}({})", directive.name, args) } } @@ -410,3 +388,60 @@ pub fn print(sd: ServiceDocument) -> String { sdl_string.trim_end_matches('\n').to_string() } + +pub struct Directive<'a> { + pub name: Cow<'a, str>, + pub args: Vec>, +} + +pub struct Arg<'a> { + pub name: Cow<'a, str>, + pub value: Cow<'a, str>, +} + +impl<'a> From<&'a ConstDirective> for Directive<'a> { + fn from(value: &'a ConstDirective) -> Self { + Self { + name: Cow::Borrowed(value.name.node.as_str()), + args: value + .arguments + .iter() + .filter_map(|(k, v)| { + if v.node != async_graphql_value::ConstValue::Null { + Some(Arg { + name: Cow::Borrowed(k.node.as_str()), + value: Cow::Owned(v.to_string()), + }) + } else { + None + } + }) + .collect(), + } + } +} + +impl<'a, Input: JsonLikeOwned> From<&'a JitDirective> for Directive<'a> { + fn from(value: &'a JitDirective) -> Self { + let to_mustache = |s: &str| -> String { + s.strip_prefix('$') + .map(|v| format!("{{{{{}}}}}", v)) + .unwrap_or_else(|| s.to_string()) + }; + Self { + name: Cow::Borrowed(value.name.as_str()), + args: value + .arguments + .iter() + .filter_map(|(k, v)| { + if !v.is_null() { + let v_str = to_mustache(&v.to_string_value()); + Some(Arg { name: Cow::Borrowed(k), value: Cow::Owned(v_str) }) + } else { + None + } + }) + .collect(), + } + } +} diff --git a/src/core/jit/transform/graphql.rs b/src/core/jit/transform/graphql.rs index 668e12beb0..f3d5f15677 100644 --- a/src/core/jit/transform/graphql.rs +++ b/src/core/jit/transform/graphql.rs @@ -3,14 +3,12 @@ use std::convert::Infallible; use std::fmt::{Debug, Display}; use std::marker::PhantomData; -use async_graphql::parser::types::{DirectiveDefinition, InputValueDefinition, Type}; -use async_graphql::{Name, Pos, Positioned}; use tailcall_valid::Valid; -use crate::core::document::print_directive; +use crate::core::document::print_directives; use crate::core::ir::model::{IO, IR}; -use crate::core::jit::{Directive, Field, OperationPlan}; -use crate::core::json::{JsonLike, JsonLikeOwned}; +use crate::core::jit::{Field, OperationPlan}; +use crate::core::json::JsonLikeOwned; use crate::core::{Mustache, Transform}; #[derive(Default)] @@ -100,54 +98,3 @@ fn format_selection_field_arguments(field: &Field) -> Cow<'static Cow::Owned(format!("({})", arguments.escape_default())) } } - -// TODO: refactor this. -pub fn print_directives<'a, A: 'a + JsonLikeOwned>( - directives: impl Iterator>, -) -> String { - directives - .map(|d| print_directive(&directive_to_sdl(d))) - .collect::>() - .join(" ") -} - -#[inline] -fn pos(a: A) -> Positioned { - Positioned::new(a, Pos::default()) -} - -fn directive_to_sdl(directive: &Directive) -> DirectiveDefinition { - let to_mustache = |s: &str| -> String { - s.strip_prefix('$') - .map(|v| format!("{{{{{}}}}}", v)) - .unwrap_or_else(|| s.to_string()) - }; - - DirectiveDefinition { - description: None, - name: pos(Name::new(directive.name.as_str())), - arguments: directive - .arguments - .iter() - .filter_map(|(k, v)| { - if !v.is_null() { - let v_str = to_mustache(&v.to_string_value()); - Some(pos(InputValueDefinition { - description: None, - name: pos(Name::new(k)), - default_value: Some(pos(JsonLike::string(Cow::Borrowed(&v_str)))), - ty: pos(Type { - nullable: true, - base: async_graphql::parser::types::BaseType::Named(Name::new(v_str)), - }), - directives: Vec::default(), - })) - } else { - None - } - }) - .collect(), - is_repeatable: true, - locations: vec![], - } -} From ab5ef488a57d25b8999798860d83a449ac12df0d Mon Sep 17 00:00:00 2001 From: laststylebender Date: Thu, 14 Nov 2024 18:59:03 +0530 Subject: [PATCH 27/35] - revert: change in eval context. --- src/core/ir/eval_context.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/core/ir/eval_context.rs b/src/core/ir/eval_context.rs index 5675494893..89c03d80c6 100644 --- a/src/core/ir/eval_context.rs +++ b/src/core/ir/eval_context.rs @@ -117,20 +117,22 @@ impl GraphQLOperationContext for EvalContext<'_, Ctx> .map(|directives| print_directives(directives.iter())) } - fn selection_set(&self, _related_fields: &RelatedFields) -> Option { - // ignore selection set and directly use selection set. + fn selection_set(&self, related_fields: &RelatedFields) -> Option { let selection_field = self.graphql_ctx.field()?; - format_selection_set(selection_field.selection_set()) + format_selection_set(selection_field.selection_set(), related_fields) } } fn format_selection_set<'a>( selection_set: impl Iterator, + related_fields: &RelatedFields, ) -> Option { let set = selection_set - .map(|field| { + .filter_map(|field| { // add to set only related fields that should be resolved with current resolver - format_selection_field(field, field.name()) + related_fields.get(field.name()).map(|related_fields| { + format_selection_field(field, &related_fields.0, &related_fields.1) + }) }) .collect::>(); @@ -141,9 +143,13 @@ fn format_selection_set<'a>( Some(format!("{{ {} }}", set.join(" "))) } -fn format_selection_field(field: &SelectionField, name: &str) -> String { +fn format_selection_field( + field: &SelectionField, + name: &str, + related_fields: &RelatedFields, +) -> String { let arguments = format_selection_field_arguments(field); - let selection_set = format_selection_set(field.selection_set()); + let selection_set = format_selection_set(field.selection_set(), related_fields); let mut output = format!("{}{}", name, arguments); From 600a7695b19b59c3e8a220cf93df3f112a4e7914 Mon Sep 17 00:00:00 2001 From: laststylebender Date: Thu, 14 Nov 2024 19:04:06 +0530 Subject: [PATCH 28/35] - use to_string method instead --- src/core/jit/model.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/jit/model.rs b/src/core/jit/model.rs index 5615c6c510..697281d0d9 100644 --- a/src/core/jit/model.rs +++ b/src/core/jit/model.rs @@ -109,11 +109,11 @@ impl Display for Arg { let v = self .value .as_ref() - .map(|v| format!("{}", v)) + .map(|v| v.to_string()) .unwrap_or_else(|| { self.default_value .as_ref() - .map(|v| format!("{}", v)) + .map(|v| v.to_string()) .unwrap_or_default() }); write!(f, "{}: {}", self.name, v) From a24351cec0a5f8c2f8d32aaad1bf4bc8fabe7428 Mon Sep 17 00:00:00 2001 From: laststylebender Date: Thu, 14 Nov 2024 19:24:05 +0530 Subject: [PATCH 29/35] - test case fix: it's okay to send the directive to upstream. --- tests/execution/graphql-datasource-query-directives.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/execution/graphql-datasource-query-directives.md b/tests/execution/graphql-datasource-query-directives.md index f56b4b6a16..e1580c70c3 100644 --- a/tests/execution/graphql-datasource-query-directives.md +++ b/tests/execution/graphql-datasource-query-directives.md @@ -21,7 +21,7 @@ type Query { - request: method: POST url: http://upstream/graphql - textBody: '{ "query": "query { user @cascade(fields: [\\\"id\\\"]) { id @options(paging: false) } }" }' + textBody: '{ "query": "query { user @cascade(fields: [\\\"id\\\"]) { id @options(paging: false) name } }" }' response: status: 200 body: From d58e496b0872805a74b42ca1c6df0a511ff3eba9 Mon Sep 17 00:00:00 2001 From: laststylebender Date: Tue, 19 Nov 2024 11:25:27 +0530 Subject: [PATCH 30/35] - add helper method modify_io, which allows user to modify io nodes. --- src/core/ir/model.rs | 22 ++++++++++ src/core/jit/transform/graphql.rs | 25 ++++++++---- src/core/jit/transform/input_resolver.rs | 52 ++++++++++++++++++++---- 3 files changed, 84 insertions(+), 15 deletions(-) diff --git a/src/core/ir/model.rs b/src/core/ir/model.rs index ecab192987..8d8a2f012f 100644 --- a/src/core/ir/model.rs +++ b/src/core/ir/model.rs @@ -128,6 +128,28 @@ impl Cache { } impl IR { + // allows to modify the IO node in the IR tree + pub fn modify_io(&mut self, io_modifier: &mut dyn FnMut(&mut IO) -> ()) { + match self { + IR::IO(io) => io_modifier(io), + IR::Cache(cache) => io_modifier(&mut cache.io), + IR::Discriminate(_, ir) | IR::Protect(ir) | IR::Path(ir, _) => { + ir.modify_io(io_modifier) + } + IR::Pipe(ir1, ir2) => { + ir1.modify_io(io_modifier); + ir2.modify_io(io_modifier); + } + IR::Entity(hash_map) => { + for ir in hash_map.values_mut() { + ir.modify_io(io_modifier); + } + } + IR::Map(map) => map.input.modify_io(io_modifier), + _ => {} + } + } + pub fn pipe(self, next: Self) -> Self { IR::Pipe(Box::new(self), Box::new(next)) } diff --git a/src/core/jit/transform/graphql.rs b/src/core/jit/transform/graphql.rs index f3d5f15677..d700ffdf5a 100644 --- a/src/core/jit/transform/graphql.rs +++ b/src/core/jit/transform/graphql.rs @@ -20,18 +20,27 @@ impl GraphQL { } } -impl Transform for GraphQL { +fn compute_selection_set(base_field: &mut Vec>) { + for field in base_field.iter_mut() { + if let Some(ir) = field.ir.as_mut() { + ir.modify_io(&mut |io| { + if let IO::GraphQL { req_template, .. } = io { + if let Some(v) = format_selection_set(field.selection.iter()) { + req_template.selection = Some(Mustache::parse(&v).into()); + } + } + }); + } + compute_selection_set(field.selection.as_mut()); + } +} + +impl Transform for GraphQL { type Value = OperationPlan; type Error = Infallible; fn transform(&self, mut plan: Self::Value) -> Valid { - for field in plan.selection.iter_mut() { - if let Some(IR::IO(IO::GraphQL { req_template, .. })) = field.ir.as_mut() { - if let Some(v) = format_selection_set(field.selection.iter()) { - req_template.selection = Some(Mustache::parse(&v).into()); - } - } - } + compute_selection_set(&mut plan.selection); Valid::succeed(plan) } diff --git a/src/core/jit/transform/input_resolver.rs b/src/core/jit/transform/input_resolver.rs index 302a9f4575..bdb465510f 100644 --- a/src/core/jit/transform/input_resolver.rs +++ b/src/core/jit/transform/input_resolver.rs @@ -71,13 +71,7 @@ where // adjust the pre-computed values in selection set like graphql query for // @graphql directive. - for field in selection.iter_mut() { - if let Some(IR::IO(IO::GraphQL { req_template, .. })) = field.ir.as_mut() { - if let Some(selection) = req_template.selection.take() { - req_template.selection = Some(selection.resolve(variables)); - } - } - } + Self::resolve_graphql_selection_set(&mut selection, variables); Ok(OperationPlan { root_name: self.plan.root_name.to_string(), @@ -92,6 +86,50 @@ where }) } + fn resolve_nested_irs(field: &Field, ir: &mut IR, variables: &Variables) { + match ir { + IR::IO(IO::GraphQL { req_template, .. }) => { + if let Some(selection) = req_template.selection.take() { + req_template.selection = Some(selection.resolve(variables)); + } + } + IR::Cache(cache) => { + let io: &mut IO = &mut cache.io; + if let IO::GraphQL { req_template, .. } = io { + if let Some(selection) = req_template.selection.take() { + req_template.selection = Some(selection.resolve(variables)); + } + } + } + IR::Discriminate(_, ir) | IR::Protect(ir) | IR::Path(ir, _) => { + Self::resolve_nested_irs(field, ir, variables); + } + IR::Pipe(ir1, ir2) => { + Self::resolve_nested_irs(field, ir1, variables); + Self::resolve_nested_irs(field, ir2, variables); + } + IR::Entity(hash_map) => { + for ir in hash_map.values_mut() { + Self::resolve_nested_irs(field, ir, variables); + } + } + IR::Map(map) => { + Self::resolve_nested_irs(field, &mut map.input, variables); + } + _ => {} + } + } + + fn resolve_graphql_selection_set(base_field: &mut Vec>, variables: &Variables) { + for field in base_field.iter_mut() { + if let Some(mut ir) = field.ir.clone() { + Self::resolve_nested_irs(field, &mut ir, variables); + field.ir = Some(ir); + } + Self::resolve_graphql_selection_set(field.selection.as_mut(), variables); + } + } + fn resolve_field( index: &Index, field: Field, From e6b7ee4103b5784ef5f1490810cf628061657759 Mon Sep 17 00:00:00 2001 From: laststylebender Date: Tue, 19 Nov 2024 11:34:50 +0530 Subject: [PATCH 31/35] - resolve the variables in selection set of graphql query with modify_io --- src/core/ir/model.rs | 2 +- src/core/jit/transform/input_resolver.rs | 51 ++++++------------------ 2 files changed, 14 insertions(+), 39 deletions(-) diff --git a/src/core/ir/model.rs b/src/core/ir/model.rs index 8d8a2f012f..65ef9ca8d4 100644 --- a/src/core/ir/model.rs +++ b/src/core/ir/model.rs @@ -129,7 +129,7 @@ impl Cache { impl IR { // allows to modify the IO node in the IR tree - pub fn modify_io(&mut self, io_modifier: &mut dyn FnMut(&mut IO) -> ()) { + pub fn modify_io(&mut self, io_modifier: &mut dyn FnMut(&mut IO)) { match self { IR::IO(io) => io_modifier(io), IR::Cache(cache) => io_modifier(&mut cache.io), diff --git a/src/core/jit/transform/input_resolver.rs b/src/core/jit/transform/input_resolver.rs index bdb465510f..40c430e59d 100644 --- a/src/core/jit/transform/input_resolver.rs +++ b/src/core/jit/transform/input_resolver.rs @@ -86,45 +86,20 @@ where }) } - fn resolve_nested_irs(field: &Field, ir: &mut IR, variables: &Variables) { - match ir { - IR::IO(IO::GraphQL { req_template, .. }) => { - if let Some(selection) = req_template.selection.take() { - req_template.selection = Some(selection.resolve(variables)); - } - } - IR::Cache(cache) => { - let io: &mut IO = &mut cache.io; - if let IO::GraphQL { req_template, .. } = io { - if let Some(selection) = req_template.selection.take() { - req_template.selection = Some(selection.resolve(variables)); - } - } - } - IR::Discriminate(_, ir) | IR::Protect(ir) | IR::Path(ir, _) => { - Self::resolve_nested_irs(field, ir, variables); - } - IR::Pipe(ir1, ir2) => { - Self::resolve_nested_irs(field, ir1, variables); - Self::resolve_nested_irs(field, ir2, variables); - } - IR::Entity(hash_map) => { - for ir in hash_map.values_mut() { - Self::resolve_nested_irs(field, ir, variables); - } - } - IR::Map(map) => { - Self::resolve_nested_irs(field, &mut map.input, variables); - } - _ => {} - } - } - - fn resolve_graphql_selection_set(base_field: &mut Vec>, variables: &Variables) { + // resolves the variables in selection set mustache template for graphql query. + fn resolve_graphql_selection_set( + base_field: &mut Vec>, + variables: &Variables, + ) { for field in base_field.iter_mut() { - if let Some(mut ir) = field.ir.clone() { - Self::resolve_nested_irs(field, &mut ir, variables); - field.ir = Some(ir); + if let Some(ir) = field.ir.as_mut() { + ir.modify_io(&mut |io| { + if let IO::GraphQL { req_template, .. } = io { + if let Some(selection) = req_template.selection.take() { + req_template.selection = Some(selection.resolve(variables)); + } + } + }); } Self::resolve_graphql_selection_set(field.selection.as_mut(), variables); } From f2ed3fb05c8cd1fa218c3a44d1b88a0a40d2c57d Mon Sep 17 00:00:00 2001 From: laststylebender Date: Tue, 19 Nov 2024 11:35:28 +0530 Subject: [PATCH 32/35] - remove unused import --- src/core/jit/transform/input_resolver.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/jit/transform/input_resolver.rs b/src/core/jit/transform/input_resolver.rs index 40c430e59d..ab19f8d4da 100644 --- a/src/core/jit/transform/input_resolver.rs +++ b/src/core/jit/transform/input_resolver.rs @@ -2,7 +2,7 @@ use async_graphql_value::{ConstValue, Value}; use super::super::{Arg, Field, OperationPlan, ResolveInputError, Variables}; use crate::core::blueprint::Index; -use crate::core::ir::model::{IO, IR}; +use crate::core::ir::model::IO; use crate::core::json::{JsonLikeOwned, JsonObjectLike}; use crate::core::Type; From e3c428a3925025a2e9352872d7e8b6eb4709a144 Mon Sep 17 00:00:00 2001 From: laststylebender Date: Tue, 19 Nov 2024 11:40:21 +0530 Subject: [PATCH 33/35] - lint changes --- src/core/jit/transform/graphql.rs | 2 +- src/core/jit/transform/input_resolver.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/jit/transform/graphql.rs b/src/core/jit/transform/graphql.rs index d700ffdf5a..79139c9891 100644 --- a/src/core/jit/transform/graphql.rs +++ b/src/core/jit/transform/graphql.rs @@ -20,7 +20,7 @@ impl GraphQL { } } -fn compute_selection_set(base_field: &mut Vec>) { +fn compute_selection_set(base_field: &mut [Field]) { for field in base_field.iter_mut() { if let Some(ir) = field.ir.as_mut() { ir.modify_io(&mut |io| { diff --git a/src/core/jit/transform/input_resolver.rs b/src/core/jit/transform/input_resolver.rs index ab19f8d4da..6b5c4f89ca 100644 --- a/src/core/jit/transform/input_resolver.rs +++ b/src/core/jit/transform/input_resolver.rs @@ -88,7 +88,7 @@ where // resolves the variables in selection set mustache template for graphql query. fn resolve_graphql_selection_set( - base_field: &mut Vec>, + base_field: &mut [Field], variables: &Variables, ) { for field in base_field.iter_mut() { From f46f4d1db8643f6a0a27b41660e01d3a056e76c1 Mon Sep 17 00:00:00 2001 From: laststylebender Date: Wed, 20 Nov 2024 14:22:51 +0530 Subject: [PATCH 34/35] - drop to_string_value method and use display instead. --- src/core/document.rs | 5 +++-- src/core/jit/model.rs | 4 ++-- src/core/jit/transform/input_resolver.rs | 4 +++- src/core/json/borrow.rs | 4 ---- src/core/json/graphql.rs | 8 -------- src/core/json/json_like.rs | 1 - src/core/json/serde.rs | 4 ---- 7 files changed, 8 insertions(+), 22 deletions(-) diff --git a/src/core/document.rs b/src/core/document.rs index f62e55730c..df70160b38 100644 --- a/src/core/document.rs +++ b/src/core/document.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::fmt::Display; use async_graphql::parser::types::*; use async_graphql::Positioned; @@ -421,7 +422,7 @@ impl<'a> From<&'a ConstDirective> for Directive<'a> { } } -impl<'a, Input: JsonLikeOwned> From<&'a JitDirective> for Directive<'a> { +impl<'a, Input: JsonLikeOwned + Display> From<&'a JitDirective> for Directive<'a> { fn from(value: &'a JitDirective) -> Self { let to_mustache = |s: &str| -> String { s.strip_prefix('$') @@ -435,7 +436,7 @@ impl<'a, Input: JsonLikeOwned> From<&'a JitDirective> for Directive<'a> { .iter() .filter_map(|(k, v)| { if !v.is_null() { - let v_str = to_mustache(&v.to_string_value()); + let v_str = to_mustache(&v.to_string()); Some(Arg { name: Cow::Borrowed(k), value: Cow::Owned(v_str) }) } else { None diff --git a/src/core/jit/model.rs b/src/core/jit/model.rs index c2d2b12d1f..9b2950a22a 100644 --- a/src/core/jit/model.rs +++ b/src/core/jit/model.rs @@ -20,10 +20,10 @@ use crate::core::scalar::Scalar; #[derive(Debug, Deserialize, Clone)] pub struct Variables(HashMap); -impl PathString for Variables { +impl PathString for Variables { fn path_string<'a, T: AsRef>(&'a self, path: &'a [T]) -> Option> { self.get(path[0].as_ref()) - .map(|v| Cow::Owned(v.to_string_value())) + .map(|v| Cow::Owned(v.to_string())) } } diff --git a/src/core/jit/transform/input_resolver.rs b/src/core/jit/transform/input_resolver.rs index 199f1a2d74..86b5fe59ba 100644 --- a/src/core/jit/transform/input_resolver.rs +++ b/src/core/jit/transform/input_resolver.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + use async_graphql_value::{ConstValue, Value}; use super::super::{Arg, Field, OperationPlan, ResolveInputError, Variables}; @@ -47,7 +49,7 @@ impl InputResolver { impl InputResolver where Input: Clone + std::fmt::Debug, - Output: Clone + JsonLikeOwned + TryFrom + std::fmt::Debug, + Output: Clone + JsonLikeOwned + TryFrom + std::fmt::Debug + Display, Input: InputResolvable, >::Error: std::fmt::Debug, { diff --git a/src/core/json/borrow.rs b/src/core/json/borrow.rs index f343212ee0..ca93926b4f 100644 --- a/src/core/json/borrow.rs +++ b/src/core/json/borrow.rs @@ -176,8 +176,4 @@ impl<'ctx> JsonLike<'ctx> for Value<'ctx> { let src = gather_path_matches(self, path, vec![]); group_by_key(src) } - - fn to_string_value(&'ctx self) -> String { - format!("{}", self) - } } diff --git a/src/core/json/graphql.rs b/src/core/json/graphql.rs index 3f489e4dd4..860154a1c1 100644 --- a/src/core/json/graphql.rs +++ b/src/core/json/graphql.rs @@ -187,10 +187,6 @@ impl<'json> JsonLike<'json> for ConstValue { fn string(s: Cow<'json, str>) -> Self { ConstValue::String(s.to_string()) } - - fn to_string_value(&'json self) -> String { - format!("{}", self) - } } impl<'json> JsonLike<'json> for Value { @@ -320,8 +316,4 @@ impl<'json> JsonLike<'json> for Value { fn string(s: Cow<'json, str>) -> Self { Value::String(s.to_string()) } - - fn to_string_value(&'json self) -> String { - format!("{}", self) - } } diff --git a/src/core/json/json_like.rs b/src/core/json/json_like.rs index e286c48974..3a346b576f 100644 --- a/src/core/json/json_like.rs +++ b/src/core/json/json_like.rs @@ -61,7 +61,6 @@ pub trait JsonLike<'json>: Sized { fn get_path>(&'json self, path: &[T]) -> Option<&'json Self>; fn get_key(&'json self, path: &str) -> Option<&'json Self>; fn group_by(&'json self, path: &[String]) -> HashMap>; - fn to_string_value(&'json self) -> String; } /// A trait for objects that can be used as JSON objects diff --git a/src/core/json/serde.rs b/src/core/json/serde.rs index 0e922f89e2..62beed0f45 100644 --- a/src/core/json/serde.rs +++ b/src/core/json/serde.rs @@ -159,8 +159,4 @@ impl<'json> JsonLike<'json> for Value { fn string(s: Cow<'json, str>) -> Self { Value::String(s.to_string()) } - - fn to_string_value(&'json self) -> String { - format!("{}", self) - } } From 00bb2c3289cc49d658ca2592cfd6294de783fe34 Mon Sep 17 00:00:00 2001 From: laststylebender Date: Mon, 25 Nov 2024 16:36:50 +0530 Subject: [PATCH 35/35] - fix merge conflicts --- src/core/ir/model.rs | 2 +- src/core/json/graphql.rs | 23 ++++++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/core/ir/model.rs b/src/core/ir/model.rs index e9576e1a3a..093796bea5 100644 --- a/src/core/ir/model.rs +++ b/src/core/ir/model.rs @@ -133,7 +133,7 @@ impl IR { match self { IR::IO(io) => io_modifier(io), IR::Cache(cache) => io_modifier(&mut cache.io), - IR::Discriminate(_, ir) | IR::Protect(ir) | IR::Path(ir, _) => { + IR::Discriminate(_, ir) | IR::Protect(_, ir) | IR::Path(ir, _) => { ir.modify_io(io_modifier) } IR::Pipe(ir1, ir2) => { diff --git a/src/core/json/graphql.rs b/src/core/json/graphql.rs index 860154a1c1..2a0bc81b33 100644 --- a/src/core/json/graphql.rs +++ b/src/core/json/graphql.rs @@ -192,6 +192,28 @@ impl<'json> JsonLike<'json> for ConstValue { impl<'json> JsonLike<'json> for Value { type JsonObject = IndexMap; + fn from_primitive(x: JsonPrimitive<'json>) -> Self { + match x { + JsonPrimitive::Null => Value::Null, + JsonPrimitive::Bool(x) => Value::Boolean(x), + JsonPrimitive::Str(s) => Value::String(s.to_string()), + JsonPrimitive::Number(number) => Value::Number(number), + } + } + + fn as_primitive(&self) -> Option { + let val = match self { + Value::Null => JsonPrimitive::Null, + Value::Boolean(x) => JsonPrimitive::Bool(*x), + Value::Number(number) => JsonPrimitive::Number(number.clone()), + Value::String(s) => JsonPrimitive::Str(s.as_ref()), + Value::Enum(e) => JsonPrimitive::Str(e.as_str()), + _ => return None, + }; + + Some(val) + } + fn as_array(&self) -> Option<&Vec> { match self { Value::List(seq) => Some(seq), @@ -216,7 +238,6 @@ impl<'json> JsonLike<'json> for Value { fn as_str(&self) -> Option<&str> { match self { Value::String(s) => Some(s), - Value::Variable(map) => Some(map.as_str()), _ => None, } }