From 26da27b8e67b1075df3e5871a30ec004b934fb0d Mon Sep 17 00:00:00 2001 From: Heikel Date: Wed, 30 Oct 2024 15:37:54 +0100 Subject: [PATCH 01/10] fix: bug with graphql nested queries --- generated/.tailcallrc.graphql | 2 + generated/.tailcallrc.schema.json | 8 +++ src/core/blueprint/operators/graphql.rs | 32 +++++---- src/core/config/directives/graphql.rs | 8 +++ .../graphql-conformance-019.md_0.snap | 23 +++++++ .../graphql-conformance-019.md_client.snap | 29 +++++++++ .../graphql-conformance-019.md_merged.snap | 29 +++++++++ tests/execution/graphql-conformance-019.md | 65 +++++++++++++++++++ 8 files changed, 185 insertions(+), 11 deletions(-) create mode 100644 tests/core/snapshots/graphql-conformance-019.md_0.snap create mode 100644 tests/core/snapshots/graphql-conformance-019.md_client.snap create mode 100644 tests/core/snapshots/graphql-conformance-019.md_merged.snap create mode 100644 tests/execution/graphql-conformance-019.md diff --git a/generated/.tailcallrc.graphql b/generated/.tailcallrc.graphql index 9dc5ed9dc1..e4c7a8dd49 100644 --- a/generated/.tailcallrc.graphql +++ b/generated/.tailcallrc.graphql @@ -79,6 +79,7 @@ directive @graphQL( as nonce-based APIs. """ dedupe: Boolean + depth: Int """ The headers parameter allows you to customize the headers of the GraphQL request made by the `@graphQL` operator. It is used by specifying a key-value map of header @@ -797,6 +798,7 @@ input GraphQL { as nonce-based APIs. """ dedupe: Boolean + depth: Int """ The headers parameter allows you to customize the headers of the GraphQL request made by the `@graphQL` operator. It is used by specifying a key-value map of header diff --git a/generated/.tailcallrc.schema.json b/generated/.tailcallrc.schema.json index e32e9b91f8..04d702216d 100644 --- a/generated/.tailcallrc.schema.json +++ b/generated/.tailcallrc.schema.json @@ -595,6 +595,14 @@ "null" ] }, + "depth": { + "type": [ + "integer", + "null" + ], + "format": "uint", + "minimum": 0.0 + }, "headers": { "description": "The headers parameter allows you to customize the headers of the GraphQL request made by the `@graphQL` operator. It is used by specifying a key-value map of header names and their values.", "type": "array", diff --git a/src/core/blueprint/operators/graphql.rs b/src/core/blueprint/operators/graphql.rs index d54547b714..067ed31ce6 100644 --- a/src/core/blueprint/operators/graphql.rs +++ b/src/core/blueprint/operators/graphql.rs @@ -1,4 +1,4 @@ -use std::collections::{HashMap, HashSet}; +use std::collections::{HashMap, HashSet, VecDeque}; use crate::core::blueprint::FieldDefinition; use crate::core::config::{ @@ -13,17 +13,20 @@ use crate::core::valid::{Valid, ValidationError, Validator}; fn create_related_fields( config: &Config, - type_name: &str, - visited: &mut HashSet, + type_names: VecDeque, + visited: &mut HashSet>, ) -> RelatedFields { let mut map = HashMap::new(); - if visited.contains(type_name) { + if visited.contains(&type_names) { return RelatedFields(map); } - visited.insert(type_name.to_string()); - + visited.insert(type_names.clone()); + let type_name = type_names.back().unwrap(); if let Some(type_) = config.find_type(type_name) { for (name, field) in &type_.fields { + let mut new_type_names = type_names.clone(); + new_type_names.pop_front(); + new_type_names.push_back(field.type_of.name().to_string()); if !field.has_resolver() { if let Some(modify) = &field.modify { if let Some(modified_name) = &modify.name { @@ -31,7 +34,7 @@ fn create_related_fields( modified_name.clone(), ( name.clone(), - create_related_fields(config, field.type_of.name(), visited), + create_related_fields(config, new_type_names, visited), ), ); } @@ -40,15 +43,18 @@ fn create_related_fields( name.clone(), ( name.clone(), - create_related_fields(config, field.type_of.name(), visited), + create_related_fields(config, new_type_names, visited), ), ); } } } - } else if let Some(union_) = config.find_union(type_name) { + } else if let Some(union_) = config.find_union(type_names.back().unwrap()) { for type_name in &union_.types { - map.extend(create_related_fields(config, type_name, visited).0); + let mut new_type_names = type_names.clone(); + new_type_names.pop_front(); + new_type_names.push_back(type_name.to_string()); + map.extend(create_related_fields(config, new_type_names, visited).0); } }; @@ -72,7 +78,11 @@ pub fn compile_graphql( &graphql.name, args, headers, - create_related_fields(config, type_name, &mut HashSet::new()), + create_related_fields( + config, + VecDeque::from(vec![type_name.to_string(); graphql.depth.unwrap_or(5)]), + &mut HashSet::new(), + ), ) .map_err(|e| ValidationError::new(e.to_string())), ) diff --git a/src/core/config/directives/graphql.rs b/src/core/config/directives/graphql.rs index e366bbaaa2..6c9c73a79f 100644 --- a/src/core/config/directives/graphql.rs +++ b/src/core/config/directives/graphql.rs @@ -56,4 +56,12 @@ pub struct GraphQL { /// with APIs that expect unique results for identical inputs, such as /// nonce-based APIs. pub dedupe: Option, + /// Specifies the maximum depth for nested queries. + /// + /// When set, this value determines the depth of nested queries beyond which + /// no further subfields will be returned. This helps to limit the complexity + /// and size of the query results. If this value is not set, the default depth + /// is 5, ensuring that queries are manageable and preventing excessively + /// deep nesting. + pub depth: Option, } diff --git a/tests/core/snapshots/graphql-conformance-019.md_0.snap b/tests/core/snapshots/graphql-conformance-019.md_0.snap new file mode 100644 index 0000000000..0b3e1b3e22 --- /dev/null +++ b/tests/core/snapshots/graphql-conformance-019.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-conformance-019.md_client.snap b/tests/core/snapshots/graphql-conformance-019.md_client.snap new file mode 100644 index 0000000000..8998a39c3c --- /dev/null +++ b/tests/core/snapshots/graphql-conformance-019.md_client.snap @@ -0,0 +1,29 @@ +--- +source: tests/core/spec.rs +expression: formatted +--- +type 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-conformance-019.md_merged.snap b/tests/core/snapshots/graphql-conformance-019.md_merged.snap new file mode 100644 index 0000000000..dec55dba71 --- /dev/null +++ b/tests/core/snapshots/graphql-conformance-019.md_merged.snap @@ -0,0 +1,29 @@ +--- +source: tests/core/spec.rs +expression: formatter +--- +schema @server(hostname: "0.0.0.0", port: 8000) @upstream { + query: Query +} + +type 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 @graphQL(url: "http://upstream/graphql", name: "nodeA", depth: 3) +} diff --git a/tests/execution/graphql-conformance-019.md b/tests/execution/graphql-conformance-019.md new file mode 100644 index 0000000000..ee92b992b9 --- /dev/null +++ b/tests/execution/graphql-conformance-019.md @@ -0,0 +1,65 @@ +# 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", depth: 3) +} + +type NodeA { + name: String + nodeB: NodeB + nodeC: NodeC +} + +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 +``` + +```yml @test +- method: POST + url: http://localhost:8080/graphql + body: + query: | + query queryNodeA { + queryNodeA { + name + nodeB { + name + } + nodeC { + name + } + } + } +``` From 8ec733aa7546b6429a22944d33554322227974cf Mon Sep 17 00:00:00 2001 From: Heikel Date: Wed, 30 Oct 2024 15:59:57 +0100 Subject: [PATCH 02/10] fix: linting --- generated/.tailcallrc.graphql | 14 ++++++++++++++ generated/.tailcallrc.schema.json | 1 + 2 files changed, 15 insertions(+) diff --git a/generated/.tailcallrc.graphql b/generated/.tailcallrc.graphql index e4c7a8dd49..e0391adb81 100644 --- a/generated/.tailcallrc.graphql +++ b/generated/.tailcallrc.graphql @@ -79,6 +79,13 @@ directive @graphQL( as nonce-based APIs. """ dedupe: Boolean + """ + Specifies the maximum depth for nested queries.When set, this value determines the + depth of nested queries beyond which no further subfields will be returned. This + helps to limit the complexity and size of the query results. If this value is not + set, the default depth is 5, ensuring that queries are manageable and preventing + excessively deep nesting. + """ depth: Int """ The headers parameter allows you to customize the headers of the GraphQL request @@ -798,6 +805,13 @@ input GraphQL { as nonce-based APIs. """ dedupe: Boolean + """ + Specifies the maximum depth for nested queries.When set, this value determines the + depth of nested queries beyond which no further subfields will be returned. This + helps to limit the complexity and size of the query results. If this value is not + set, the default depth is 5, ensuring that queries are manageable and preventing + excessively deep nesting. + """ depth: Int """ The headers parameter allows you to customize the headers of the GraphQL request diff --git a/generated/.tailcallrc.schema.json b/generated/.tailcallrc.schema.json index 04d702216d..b8a07edadf 100644 --- a/generated/.tailcallrc.schema.json +++ b/generated/.tailcallrc.schema.json @@ -596,6 +596,7 @@ ] }, "depth": { + "description": "Specifies the maximum depth for nested queries.\n\nWhen set, this value determines the depth of nested queries beyond which no further subfields will be returned. This helps to limit the complexity and size of the query results. If this value is not set, the default depth is 5, ensuring that queries are manageable and preventing excessively deep nesting.", "type": [ "integer", "null" From e766c228019257f93acce2ea8e3e1ce34b66d193 Mon Sep 17 00:00:00 2001 From: Heikel Date: Wed, 30 Oct 2024 16:10:14 +0100 Subject: [PATCH 03/10] fix: linting --- generated/.tailcallrc.graphql | 4 ++-- generated/.tailcallrc.schema.json | 2 +- src/core/config/directives/graphql.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/generated/.tailcallrc.graphql b/generated/.tailcallrc.graphql index e0391adb81..5a67d2159c 100644 --- a/generated/.tailcallrc.graphql +++ b/generated/.tailcallrc.graphql @@ -80,7 +80,7 @@ directive @graphQL( """ dedupe: Boolean """ - Specifies the maximum depth for nested queries.When set, this value determines the + Specifies the maximum depth for nested queries. When set, this value determines the depth of nested queries beyond which no further subfields will be returned. This helps to limit the complexity and size of the query results. If this value is not set, the default depth is 5, ensuring that queries are manageable and preventing @@ -806,7 +806,7 @@ input GraphQL { """ dedupe: Boolean """ - Specifies the maximum depth for nested queries.When set, this value determines the + Specifies the maximum depth for nested queries. When set, this value determines the depth of nested queries beyond which no further subfields will be returned. This helps to limit the complexity and size of the query results. If this value is not set, the default depth is 5, ensuring that queries are manageable and preventing diff --git a/generated/.tailcallrc.schema.json b/generated/.tailcallrc.schema.json index b8a07edadf..7dc8cb215c 100644 --- a/generated/.tailcallrc.schema.json +++ b/generated/.tailcallrc.schema.json @@ -596,7 +596,7 @@ ] }, "depth": { - "description": "Specifies the maximum depth for nested queries.\n\nWhen set, this value determines the depth of nested queries beyond which no further subfields will be returned. This helps to limit the complexity and size of the query results. If this value is not set, the default depth is 5, ensuring that queries are manageable and preventing excessively deep nesting.", + "description": "Specifies the maximum depth for nested queries. When set, this value determines the depth of nested queries beyond which no further subfields will be returned. This helps to limit the complexity and size of the query results. If this value is not set, the default depth is 5, ensuring that queries are manageable and preventing excessively deep nesting.", "type": [ "integer", "null" diff --git a/src/core/config/directives/graphql.rs b/src/core/config/directives/graphql.rs index 6c9c73a79f..00900c45d5 100644 --- a/src/core/config/directives/graphql.rs +++ b/src/core/config/directives/graphql.rs @@ -56,8 +56,8 @@ pub struct GraphQL { /// with APIs that expect unique results for identical inputs, such as /// nonce-based APIs. pub dedupe: Option, + /// Specifies the maximum depth for nested queries. - /// /// When set, this value determines the depth of nested queries beyond which /// no further subfields will be returned. This helps to limit the complexity /// and size of the query results. If this value is not set, the default depth From 859a1797d0c7f82af118c3b205adc64d1d58697b Mon Sep 17 00:00:00 2001 From: Sandipsinh Rathod Date: Wed, 30 Oct 2024 11:11:09 -0400 Subject: [PATCH 04/10] avoid unwrapping in the logic --- src/core/blueprint/operators/graphql.rs | 50 ++++++++++++++----------- src/core/config/directives/graphql.rs | 8 ++-- 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/src/core/blueprint/operators/graphql.rs b/src/core/blueprint/operators/graphql.rs index 067ed31ce6..1aa01b5c42 100644 --- a/src/core/blueprint/operators/graphql.rs +++ b/src/core/blueprint/operators/graphql.rs @@ -15,13 +15,13 @@ fn create_related_fields( config: &Config, type_names: VecDeque, visited: &mut HashSet>, -) -> RelatedFields { +) -> Option { let mut map = HashMap::new(); if visited.contains(&type_names) { - return RelatedFields(map); + return Some(RelatedFields(map)); } visited.insert(type_names.clone()); - let type_name = type_names.back().unwrap(); + let type_name = type_names.back()?; if let Some(type_) = config.find_type(type_name) { for (name, field) in &type_.fields { let mut new_type_names = type_names.clone(); @@ -34,7 +34,7 @@ fn create_related_fields( modified_name.clone(), ( name.clone(), - create_related_fields(config, new_type_names, visited), + create_related_fields(config, new_type_names, visited)?, ), ); } @@ -43,22 +43,22 @@ fn create_related_fields( name.clone(), ( name.clone(), - create_related_fields(config, new_type_names, visited), + create_related_fields(config, new_type_names, visited)?, ), ); } } } - } else if let Some(union_) = config.find_union(type_names.back().unwrap()) { + } else if let Some(union_) = config.find_union(type_names.back()?) { for type_name in &union_.types { let mut new_type_names = type_names.clone(); new_type_names.pop_front(); new_type_names.push_back(type_name.to_string()); - map.extend(create_related_fields(config, new_type_names, visited).0); + map.extend(create_related_fields(config, new_type_names, visited)?.0); } }; - RelatedFields(map) + Some(RelatedFields(map)) } pub fn compile_graphql( @@ -71,21 +71,27 @@ 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, - VecDeque::from(vec![type_name.to_string(); graphql.depth.unwrap_or(5)]), - &mut HashSet::new(), - ), - ) - .map_err(|e| ValidationError::new(e.to_string())), + Valid::from_option( + create_related_fields( + config, + VecDeque::from(vec![type_name.to_string(); graphql.depth.unwrap_or(5)]), + &mut HashSet::new(), + ), + "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(); diff --git a/src/core/config/directives/graphql.rs b/src/core/config/directives/graphql.rs index 6c9c73a79f..d51dc8d421 100644 --- a/src/core/config/directives/graphql.rs +++ b/src/core/config/directives/graphql.rs @@ -59,9 +59,9 @@ pub struct GraphQL { /// Specifies the maximum depth for nested queries. /// /// When set, this value determines the depth of nested queries beyond which - /// no further subfields will be returned. This helps to limit the complexity - /// and size of the query results. If this value is not set, the default depth - /// is 5, ensuring that queries are manageable and preventing excessively - /// deep nesting. + /// no further subfields will be returned. This helps to limit the + /// complexity and size of the query results. If this value is not set, + /// the default depth is 5, ensuring that queries are manageable and + /// preventing excessively deep nesting. pub depth: Option, } From 36d4b41c55c5e3d8071dab53935d2d9ffc8e9b01 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 30 Oct 2024 18:07:44 +0000 Subject: [PATCH 05/10] chore(deps): update dependency wrangler to v3.84.0 --- tailcall-cloudflare/package-lock.json | 28 +++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tailcall-cloudflare/package-lock.json b/tailcall-cloudflare/package-lock.json index 99761885e0..32eeea7ecb 100644 --- a/tailcall-cloudflare/package-lock.json +++ b/tailcall-cloudflare/package-lock.json @@ -1791,9 +1791,9 @@ }, "node_modules/unenv": { "name": "unenv-nightly", - "version": "2.0.0-20241018-011344-e666fcf", - "resolved": "https://registry.npmjs.org/unenv-nightly/-/unenv-nightly-2.0.0-20241018-011344-e666fcf.tgz", - "integrity": "sha512-D00bYn8rzkCBOlLx+k1iHQlc69jvtJRT7Eek4yIGQ6461a2tUBjngGZdRpqsoXAJCz/qBW0NgPting7Zvg+ysg==", + "version": "2.0.0-20241024-111401-d4156ac", + "resolved": "https://registry.npmjs.org/unenv-nightly/-/unenv-nightly-2.0.0-20241024-111401-d4156ac.tgz", + "integrity": "sha512-xJO1hfY+Te+/XnfCYrCbFbRcgu6XEODND1s5wnVbaBCkuQX7JXF7fHEXPrukFE2j8EOH848P8QN19VO47XN8hw==", "dev": true, "dependencies": { "defu": "^6.1.4", @@ -1980,9 +1980,9 @@ } }, "node_modules/wrangler": { - "version": "3.83.0", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.83.0.tgz", - "integrity": "sha512-qDzdUuTngKqmm2OJUZm7Gk4+Hv37F2nNNAHuhIgItEIhxBdOVDsgKmvpd+f41MFxyuGg3fbGWYANHI+0V2Z5yw==", + "version": "3.84.0", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.84.0.tgz", + "integrity": "sha512-EA8oh7YQmZ3kD+a5MId9reHKGgXpodmsPWMLriE5gT5YmG9is66n0AA2tyLzQZKZXmgbo6JyGxvCDPcLeb/X0w==", "dev": true, "dependencies": { "@cloudflare/kv-asset-handler": "0.3.4", @@ -2001,7 +2001,7 @@ "resolve.exports": "^2.0.2", "selfsigned": "^2.0.1", "source-map": "^0.6.1", - "unenv": "npm:unenv-nightly@2.0.0-20241018-011344-e666fcf", + "unenv": "npm:unenv-nightly@2.0.0-20241024-111401-d4156ac", "workerd": "1.20241022.0", "xxhash-wasm": "^1.0.1" }, @@ -3616,9 +3616,9 @@ "dev": true }, "unenv": { - "version": "npm:unenv-nightly@2.0.0-20241018-011344-e666fcf", - "resolved": "https://registry.npmjs.org/unenv-nightly/-/unenv-nightly-2.0.0-20241018-011344-e666fcf.tgz", - "integrity": "sha512-D00bYn8rzkCBOlLx+k1iHQlc69jvtJRT7Eek4yIGQ6461a2tUBjngGZdRpqsoXAJCz/qBW0NgPting7Zvg+ysg==", + "version": "npm:unenv-nightly@2.0.0-20241024-111401-d4156ac", + "resolved": "https://registry.npmjs.org/unenv-nightly/-/unenv-nightly-2.0.0-20241024-111401-d4156ac.tgz", + "integrity": "sha512-xJO1hfY+Te+/XnfCYrCbFbRcgu6XEODND1s5wnVbaBCkuQX7JXF7fHEXPrukFE2j8EOH848P8QN19VO47XN8hw==", "dev": true, "requires": { "defu": "^6.1.4", @@ -3703,9 +3703,9 @@ } }, "wrangler": { - "version": "3.83.0", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.83.0.tgz", - "integrity": "sha512-qDzdUuTngKqmm2OJUZm7Gk4+Hv37F2nNNAHuhIgItEIhxBdOVDsgKmvpd+f41MFxyuGg3fbGWYANHI+0V2Z5yw==", + "version": "3.84.0", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.84.0.tgz", + "integrity": "sha512-EA8oh7YQmZ3kD+a5MId9reHKGgXpodmsPWMLriE5gT5YmG9is66n0AA2tyLzQZKZXmgbo6JyGxvCDPcLeb/X0w==", "dev": true, "requires": { "@cloudflare/kv-asset-handler": "0.3.4", @@ -3725,7 +3725,7 @@ "resolve.exports": "^2.0.2", "selfsigned": "^2.0.1", "source-map": "^0.6.1", - "unenv": "npm:unenv-nightly@2.0.0-20241018-011344-e666fcf", + "unenv": "npm:unenv-nightly@2.0.0-20241024-111401-d4156ac", "workerd": "1.20241022.0", "xxhash-wasm": "^1.0.1" }, From 0065b60fc7c81a9a15890d60a358a848a9c2d0f0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 31 Oct 2024 17:52:36 +0000 Subject: [PATCH 06/10] chore(deps): update dependency wrangler to v3.84.1 --- tailcall-cloudflare/package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tailcall-cloudflare/package-lock.json b/tailcall-cloudflare/package-lock.json index 32eeea7ecb..26825ad5f7 100644 --- a/tailcall-cloudflare/package-lock.json +++ b/tailcall-cloudflare/package-lock.json @@ -1980,9 +1980,9 @@ } }, "node_modules/wrangler": { - "version": "3.84.0", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.84.0.tgz", - "integrity": "sha512-EA8oh7YQmZ3kD+a5MId9reHKGgXpodmsPWMLriE5gT5YmG9is66n0AA2tyLzQZKZXmgbo6JyGxvCDPcLeb/X0w==", + "version": "3.84.1", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.84.1.tgz", + "integrity": "sha512-w27/QpIk2qz6aMIVi9T8cDcXMvh/RXjcL+vf4o5J2GpQAE4U7wTCNHyaY9H3oTJWRN97KqCAEbiHBNtTKoUJEw==", "dev": true, "dependencies": { "@cloudflare/kv-asset-handler": "0.3.4", @@ -3703,9 +3703,9 @@ } }, "wrangler": { - "version": "3.84.0", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.84.0.tgz", - "integrity": "sha512-EA8oh7YQmZ3kD+a5MId9reHKGgXpodmsPWMLriE5gT5YmG9is66n0AA2tyLzQZKZXmgbo6JyGxvCDPcLeb/X0w==", + "version": "3.84.1", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.84.1.tgz", + "integrity": "sha512-w27/QpIk2qz6aMIVi9T8cDcXMvh/RXjcL+vf4o5J2GpQAE4U7wTCNHyaY9H3oTJWRN97KqCAEbiHBNtTKoUJEw==", "dev": true, "requires": { "@cloudflare/kv-asset-handler": "0.3.4", From dc505ad74d0ec23ad7da65f5b4444e45c26517ef Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 31 Oct 2024 20:43:28 +0000 Subject: [PATCH 07/10] chore(deps): update rust crate insta to v1.41.1 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index af839361c9..c40dc81ff9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2528,9 +2528,9 @@ dependencies = [ [[package]] name = "insta" -version = "1.41.0" +version = "1.41.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f72d3e19488cf7d8ea52d2fc0f8754fc933398b337cd3cbdb28aaeb35159ef" +checksum = "7e9ffc4d4892617c50a928c52b2961cb5174b6fc6ebf252b2fac9d21955c48b8" dependencies = [ "console", "lazy_static", From ec22a4e674b2fac31ede807463a6072635aeba60 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 1 Nov 2024 02:25:53 +0000 Subject: [PATCH 08/10] chore(deps): update rust crate thiserror to v1.0.66 --- Cargo.lock | 98 +++++++++++++++++++++++++++--------------------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c40dc81ff9..20dfa90a39 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -280,7 +280,7 @@ dependencies = [ "proc-macro2", "quote", "strum", - "syn 2.0.85", + "syn 2.0.86", "thiserror", ] @@ -407,7 +407,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -475,7 +475,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -492,7 +492,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -912,7 +912,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -1192,7 +1192,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -1214,7 +1214,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core 0.20.10", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -1266,7 +1266,7 @@ checksum = "74ef43543e701c01ad77d3a5922755c6a1d71b22d942cb8042be4994b380caff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -1310,7 +1310,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -1330,7 +1330,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", "unicode-xid", ] @@ -1343,7 +1343,7 @@ dependencies = [ "darling 0.20.10", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -1696,7 +1696,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -2880,7 +2880,7 @@ dependencies = [ "proc-macro2", "quote", "regex-syntax 0.8.5", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -3028,7 +3028,7 @@ checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -3039,7 +3039,7 @@ checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -3633,7 +3633,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -3710,7 +3710,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -3838,7 +3838,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" dependencies = [ "proc-macro2", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -3945,7 +3945,7 @@ dependencies = [ "prost 0.12.6", "prost-types 0.12.6", "regex", - "syn 2.0.85", + "syn 2.0.86", "tempfile", ] @@ -3959,7 +3959,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -3972,7 +3972,7 @@ dependencies = [ "itertools 0.13.0", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -4628,7 +4628,7 @@ dependencies = [ "proc-macro2", "quote", "rquickjs-core", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -4824,7 +4824,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -4942,7 +4942,7 @@ checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -4953,7 +4953,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -5273,7 +5273,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -5295,9 +5295,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.85" +version = "2.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" +checksum = "e89275301d38033efb81a6e60e3497e734dfcc62571f2854bf4b16690398824c" dependencies = [ "proc-macro2", "quote", @@ -5577,7 +5577,7 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -5760,27 +5760,27 @@ checksum = "5999e24eaa32083191ba4e425deb75cdf25efefabe5aaccb7446dd0d4122a3f5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] name = "thiserror" -version = "1.0.65" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" +checksum = "5d171f59dbaa811dbbb1aee1e73db92ec2b122911a48e1390dfe327a821ddede" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.65" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" +checksum = "b08be0f17bd307950653ce45db00cd31200d82b624b36e181337d9c7d92765b5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -5903,7 +5903,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -6063,7 +6063,7 @@ dependencies = [ "proc-macro2", "prost-build", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -6076,7 +6076,7 @@ dependencies = [ "proc-macro2", "prost-build", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -6174,7 +6174,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -6484,7 +6484,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", "wasm-bindgen-shared", ] @@ -6518,7 +6518,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6552,7 +6552,7 @@ checksum = "c97b2ef2c8d627381e51c071c2ab328eac606d3f69dd82bcbca20a9e389d95f0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -6753,7 +6753,7 @@ checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -6764,7 +6764,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -6786,7 +6786,7 @@ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -6797,7 +6797,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -7100,7 +7100,7 @@ dependencies = [ "async-trait", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-macro-support", @@ -7128,7 +7128,7 @@ dependencies = [ "darling 0.20.10", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -7161,7 +7161,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] From a8c89d82db24c445b2b73454f3a601a9aaffd004 Mon Sep 17 00:00:00 2001 From: Heikel Date: Fri, 1 Nov 2024 10:50:52 +0100 Subject: [PATCH 09/10] fix: query on nodes with the same type --- src/core/blueprint/operators/graphql.rs | 26 +++++++++++-------- src/core/ir/eval_context.rs | 8 ++++-- src/core/ir/mod.rs | 4 +-- .../graphql-conformance-019.md_0.snap | 4 +++ .../graphql-conformance-019.md_client.snap | 2 ++ .../graphql-conformance-019.md_merged.snap | 2 ++ tests/execution/graphql-conformance-019.md | 8 +++++- 7 files changed, 38 insertions(+), 16 deletions(-) diff --git a/src/core/blueprint/operators/graphql.rs b/src/core/blueprint/operators/graphql.rs index 1aa01b5c42..6cd8fff5e2 100644 --- a/src/core/blueprint/operators/graphql.rs +++ b/src/core/blueprint/operators/graphql.rs @@ -26,24 +26,28 @@ fn create_related_fields( for (name, field) in &type_.fields { let mut new_type_names = type_names.clone(); new_type_names.pop_front(); + let bool_self = field.type_of.name() == new_type_names.back()?; new_type_names.push_back(field.type_of.name().to_string()); 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, new_type_names, visited)?, - ), - ); - } + let used_name = match &field.modify { + Some(modify) => match &modify.name { + Some(modified_name) => Some(modified_name), + _ => None, + }, + _ => Some(name), + }; + if bool_self { + map.insert( + used_name?.to_string(), + (name.clone(), RelatedFields(HashMap::new()), true), + ); } else { map.insert( - name.clone(), + used_name?.to_string(), ( name.clone(), create_related_fields(config, new_type_names, visited)?, + false, ), ); } diff --git a/src/core/ir/eval_context.rs b/src/core/ir/eval_context.rs index c733882f85..4407b414df 100644 --- a/src/core/ir/eval_context.rs +++ b/src/core/ir/eval_context.rs @@ -130,8 +130,12 @@ fn format_selection_set<'a>( 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| { + if new_related_fields.2 { + format_selection_field(field, &new_related_fields.0, related_fields) + } else { + format_selection_field(field, &new_related_fields.0, &new_related_fields.1) + } }) }) .collect::>(); diff --git a/src/core/ir/mod.rs b/src/core/ir/mod.rs index 4540424848..60a3006065 100644 --- a/src/core/ir/mod.rs +++ b/src/core/ir/mod.rs @@ -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); +pub struct RelatedFields(pub HashMap); impl Deref for RelatedFields { - type Target = HashMap; + type Target = HashMap; fn deref(&self) -> &Self::Target { &self.0 diff --git a/tests/core/snapshots/graphql-conformance-019.md_0.snap b/tests/core/snapshots/graphql-conformance-019.md_0.snap index 0b3e1b3e22..8e83a92a48 100644 --- a/tests/core/snapshots/graphql-conformance-019.md_0.snap +++ b/tests/core/snapshots/graphql-conformance-019.md_0.snap @@ -1,6 +1,7 @@ --- source: tests/core/spec.rs expression: response +snapshot_kind: text --- { "status": 200, @@ -16,6 +17,9 @@ expression: response }, "nodeC": { "name": "nodeC" + }, + "child": { + "name": "nodeA" } } } diff --git a/tests/core/snapshots/graphql-conformance-019.md_client.snap b/tests/core/snapshots/graphql-conformance-019.md_client.snap index 8998a39c3c..eeb5a90041 100644 --- a/tests/core/snapshots/graphql-conformance-019.md_client.snap +++ b/tests/core/snapshots/graphql-conformance-019.md_client.snap @@ -1,8 +1,10 @@ --- source: tests/core/spec.rs expression: formatted +snapshot_kind: text --- type NodeA { + child: NodeA name: String nodeB: NodeB nodeC: NodeC diff --git a/tests/core/snapshots/graphql-conformance-019.md_merged.snap b/tests/core/snapshots/graphql-conformance-019.md_merged.snap index dec55dba71..e3cbb1674b 100644 --- a/tests/core/snapshots/graphql-conformance-019.md_merged.snap +++ b/tests/core/snapshots/graphql-conformance-019.md_merged.snap @@ -1,6 +1,7 @@ --- source: tests/core/spec.rs expression: formatter +snapshot_kind: text --- schema @server(hostname: "0.0.0.0", port: 8000) @upstream { query: Query @@ -8,6 +9,7 @@ schema @server(hostname: "0.0.0.0", port: 8000) @upstream { type NodeA { name: String + nodeA: NodeA @modify(name: "child") nodeB: NodeB nodeC: NodeC } diff --git a/tests/execution/graphql-conformance-019.md b/tests/execution/graphql-conformance-019.md index ee92b992b9..4ffaf83463 100644 --- a/tests/execution/graphql-conformance-019.md +++ b/tests/execution/graphql-conformance-019.md @@ -13,6 +13,7 @@ type NodeA { name: String nodeB: NodeB nodeC: NodeC + nodeA: NodeA @modify(name: "child") } type NodeB { @@ -32,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 } nodeA { name } } }" }' expectedHits: 1 response: status: 200 @@ -44,6 +45,8 @@ type NodeC { name: nodeB nodeC: name: nodeC + nodeA: + name: nodeA ``` ```yml @test @@ -60,6 +63,9 @@ type NodeC { nodeC { name } + child { + name + } } } ``` From c5514867000e26eba97aba5f0a6a940cee43927d Mon Sep 17 00:00:00 2001 From: Heikel Date: Tue, 5 Nov 2024 15:47:07 +0100 Subject: [PATCH 10/10] fix: remove the depth dependency --- generated/.tailcallrc.graphql | 18 +---- generated/.tailcallrc.schema.json | 11 +-- src/core/blueprint/operators/graphql.rs | 71 +++++++++++-------- src/core/config/directives/graphql.rs | 8 --- src/core/ir/eval_context.rs | 34 ++++++--- src/core/ir/mod.rs | 4 +- .../graphql-conformance-019.md_merged.snap | 2 +- tests/execution/graphql-conformance-019.md | 2 +- 8 files changed, 72 insertions(+), 78 deletions(-) diff --git a/generated/.tailcallrc.graphql b/generated/.tailcallrc.graphql index 5a67d2159c..9db2a5fec3 100644 --- a/generated/.tailcallrc.graphql +++ b/generated/.tailcallrc.graphql @@ -80,14 +80,6 @@ directive @graphQL( """ dedupe: Boolean """ - Specifies the maximum depth for nested queries. When set, this value determines the - depth of nested queries beyond which no further subfields will be returned. This - helps to limit the complexity and size of the query results. If this value is not - set, the default depth is 5, ensuring that queries are manageable and preventing - excessively deep nesting. - """ - depth: Int - """ The headers parameter allows you to customize the headers of the GraphQL request made by the `@graphQL` operator. It is used by specifying a key-value map of header names and their values. @@ -806,14 +798,6 @@ input GraphQL { """ dedupe: Boolean """ - Specifies the maximum depth for nested queries. When set, this value determines the - depth of nested queries beyond which no further subfields will be returned. This - helps to limit the complexity and size of the query results. If this value is not - set, the default depth is 5, ensuring that queries are manageable and preventing - excessively deep nesting. - """ - depth: Int - """ The headers parameter allows you to customize the headers of the GraphQL request made by the `@graphQL` operator. It is used by specifying a key-value map of header names and their values. @@ -1049,4 +1033,4 @@ Output format for prometheus data enum PrometheusFormat { text protobuf -} \ No newline at end of file +} diff --git a/generated/.tailcallrc.schema.json b/generated/.tailcallrc.schema.json index 7dc8cb215c..e3f09ec7a7 100644 --- a/generated/.tailcallrc.schema.json +++ b/generated/.tailcallrc.schema.json @@ -595,15 +595,6 @@ "null" ] }, - "depth": { - "description": "Specifies the maximum depth for nested queries. When set, this value determines the depth of nested queries beyond which no further subfields will be returned. This helps to limit the complexity and size of the query results. If this value is not set, the default depth is 5, ensuring that queries are manageable and preventing excessively deep nesting.", - "type": [ - "integer", - "null" - ], - "format": "uint", - "minimum": 0.0 - }, "headers": { "description": "The headers parameter allows you to customize the headers of the GraphQL request made by the `@graphQL` operator. It is used by specifying a key-value map of header names and their values.", "type": "array", @@ -1822,4 +1813,4 @@ ] } } -} \ No newline at end of file +} diff --git a/src/core/blueprint/operators/graphql.rs b/src/core/blueprint/operators/graphql.rs index 6cd8fff5e2..05e83348d0 100644 --- a/src/core/blueprint/operators/graphql.rs +++ b/src/core/blueprint/operators/graphql.rs @@ -1,4 +1,4 @@ -use std::collections::{HashMap, HashSet, VecDeque}; +use std::collections::{HashMap, HashSet}; use crate::core::blueprint::FieldDefinition; use crate::core::config::{ @@ -13,21 +13,19 @@ use crate::core::valid::{Valid, ValidationError, Validator}; fn create_related_fields( config: &Config, - type_names: VecDeque, - visited: &mut HashSet>, + type_name: &str, + visited: &mut HashSet, + paths: &mut HashMap>, + path: Vec, + root: bool, ) -> Option { let mut map = HashMap::new(); - if visited.contains(&type_names) { + if visited.contains(type_name) { return Some(RelatedFields(map)); } - visited.insert(type_names.clone()); - let type_name = type_names.back()?; + visited.insert(type_name.to_string()); if let Some(type_) = config.find_type(type_name) { for (name, field) in &type_.fields { - let mut new_type_names = type_names.clone(); - new_type_names.pop_front(); - let bool_self = field.type_of.name() == new_type_names.back()?; - new_type_names.push_back(field.type_of.name().to_string()); if !field.has_resolver() { let used_name = match &field.modify { Some(modify) => match &modify.name { @@ -36,29 +34,39 @@ fn create_related_fields( }, _ => Some(name), }; - if bool_self { - map.insert( - used_name?.to_string(), - (name.clone(), RelatedFields(HashMap::new()), true), - ); - } else { - map.insert( - used_name?.to_string(), - ( - name.clone(), - create_related_fields(config, new_type_names, visited)?, + 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(), + 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_names.back()?) { + } else if let Some(union_) = config.find_union(type_name) { for type_name in &union_.types { - let mut new_type_names = type_names.clone(); - new_type_names.pop_front(); - new_type_names.push_back(type_name.to_string()); - map.extend(create_related_fields(config, new_type_names, 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, + ); } }; @@ -78,8 +86,11 @@ pub fn compile_graphql( Valid::from_option( create_related_fields( config, - VecDeque::from(vec![type_name.to_string(); graphql.depth.unwrap_or(5)]), + type_name, &mut HashSet::new(), + &mut HashMap::new(), + vec![], + true, ), "Logical error occurred while creating Related Fields".to_string(), ) diff --git a/src/core/config/directives/graphql.rs b/src/core/config/directives/graphql.rs index dde6e41ce0..e366bbaaa2 100644 --- a/src/core/config/directives/graphql.rs +++ b/src/core/config/directives/graphql.rs @@ -56,12 +56,4 @@ pub struct GraphQL { /// with APIs that expect unique results for identical inputs, such as /// nonce-based APIs. pub dedupe: Option, - - /// Specifies the maximum depth for nested queries. - /// When set, this value determines the depth of nested queries beyond which - /// no further subfields will be returned. This helps to limit the - /// complexity and size of the query results. If this value is not set, - /// the default depth is 5, ensuring that queries are manageable and - /// preventing excessively deep nesting. - pub depth: Option, } diff --git a/src/core/ir/eval_context.rs b/src/core/ir/eval_context.rs index 4407b414df..019eeb54e0 100644 --- a/src/core/ir/eval_context.rs +++ b/src/core/ir/eval_context.rs @@ -119,23 +119,37 @@ impl<'a, Ctx: ResolverContextLike> GraphQLOperationContext for EvalContext<'a, C fn selection_set(&self, related_fields: &RelatedFields) -> Option { 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, - related_fields: &RelatedFields, + related_fields: Option<&RelatedFields>, + global_related_fields: &RelatedFields, ) -> Option { 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(|new_related_fields| { - if new_related_fields.2 { - format_selection_field(field, &new_related_fields.0, related_fields) + related_fields?.get(field.name()).map(|new_related_fields| { + let next_related_fields = if new_related_fields.3 { + Some(global_related_fields) } else { - format_selection_field(field, &new_related_fields.0, &new_related_fields.1) - } + new_related_fields.2.iter().fold( + 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::>(); @@ -150,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); diff --git a/src/core/ir/mod.rs b/src/core/ir/mod.rs index 60a3006065..076d4c0187 100644 --- a/src/core/ir/mod.rs +++ b/src/core/ir/mod.rs @@ -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); +pub struct RelatedFields(pub HashMap, bool)>); impl Deref for RelatedFields { - type Target = HashMap; + type Target = HashMap, bool)>; fn deref(&self) -> &Self::Target { &self.0 diff --git a/tests/core/snapshots/graphql-conformance-019.md_merged.snap b/tests/core/snapshots/graphql-conformance-019.md_merged.snap index e3cbb1674b..bd3b791805 100644 --- a/tests/core/snapshots/graphql-conformance-019.md_merged.snap +++ b/tests/core/snapshots/graphql-conformance-019.md_merged.snap @@ -27,5 +27,5 @@ type NodeC { } type Query { - queryNodeA: NodeA @graphQL(url: "http://upstream/graphql", name: "nodeA", depth: 3) + queryNodeA: NodeA @graphQL(url: "http://upstream/graphql", name: "nodeA") } diff --git a/tests/execution/graphql-conformance-019.md b/tests/execution/graphql-conformance-019.md index 4ffaf83463..949ef7e02f 100644 --- a/tests/execution/graphql-conformance-019.md +++ b/tests/execution/graphql-conformance-019.md @@ -6,7 +6,7 @@ schema @server(port: 8000, hostname: "0.0.0.0") { } type Query { - queryNodeA: NodeA @graphQL(url: "http://upstream/graphql", name: "nodeA", depth: 3) + queryNodeA: NodeA @graphQL(url: "http://upstream/graphql", name: "nodeA") } type NodeA {