Skip to content

Commit 0b9c439

Browse files
Move the ParsedDocument hack (#7610)
1 parent d9f10a9 commit 0b9c439

10 files changed

+162
-140
lines changed

apollo-router/src/services/router/service.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ pub(crate) async fn from_supergraph_mock_callback_and_configuration(
146146
supergraph_service
147147
});
148148

149-
let (_, supergraph_creator) = crate::TestHarness::builder()
149+
let (_, _, supergraph_creator) = crate::TestHarness::builder()
150150
.configuration(configuration.clone())
151151
.supergraph_hook(move |_| supergraph_service.clone().boxed())
152152
.build_common()
@@ -196,7 +196,7 @@ pub(crate) async fn empty() -> impl Service<
196196
.expect_clone()
197197
.returning(MockSupergraphService::new);
198198

199-
let (_, supergraph_creator) = crate::TestHarness::builder()
199+
let (_, _, supergraph_creator) = crate::TestHarness::builder()
200200
.configuration(Default::default())
201201
.supergraph_hook(move |_| supergraph_service.clone().boxed())
202202
.build_common()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
source: apollo-router/src/services/router/tests.rs
3+
expression: response
4+
snapshot_kind: text
5+
---
6+
{
7+
"errors": [
8+
{
9+
"message": "Value \"C\" does not exist in \"InputEnum\" enum.",
10+
"locations": [
11+
{
12+
"line": 1,
13+
"column": 21
14+
}
15+
],
16+
"extensions": {
17+
"code": "GRAPHQL_VALIDATION_FAILED"
18+
}
19+
}
20+
]
21+
}

apollo-router/src/services/router/tests.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -832,3 +832,74 @@ fn batch_with_three_unique_queries() -> Request<RouterBody> {
832832
router::body::from_bytes(result)
833833
})
834834
}
835+
836+
const ENUM_SCHEMA: &str = r#"schema
837+
@link(url: "https://specs.apollo.dev/link/v1.0")
838+
@link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION) {
839+
query: Query
840+
}
841+
directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA
842+
directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE
843+
directive @join__field(graph: join__Graph, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION
844+
directive @join__graph(name: String!, url: String!) on ENUM_VALUE
845+
directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE
846+
directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true, isInterfaceObject: Boolean! = false) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR
847+
directive @join__unionMember(graph: join__Graph!, member: String!) repeatable on UNION
848+
849+
scalar link__Import
850+
851+
enum link__Purpose {
852+
SECURITY
853+
EXECUTION
854+
}
855+
856+
scalar join__FieldSet
857+
858+
enum join__Graph {
859+
USER @join__graph(name: "user", url: "http://localhost:4001/graphql")
860+
ORGA @join__graph(name: "orga", url: "http://localhost:4002/graphql")
861+
}
862+
type Query @join__type(graph: USER) @join__type(graph: ORGA){
863+
test(input: InputEnum): String @join__field(graph: USER)
864+
}
865+
866+
enum InputEnum @join__type(graph: USER) @join__type(graph: ORGA) {
867+
A
868+
B
869+
}"#;
870+
871+
// Companion test: services::supergraph::tests::invalid_input_enum
872+
#[tokio::test]
873+
async fn invalid_input_enum() {
874+
let service = crate::TestHarness::builder()
875+
.configuration_json(serde_json::json!({
876+
"include_subgraph_errors": {
877+
"all": true,
878+
},
879+
}))
880+
.unwrap()
881+
.schema(ENUM_SCHEMA)
882+
.build_router()
883+
.await
884+
.unwrap();
885+
886+
let request = supergraph::Request::fake_builder()
887+
.query("query { test(input: C) }")
888+
.build()
889+
.unwrap()
890+
.try_into()
891+
.unwrap();
892+
let response = service
893+
.clone()
894+
.oneshot(request)
895+
.await
896+
.unwrap()
897+
.next_response()
898+
.await
899+
.expect("should have one response")
900+
.unwrap();
901+
902+
let response: serde_json::Value = serde_json::from_slice(&response).unwrap();
903+
904+
insta::assert_json_snapshot!(response);
905+
}

apollo-router/src/services/supergraph/service.rs

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,6 @@ async fn service_call(
205205
planning,
206206
body.operation_name.clone(),
207207
context.clone(),
208-
schema.clone(),
209208
// We cannot assume that the query is present as it may have been modified by coprocessors or plugins.
210209
// There is a deeper issue here in that query analysis is doing a bunch of stuff that it should not and
211210
// places the results in context. Therefore plugins that have modified the query won't actually take effect.
@@ -787,31 +786,8 @@ async fn plan_query(
787786
mut planning: CachingQueryPlanner<QueryPlannerService>,
788787
operation_name: Option<String>,
789788
context: Context,
790-
schema: Arc<Schema>,
791789
query_str: String,
792790
) -> Result<QueryPlannerResponse, CacheResolverError> {
793-
// FIXME: we have about 80 tests creating a supergraph service and crafting a supergraph request for it
794-
// none of those tests create an executable document to put it in the context, and the document cannot be created
795-
// from inside the supergraph request fake builder, because it needs a schema matching the query.
796-
// So while we are updating the tests to create a document manually, this here will make sure current
797-
// tests will pass.
798-
// During a regular request, `ParsedDocument` is already populated during query analysis.
799-
// Some tests do populate the document, so we only do it if it's not already there.
800-
if !context.extensions().with_lock(|lock| {
801-
lock.contains_key::<crate::services::layers::query_analysis::ParsedDocument>()
802-
}) {
803-
let doc = crate::spec::Query::parse_document(
804-
&query_str,
805-
operation_name.as_deref(),
806-
&schema,
807-
&Configuration::default(),
808-
)
809-
.map_err(crate::error::QueryPlannerError::from)?;
810-
context.extensions().with_lock(|lock| {
811-
lock.insert::<crate::services::layers::query_analysis::ParsedDocument>(doc)
812-
});
813-
}
814-
815791
let qpr = planning
816792
.call(
817793
query_planner::CachingRequest::builder()

apollo-router/src/services/supergraph/snapshots/apollo_router__services__supergraph__tests__invalid_input_enum-2.snap

Lines changed: 0 additions & 15 deletions
This file was deleted.

apollo-router/src/services/supergraph/snapshots/apollo_router__services__supergraph__tests__invalid_input_enum.snap

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,15 @@
11
---
22
source: apollo-router/src/services/supergraph/tests.rs
33
expression: response
4+
snapshot_kind: text
45
---
56
{
67
"errors": [
78
{
8-
"message": "Value \"C\" does not exist in \"InputEnum\" enum.",
9-
"locations": [
10-
{
11-
"line": 1,
12-
"column": 21
13-
}
14-
],
9+
"message": "invalid variable `$input`: found JSON string for GraphQL type `InputEnum`",
1510
"extensions": {
16-
"code": "GRAPHQL_VALIDATION_FAILED"
11+
"name": "input",
12+
"code": "VALIDATION_INVALID_TYPE_VARIABLE"
1713
}
1814
}
1915
]

apollo-router/src/services/supergraph/tests.rs

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3634,41 +3634,30 @@ const ENUM_SCHEMA: &str = r#"schema
36343634
B
36353635
}"#;
36363636

3637+
// Companion test: services::router::tests::invalid_input_enum
36373638
#[tokio::test]
36383639
async fn invalid_input_enum() {
36393640
let service = TestHarness::builder()
3640-
.configuration_json(serde_json::json!({"include_subgraph_errors": { "all": true } }))
3641+
.configuration_json(serde_json::json!({
3642+
"include_subgraph_errors": {
3643+
"all": true,
3644+
},
3645+
}))
36413646
.unwrap()
36423647
.schema(ENUM_SCHEMA)
36433648
//.extra_plugin(subgraphs)
36443649
.build_supergraph()
36453650
.await
36463651
.unwrap();
36473652

3648-
let request = supergraph::Request::fake_builder()
3649-
.query("query { test(input: C) }")
3650-
.context(defer_context())
3651-
// Request building here
3652-
.build()
3653-
.unwrap();
3654-
let response = service
3655-
.clone()
3656-
.oneshot(request)
3657-
.await
3658-
.unwrap()
3659-
.next_response()
3660-
.await
3661-
.unwrap();
3662-
3663-
insta::assert_json_snapshot!(response);
3664-
36653653
let request = supergraph::Request::fake_builder()
36663654
.query("query($input: InputEnum) { test(input: $input) }")
36673655
.variable("input", "INVALID")
36683656
.context(defer_context())
36693657
// Request building here
36703658
.build()
36713659
.unwrap();
3660+
36723661
let response = service
36733662
.oneshot(request)
36743663
.await

apollo-router/src/test_harness.rs

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ impl<'a> TestHarness<'a> {
272272

273273
pub(crate) async fn build_common(
274274
self,
275-
) -> Result<(Arc<Configuration>, SupergraphCreator), BoxError> {
275+
) -> Result<(Arc<Configuration>, Arc<Schema>, SupergraphCreator), BoxError> {
276276
let builder = if self.schema.is_none() {
277277
self.subgraph_hook(|subgraph_name, default| match subgraph_name {
278278
"products" => canned::products_subgraph().boxed(),
@@ -297,32 +297,57 @@ impl<'a> TestHarness<'a> {
297297
let supergraph_creator = YamlRouterFactory
298298
.inner_create_supergraph(
299299
config.clone(),
300-
schema,
300+
schema.clone(),
301301
None,
302302
None,
303303
Some(builder.extra_plugins),
304304
Default::default(),
305305
)
306306
.await?;
307307

308-
Ok((config, supergraph_creator))
308+
Ok((config, schema, supergraph_creator))
309309
}
310310

311311
/// Builds the supergraph service
312312
pub async fn build_supergraph(self) -> Result<supergraph::BoxCloneService, BoxError> {
313-
let (_config, supergraph_creator) = self.build_common().await?;
313+
let (config, schema, supergraph_creator) = self.build_common().await?;
314314

315-
Ok(tower::service_fn(move |request| {
315+
Ok(tower::service_fn(move |request: supergraph::Request| {
316316
let router = supergraph_creator.make();
317317

318+
// The supergraph service expects a ParsedDocument in the context. In the real world,
319+
// that is always populated by the router service. For the testing harness, however,
320+
// tests normally craft a supergraph request manually, and it's inconvenient to
321+
// manually populate the ParsedDocument. Instead of doing it many different ways
322+
// over and over in different tests, we simulate that part of the router service here.
323+
let body = request.supergraph_request.body();
324+
// If we don't have a query we definitely won't have a parsed document.
325+
if let Some(query_str) = body.query.as_deref() {
326+
let operation_name = body.operation_name.as_deref();
327+
if !request.context.extensions().with_lock(|lock| {
328+
lock.contains_key::<crate::services::layers::query_analysis::ParsedDocument>()
329+
}) {
330+
let doc = crate::spec::Query::parse_document(
331+
query_str,
332+
operation_name,
333+
&schema,
334+
&config,
335+
)
336+
.expect("parse error in test");
337+
request.context.extensions().with_lock(|lock| {
338+
lock.insert::<crate::services::layers::query_analysis::ParsedDocument>(doc)
339+
});
340+
}
341+
}
342+
318343
async move { router.oneshot(request).await }
319344
})
320345
.boxed_clone())
321346
}
322347

323348
/// Builds the router service
324349
pub async fn build_router(self) -> Result<router::BoxCloneService, BoxError> {
325-
let (config, supergraph_creator) = self.build_common().await?;
350+
let (config, _schema, supergraph_creator) = self.build_common().await?;
326351
let router_creator = RouterCreator::new(
327352
QueryAnalysisLayer::new(supergraph_creator.schema(), Arc::clone(&config)).await,
328353
Arc::new(PersistedQueryLayer::new(&config).await.unwrap()),
@@ -350,7 +375,7 @@ impl<'a> TestHarness<'a> {
350375
use crate::axum_factory::axum_http_server_factory::make_axum_router;
351376
use crate::router_factory::RouterFactory;
352377

353-
let (config, supergraph_creator) = self.build_common().await?;
378+
let (config, _schema, supergraph_creator) = self.build_common().await?;
354379
let router_creator = RouterCreator::new(
355380
QueryAnalysisLayer::new(supergraph_creator.schema(), Arc::clone(&config)).await,
356381
Arc::new(PersistedQueryLayer::new(&config).await.unwrap()),

apollo-router/tests/integration/introspection.rs

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -107,51 +107,6 @@ async fn two_operations() {
107107
"###);
108108
}
109109

110-
#[tokio::test]
111-
async fn operation_name_error() {
112-
let request = Request::fake_builder()
113-
.query(
114-
r#"
115-
query ThisOp { me { id } }
116-
query OtherOp { me { id } }
117-
"#,
118-
)
119-
.build()
120-
.unwrap();
121-
let response = make_request(request).await;
122-
insta::assert_json_snapshot!(response, @r###"
123-
{
124-
"errors": [
125-
{
126-
"message": "Must provide operation name if query contains multiple operations.",
127-
"extensions": {
128-
"code": "GRAPHQL_VALIDATION_FAILED"
129-
}
130-
}
131-
]
132-
}
133-
"###);
134-
135-
let request = Request::fake_builder()
136-
.query("query ThisOp { me { id } }")
137-
.operation_name("NonExistentOp")
138-
.build()
139-
.unwrap();
140-
let response = make_request(request).await;
141-
insta::assert_json_snapshot!(response, @r###"
142-
{
143-
"errors": [
144-
{
145-
"message": "Unknown operation named \"NonExistentOp\"",
146-
"extensions": {
147-
"code": "GRAPHQL_UNKNOWN_OPERATION_NAME"
148-
}
149-
}
150-
]
151-
}
152-
"###);
153-
}
154-
155110
#[tokio::test]
156111
async fn mixed() {
157112
let request = Request::fake_builder()

0 commit comments

Comments
 (0)