Skip to content

Commit

Permalink
fix: graphql nested queries (#5)
Browse files Browse the repository at this point in the history
* fix: bug with graphql nested queries

* fix: linting

* fix: linting

* avoid unwrapping in the logic

* chore(deps): update dependency wrangler to v3.84.0

* chore(deps): update dependency wrangler to v3.84.1

* chore(deps): update rust crate insta to v1.41.1

* chore(deps): update rust crate thiserror to v1.0.66

* fix: query on nodes with the same type

* fix: remove the depth dependency

---------

Co-authored-by: Sandipsinh Rathod <[email protected]>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Nov 5, 2024
1 parent 08b55ed commit ff8d0cf
Show file tree
Hide file tree
Showing 11 changed files with 319 additions and 108 deletions.
102 changes: 51 additions & 51 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion generated/.tailcallrc.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -1033,4 +1033,4 @@ Output format for prometheus data
enum PrometheusFormat {
text
protobuf
}
}
2 changes: 1 addition & 1 deletion generated/.tailcallrc.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1813,4 +1813,4 @@
]
}
}
}
}
97 changes: 64 additions & 33 deletions src/core/blueprint/operators/graphql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,44 +15,62 @@ fn create_related_fields(
config: &Config,
type_name: &str,
visited: &mut HashSet<String>,
) -> RelatedFields {
paths: &mut HashMap<String, Vec<String>>,
path: Vec<String>,
root: bool,
) -> Option<RelatedFields> {
let mut map = HashMap::new();
if visited.contains(type_name) {
return RelatedFields(map);
return Some(RelatedFields(map));
}
visited.insert(type_name.to_string());

if let Some(type_) = config.find_type(type_name) {
for (name, field) in &type_.fields {
if !field.has_resolver() {
if let Some(modify) = &field.modify {
if let Some(modified_name) = &modify.name {
map.insert(
modified_name.clone(),
(
name.clone(),
create_related_fields(config, field.type_of.name(), visited),
),
);
}
} else {
map.insert(
let used_name = match &field.modify {
Some(modify) => match &modify.name {
Some(modified_name) => Some(modified_name),
_ => None,
},
_ => Some(name),
};
let mut next_path = path.clone();
next_path.push(used_name?.to_string());
if !(paths.contains_key(field.type_of.name())) {
paths.insert(field.type_of.name().to_string(), next_path.clone());
};
map.insert(
used_name?.to_string(),
(
name.clone(),
(
name.clone(),
create_related_fields(config, field.type_of.name(), visited),
),
);
}
create_related_fields(
config,
field.type_of.name(),
visited,
paths,
next_path.clone(),
false,
)?,
paths.get(field.type_of.name())?.to_vec(),
root && (type_name == field.type_of.name()),
),
);
}
}
} else if let Some(union_) = config.find_union(type_name) {
for type_name in &union_.types {
map.extend(create_related_fields(config, type_name, visited).0);
let mut next_path = path.clone();
next_path.push(type_name.to_string());
if !(paths.contains_key(type_name)) {
paths.insert(type_name.to_string(), next_path.clone());
};
map.extend(
create_related_fields(config, type_name, visited, paths, next_path, root)?.0,
);
}
};

RelatedFields(map)
Some(RelatedFields(map))
}

pub fn compile_graphql(
Expand All @@ -65,17 +83,30 @@ pub fn compile_graphql(
Valid::succeed(graphql.url.as_str())
.zip(helpers::headers::to_mustache_headers(&graphql.headers))
.and_then(|(base_url, headers)| {
Valid::from(
RequestTemplate::new(
base_url.to_owned(),
operation_type,
&graphql.name,
args,
headers,
create_related_fields(config, type_name, &mut HashSet::new()),
)
.map_err(|e| ValidationError::new(e.to_string())),
Valid::from_option(
create_related_fields(
config,
type_name,
&mut HashSet::new(),
&mut HashMap::new(),
vec![],
true,
),
"Logical error occurred while creating Related Fields".to_string(),
)
.and_then(|related_fields| {
Valid::from(
RequestTemplate::new(
base_url.to_owned(),
operation_type,
&graphql.name,
args,
headers,
related_fields,
)
.map_err(|e| ValidationError::new(e.to_string())),
)
})
})
.map(|req_template| {
let field_name = graphql.name.clone();
Expand Down
32 changes: 26 additions & 6 deletions src/core/ir/eval_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,19 +119,37 @@ impl<'a, Ctx: ResolverContextLike> GraphQLOperationContext for EvalContext<'a, C

fn selection_set(&self, related_fields: &RelatedFields) -> Option<String> {
let selection_field = self.graphql_ctx.field()?;
format_selection_set(selection_field.selection_set(), related_fields)
format_selection_set(
selection_field.selection_set(),
Some(related_fields),
related_fields,
)
}
}

fn format_selection_set<'a>(
selection_set: impl Iterator<Item = &'a SelectionField>,
related_fields: &RelatedFields,
related_fields: Option<&RelatedFields>,
global_related_fields: &RelatedFields,
) -> Option<String> {
let set = selection_set
.filter_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)
related_fields?.get(field.name()).map(|new_related_fields| {
let next_related_fields = if new_related_fields.3 {
Some(global_related_fields)
} else {
new_related_fields.2.iter().fold(

Check failure on line 142 in src/core/ir/eval_context.rs

View workflow job for this annotation

GitHub Actions / Run Formatter and Lint Check

usage of `Iterator::fold` on a type that implements `Try`
Some(global_related_fields),
|parent_related_fields, node| Some(&parent_related_fields?.get(node)?.1),
)
};
format_selection_field(
field,
&new_related_fields.0,
next_related_fields,
global_related_fields,
)
})
})
.collect::<Vec<_>>();
Expand All @@ -146,10 +164,12 @@ fn format_selection_set<'a>(
fn format_selection_field(
field: &SelectionField,
name: &str,
related_fields: &RelatedFields,
related_fields: Option<&RelatedFields>,
global_related_fields: &RelatedFields,
) -> 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(), related_fields, global_related_fields);

let mut output = format!("{}{}", name, arguments);

Expand Down
4 changes: 2 additions & 2 deletions src/core/ir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ pub use resolver_context_like::{
/// resolver i.e. fields that don't have their own resolver and are resolved by
/// the ancestor
#[derive(Debug, Default, Clone)]
pub struct RelatedFields(pub HashMap<String, (String, RelatedFields)>);
pub struct RelatedFields(pub HashMap<String, (String, RelatedFields, Vec<String>, bool)>);

impl Deref for RelatedFields {
type Target = HashMap<String, (String, RelatedFields)>;
type Target = HashMap<String, (String, RelatedFields, Vec<String>, bool)>;

fn deref(&self) -> &Self::Target {
&self.0
Expand Down
28 changes: 14 additions & 14 deletions tailcall-cloudflare/package-lock.json

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

27 changes: 27 additions & 0 deletions tests/core/snapshots/graphql-conformance-019.md_0.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
source: tests/core/spec.rs
expression: response
snapshot_kind: text
---
{
"status": 200,
"headers": {
"content-type": "application/json"
},
"body": {
"data": {
"queryNodeA": {
"name": "nodeA",
"nodeB": {
"name": "nodeB"
},
"nodeC": {
"name": "nodeC"
},
"child": {
"name": "nodeA"
}
}
}
}
}
31 changes: 31 additions & 0 deletions tests/core/snapshots/graphql-conformance-019.md_client.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
source: tests/core/spec.rs
expression: formatted
snapshot_kind: text
---
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
}
31 changes: 31 additions & 0 deletions tests/core/snapshots/graphql-conformance-019.md_merged.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
source: tests/core/spec.rs
expression: formatter
snapshot_kind: text
---
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")
}
Loading

0 comments on commit ff8d0cf

Please sign in to comment.