Extract RequestOutput base class

See gh-229
This commit is contained in:
rstoyanchev
2022-01-07 16:07:11 +00:00
parent 44efeb6473
commit c84f342daf
13 changed files with 148 additions and 88 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -36,6 +36,7 @@ import reactor.core.publisher.Mono;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.graphql.GraphQlService;
import org.springframework.graphql.RequestInput;
import org.springframework.graphql.RequestOutput;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
@@ -351,8 +352,9 @@ public class GraphQlTesterTests {
if (!CollectionUtils.isEmpty(errors)) {
builder.addErrors(errors);
}
RequestInput input = new RequestInput("{}", null, null, null, "1");
ExecutionResult result = builder.build();
given(this.service.execute(this.inputCaptor.capture())).willReturn(Mono.just(result));
given(this.service.execute(this.inputCaptor.capture())).willReturn(Mono.just(new RequestOutput(input, result)));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,7 +16,6 @@
package org.springframework.graphql;
import graphql.ExecutionResult;
import reactor.core.publisher.Mono;
/**
@@ -33,6 +32,6 @@ public interface GraphQlService {
* @param input container for the GraphQL request input
* @return the execution result
*/
Mono<ExecutionResult> execute(RequestInput input);
Mono<RequestOutput> execute(RequestInput input);
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.graphql;
import java.util.List;
import java.util.Map;
import graphql.ExecutionResult;
import graphql.GraphQLError;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* An {@link ExecutionResult} that also holds the {@link RequestInput}.
*
* @author Rossen Stoyanchev
* @since 1.0.0
*/
public class RequestOutput implements ExecutionResult {
private final RequestInput requestInput;
private final ExecutionResult executionResult;
/**
* Create an instance that wraps the given {@link ExecutionResult}.
* @param requestInput the container for the GraphQL input
* @param executionResult the result of performing a graphql query
*/
public RequestOutput(RequestInput requestInput, ExecutionResult executionResult) {
Assert.notNull(requestInput, "RequestInput is required.");
Assert.notNull(executionResult, "ExecutionResult is required.");
this.requestInput = requestInput;
this.executionResult = executionResult;
}
/**
* Return the associated {@link RequestInput} used for the execution.
* @return the associated WebInput
*/
public RequestInput getRequestInput() {
return this.requestInput;
}
@Nullable
@Override
public <T> T getData() {
return this.executionResult.getData();
}
@Override
public boolean isDataPresent() {
return this.executionResult.isDataPresent();
}
public List<GraphQLError> getErrors() {
return this.executionResult.getErrors();
}
@Nullable
public Map<Object, Object> getExtensions() {
return this.executionResult.getExtensions();
}
@Override
public Map<String, Object> toSpecification() {
return this.executionResult.toSpecification();
}
@Override
public String toString() {
return this.executionResult.toString();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,7 +20,6 @@ import java.util.ArrayList;
import java.util.List;
import graphql.ExecutionInput;
import graphql.ExecutionResult;
import graphql.GraphQL;
import graphql.GraphQLContext;
import org.dataloader.DataLoaderRegistry;
@@ -28,6 +27,7 @@ import reactor.core.publisher.Mono;
import org.springframework.graphql.GraphQlService;
import org.springframework.graphql.RequestInput;
import org.springframework.graphql.RequestOutput;
/**
* {@link GraphQlService} that uses a {@link GraphQlSource} to obtain a
@@ -59,12 +59,13 @@ public class ExecutionGraphQlService implements GraphQlService {
@Override
public final Mono<ExecutionResult> execute(RequestInput requestInput) {
public final Mono<RequestOutput> execute(RequestInput requestInput) {
return Mono.deferContextual((contextView) -> {
ExecutionInput executionInput = requestInput.toExecutionInput();
ReactorContextManager.setReactorContext(contextView, executionInput);
executionInput = registerDataLoaders(executionInput);
return Mono.fromFuture(this.graphQlSource.graphQl().executeAsync(executionInput));
return Mono.fromFuture(this.graphQlSource.graphQl().executeAsync(executionInput))
.map(result -> new RequestOutput(requestInput, result));
});
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,9 +25,9 @@ import graphql.ExecutionResult;
import graphql.ExecutionResultImpl;
import graphql.GraphQLError;
import org.springframework.graphql.RequestOutput;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Decorate an {@link ExecutionResult}, provide a way to {@link #transform(Consumer)
@@ -37,15 +37,12 @@ import org.springframework.util.Assert;
* @author Rossen Stoyanchev
* @since 1.0.0
*/
public class WebOutput implements ExecutionResult {
private final WebInput input;
private final ExecutionResult executionResult;
public class WebOutput extends RequestOutput {
@Nullable
private final HttpHeaders responseHeaders;
/**
* Create an instance that wraps the given {@link ExecutionResult}.
* @param input the container for the GraphQL input
@@ -56,44 +53,14 @@ public class WebOutput implements ExecutionResult {
}
private WebOutput(WebInput input, ExecutionResult executionResult, @Nullable HttpHeaders responseHeaders) {
Assert.notNull(input, "WebInput is required.");
Assert.notNull(executionResult, "ExecutionResult is required.");
this.input = input;
this.executionResult = executionResult;
super(input, executionResult);
this.responseHeaders = responseHeaders;
}
/**
* Return the associated {@link WebInput} used for the execution.
* @return the associated WebInput
*/
public WebInput getWebInput() {
return this.input;
}
@Nullable
@Override
public <T> T getData() {
return this.executionResult.getData();
}
@Override
public boolean isDataPresent() {
return this.executionResult.isDataPresent();
}
public List<GraphQLError> getErrors() {
return this.executionResult.getErrors();
}
@Nullable
public Map<Object, Object> getExtensions() {
return this.executionResult.getExtensions();
}
@Override
public Map<String, Object> toSpecification() {
return this.executionResult.toSpecification();
public WebInput getRequestInput() {
return (WebInput) super.getRequestInput();
}
/**
@@ -120,6 +87,7 @@ public class WebOutput implements ExecutionResult {
return builder.build();
}
/**
* Builder to transform a {@link WebOutput}.
*/
@@ -139,7 +107,7 @@ public class WebOutput implements ExecutionResult {
private HttpHeaders headers;
private Builder(WebOutput output) {
this.input = output.getWebInput();
this.input = output.getRequestInput();
this.data = output.getData();
this.errors = output.getErrors();
this.extensions = output.getExtensions();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -149,7 +149,7 @@ public class GraphQlResponse {
}
@SuppressWarnings("BlockingMethodInNonBlockingContext")
public static Flux<GraphQlResponse> forSubscription(Mono<ExecutionResult> resultMono) {
public static Flux<GraphQlResponse> forSubscription(Mono<? extends ExecutionResult> resultMono) {
ExecutionResult result = resultMono.block(Duration.ofSeconds(5));
assertThat(result).isNotNull();
return forSubscription(result);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,7 +22,6 @@ import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import graphql.ExecutionResult;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
@@ -31,6 +30,7 @@ import reactor.core.publisher.Mono;
import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.RequestInput;
import org.springframework.graphql.RequestOutput;
import org.springframework.graphql.data.method.annotation.BatchMapping;
import org.springframework.stereotype.Controller;
@@ -70,7 +70,7 @@ public class BatchMappingInvocationTests extends BatchMappingTestSupport {
" }" +
"}";
Mono<ExecutionResult> resultMono = createGraphQlService(controller)
Mono<RequestOutput> resultMono = createGraphQlService(controller)
.execute(new RequestInput(query, null, null, null, "1"));
List<Course> actualCourses = GraphQlResponse.from(resultMono).toList("courses", Course.class);
@@ -103,7 +103,7 @@ public class BatchMappingInvocationTests extends BatchMappingTestSupport {
" }" +
"}";
Mono<ExecutionResult> resultMono = createGraphQlService(controller)
Mono<RequestOutput> resultMono = createGraphQlService(controller)
.execute(new RequestInput(query, null, null, null, "1"));
List<Course> actualCourses = GraphQlResponse.from(resultMono).toList("courses", Course.class);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,7 +23,6 @@ import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import graphql.ExecutionResult;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
@@ -33,6 +32,7 @@ import reactor.util.context.Context;
import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.RequestInput;
import org.springframework.graphql.RequestOutput;
import org.springframework.graphql.data.method.annotation.BatchMapping;
import org.springframework.graphql.execution.ReactorContextManager;
import org.springframework.graphql.security.SecurityContextThreadLocalAccessor;
@@ -92,7 +92,7 @@ public class BatchMappingPrincipalMethodArgumentResolverTests extends BatchMappi
}
private void testBatchLoading(PrincipalCourseController controller, Function<Context, Context> contextWriter) {
Mono<ExecutionResult> resultMono = Mono.delay(Duration.ofMillis(10))
Mono<RequestOutput> resultMono = Mono.delay(Duration.ofMillis(10))
.flatMap(aLong -> {
String query = "{ courses { id instructor { id } } }";
return createGraphQlService(controller).execute(new RequestInput(query, null, null, null, "1"));

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,7 +19,6 @@ import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
import graphql.ExecutionResult;
import graphql.GraphQLContext;
import graphql.schema.DataFetchingEnvironment;
import org.dataloader.DataLoader;
@@ -38,6 +37,7 @@ import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.GraphQlService;
import org.springframework.graphql.GraphQlSetup;
import org.springframework.graphql.RequestInput;
import org.springframework.graphql.RequestOutput;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.MutationMapping;
import org.springframework.graphql.data.method.annotation.QueryMapping;
@@ -70,7 +70,7 @@ public class SchemaMappingInvocationTests {
" }" +
"}";
Mono<ExecutionResult> resultMono = graphQlService().execute(new RequestInput(query, null, null, null, "1"));
Mono<RequestOutput> resultMono = graphQlService().execute(new RequestInput(query, null, null, null, "1"));
Book book = GraphQlResponse.from(resultMono).toEntity("bookById", Book.class);
assertThat(book.getId()).isEqualTo(1);
@@ -90,7 +90,7 @@ public class SchemaMappingInvocationTests {
" }" +
"}";
Mono<ExecutionResult> resultMono = graphQlService().execute(new RequestInput(query, null, null, null, "1"));
Mono<RequestOutput> resultMono = graphQlService().execute(new RequestInput(query, null, null, null, "1"));
List<Book> bookList = GraphQlResponse.from(resultMono).toList("booksByCriteria", Book.class);
assertThat(bookList).hasSize(2);
@@ -107,7 +107,7 @@ public class SchemaMappingInvocationTests {
" }" +
"}";
Mono<ExecutionResult> resultMono = graphQlService().execute(new RequestInput(query, null, null, null, "1"));
Mono<RequestOutput> resultMono = graphQlService().execute(new RequestInput(query, null, null, null, "1"));
List<Book> bookList = GraphQlResponse.from(resultMono).toList("booksByProjectedArguments", Book.class);
assertThat(bookList).hasSize(2);
@@ -124,7 +124,7 @@ public class SchemaMappingInvocationTests {
" }" +
"}";
Mono<ExecutionResult> resultMono = graphQlService().execute(new RequestInput(query, null, null, null, "1"));
Mono<RequestOutput> resultMono = graphQlService().execute(new RequestInput(query, null, null, null, "1"));
List<Book> bookList = GraphQlResponse.from(resultMono).toList("booksByProjectedCriteria", Book.class);
assertThat(bookList).hasSize(2);
@@ -149,7 +149,7 @@ public class SchemaMappingInvocationTests {
return executionInput;
});
Mono<ExecutionResult> resultMono = graphQlService().execute(requestInput);
Mono<RequestOutput> resultMono = graphQlService().execute(requestInput);
Author author = GraphQlResponse.from(resultMono).toEntity("authorById", Author.class);
assertThat(author.getId()).isEqualTo(101);
@@ -169,7 +169,7 @@ public class SchemaMappingInvocationTests {
" }" +
"}";
Mono<ExecutionResult> resultMono = graphQlService()
Mono<RequestOutput> resultMono = graphQlService()
.execute(new RequestInput(operation, null, null, null, "1"));
Author author = GraphQlResponse.from(resultMono).toEntity("addAuthor", Author.class);
@@ -187,7 +187,7 @@ public class SchemaMappingInvocationTests {
" }" +
"}";
Mono<ExecutionResult> resultMono = graphQlService()
Mono<RequestOutput> resultMono = graphQlService()
.execute(new RequestInput(operation, null, null, null, "1"));
Flux<Book> bookFlux = GraphQlResponse.forSubscription(resultMono)

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,7 +20,6 @@ import java.security.Principal;
import java.time.Duration;
import java.util.function.Function;
import graphql.ExecutionResult;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
@@ -31,11 +30,11 @@ import reactor.test.StepVerifier;
import reactor.util.context.Context;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.core.MethodParameter;
import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.GraphQlSetup;
import org.springframework.graphql.RequestInput;
import org.springframework.graphql.RequestOutput;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.graphql.data.method.annotation.SubscriptionMapping;
import org.springframework.graphql.execution.ExecutionGraphQlService;
@@ -104,7 +103,7 @@ public class SchemaMappingPrincipalMethodArgumentResolverTests {
}
private void testQuery(String field, Function<Context, Context> contextWriter) {
Mono<ExecutionResult> resultMono = executeAsync(
Mono<RequestOutput> resultMono = executeAsync(
"type Query { " + field + ": String }", "{ " + field + " }", contextWriter);
String greeting = GraphQlResponse.from(resultMono).toEntity(field, String.class);
@@ -137,7 +136,7 @@ public class SchemaMappingPrincipalMethodArgumentResolverTests {
private void testSubscription(Function<Context, Context> contextModifier) {
String field = "greetingSubscription";
Mono<ExecutionResult> resultMono = executeAsync(
Mono<RequestOutput> resultMono = executeAsync(
"type Query { greeting: String } type Subscription { " + field + ": String }",
"subscription Greeting { " + field + " }",
contextModifier);
@@ -151,7 +150,7 @@ public class SchemaMappingPrincipalMethodArgumentResolverTests {
}
private Mono<ExecutionResult> executeAsync(
private Mono<RequestOutput> executeAsync(
String schema, String op, Function<Context, Context> contextWriter) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,7 +19,6 @@ import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import graphql.ExecutionResult;
import org.dataloader.DataLoader;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
@@ -32,6 +31,7 @@ import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.GraphQlService;
import org.springframework.graphql.GraphQlSetup;
import org.springframework.graphql.RequestInput;
import org.springframework.graphql.RequestOutput;
import static org.assertj.core.api.Assertions.assertThat;
@@ -76,7 +76,7 @@ public class BatchLoadingTests {
.dataLoaders(this.registry)
.toGraphQlService();
Mono<ExecutionResult> resultMono = service.execute(new RequestInput(query, null, null, null, "1"));
Mono<RequestOutput> resultMono = service.execute(new RequestInput(query, null, null, null, "1"));
List<Book> books = GraphQlResponse.from(resultMono).toList("booksByCriteria", Book.class);
assertThat(books).hasSize(2);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,13 +19,13 @@ import java.util.Arrays;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import graphql.ExecutionResult;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.GraphQlSetup;
import org.springframework.graphql.RequestInput;
import org.springframework.graphql.RequestOutput;
import static org.assertj.core.api.Assertions.assertThat;
@@ -83,7 +83,7 @@ public class ClassNameTypeResolverTests {
" }" +
"}";
Mono<ExecutionResult> resultMono = graphQlSetup.queryFetcher("animals", env -> animalList)
Mono<RequestOutput> resultMono = graphQlSetup.queryFetcher("animals", env -> animalList)
.toGraphQlService()
.execute(new RequestInput(query, null, null, null, "1"));
@@ -125,7 +125,7 @@ public class ClassNameTypeResolverTests {
ClassNameTypeResolver typeResolver = new ClassNameTypeResolver();
typeResolver.addMapping(Tree.class, "Plant");
Mono<ExecutionResult> result = graphQlSetup.queryFetcher("sightings", env -> animalAndPlantList)
Mono<RequestOutput> result = graphQlSetup.queryFetcher("sightings", env -> animalAndPlantList)
.typeResolver(typeResolver)
.toGraphQlService()
.execute(new RequestInput(query, null, null, null, "1"));

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,12 +22,13 @@ import java.util.Collections;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import graphql.ExecutionResult;
import graphql.ExecutionResultImpl;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import org.springframework.graphql.RequestInput;
import org.springframework.graphql.RequestOutput;
import org.springframework.http.HttpHeaders;
import static org.assertj.core.api.Assertions.assertThat;
@@ -45,7 +46,7 @@ public class WebInterceptorTests {
void interceptorOrder() {
StringBuilder output = new StringBuilder();
WebGraphQlHandler handler = WebGraphQlHandler.builder((input) -> emptyExecutionResult())
WebGraphQlHandler handler = WebGraphQlHandler.builder(this::emptyExecutionResult)
.interceptors(Arrays.asList(
new OrderInterceptor(1, output),
new OrderInterceptor(2, output),
@@ -61,7 +62,7 @@ public class WebInterceptorTests {
Function<WebOutput, WebOutput> headerFunction = (output) ->
output.transform((builder) -> builder.responseHeader("testHeader", "testValue"));
WebGraphQlHandler handler = WebGraphQlHandler.builder((input) -> emptyExecutionResult())
WebGraphQlHandler handler = WebGraphQlHandler.builder(this::emptyExecutionResult)
.interceptor((input, next) -> next.next(input).map(headerFunction))
.build();
@@ -77,7 +78,7 @@ public class WebInterceptorTests {
WebGraphQlHandler handler = WebGraphQlHandler
.builder((input) -> {
actualName.set(input.toExecutionInput().getOperationName());
return emptyExecutionResult();
return emptyExecutionResult(input);
})
.interceptor((webInput, next) -> {
webInput.configureExecutionInput((input, builder) -> builder.operationName("testOp").build());
@@ -90,8 +91,8 @@ public class WebInterceptorTests {
assertThat(actualName.get()).isEqualTo("testOp");
}
private Mono<ExecutionResult> emptyExecutionResult() {
return Mono.just(ExecutionResultImpl.newExecutionResult().build());
private Mono<RequestOutput> emptyExecutionResult(RequestInput input) {
return Mono.just(new RequestOutput(input, ExecutionResultImpl.newExecutionResult().build()));
}
private static class OrderInterceptor implements WebInterceptor {