Skip to content

ContextDataFetcherDecorator ignores subscriptions when "subscription" type is renamed #590

Closed
@etienne-sf

Description

@etienne-sf

Hello,

In GraphQL, it is possible to rename the query, mutation and subscription objects in the schema. When doing so, data fetcher that returns Flux won't work properly in spring-graphql.

Sample schema:

schema
  query: MyQuery
  subscription: MySubscription
}
type MyQuery {
  greetings: [String]
}
type MySubscription {
  greetings: String
}

For subscription, this is due to this line in org.springframework.graphql.execution.ContextDataFetcherDecorator:

boolean handlesSubscription = parent.getName().equals("Subscription");

To check that, I executed the test provided below, after changing Subscription to MySubscription in ContextDataFetcherDecorator: doing this makes the test pass ok.

My first attempt to create a unit test generates wrong failures for Query and Mutation, as the queryFetcher and mutationFetcher hard coded the Query and Mutation name. So I updated the test below which is ok for query and mutation.

I added the test below in the org.springframework.graphql.execution.ContextDataFetcherDecoratorTests. This test is basically a copy/paste of the fluxDataFetcher() and fluxDataFetcherSubscription() tests:

	@Test
	void fluxDataFetcherRenamedSubscription() throws Exception {
		GraphQL graphQl = GraphQlSetup.schemaContent("" +
				"schema {" +
				"  query: MyQuery" +
				"  mutation: MyMutation" +
				"  subscription: MySubscription" +
				"}" +
				"type MyQuery { " +
				"  querySomeData: [String] " +
				"} " +
				"type MyMutation { " +
				"  createSomeData: [String] " +
				"} " +
				"type MySubscription { " +
				"  greetings: String " +
				"}")
			// A query fetcher that returns a Flux (to check Flux behavior on Subscription and non Subscription)
			// (need to use the dataFetcher method as queryFetcher hard coded the query name to "Query")
			.dataFetcher("MyQuery", "querySomeData", (env) ->
				Mono.delay(Duration.ofMillis(50))
						.flatMapMany((aLong) -> Flux.deferContextual((context) -> {
							String name = context.get("name");
							return Flux.just("Hi", "Bonjour", "Hola").map((s) -> s + " " + name);
						})))
			// (need to use the dataFetcher method as queryFetcher hard coded the mutation name to "Mutation")
			.dataFetcher("MyMutation", "createSomeData", (env) ->
				Mono.delay(Duration.ofMillis(50))
						.flatMapMany((aLong) -> Flux.deferContextual((context) -> {
							String name = context.get("name");
							return Flux.just("Hi", "Bonjour", "Hola").map((s) -> s + " " + name);
						})))
			.dataFetcher("MySubscription", "greetings", (env) ->
				Mono.delay(Duration.ofMillis(50))
						.flatMapMany((aLong) -> Flux.deferContextual((context) -> {
							String name = context.get("name");
							return Flux.just("Hi", "Bonjour", "Hola").map((s) -> s + " " + name);
						})))
			.toGraphQl();

		//////////////////////////////////////////////////////////////////////////////////////
		// MyQuery check
		ExecutionInput input = ExecutionInput.newExecutionInput().query("{ querySomeData }").build();
		ReactorContextManager.setReactorContext(Context.of("name", "007"), input.getGraphQLContext());

		ExecutionResult result = graphQl.executeAsync(input).get();

		List<String> data = ResponseHelper.forResult(result).toList("querySomeData", String.class);
		assertThat(data).containsExactly("Hi 007", "Bonjour 007", "Hola 007");

		//////////////////////////////////////////////////////////////////////////////////////
		// MyMutation check
		input = ExecutionInput.newExecutionInput().query("mutation { createSomeData }").build();
		ReactorContextManager.setReactorContext(Context.of("name", "007"), input.getGraphQLContext());

		result = graphQl.executeAsync(input).get();

		data = ResponseHelper.forResult(result).toList("createSomeData", String.class);
		assertThat(data).containsExactly("Hi 007", "Bonjour 007", "Hola 007");

		//////////////////////////////////////////////////////////////////////////////////////
		// MySubscription check
		input = ExecutionInput.newExecutionInput().query("subscription { greetings }").build();
		ReactorContextManager.setReactorContext(Context.of("name", "007"), input.getGraphQLContext());

		result = graphQl.executeAsync(input).get();

		Flux<String> greetingsFlux = ResponseHelper.forSubscription(result)
				.map(response -> response.toEntity("greetings", String.class));

		StepVerifier.create(greetingsFlux)
				.expectNext("Hi 007", "Bonjour 007", "Hola 007")
				.verifyComplete();
	}

I'll try to find how to solve this issue, and propose a PR.

If you have hint, it would be nice: I don't know how to retrieve the GraphQL schema's data from the org.springframework.graphql.execution.ContextDataFetcherDecorator.createVisitor(..) method.

Etienne

Metadata

Metadata

Assignees

Labels

status: backportedAn issue that has been backported to maintenance branchestype: enhancementA general enhancement

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions