Merge branch '1.2.x'
This commit is contained in:
@@ -18,16 +18,16 @@ ext {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
checkstyle "io.spring.javaformat:spring-javaformat-checkstyle:${javaFormatVersion}"
|
||||
checkstyle("com.puppycrawl.tools:checkstyle:${checkstyle.toolVersion}")
|
||||
checkstyle("io.spring.javaformat:spring-javaformat-checkstyle:${javaFormatVersion}")
|
||||
|
||||
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}")
|
||||
implementation("org.jetbrains.kotlin:kotlin-compiler-embeddable:${kotlinVersion}")
|
||||
implementation("io.spring.javaformat:spring-javaformat-gradle-plugin:${javaFormatVersion}")
|
||||
}
|
||||
|
||||
checkstyle {
|
||||
def archive = configurations.checkstyle.filter { it.name.startsWith("spring-javaformat-checkstyle")}
|
||||
config = resources.text.fromArchiveEntry(archive, "io/spring/javaformat/checkstyle/checkstyle.xml")
|
||||
toolVersion = 8.11
|
||||
toolVersion = "10.12.4"
|
||||
}
|
||||
|
||||
gradlePlugin {
|
||||
|
||||
@@ -1 +1 @@
|
||||
javaFormatVersion=0.0.28
|
||||
javaFormatVersion=0.0.41
|
||||
@@ -23,6 +23,7 @@ import org.gradle.api.plugins.JavaBasePlugin;
|
||||
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin;
|
||||
|
||||
import org.springframework.graphql.build.conventions.DeploymentConventions;
|
||||
import org.springframework.graphql.build.conventions.FormattingConventions;
|
||||
import org.springframework.graphql.build.conventions.JavaConventions;
|
||||
import org.springframework.graphql.build.conventions.KotlinConventions;
|
||||
|
||||
@@ -42,6 +43,7 @@ public class ConventionsPlugin implements Plugin<Project> {
|
||||
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
new FormattingConventions().apply(project);
|
||||
new JavaConventions().apply(project);
|
||||
new KotlinConventions().apply(project);
|
||||
new DeploymentConventions().apply(project);
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.springframework.graphql.build.conventions;
|
||||
|
||||
import io.spring.javaformat.gradle.FormatTask;
|
||||
import io.spring.javaformat.gradle.SpringJavaFormatPlugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.artifacts.DependencySet;
|
||||
@@ -26,8 +25,7 @@ import org.gradle.api.plugins.quality.CheckstylePlugin;
|
||||
|
||||
/**
|
||||
* Conventions that are applied in the presence of the {@link JavaBasePlugin}. When the
|
||||
* plugin is applied, the {@link SpringJavaFormatPlugin Spring Java Format} and
|
||||
* {@link CheckstylePlugin Checkstyle}.
|
||||
* plugin is applied, {@link CheckstylePlugin Checkstyle} is applied and configured.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
@@ -38,14 +36,14 @@ public class FormattingConventions {
|
||||
}
|
||||
|
||||
private void applySpringJavaFormat(Project project) {
|
||||
project.getPlugins().apply(SpringJavaFormatPlugin.class);
|
||||
project.getTasks().withType(FormatTask.class, (formatTask) -> formatTask.setEncoding("UTF-8"));
|
||||
project.getPlugins().apply(CheckstylePlugin.class);
|
||||
CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class);
|
||||
checkstyle.setToolVersion("8.43");
|
||||
checkstyle.setToolVersion("10.12.4");
|
||||
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
|
||||
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
|
||||
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
|
||||
checkstyleDependencies
|
||||
.add(project.getDependencies().create("com.puppycrawl.tools:checkstyle:" + checkstyle.getToolVersion()));
|
||||
checkstyleDependencies
|
||||
.add(project.getDependencies().create("io.spring.javaformat:spring-javaformat-checkstyle:" + version));
|
||||
}
|
||||
|
||||
@@ -31,8 +31,8 @@ public class GraphQlConfiguration {
|
||||
@Bean
|
||||
RuntimeWiringConfigurer customWiringConfigurer(BookRepository bookRepository) { // <1>
|
||||
DataFetcher<Book> dataFetcher = QuerydslDataFetcher.builder(bookRepository).single();
|
||||
return wiringBuilder -> wiringBuilder
|
||||
.type("Query", builder -> builder.dataFetcher("book", dataFetcher)); // <2>
|
||||
return (wiringBuilder) -> wiringBuilder
|
||||
.type("Query", (builder) -> builder.dataFetcher("book", dataFetcher)); // <2>
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -28,13 +28,13 @@ import org.springframework.web.servlet.function.ServerResponse;
|
||||
@Configuration
|
||||
public class GraphiQlConfiguration {
|
||||
|
||||
@Bean
|
||||
@Order(0)
|
||||
public RouterFunction<ServerResponse> graphiQlRouterFunction() {
|
||||
RouterFunctions.Builder builder = RouterFunctions.route();
|
||||
ClassPathResource graphiQlPage = new ClassPathResource("graphiql/index.html"); // <1>
|
||||
GraphiQlHandler graphiQLHandler = new GraphiQlHandler("/graphql", "", graphiQlPage); // <2>
|
||||
builder = builder.GET("/graphiql", graphiQLHandler::handleRequest); // <3>
|
||||
return builder.build(); // <4>
|
||||
}
|
||||
@Bean
|
||||
@Order(0)
|
||||
public RouterFunction<ServerResponse> graphiQlRouterFunction() {
|
||||
RouterFunctions.Builder builder = RouterFunctions.route();
|
||||
ClassPathResource graphiQlPage = new ClassPathResource("graphiql/index.html"); // <1>
|
||||
GraphiQlHandler graphiQLHandler = new GraphiQlHandler("/graphql", "", graphiQlPage); // <2>
|
||||
builder = builder.GET("/graphiql", graphiQLHandler::handleRequest); // <3>
|
||||
return builder.build(); // <4>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,20 +30,20 @@ class RequestErrorInterceptor implements WebGraphQlInterceptor {
|
||||
|
||||
@Override
|
||||
public Mono<WebGraphQlResponse> intercept(WebGraphQlRequest request, Chain chain) {
|
||||
return chain.next(request).map(response -> {
|
||||
return chain.next(request).map((response) -> {
|
||||
if (response.isValid()) {
|
||||
return response; // <1>
|
||||
}
|
||||
|
||||
List<GraphQLError> errors = response.getErrors().stream() // <2>
|
||||
.map(error -> {
|
||||
.map((error) -> {
|
||||
GraphqlErrorBuilder<?> builder = GraphqlErrorBuilder.newError();
|
||||
// ...
|
||||
return builder.build();
|
||||
})
|
||||
.toList();
|
||||
|
||||
return response.transform(builder -> builder.errors(errors).build()); // <3>
|
||||
return response.transform((builder) -> builder.errors(errors).build()); // <3>
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ class ResponseHeaderInterceptor implements WebGraphQlInterceptor {
|
||||
|
||||
@Override
|
||||
public Mono<WebGraphQlResponse> intercept(WebGraphQlRequest request, Chain chain) { // <2>
|
||||
return chain.next(request).doOnNext(response -> {
|
||||
return chain.next(request).doOnNext((response) -> {
|
||||
String value = response.getExecutionInput().getGraphQLContext().get("cookieName");
|
||||
ResponseCookie cookie = ResponseCookie.from("cookieName", value).build();
|
||||
response.getResponseHeaders().add(HttpHeaders.SET_COOKIE, cookie.toString());
|
||||
|
||||
@@ -43,4 +43,4 @@ public class GraphQlRSocketController {
|
||||
public Flux<Map<String, Object>> handleSubscription(Map<String, Object> payload) {
|
||||
return this.handler.handleSubscription(payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,6 @@ import org.springframework.util.IdGenerator;
|
||||
* to a server-side GraphQL handler or service.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
*/
|
||||
abstract class AbstractDirectGraphQlTransport implements GraphQlTransport {
|
||||
|
||||
@@ -56,7 +55,7 @@ abstract class AbstractDirectGraphQlTransport implements GraphQlTransport {
|
||||
@SuppressWarnings({"ConstantConditions", "unchecked"})
|
||||
@Override
|
||||
public Flux<GraphQlResponse> executeSubscription(GraphQlRequest request) {
|
||||
return executeInternal(toExecutionRequest(request)).flatMapMany(response -> {
|
||||
return executeInternal(toExecutionRequest(request)).flatMapMany((response) -> {
|
||||
try {
|
||||
Object data = response.getData();
|
||||
AssertionErrors.assertTrue("Not a Publisher: " + data, data instanceof Publisher);
|
||||
@@ -64,7 +63,7 @@ abstract class AbstractDirectGraphQlTransport implements GraphQlTransport {
|
||||
List<ResponseError> errors = response.getErrors();
|
||||
AssertionErrors.assertTrue("Subscription errors: " + errors, CollectionUtils.isEmpty(errors));
|
||||
|
||||
return Flux.from((Publisher<ExecutionResult>) data).map(executionResult ->
|
||||
return Flux.from((Publisher<ExecutionResult>) data).map((executionResult) ->
|
||||
new DefaultExecutionGraphQlResponse(response.getExecutionInput(), executionResult));
|
||||
}
|
||||
catch (AssertionError ex) {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.test.tester;
|
||||
|
||||
import java.time.Duration;
|
||||
@@ -35,7 +36,6 @@ import org.springframework.graphql.ResponseError;
|
||||
import org.springframework.graphql.client.AbstractGraphQlClientBuilder;
|
||||
import org.springframework.graphql.client.GraphQlClient;
|
||||
import org.springframework.graphql.client.GraphQlTransport;
|
||||
import org.springframework.graphql.support.CachingDocumentSource;
|
||||
import org.springframework.graphql.support.DocumentSource;
|
||||
import org.springframework.graphql.support.ResourceDocumentSource;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -51,6 +51,7 @@ import org.springframework.util.ClassUtils;
|
||||
* agnostic {@code GraphQlTester}. A transport specific extension can then wrap
|
||||
* this default tester by extending {@link AbstractDelegatingGraphQlTester}.
|
||||
*
|
||||
* @param <B> the type of builder
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
* @see AbstractDelegatingGraphQlTester
|
||||
@@ -86,7 +87,7 @@ public abstract class AbstractGraphQlTesterBuilder<B extends AbstractGraphQlTest
|
||||
|
||||
@Override
|
||||
public B errorFilter(Predicate<ResponseError> predicate) {
|
||||
this.errorFilter = (this.errorFilter != null ? errorFilter.and(predicate) : predicate);
|
||||
this.errorFilter = (this.errorFilter != null) ? this.errorFilter.and(predicate) : predicate;
|
||||
return self();
|
||||
}
|
||||
|
||||
@@ -115,6 +116,7 @@ public abstract class AbstractGraphQlTesterBuilder<B extends AbstractGraphQlTest
|
||||
/**
|
||||
* Allow transport-specific subclass builders to register a JSON Path
|
||||
* {@link MappingProvider} that matches the JSON encoding/decoding they use.
|
||||
* @param configurer a function applied to the JSON Path configuration
|
||||
*/
|
||||
protected void configureJsonPathConfig(Function<Configuration, Configuration> configurer) {
|
||||
this.jsonPathConfig = configurer.apply(this.jsonPathConfig);
|
||||
@@ -123,6 +125,7 @@ public abstract class AbstractGraphQlTesterBuilder<B extends AbstractGraphQlTest
|
||||
/**
|
||||
* Build the default transport-agnostic client that subclasses can then wrap
|
||||
* with {@link AbstractDelegatingGraphQlTester}.
|
||||
* @param transport the graphql transport to use
|
||||
*/
|
||||
protected GraphQlTester buildGraphQlTester(GraphQlTransport transport) {
|
||||
|
||||
@@ -139,12 +142,12 @@ public abstract class AbstractGraphQlTesterBuilder<B extends AbstractGraphQlTest
|
||||
* initialize new builder instances with, based on "this" builder.
|
||||
*/
|
||||
protected Consumer<AbstractGraphQlTesterBuilder<?>> getBuilderInitializer() {
|
||||
return builder -> {
|
||||
return (builder) -> {
|
||||
if (this.errorFilter != null) {
|
||||
builder.errorFilter(this.errorFilter);
|
||||
}
|
||||
builder.documentSource(this.documentSource);
|
||||
builder.configureJsonPathConfig(config -> this.jsonPathConfig);
|
||||
builder.configureJsonPathConfig((config) -> this.jsonPathConfig);
|
||||
builder.responseTimeout(this.responseTimeout);
|
||||
};
|
||||
}
|
||||
@@ -153,6 +156,7 @@ public abstract class AbstractGraphQlTesterBuilder<B extends AbstractGraphQlTest
|
||||
* For cases where the Tester needs the {@link GraphQlTransport}, we can't use
|
||||
* transports directly since they are package private, but we can adapt the corresponding
|
||||
* {@link GraphQlClient} and adapt it to {@code GraphQlTransport}.
|
||||
* @param client the graphql client to use for extracting the transport
|
||||
*/
|
||||
protected static GraphQlTransport asTransport(GraphQlClient client) {
|
||||
return new GraphQlTransport() {
|
||||
@@ -180,7 +184,7 @@ public abstract class AbstractGraphQlTesterBuilder<B extends AbstractGraphQlTest
|
||||
}
|
||||
|
||||
|
||||
private static class Jackson2Configurer {
|
||||
private static final class Jackson2Configurer {
|
||||
|
||||
private static final Class<?> defaultJsonProviderType;
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@ import org.springframework.util.Assert;
|
||||
* wraps an {@code ExecutionGraphQlService}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class DefaultExecutionGraphQlServiceTesterBuilder
|
||||
extends AbstractGraphQlTesterBuilder<DefaultExecutionGraphQlServiceTesterBuilder>
|
||||
@@ -95,7 +94,7 @@ final class DefaultExecutionGraphQlServiceTesterBuilder
|
||||
|
||||
private void registerJsonPathMappingProvider() {
|
||||
if (this.encoder != null && this.decoder != null) {
|
||||
configureJsonPathConfig(config -> {
|
||||
configureJsonPathConfig((config) -> {
|
||||
EncoderDecoderMappingProvider provider = new EncoderDecoderMappingProvider(
|
||||
Collections.singletonList(this.encoder), Collections.singletonList(this.decoder));
|
||||
return config.mappingProvider(provider);
|
||||
@@ -111,7 +110,7 @@ final class DefaultExecutionGraphQlServiceTesterBuilder
|
||||
/**
|
||||
* Default {@link ExecutionGraphQlServiceTester} implementation.
|
||||
*/
|
||||
private static class DefaultExecutionGraphQlServiceTester
|
||||
private static final class DefaultExecutionGraphQlServiceTester
|
||||
extends AbstractDelegatingGraphQlTester implements ExecutionGraphQlServiceTester {
|
||||
|
||||
private final GraphQlServiceGraphQlTransport transport;
|
||||
|
||||
@@ -153,7 +153,7 @@ final class DefaultGraphQlTester implements GraphQlTester {
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Override
|
||||
public Response execute() {
|
||||
return transport.execute(request()).map(response -> mapResponse(response, request())).block(responseTimeout);
|
||||
return DefaultGraphQlTester.this.transport.execute(request()).map((response) -> mapResponse(response, request())).block(DefaultGraphQlTester.this.responseTimeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -163,7 +163,7 @@ final class DefaultGraphQlTester implements GraphQlTester {
|
||||
|
||||
@Override
|
||||
public Subscription executeSubscription() {
|
||||
return () -> transport.executeSubscription(request()).map(result -> mapResponse(result, request()));
|
||||
return () -> DefaultGraphQlTester.this.transport.executeSubscription(request()).map((result) -> mapResponse(result, request()));
|
||||
}
|
||||
|
||||
private GraphQlRequest request() {
|
||||
@@ -171,7 +171,7 @@ final class DefaultGraphQlTester implements GraphQlTester {
|
||||
}
|
||||
|
||||
private DefaultResponse mapResponse(GraphQlResponse response, GraphQlRequest request) {
|
||||
return new DefaultResponse(response, errorFilter, assertDecorator(request), jsonPathConfig);
|
||||
return new DefaultResponse(response, DefaultGraphQlTester.this.errorFilter, assertDecorator(request), DefaultGraphQlTester.this.jsonPathConfig);
|
||||
}
|
||||
|
||||
private Consumer<Runnable> assertDecorator(GraphQlRequest request) {
|
||||
@@ -191,7 +191,7 @@ final class DefaultGraphQlTester implements GraphQlTester {
|
||||
/**
|
||||
* Container for GraphQL response data and errors along with convenience methods.
|
||||
*/
|
||||
private final static class ResponseDelegate {
|
||||
private static final class ResponseDelegate {
|
||||
|
||||
private final DocumentContext jsonDoc;
|
||||
|
||||
@@ -258,7 +258,7 @@ final class DefaultGraphQlTester implements GraphQlTester {
|
||||
}
|
||||
|
||||
void consumeErrors(Consumer<List<ResponseError>> consumer) {
|
||||
filterErrors(error -> true);
|
||||
filterErrors((error) -> true);
|
||||
consumer.accept(this.errors);
|
||||
}
|
||||
|
||||
@@ -392,7 +392,7 @@ final class DefaultGraphQlTester implements GraphQlTester {
|
||||
this.delegate.doAssert(() -> {
|
||||
Object value = this.pathHelper.evaluateJsonPath(this.delegate.jsonContent());
|
||||
AssertionErrors.assertNull(
|
||||
"Expected null value at JSON path \"" + path + "\" but found " + value, value);
|
||||
"Expected null value at JSON path \"" + this.path + "\" but found " + value, value);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
@@ -464,7 +464,7 @@ final class DefaultGraphQlTester implements GraphQlTester {
|
||||
}
|
||||
|
||||
private static String joinPaths(@Nullable String basePath, String path) {
|
||||
return (basePath != null ? basePath + "." + path : path);
|
||||
return (basePath != null) ? basePath + "." + path : path;
|
||||
}
|
||||
|
||||
|
||||
@@ -476,7 +476,7 @@ final class DefaultGraphQlTester implements GraphQlTester {
|
||||
private final D entity;
|
||||
|
||||
protected DefaultEntity(TypeRefAdapter<D> typeAdapter) {
|
||||
this.entity = delegate.read(jsonPath, typeAdapter);
|
||||
this.entity = DefaultPath.this.delegate.read(DefaultPath.this.jsonPath, typeAdapter);
|
||||
}
|
||||
|
||||
protected D getEntity() {
|
||||
@@ -484,57 +484,57 @@ final class DefaultGraphQlTester implements GraphQlTester {
|
||||
}
|
||||
|
||||
protected void doAssert(Runnable task) {
|
||||
delegate.doAssert(task);
|
||||
DefaultPath.this.delegate.doAssert(task);
|
||||
}
|
||||
|
||||
protected String getPath() {
|
||||
return path;
|
||||
return DefaultPath.this.path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path path(String path) {
|
||||
return forPath(basePath, path, delegate);
|
||||
return forPath(DefaultPath.this.basePath, path, DefaultPath.this.delegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path path(String path, Consumer<Path> pathConsumer) {
|
||||
return forNestedPath(basePath, path, delegate, pathConsumer);
|
||||
return forNestedPath(DefaultPath.this.basePath, path, DefaultPath.this.delegate, pathConsumer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends S> T isEqualTo(Object expected) {
|
||||
delegate.doAssert(() -> AssertionErrors.assertEquals(path, expected, this.entity));
|
||||
DefaultPath.this.delegate.doAssert(() -> AssertionErrors.assertEquals(DefaultPath.this.path, expected, this.entity));
|
||||
return self();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends S> T isNotEqualTo(Object other) {
|
||||
delegate.doAssert(() -> AssertionErrors.assertNotEquals(path, other, this.entity));
|
||||
DefaultPath.this.delegate.doAssert(() -> AssertionErrors.assertNotEquals(DefaultPath.this.path, other, this.entity));
|
||||
return self();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends S> T isSameAs(Object expected) {
|
||||
delegate.doAssert(() -> AssertionErrors.assertTrue(path, expected == this.entity));
|
||||
DefaultPath.this.delegate.doAssert(() -> AssertionErrors.assertTrue(DefaultPath.this.path, expected == this.entity));
|
||||
return self();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends S> T isNotSameAs(Object other) {
|
||||
delegate.doAssert(() -> AssertionErrors.assertTrue(path, other != this.entity));
|
||||
DefaultPath.this.delegate.doAssert(() -> AssertionErrors.assertTrue(DefaultPath.this.path, other != this.entity));
|
||||
return self();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends S> T matches(Predicate<D> predicate) {
|
||||
delegate
|
||||
.doAssert(() -> AssertionErrors.assertTrue(path, predicate.test(this.entity)));
|
||||
DefaultPath.this.delegate
|
||||
.doAssert(() -> AssertionErrors.assertTrue(DefaultPath.this.path, predicate.test(this.entity)));
|
||||
return self();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends S> T satisfies(Consumer<D> consumer) {
|
||||
delegate.doAssert(() -> consumer.accept(this.entity));
|
||||
DefaultPath.this.delegate.doAssert(() -> consumer.accept(this.entity));
|
||||
return self();
|
||||
}
|
||||
|
||||
@@ -557,7 +557,7 @@ final class DefaultGraphQlTester implements GraphQlTester {
|
||||
private final class DefaultEntityList<E>
|
||||
extends DefaultEntity<List<E>, EntityList<E>> implements EntityList<E> {
|
||||
|
||||
public DefaultEntityList(TypeRefAdapter<List<E>> typeAdapter) {
|
||||
DefaultEntityList(TypeRefAdapter<List<E>> typeAdapter) {
|
||||
super(typeAdapter);
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@ import org.springframework.web.util.UriComponentsBuilder;
|
||||
* {@link WebTestClient.Builder}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class DefaultHttpGraphQlTesterBuilder
|
||||
extends AbstractGraphQlTesterBuilder<DefaultHttpGraphQlTesterBuilder>
|
||||
@@ -93,8 +92,8 @@ final class DefaultHttpGraphQlTesterBuilder
|
||||
}
|
||||
|
||||
private void registerJsonPathMappingProvider() {
|
||||
this.webTestClientBuilder.codecs(codecConfigurer ->
|
||||
configureJsonPathConfig(config -> {
|
||||
this.webTestClientBuilder.codecs((codecConfigurer) ->
|
||||
configureJsonPathConfig((config) -> {
|
||||
EncoderDecoderMappingProvider provider = new EncoderDecoderMappingProvider(codecConfigurer);
|
||||
return config.mappingProvider(provider);
|
||||
}));
|
||||
@@ -105,7 +104,7 @@ final class DefaultHttpGraphQlTesterBuilder
|
||||
* Default {@link HttpGraphQlTester} that builds and uses a {@link WebTestClient}
|
||||
* for request execution.
|
||||
*/
|
||||
private static class DefaultHttpGraphQlTester extends AbstractDelegatingGraphQlTester implements HttpGraphQlTester {
|
||||
private static final class DefaultHttpGraphQlTester extends AbstractDelegatingGraphQlTester implements HttpGraphQlTester {
|
||||
|
||||
private final WebTestClient webTestClient;
|
||||
|
||||
|
||||
@@ -113,9 +113,9 @@ public class DefaultRSocketGraphQlTesterBuilder
|
||||
}
|
||||
|
||||
private void registerJsonPathMappingProvider() {
|
||||
this.rsocketGraphQlClientBuilder.rsocketRequester(builder ->
|
||||
builder.rsocketStrategies(strategiesBuilder ->
|
||||
configureJsonPathConfig(config -> {
|
||||
this.rsocketGraphQlClientBuilder.rsocketRequester((builder) ->
|
||||
builder.rsocketStrategies((strategiesBuilder) ->
|
||||
configureJsonPathConfig((config) -> {
|
||||
RSocketStrategies strategies = strategiesBuilder.build();
|
||||
List<Encoder<?>> encoders = strategies.encoders();
|
||||
List<Decoder<?>> decoders = strategies.decoders();
|
||||
|
||||
@@ -27,7 +27,6 @@ import org.springframework.util.Assert;
|
||||
* Default {@link GraphQlTester.Builder} with a given, externally prepared transport.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class DefaultTransportGraphQlTesterBuilder
|
||||
extends AbstractGraphQlTesterBuilder<DefaultTransportGraphQlTesterBuilder> {
|
||||
@@ -50,7 +49,7 @@ final class DefaultTransportGraphQlTesterBuilder
|
||||
/**
|
||||
* {@link GraphQlTester} with a given transport.
|
||||
*/
|
||||
private static class DefaultTransportGraphQlTester extends AbstractDelegatingGraphQlTester {
|
||||
private static final class DefaultTransportGraphQlTester extends AbstractDelegatingGraphQlTester {
|
||||
|
||||
private final GraphQlTransport transport;
|
||||
|
||||
|
||||
@@ -34,7 +34,6 @@ import org.springframework.web.util.DefaultUriBuilderFactory;
|
||||
* {@link WebGraphQlHandler} for request execution.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class DefaultWebGraphQlTesterBuilder
|
||||
extends AbstractGraphQlTesterBuilder<DefaultWebGraphQlTesterBuilder>
|
||||
@@ -104,7 +103,7 @@ final class DefaultWebGraphQlTesterBuilder
|
||||
}
|
||||
|
||||
private void registerJsonPathMappingProvider() {
|
||||
configureJsonPathConfig(jsonPathConfig -> {
|
||||
configureJsonPathConfig((jsonPathConfig) -> {
|
||||
EncoderDecoderMappingProvider provider = new EncoderDecoderMappingProvider(this.codecConfigurer);
|
||||
return jsonPathConfig.mappingProvider(provider);
|
||||
});
|
||||
@@ -114,7 +113,7 @@ final class DefaultWebGraphQlTesterBuilder
|
||||
/**
|
||||
* Default {@link WebGraphQlTester} implementation.
|
||||
*/
|
||||
private static class DefaultWebGraphQlTester extends AbstractDelegatingGraphQlTester implements WebGraphQlTester {
|
||||
private static final class DefaultWebGraphQlTester extends AbstractDelegatingGraphQlTester implements WebGraphQlTester {
|
||||
|
||||
private final WebGraphQlHandlerGraphQlTransport transport;
|
||||
|
||||
@@ -34,7 +34,6 @@ import org.springframework.web.reactive.socket.client.WebSocketClient;
|
||||
* {@link WebSocketGraphQlClient.Builder}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class DefaultWebSocketGraphQlTesterBuilder
|
||||
extends AbstractGraphQlTesterBuilder<DefaultWebSocketGraphQlTesterBuilder>
|
||||
@@ -108,8 +107,8 @@ final class DefaultWebSocketGraphQlTesterBuilder
|
||||
}
|
||||
|
||||
private void registerJsonPathMappingProvider() {
|
||||
this.graphQlClientBuilder.codecConfigurer(codecConfigurer -> {
|
||||
configureJsonPathConfig(jsonPathConfig -> {
|
||||
this.graphQlClientBuilder.codecConfigurer((codecConfigurer) -> {
|
||||
configureJsonPathConfig((jsonPathConfig) -> {
|
||||
EncoderDecoderMappingProvider provider = new EncoderDecoderMappingProvider(codecConfigurer);
|
||||
return jsonPathConfig.mappingProvider(provider);
|
||||
});
|
||||
@@ -120,7 +119,7 @@ final class DefaultWebSocketGraphQlTesterBuilder
|
||||
/**
|
||||
* Default {@link WebSocketGraphQlTester} implementation.
|
||||
*/
|
||||
private static class DefaultWebSocketGraphQlTester extends AbstractDelegatingGraphQlTester implements WebSocketGraphQlTester {
|
||||
private static final class DefaultWebSocketGraphQlTester extends AbstractDelegatingGraphQlTester implements WebSocketGraphQlTester {
|
||||
|
||||
private final WebSocketGraphQlClient client;
|
||||
|
||||
@@ -45,7 +45,6 @@ import org.springframework.util.MimeTypeUtils;
|
||||
* JSON Path {@link MappingProvider} that uses {@link Encoder} and {@link Decoder}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class EncoderDecoderMappingProvider implements MappingProvider {
|
||||
|
||||
@@ -60,29 +59,29 @@ final class EncoderDecoderMappingProvider implements MappingProvider {
|
||||
/**
|
||||
* Create an instance with a {@link CodecConfigurer}.
|
||||
*/
|
||||
public EncoderDecoderMappingProvider(CodecConfigurer configurer) {
|
||||
EncoderDecoderMappingProvider(CodecConfigurer configurer) {
|
||||
this.encoder = findJsonEncoder(configurer);
|
||||
this.decoder = findJsonDecoder(configurer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance with a List of encoders and decoders>
|
||||
* Create an instance with a List of encoders and decoders.
|
||||
*/
|
||||
public EncoderDecoderMappingProvider(List<Encoder<?>> encoders, List<Decoder<?>> decoders) {
|
||||
EncoderDecoderMappingProvider(List<Encoder<?>> encoders, List<Decoder<?>> decoders) {
|
||||
this.encoder = findJsonEncoder(encoders);
|
||||
this.decoder = findJsonDecoder(decoders);
|
||||
}
|
||||
|
||||
private static Encoder<?> findJsonEncoder(CodecConfigurer configurer) {
|
||||
return findJsonEncoder(configurer.getWriters().stream()
|
||||
.filter(writer -> writer instanceof EncoderHttpMessageWriter)
|
||||
.map(writer -> ((EncoderHttpMessageWriter<?>) writer).getEncoder()));
|
||||
.filter((writer) -> writer instanceof EncoderHttpMessageWriter)
|
||||
.map((writer) -> ((EncoderHttpMessageWriter<?>) writer).getEncoder()));
|
||||
}
|
||||
|
||||
private static Decoder<?> findJsonDecoder(CodecConfigurer configurer) {
|
||||
return findJsonDecoder(configurer.getReaders().stream()
|
||||
.filter(reader -> reader instanceof DecoderHttpMessageReader)
|
||||
.map(reader -> ((DecoderHttpMessageReader<?>) reader).getDecoder()));
|
||||
.filter((reader) -> reader instanceof DecoderHttpMessageReader)
|
||||
.map((reader) -> ((DecoderHttpMessageReader<?>) reader).getDecoder()));
|
||||
}
|
||||
|
||||
private static Encoder<?> findJsonEncoder(List<Encoder<?>> encoders) {
|
||||
@@ -95,14 +94,14 @@ final class EncoderDecoderMappingProvider implements MappingProvider {
|
||||
|
||||
private static Encoder<?> findJsonEncoder(Stream<Encoder<?>> stream) {
|
||||
return stream
|
||||
.filter(encoder -> encoder.canEncode(MAP_TYPE, MediaType.APPLICATION_JSON))
|
||||
.filter((encoder) -> encoder.canEncode(MAP_TYPE, MediaType.APPLICATION_JSON))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalArgumentException("No JSON Encoder"));
|
||||
}
|
||||
|
||||
private static Decoder<?> findJsonDecoder(Stream<Decoder<?>> decoderStream) {
|
||||
return decoderStream
|
||||
.filter(decoder -> decoder.canDecode(MAP_TYPE, MediaType.APPLICATION_JSON))
|
||||
.filter((decoder) -> decoder.canDecode(MAP_TYPE, MediaType.APPLICATION_JSON))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalArgumentException("No JSON Decoder"));
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ public interface ExecutionGraphQlServiceTester extends GraphQlTester {
|
||||
|
||||
/**
|
||||
* Create a {@link ExecutionGraphQlServiceTester} instance.
|
||||
* @param service the GraphQL service to use
|
||||
*/
|
||||
static ExecutionGraphQlServiceTester create(ExecutionGraphQlService service) {
|
||||
return builder(service).build();
|
||||
@@ -47,6 +48,7 @@ public interface ExecutionGraphQlServiceTester extends GraphQlTester {
|
||||
|
||||
/**
|
||||
* Return a builder for {@link ExecutionGraphQlServiceTester}.
|
||||
* @param service the GraphQL service to use
|
||||
*/
|
||||
static ExecutionGraphQlServiceTester.Builder<?> builder(ExecutionGraphQlService service) {
|
||||
return new DefaultExecutionGraphQlServiceTesterBuilder(service);
|
||||
@@ -55,12 +57,14 @@ public interface ExecutionGraphQlServiceTester extends GraphQlTester {
|
||||
|
||||
/**
|
||||
* Default {@link ExecutionGraphQlServiceTester.Builder} implementation.
|
||||
* @param <B> the type of builder
|
||||
*/
|
||||
interface Builder<B extends Builder<B>> extends GraphQlTester.Builder<B> {
|
||||
|
||||
/**
|
||||
* Provide a {@code BiFunction} to help initialize the
|
||||
* {@link ExecutionInput} with.
|
||||
* @param configurer the function that initializes the execution input
|
||||
* @since 1.1.2
|
||||
* @see org.springframework.graphql.ExecutionGraphQlRequest#configureExecutionInput(BiFunction)
|
||||
*/
|
||||
@@ -69,12 +73,14 @@ public interface ExecutionGraphQlServiceTester extends GraphQlTester {
|
||||
/**
|
||||
* Configure the JSON encoder to use for mapping response data to
|
||||
* higher level objects.
|
||||
* @param encoder the JSON encoder to use
|
||||
*/
|
||||
B encoder(Encoder<?> encoder);
|
||||
|
||||
/**
|
||||
* Configure the JSON decoder to use for mapping response data to
|
||||
* higher level objects.
|
||||
* @param decoder the JSON decoder to use
|
||||
*/
|
||||
B decoder(Decoder<?> decoder);
|
||||
|
||||
|
||||
@@ -34,7 +34,6 @@ import org.springframework.util.Assert;
|
||||
* {@code GraphQlTransport} that calls directly a {@link ExecutionGraphQlService}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class GraphQlServiceGraphQlTransport extends AbstractDirectGraphQlTransport {
|
||||
|
||||
@@ -53,11 +52,11 @@ final class GraphQlServiceGraphQlTransport extends AbstractDirectGraphQlTranspor
|
||||
}
|
||||
|
||||
|
||||
public ExecutionGraphQlService getGraphQlService() {
|
||||
ExecutionGraphQlService getGraphQlService() {
|
||||
return this.graphQlService;
|
||||
}
|
||||
|
||||
public List<BiFunction<ExecutionInput, ExecutionInput.Builder, ExecutionInput>> getExecutionInputConfigurers() {
|
||||
List<BiFunction<ExecutionInput, ExecutionInput.Builder, ExecutionInput>> getExecutionInputConfigurers() {
|
||||
return this.executionInputConfigurers;
|
||||
}
|
||||
|
||||
|
||||
@@ -66,6 +66,7 @@ public interface GraphQlTester {
|
||||
* Variant of {@link #document(String)} that uses the given key to resolve
|
||||
* the GraphQL document from a file with the help of the configured
|
||||
* {@link Builder#documentSource(DocumentSource) DocumentSource}.
|
||||
* @param documentName the name of the document to send
|
||||
* @return spec for response assertions
|
||||
* @throws IllegalArgumentException if the documentName cannot be resolved
|
||||
* @throws AssertionError if the response status is not 200 (OK)
|
||||
@@ -94,6 +95,7 @@ public interface GraphQlTester {
|
||||
|
||||
/**
|
||||
* A builder to create a {@link GraphQlTester} instance.
|
||||
* @param <B> the type of builder
|
||||
*/
|
||||
interface Builder<B extends Builder<B>> {
|
||||
|
||||
@@ -111,6 +113,7 @@ public interface GraphQlTester {
|
||||
* <p>By default, this is set to {@link ResourceDocumentSource} with
|
||||
* classpath location {@code "graphql-test/"} and
|
||||
* {@link ResourceDocumentSource#FILE_EXTENSIONS} as extensions.
|
||||
* @param contentLoader the document content loader
|
||||
*/
|
||||
B documentSource(DocumentSource contentLoader);
|
||||
|
||||
@@ -130,6 +133,7 @@ public interface GraphQlTester {
|
||||
|
||||
/**
|
||||
* Declare options to gather input for a GraphQL request and execute it.
|
||||
* @param <T> the type of request
|
||||
*/
|
||||
interface Request<T extends Request<T>> {
|
||||
|
||||
@@ -311,7 +315,7 @@ public interface GraphQlTester {
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains a decoded entity and provides options to assert it
|
||||
* Contains a decoded entity and provides options to assert it.
|
||||
*
|
||||
* @param <D> the entity type
|
||||
* @param <S> the {@code Entity} spec type
|
||||
@@ -320,6 +324,7 @@ public interface GraphQlTester {
|
||||
|
||||
/**
|
||||
* Verify the decoded entity is equal to the given value.
|
||||
* @param <T> the {@code Entity} spec type
|
||||
* @param expected the expected value
|
||||
* @return the {@code Entity} spec for further assertions
|
||||
*/
|
||||
@@ -327,6 +332,7 @@ public interface GraphQlTester {
|
||||
|
||||
/**
|
||||
* Verify the decoded entity is not equal to the given value.
|
||||
* @param <T> the {@code Entity} spec type
|
||||
* @param other the value to check against
|
||||
* @return the {@code Entity} spec for further assertions
|
||||
*/
|
||||
@@ -334,6 +340,7 @@ public interface GraphQlTester {
|
||||
|
||||
/**
|
||||
* Verify the decoded entity is the same instance as the given value.
|
||||
* @param <T> the {@code Entity} spec type
|
||||
* @param expected the expected value
|
||||
* @return the {@code Entity} spec for further assertions
|
||||
*/
|
||||
@@ -341,6 +348,7 @@ public interface GraphQlTester {
|
||||
|
||||
/**
|
||||
* Verify the decoded entity is not the same instance as the given value.
|
||||
* @param <T> the {@code Entity} spec type
|
||||
* @param other the value to check against
|
||||
* @return the {@code Entity} spec for further assertions
|
||||
*/
|
||||
@@ -348,6 +356,7 @@ public interface GraphQlTester {
|
||||
|
||||
/**
|
||||
* Verify the decoded entity matches the given predicate.
|
||||
* @param <T> the {@code Entity} spec type
|
||||
* @param predicate the predicate to apply
|
||||
* @return the {@code Entity} spec for further assertions
|
||||
*/
|
||||
@@ -355,6 +364,7 @@ public interface GraphQlTester {
|
||||
|
||||
/**
|
||||
* Verify the entity with the given {@link Consumer}.
|
||||
* @param <T> the {@code Entity} spec type
|
||||
* @param consumer the consumer to apply
|
||||
* @return the {@code Entity} spec for further assertions
|
||||
*/
|
||||
|
||||
@@ -37,6 +37,7 @@ public interface HttpGraphQlTester extends WebGraphQlTester {
|
||||
|
||||
/**
|
||||
* Create an {@link HttpGraphQlTester} that uses the given {@link WebTestClient}.
|
||||
* @param webTestClient the {@code WebTestClient} to use
|
||||
*/
|
||||
static HttpGraphQlTester create(WebTestClient webTestClient) {
|
||||
return builder(webTestClient.mutate()).build();
|
||||
@@ -45,6 +46,7 @@ public interface HttpGraphQlTester extends WebGraphQlTester {
|
||||
/**
|
||||
* Return a builder to initialize an {@link HttpGraphQlTester} by creating
|
||||
* the underlying {@link WebTestClient} through the given builder.
|
||||
* @param webTestClientBuilder the {@code WebTestClient} builder to use
|
||||
*/
|
||||
static HttpGraphQlTester.Builder<?> builder(WebTestClient.Builder webTestClientBuilder) {
|
||||
return new DefaultHttpGraphQlTesterBuilder(webTestClientBuilder);
|
||||
@@ -53,6 +55,7 @@ public interface HttpGraphQlTester extends WebGraphQlTester {
|
||||
|
||||
/**
|
||||
* Builder for the GraphQL over HTTP tester.
|
||||
* @param <B> the type of builder
|
||||
*/
|
||||
interface Builder<B extends Builder<B>> extends WebGraphQlTester.Builder<B> {
|
||||
|
||||
@@ -60,6 +63,7 @@ public interface HttpGraphQlTester extends WebGraphQlTester {
|
||||
* Customize the {@code WebTestClient} to use.
|
||||
* <p>Note that some properties of {@code WebTestClient.Builder} like the
|
||||
* base URL, headers, and codecs can be customized through this builder.
|
||||
* @param webClient a consumer that customizes the {@code WebClient} builder
|
||||
* @see #url(String)
|
||||
* @see #header(String, String...)
|
||||
* @see #codecConfigurer(Consumer)
|
||||
|
||||
@@ -64,6 +64,7 @@ public interface RSocketGraphQlTester extends GraphQlTester {
|
||||
|
||||
/**
|
||||
* Start with a given {@link #builder()}.
|
||||
* @param requesterBuilder the builder to use as a baseline
|
||||
*/
|
||||
static RSocketGraphQlTester.Builder<?> builder(RSocketRequester.Builder requesterBuilder) {
|
||||
return new DefaultRSocketGraphQlTesterBuilder(requesterBuilder);
|
||||
@@ -72,6 +73,7 @@ public interface RSocketGraphQlTester extends GraphQlTester {
|
||||
|
||||
/**
|
||||
* Builder for a GraphQL over RSocket tester.
|
||||
* @param <B> the type of builder
|
||||
*/
|
||||
interface Builder<B extends Builder<B>> extends GraphQlTester.Builder<B> {
|
||||
|
||||
@@ -119,11 +121,12 @@ public interface RSocketGraphQlTester extends GraphQlTester {
|
||||
* <p>Note that some properties of {@code RSocketRequester.Builder} like the
|
||||
* data MimeType, and the underlying RSocket transport can be customized
|
||||
* through this builder.
|
||||
* @param requester a consumer that customizes the {@code RSocketRequester} through its builder
|
||||
* @return the same builder instance
|
||||
* @see #dataMimeType(MimeType)
|
||||
* @see #tcp(String, int)
|
||||
* @see #webSocket(URI)
|
||||
* @see #clientTransport(ClientTransport)
|
||||
* @return the same builder instance
|
||||
*/
|
||||
B rsocketRequester(Consumer<RSocketRequester.Builder> requester);
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@ import org.springframework.lang.Nullable;
|
||||
* {@code GraphQlTransport} that calls directly a {@link WebGraphQlHandler}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class WebGraphQlHandlerGraphQlTransport extends AbstractDirectGraphQlTransport {
|
||||
|
||||
@@ -51,26 +50,26 @@ final class WebGraphQlHandlerGraphQlTransport extends AbstractDirectGraphQlTrans
|
||||
WebGraphQlHandlerGraphQlTransport(
|
||||
@Nullable URI url, HttpHeaders headers, WebGraphQlHandler handler, CodecConfigurer configurer) {
|
||||
|
||||
this.url = (url != null ? url : URI.create(""));
|
||||
this.url = (url != null) ? url : URI.create("");
|
||||
this.headers.addAll(headers);
|
||||
this.graphQlHandler = handler;
|
||||
this.codecConfigurer = configurer;
|
||||
}
|
||||
|
||||
|
||||
public URI getUrl() {
|
||||
URI getUrl() {
|
||||
return this.url;
|
||||
}
|
||||
|
||||
public HttpHeaders getHeaders() {
|
||||
HttpHeaders getHeaders() {
|
||||
return this.headers;
|
||||
}
|
||||
|
||||
public WebGraphQlHandler getGraphQlHandler() {
|
||||
WebGraphQlHandler getGraphQlHandler() {
|
||||
return this.graphQlHandler;
|
||||
}
|
||||
|
||||
public CodecConfigurer getCodecConfigurer() {
|
||||
CodecConfigurer getCodecConfigurer() {
|
||||
return this.codecConfigurer;
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ public interface WebGraphQlTester extends GraphQlTester {
|
||||
|
||||
/**
|
||||
* Create a {@link WebGraphQlTester} instance.
|
||||
* @param graphQlHandler the web GraphQL handler to be tested
|
||||
*/
|
||||
static WebGraphQlTester create(WebGraphQlHandler graphQlHandler) {
|
||||
return builder(graphQlHandler).build();
|
||||
@@ -59,6 +60,7 @@ public interface WebGraphQlTester extends GraphQlTester {
|
||||
|
||||
/**
|
||||
* Common builder for Web {@code GraphQlTester} extensions.
|
||||
* @param <B> the type of builder
|
||||
*/
|
||||
interface Builder<B extends Builder<B>> extends GraphQlTester.Builder<B> {
|
||||
|
||||
@@ -91,6 +93,7 @@ public interface WebGraphQlTester extends GraphQlTester {
|
||||
/**
|
||||
* Configure the underlying {@code CodecConfigurer} to use for all JSON
|
||||
* encoding and decoding needs.
|
||||
* @param codecsConsumer a consumer that customizes the configured codecs
|
||||
*/
|
||||
B codecConfigurer(Consumer<CodecConfigurer> codecsConsumer);
|
||||
|
||||
|
||||
@@ -77,6 +77,7 @@ public interface WebSocketGraphQlTester extends WebGraphQlTester {
|
||||
|
||||
/**
|
||||
* Builder for a GraphQL over WebSocket tester.
|
||||
* @param <B> the type of builder
|
||||
*/
|
||||
interface Builder<B extends Builder<B>> extends WebGraphQlTester.Builder<B> {
|
||||
|
||||
|
||||
@@ -34,12 +34,11 @@ import org.springframework.util.Assert;
|
||||
* {@code GraphQlTransport} for GraphQL over HTTP via {@link WebTestClient}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class WebTestClientTransport implements GraphQlTransport {
|
||||
|
||||
private static final ParameterizedTypeReference<Map<String, Object>> MAP_TYPE =
|
||||
new ParameterizedTypeReference<Map<String, Object>>() {};
|
||||
new ParameterizedTypeReference<Map<String, Object>>() { };
|
||||
|
||||
|
||||
private final WebTestClient webTestClient;
|
||||
@@ -65,7 +64,7 @@ final class WebTestClientTransport implements GraphQlTransport {
|
||||
.returnResult()
|
||||
.getResponseBody();
|
||||
|
||||
responseMap = (responseMap != null ? responseMap : Collections.emptyMap());
|
||||
responseMap = (responseMap != null) ? responseMap : Collections.emptyMap();
|
||||
GraphQlResponse response = GraphQlTransport.createResponse(responseMap);
|
||||
return Mono.just(response);
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ public class GraphQlTesterTests extends GraphQlTesterTestSupport {
|
||||
assertThat(actual.getName()).isEqualTo("Luke Skywalker");
|
||||
|
||||
response.path("")
|
||||
.entity(new ParameterizedTypeReference<Map<String, MovieCharacter>>() {})
|
||||
.entity(new ParameterizedTypeReference<Map<String, MovieCharacter>>() { })
|
||||
.isEqualTo(Collections.singletonMap("me", luke));
|
||||
|
||||
assertThat(getActualRequestDocument()).contains(document);
|
||||
@@ -186,7 +186,7 @@ public class GraphQlTesterTests extends GraphQlTesterTestSupport {
|
||||
"Request: document='{me {name, friends}}'");
|
||||
|
||||
response.path("me.friends")
|
||||
.entityList(new ParameterizedTypeReference<MovieCharacter>() {})
|
||||
.entityList(new ParameterizedTypeReference<MovieCharacter>() { })
|
||||
.containsExactly(han, leia);
|
||||
|
||||
assertThat(getActualRequestDocument()).contains(document);
|
||||
|
||||
@@ -117,7 +117,7 @@ public class RSocketGraphQlTesterBuilderTests {
|
||||
assertThat(testDecoder.getLastValue()).isEqualTo(character);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static class BuilderSetup {
|
||||
|
||||
private final MockExecutionGraphQlService graphQlService = new MockExecutionGraphQlService();
|
||||
|
||||
@@ -61,7 +61,7 @@ public interface GraphQlRequest {
|
||||
/**
|
||||
* Convert the request to a {@link Map} as defined in
|
||||
* <a href="https://github.com/graphql/graphql-over-http/blob/main/spec/GraphQLOverHTTP.md">GraphQL over HTTP</a> and
|
||||
* <a href="https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md">GraphQL over WebSocket</a>:
|
||||
* <a href="https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md">GraphQL over WebSocket</a>.
|
||||
* <table>
|
||||
* <tr><th>Key</th><th>Value</th></tr>
|
||||
* <tr><td>query</td><td>{@link #getDocument() document}</td></tr>
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* Copyright 2020-2024 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;
|
||||
|
||||
@@ -47,6 +47,7 @@ import org.springframework.util.ClassUtils;
|
||||
* agnostic {@code GraphQlClient}. A transport specific extension can then wrap
|
||||
* this default tester by extending {@link AbstractDelegatingGraphQlClient}.
|
||||
*
|
||||
* @param <B> the type of builder
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
* @see AbstractDelegatingGraphQlClient
|
||||
@@ -123,6 +124,8 @@ public abstract class AbstractGraphQlClientBuilder<B extends AbstractGraphQlClie
|
||||
* Transport-specific subclasses can provide their JSON {@code Encoder} and
|
||||
* {@code Decoder} for use at the client level, for mapping response data
|
||||
* to some target entity type.
|
||||
* @param encoder the JSON encoder
|
||||
* @param decoder the JSON decoder
|
||||
*/
|
||||
protected void setJsonCodecs(Encoder<?> encoder, Decoder<?> decoder) {
|
||||
this.jsonEncoder = encoder;
|
||||
@@ -131,6 +134,7 @@ public abstract class AbstractGraphQlClientBuilder<B extends AbstractGraphQlClie
|
||||
|
||||
/**
|
||||
* Variant of {@link #setJsonCodecs} for setting each codec individually.
|
||||
* @param encoder the JSON encoder
|
||||
*/
|
||||
protected void setJsonEncoder(Encoder<?> encoder) {
|
||||
this.jsonEncoder = encoder;
|
||||
@@ -146,6 +150,7 @@ public abstract class AbstractGraphQlClientBuilder<B extends AbstractGraphQlClie
|
||||
|
||||
/**
|
||||
* Variant of {@link #setJsonCodecs} for setting each codec individually.
|
||||
* @param decoder the JSON decoder
|
||||
*/
|
||||
protected void setJsonDecoder(Decoder<?> decoder) {
|
||||
this.jsonDecoder = decoder;
|
||||
@@ -170,12 +175,13 @@ public abstract class AbstractGraphQlClientBuilder<B extends AbstractGraphQlClie
|
||||
/**
|
||||
* Build the default transport-agnostic client that subclasses can then wrap
|
||||
* with {@link AbstractDelegatingGraphQlClient}.
|
||||
* @param transport the GraphQL transport to be used by the client
|
||||
*/
|
||||
protected GraphQlClient buildGraphQlClient(GraphQlTransport transport) {
|
||||
|
||||
if (jackson2Present) {
|
||||
this.jsonEncoder = (this.jsonEncoder == null ? DefaultJackson2Codecs.encoder() : this.jsonEncoder);
|
||||
this.jsonDecoder = (this.jsonDecoder == null ? DefaultJackson2Codecs.decoder() : this.jsonDecoder);
|
||||
this.jsonEncoder = (this.jsonEncoder == null) ? DefaultJackson2Codecs.encoder() : this.jsonEncoder;
|
||||
this.jsonDecoder = (this.jsonDecoder == null) ? DefaultJackson2Codecs.decoder() : this.jsonDecoder;
|
||||
}
|
||||
|
||||
return new DefaultGraphQlClient(this.documentSource,
|
||||
@@ -186,33 +192,33 @@ public abstract class AbstractGraphQlClientBuilder<B extends AbstractGraphQlClie
|
||||
* Return a {@code Consumer} to initialize new builders from "this" builder.
|
||||
*/
|
||||
protected Consumer<AbstractGraphQlClientBuilder<?>> getBuilderInitializer() {
|
||||
return builder -> {
|
||||
builder.interceptors(interceptorList -> interceptorList.addAll(interceptors));
|
||||
builder.documentSource(documentSource);
|
||||
return (builder) -> {
|
||||
builder.interceptors((interceptorList) -> interceptorList.addAll(this.interceptors));
|
||||
builder.documentSource(this.documentSource);
|
||||
builder.setJsonCodecs(getEncoder(), getDecoder());
|
||||
};
|
||||
}
|
||||
|
||||
private Chain createExecuteChain(GraphQlTransport transport) {
|
||||
|
||||
Chain chain = request -> transport.execute(request)
|
||||
.map(response -> new DefaultClientGraphQlResponse(request, response, getEncoder(), getDecoder()));
|
||||
Chain chain = (request) -> transport.execute(request)
|
||||
.map((response) -> new DefaultClientGraphQlResponse(request, response, getEncoder(), getDecoder()));
|
||||
|
||||
return this.interceptors.stream()
|
||||
.reduce(GraphQlClientInterceptor::andThen)
|
||||
.map(i -> (Chain) (request) -> i.intercept(request, chain))
|
||||
.map((i) -> (Chain) (request) -> i.intercept(request, chain))
|
||||
.orElse(chain);
|
||||
}
|
||||
|
||||
private SubscriptionChain createSubscriptionChain(GraphQlTransport transport) {
|
||||
|
||||
SubscriptionChain chain = request -> transport
|
||||
SubscriptionChain chain = (request) -> transport
|
||||
.executeSubscription(request)
|
||||
.map(response -> new DefaultClientGraphQlResponse(request, response, getEncoder(), getDecoder()));
|
||||
.map((response) -> new DefaultClientGraphQlResponse(request, response, getEncoder(), getDecoder()));
|
||||
|
||||
return this.interceptors.stream()
|
||||
.reduce(GraphQlClientInterceptor::andThen)
|
||||
.map(i -> (SubscriptionChain) (request) -> i.interceptSubscription(request, chain))
|
||||
.map((i) -> (SubscriptionChain) (request) -> i.interceptSubscription(request, chain))
|
||||
.orElse(chain);
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ import org.springframework.util.ClassUtils;
|
||||
* agnostic {@code GraphQlClient}. A transport specific extension can then wrap
|
||||
* this default tester by extending {@link AbstractDelegatingGraphQlClient}.
|
||||
*
|
||||
* @param <B> the type of builder
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.3
|
||||
* @see AbstractDelegatingGraphQlClient
|
||||
@@ -131,6 +132,7 @@ public abstract class AbstractGraphQlClientSyncBuilder<B extends AbstractGraphQl
|
||||
* Transport-specific subclasses can provide their JSON {@code Encoder} and
|
||||
* {@code Decoder} for use at the client level, for mapping response data
|
||||
* to some target entity type.
|
||||
* @param converter the message converter for JSON payloads
|
||||
*/
|
||||
protected void setJsonConverter(HttpMessageConverter<Object> converter) {
|
||||
this.jsonConverter = converter;
|
||||
@@ -140,12 +142,13 @@ public abstract class AbstractGraphQlClientSyncBuilder<B extends AbstractGraphQl
|
||||
/**
|
||||
* Build the default transport-agnostic client that subclasses can then wrap
|
||||
* with {@link AbstractDelegatingGraphQlClient}.
|
||||
* @param transport the GraphQL transport to be used by the client
|
||||
*/
|
||||
protected GraphQlClient buildGraphQlClient(SyncGraphQlTransport transport) {
|
||||
|
||||
if (jackson2Present) {
|
||||
this.jsonConverter = (this.jsonConverter == null ?
|
||||
DefaultJacksonConverter.initialize() : this.jsonConverter);
|
||||
this.jsonConverter = (this.jsonConverter == null) ?
|
||||
DefaultJacksonConverter.initialize() : this.jsonConverter;
|
||||
}
|
||||
|
||||
return new DefaultGraphQlClient(
|
||||
@@ -156,9 +159,9 @@ public abstract class AbstractGraphQlClientSyncBuilder<B extends AbstractGraphQl
|
||||
* Return a {@code Consumer} to initialize new builders from "this" builder.
|
||||
*/
|
||||
protected Consumer<AbstractGraphQlClientSyncBuilder<?>> getBuilderInitializer() {
|
||||
return builder -> {
|
||||
builder.interceptors(interceptorList -> interceptorList.addAll(interceptors));
|
||||
builder.documentSource(documentSource);
|
||||
return (builder) -> {
|
||||
builder.interceptors((interceptorList) -> interceptorList.addAll(this.interceptors));
|
||||
builder.documentSource(this.documentSource);
|
||||
builder.setJsonConverter(getJsonConverter());
|
||||
};
|
||||
}
|
||||
@@ -168,14 +171,14 @@ public abstract class AbstractGraphQlClientSyncBuilder<B extends AbstractGraphQl
|
||||
Encoder<?> encoder = HttpMessageConverterDelegate.asEncoder(getJsonConverter());
|
||||
Decoder<?> decoder = HttpMessageConverterDelegate.asDecoder(getJsonConverter());
|
||||
|
||||
Chain chain = request -> {
|
||||
Chain chain = (request) -> {
|
||||
GraphQlResponse response = transport.execute(request);
|
||||
return new DefaultClientGraphQlResponse(request, response, encoder, decoder);
|
||||
};
|
||||
|
||||
return this.interceptors.stream()
|
||||
.reduce(SyncGraphQlClientInterceptor::andThen)
|
||||
.map(i -> (Chain) (request) -> i.intercept(request, chain))
|
||||
.map((i) -> (Chain) (request) -> i.intercept(request, chain))
|
||||
.orElse(chain);
|
||||
}
|
||||
|
||||
@@ -185,7 +188,7 @@ public abstract class AbstractGraphQlClientSyncBuilder<B extends AbstractGraphQl
|
||||
}
|
||||
|
||||
|
||||
private static class DefaultJacksonConverter {
|
||||
private static final class DefaultJacksonConverter {
|
||||
|
||||
static HttpMessageConverter<Object> initialize() {
|
||||
return new MappingJackson2HttpMessageConverter();
|
||||
|
||||
@@ -32,10 +32,12 @@ public interface ClientGraphQlResponse extends GraphQlResponse {
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
ClientResponseField field(String path);
|
||||
|
||||
/**
|
||||
* Decode the full response map to the given target type.
|
||||
* @param <D> the target type
|
||||
* @param type the target class
|
||||
* @return the decoded value, or never {@code null}
|
||||
* @throws FieldAccessException if the response is not {@link #isValid() valid}
|
||||
@@ -44,7 +46,8 @@ public interface ClientGraphQlResponse extends GraphQlResponse {
|
||||
|
||||
/**
|
||||
* Variant of {@link #toEntity(Class)} with a {@link ParameterizedTypeReference}.
|
||||
* @param type the target type
|
||||
* @param <D> the target type
|
||||
* @param type the target parameterized type
|
||||
* @return the decoded value, or never {@code null}
|
||||
* @throws FieldAccessException if the response is not {@link #isValid() valid}
|
||||
*/
|
||||
|
||||
@@ -34,6 +34,7 @@ public interface ClientResponseField extends ResponseField {
|
||||
|
||||
/**
|
||||
* Decode the field to an entity of the given type.
|
||||
* @param <D> the entity type
|
||||
* @param entityType the type to convert to
|
||||
* @return the decoded entity, or {@code null} if the field is {@code null}
|
||||
* but otherwise there are no errors
|
||||
@@ -46,12 +47,15 @@ public interface ClientResponseField extends ResponseField {
|
||||
|
||||
/**
|
||||
* Variant of {@link #toEntity(Class)} with a {@link ParameterizedTypeReference}.
|
||||
* @param <D> the entity type
|
||||
* @param entityType the type to convert to
|
||||
*/
|
||||
@Nullable
|
||||
<D> D toEntity(ParameterizedTypeReference<D> entityType);
|
||||
|
||||
/**
|
||||
* Variant of {@link #toEntity(Class)} to decode to a list of entities.
|
||||
* @param <D> the entity type
|
||||
* @param elementType the type of elements in the list
|
||||
* @return the list of decoded entities, or an empty list if the field is
|
||||
* {@code null} but otherwise there are no errors
|
||||
@@ -61,15 +65,16 @@ public interface ClientResponseField extends ResponseField {
|
||||
*/
|
||||
<D> List<D> toEntityList(Class<D> elementType);
|
||||
|
||||
/**
|
||||
* Variant of {@link #toEntity(Class)} to decode to a list of entities.
|
||||
* @param elementType the type of elements in the list
|
||||
* @return the list of decoded entities, or an empty list if the field is
|
||||
* {@code null} but otherwise there are no errors
|
||||
* @throws FieldAccessException if the target field is {@code null} and the
|
||||
* response is not {@link GraphQlResponse#isValid() valid} or the field has
|
||||
* {@link ResponseField#getErrors() errors}.
|
||||
*/
|
||||
/**
|
||||
* Variant of {@link #toEntity(Class)} to decode to a list of entities.
|
||||
* @param <D> the entity type
|
||||
* @param elementType the type of elements in the list
|
||||
* @return the list of decoded entities, or an empty list if the field is
|
||||
* {@code null} but otherwise there are no errors
|
||||
* @throws FieldAccessException if the target field is {@code null} and the
|
||||
* response is not {@link GraphQlResponse#isValid() valid} or the field has
|
||||
* {@link ResponseField#getErrors() errors}.
|
||||
*/
|
||||
<D> List<D> toEntityList(ParameterizedTypeReference<D> elementType);
|
||||
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.client;
|
||||
|
||||
import java.util.List;
|
||||
@@ -37,7 +38,6 @@ import org.springframework.web.reactive.socket.WebSocketSession;
|
||||
* Helper class for encoding and decoding GraphQL messages.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class CodecDelegate {
|
||||
|
||||
@@ -60,14 +60,14 @@ final class CodecDelegate {
|
||||
|
||||
static Encoder<?> findJsonEncoder(CodecConfigurer configurer) {
|
||||
return findJsonEncoder(configurer.getWriters().stream()
|
||||
.filter(writer -> writer instanceof EncoderHttpMessageWriter)
|
||||
.map(writer -> ((EncoderHttpMessageWriter<?>) writer).getEncoder()));
|
||||
.filter((writer) -> writer instanceof EncoderHttpMessageWriter)
|
||||
.map((writer) -> ((EncoderHttpMessageWriter<?>) writer).getEncoder()));
|
||||
}
|
||||
|
||||
static Decoder<?> findJsonDecoder(CodecConfigurer configurer) {
|
||||
return findJsonDecoder(configurer.getReaders().stream()
|
||||
.filter(reader -> reader instanceof DecoderHttpMessageReader)
|
||||
.map(reader -> ((DecoderHttpMessageReader<?>) reader).getDecoder()));
|
||||
.filter((reader) -> reader instanceof DecoderHttpMessageReader)
|
||||
.map((reader) -> ((DecoderHttpMessageReader<?>) reader).getDecoder()));
|
||||
}
|
||||
|
||||
static Encoder<?> findJsonEncoder(List<Encoder<?>> encoders) {
|
||||
@@ -80,26 +80,26 @@ final class CodecDelegate {
|
||||
|
||||
private static Encoder<?> findJsonEncoder(Stream<Encoder<?>> stream) {
|
||||
return stream
|
||||
.filter(encoder -> encoder.canEncode(MESSAGE_TYPE, MediaType.APPLICATION_JSON))
|
||||
.filter((encoder) -> encoder.canEncode(MESSAGE_TYPE, MediaType.APPLICATION_JSON))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalArgumentException("No JSON Encoder"));
|
||||
}
|
||||
|
||||
private static Decoder<?> findJsonDecoder(Stream<Decoder<?>> decoderStream) {
|
||||
return decoderStream
|
||||
.filter(decoder -> decoder.canDecode(MESSAGE_TYPE, MediaType.APPLICATION_JSON))
|
||||
.filter((decoder) -> decoder.canDecode(MESSAGE_TYPE, MediaType.APPLICATION_JSON))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalArgumentException("No JSON Decoder"));
|
||||
}
|
||||
|
||||
|
||||
public CodecConfigurer getCodecConfigurer() {
|
||||
CodecConfigurer getCodecConfigurer() {
|
||||
return this.codecConfigurer;
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> WebSocketMessage encode(WebSocketSession session, GraphQlWebSocketMessage message) {
|
||||
<T> WebSocketMessage encode(WebSocketSession session, GraphQlWebSocketMessage message) {
|
||||
|
||||
DataBuffer buffer = ((Encoder<T>) this.encoder).encodeValue(
|
||||
(T) message, session.bufferFactory(), MESSAGE_TYPE, MimeTypeUtils.APPLICATION_JSON, null);
|
||||
@@ -108,7 +108,7 @@ final class CodecDelegate {
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
public GraphQlWebSocketMessage decode(WebSocketMessage webSocketMessage) {
|
||||
GraphQlWebSocketMessage decode(WebSocketMessage webSocketMessage) {
|
||||
DataBuffer buffer = DataBufferUtils.retain(webSocketMessage.getPayload());
|
||||
return (GraphQlWebSocketMessage) this.decoder.decode(buffer, MESSAGE_TYPE, null, null);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ import org.springframework.lang.Nullable;
|
||||
* Default implementation of {@link ClientGraphQlRequest}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class DefaultClientGraphQlRequest extends DefaultGraphQlRequest implements ClientGraphQlRequest {
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ import org.springframework.graphql.GraphQlResponse;
|
||||
* Default implementation of {@link ClientGraphQlResponse}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class DefaultClientGraphQlResponse extends ResponseMapGraphQlResponse implements ClientGraphQlResponse {
|
||||
|
||||
|
||||
@@ -40,7 +40,6 @@ import org.springframework.util.MimeTypeUtils;
|
||||
* support for decoding.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class DefaultClientResponseField implements ClientResponseField {
|
||||
|
||||
@@ -88,13 +87,13 @@ final class DefaultClientResponseField implements ClientResponseField {
|
||||
@Override
|
||||
public <D> List<D> toEntityList(Class<D> elementType) {
|
||||
List<D> list = toEntity(ResolvableType.forClassWithGenerics(List.class, elementType));
|
||||
return (list != null ? list : Collections.emptyList());
|
||||
return (list != null) ? list : Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <D> List<D> toEntityList(ParameterizedTypeReference<D> elementType) {
|
||||
List<D> list = toEntity(ResolvableType.forClassWithGenerics(List.class, ResolvableType.forType(elementType)));
|
||||
return (list != null ? list : Collections.emptyList());
|
||||
return (list != null) ? list : Collections.emptyList();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
||||
@@ -36,7 +36,6 @@ import org.springframework.util.Assert;
|
||||
* Default, final {@link GraphQlClient} implementation for use with any transport.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class DefaultGraphQlClient implements GraphQlClient {
|
||||
|
||||
@@ -63,7 +62,7 @@ final class DefaultGraphQlClient implements GraphQlClient {
|
||||
this.documentSource = documentSource;
|
||||
this.blockingChain = blockingChain;
|
||||
this.nonBlockingChain = adaptToNonBlockingChain(blockingChain, scheduler);
|
||||
this.subscriptionChain = request -> Flux.error(new IllegalStateException("Subscriptions on supported"));
|
||||
this.subscriptionChain = (request) -> Flux.error(new IllegalStateException("Subscriptions on supported"));
|
||||
this.blockingTimeout = blockingTimeout;
|
||||
}
|
||||
|
||||
@@ -87,14 +86,14 @@ final class DefaultGraphQlClient implements GraphQlClient {
|
||||
private static GraphQlClientInterceptor.Chain adaptToNonBlockingChain(
|
||||
SyncGraphQlClientInterceptor.Chain blockingChain, Scheduler scheduler) {
|
||||
|
||||
return request -> Mono.fromCallable(() -> blockingChain.next(request)).subscribeOn(scheduler);
|
||||
return (request) -> Mono.fromCallable(() -> blockingChain.next(request)).subscribeOn(scheduler);
|
||||
}
|
||||
|
||||
@SuppressWarnings("DataFlowIssue")
|
||||
private static SyncGraphQlClientInterceptor.Chain adaptToBlockingChain(
|
||||
GraphQlClientInterceptor.Chain executeChain, @Nullable Duration blockingTimeout) {
|
||||
|
||||
return (request -> blockingTimeout != null ?
|
||||
return ((request) -> (blockingTimeout != null) ?
|
||||
executeChain.next(request).block(blockingTimeout) : executeChain.next(request).block());
|
||||
}
|
||||
|
||||
@@ -203,28 +202,28 @@ final class DefaultGraphQlClient implements GraphQlClient {
|
||||
@Override
|
||||
public ClientGraphQlResponse executeSync() {
|
||||
Mono<ClientGraphQlRequest> mono = initRequest();
|
||||
ClientGraphQlRequest request = (blockingTimeout != null ? mono.block(blockingTimeout) : mono.block());
|
||||
return blockingChain.next(request);
|
||||
ClientGraphQlRequest request = (DefaultGraphQlClient.this.blockingTimeout != null) ? mono.block(DefaultGraphQlClient.this.blockingTimeout) : mono.block();
|
||||
return DefaultGraphQlClient.this.blockingChain.next(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ClientGraphQlResponse> execute() {
|
||||
return initRequest().flatMap(request -> nonBlockingChain.next(request)
|
||||
return initRequest().flatMap((request) -> DefaultGraphQlClient.this.nonBlockingChain.next(request)
|
||||
.onErrorResume(
|
||||
ex -> !(ex instanceof GraphQlClientException),
|
||||
ex -> Mono.error(new GraphQlTransportException(ex, request))));
|
||||
(ex) -> !(ex instanceof GraphQlClientException),
|
||||
(ex) -> Mono.error(new GraphQlTransportException(ex, request))));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ClientGraphQlResponse> executeSubscription() {
|
||||
return initRequest().flatMapMany(request -> subscriptionChain.next(request)
|
||||
return initRequest().flatMapMany((request) -> DefaultGraphQlClient.this.subscriptionChain.next(request)
|
||||
.onErrorResume(
|
||||
ex -> !(ex instanceof GraphQlClientException),
|
||||
ex -> Mono.error(new GraphQlTransportException(ex, request))));
|
||||
(ex) -> !(ex instanceof GraphQlClientException),
|
||||
(ex) -> Mono.error(new GraphQlTransportException(ex, request))));
|
||||
}
|
||||
|
||||
private Mono<ClientGraphQlRequest> initRequest() {
|
||||
return this.documentMono.map(document -> new DefaultClientGraphQlRequest(
|
||||
return this.documentMono.map((document) -> new DefaultClientGraphQlRequest(
|
||||
document, this.operationName, this.variables, this.extensions, this.attributes));
|
||||
}
|
||||
|
||||
@@ -252,7 +251,7 @@ final class DefaultGraphQlClient implements GraphQlClient {
|
||||
throw new FieldAccessException(
|
||||
((DefaultClientGraphQlResponse) response).getRequest(), response, field);
|
||||
}
|
||||
return (field.getValue() != null ? field : null);
|
||||
return (field.getValue() != null) ? field : null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -270,25 +269,25 @@ final class DefaultGraphQlClient implements GraphQlClient {
|
||||
@Override
|
||||
public <D> D toEntity(Class<D> entityType) {
|
||||
ClientResponseField field = getValidField(this.response);
|
||||
return (field != null ? field.toEntity(entityType) : null);
|
||||
return (field != null) ? field.toEntity(entityType) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <D> D toEntity(ParameterizedTypeReference<D> entityType) {
|
||||
ClientResponseField field = getValidField(this.response);
|
||||
return (field != null ? field.toEntity(entityType) : null);
|
||||
return (field != null) ? field.toEntity(entityType) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <D> List<D> toEntityList(Class<D> elementType) {
|
||||
ClientResponseField field = getValidField(this.response);
|
||||
return (field != null ? field.toEntityList(elementType) : Collections.emptyList());
|
||||
return (field != null) ? field.toEntityList(elementType) : Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <D> List<D> toEntityList(ParameterizedTypeReference<D> elementType) {
|
||||
ClientResponseField field = getValidField(this.response);
|
||||
return (field != null ? field.toEntityList(elementType) : Collections.emptyList());
|
||||
return (field != null) ? field.toEntityList(elementType) : Collections.emptyList();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -305,27 +304,27 @@ final class DefaultGraphQlClient implements GraphQlClient {
|
||||
|
||||
@Override
|
||||
public <D> Mono<D> toEntity(Class<D> entityType) {
|
||||
return this.responseMono.mapNotNull(this::getValidField).mapNotNull(field -> field.toEntity(entityType));
|
||||
return this.responseMono.mapNotNull(this::getValidField).mapNotNull((field) -> field.toEntity(entityType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <D> Mono<D> toEntity(ParameterizedTypeReference<D> entityType) {
|
||||
return this.responseMono.mapNotNull(this::getValidField).mapNotNull(field -> field.toEntity(entityType));
|
||||
return this.responseMono.mapNotNull(this::getValidField).mapNotNull((field) -> field.toEntity(entityType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <D> Mono<List<D>> toEntityList(Class<D> elementType) {
|
||||
return this.responseMono.map(response -> {
|
||||
return this.responseMono.map((response) -> {
|
||||
ClientResponseField field = getValidField(response);
|
||||
return (field != null ? field.toEntityList(elementType) : Collections.emptyList());
|
||||
return (field != null) ? field.toEntityList(elementType) : Collections.emptyList();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public <D> Mono<List<D>> toEntityList(ParameterizedTypeReference<D> elementType) {
|
||||
return this.responseMono.map(response -> {
|
||||
return this.responseMono.map((response) -> {
|
||||
ClientResponseField field = getValidField(response);
|
||||
return (field != null ? field.toEntityList(elementType) : Collections.emptyList());
|
||||
return (field != null) ? field.toEntityList(elementType) : Collections.emptyList();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -343,27 +342,27 @@ final class DefaultGraphQlClient implements GraphQlClient {
|
||||
|
||||
@Override
|
||||
public <D> Flux<D> toEntity(Class<D> entityType) {
|
||||
return this.responseFlux.mapNotNull(this::getValidField).mapNotNull(field -> field.toEntity(entityType));
|
||||
return this.responseFlux.mapNotNull(this::getValidField).mapNotNull((field) -> field.toEntity(entityType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <D> Flux<D> toEntity(ParameterizedTypeReference<D> entityType) {
|
||||
return this.responseFlux.mapNotNull(this::getValidField).mapNotNull(field -> field.toEntity(entityType));
|
||||
return this.responseFlux.mapNotNull(this::getValidField).mapNotNull((field) -> field.toEntity(entityType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <D> Flux<List<D>> toEntityList(Class<D> elementType) {
|
||||
return this.responseFlux.map(response -> {
|
||||
return this.responseFlux.map((response) -> {
|
||||
ClientResponseField field = getValidField(response);
|
||||
return (field != null ? field.toEntityList(elementType) : Collections.emptyList());
|
||||
return (field != null) ? field.toEntityList(elementType) : Collections.emptyList();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public <D> Flux<List<D>> toEntityList(ParameterizedTypeReference<D> elementType) {
|
||||
return this.responseFlux.map(response -> {
|
||||
return this.responseFlux.map((response) -> {
|
||||
ClientResponseField field = getValidField(response);
|
||||
return (field != null ? field.toEntityList(elementType) : Collections.emptyList());
|
||||
return (field != null) ? field.toEntityList(elementType) : Collections.emptyList();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@ import org.springframework.web.util.UriComponentsBuilder;
|
||||
* around a {@link WebClient.Builder}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class DefaultHttpGraphQlClientBuilder
|
||||
extends AbstractGraphQlClientBuilder<DefaultHttpGraphQlClientBuilder>
|
||||
@@ -105,7 +104,7 @@ final class DefaultHttpGraphQlClientBuilder
|
||||
public HttpGraphQlClient build() {
|
||||
|
||||
// Pass the codecs to the parent for response decoding
|
||||
this.webClientBuilder.codecs(configurer ->
|
||||
this.webClientBuilder.codecs((configurer) ->
|
||||
setJsonCodecs(
|
||||
CodecDelegate.findJsonEncoder(configurer),
|
||||
CodecDelegate.findJsonDecoder(configurer)));
|
||||
@@ -139,6 +138,7 @@ final class DefaultHttpGraphQlClientBuilder
|
||||
this.builderInitializer = builderInitializer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultHttpGraphQlClientBuilder mutate() {
|
||||
DefaultHttpGraphQlClientBuilder builder = new DefaultHttpGraphQlClientBuilder(this.webClient);
|
||||
this.builderInitializer.accept(builder);
|
||||
|
||||
@@ -41,7 +41,6 @@ import org.springframework.util.MimeTypeUtils;
|
||||
* a {@link RSocketRequester.Builder}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class DefaultRSocketGraphQlClientBuilder
|
||||
extends AbstractGraphQlClientBuilder<DefaultRSocketGraphQlClientBuilder>
|
||||
@@ -134,9 +133,9 @@ final class DefaultRSocketGraphQlClientBuilder
|
||||
public RSocketGraphQlClient build() {
|
||||
|
||||
// Pass the codecs to the parent for response decoding
|
||||
this.requesterBuilder.rsocketStrategies(builder -> {
|
||||
builder.decoders(decoders -> setJsonDecoder(CodecDelegate.findJsonDecoder(decoders)));
|
||||
builder.encoders(encoders -> setJsonEncoder(CodecDelegate.findJsonEncoder(encoders)));
|
||||
this.requesterBuilder.rsocketStrategies((builder) -> {
|
||||
builder.decoders((decoders) -> setJsonDecoder(CodecDelegate.findJsonDecoder(decoders)));
|
||||
builder.encoders((encoders) -> setJsonEncoder(CodecDelegate.findJsonEncoder(encoders)));
|
||||
});
|
||||
|
||||
RSocketRequester requester;
|
||||
|
||||
@@ -34,7 +34,6 @@ import org.springframework.web.util.UriComponentsBuilder;
|
||||
* around a {@link RestClient.Builder}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.3
|
||||
*/
|
||||
final class DefaultSyncHttpGraphQlClientBuilder
|
||||
extends AbstractGraphQlClientSyncBuilder<DefaultSyncHttpGraphQlClientBuilder>
|
||||
@@ -105,7 +104,7 @@ final class DefaultSyncHttpGraphQlClientBuilder
|
||||
@Override
|
||||
public HttpSyncGraphQlClient build() {
|
||||
|
||||
this.restClientBuilder.messageConverters(converters -> {
|
||||
this.restClientBuilder.messageConverters((converters) -> {
|
||||
HttpMessageConverter<Object> converter = HttpMessageConverterDelegate.findJsonConverter(converters);
|
||||
setJsonConverter(converter);
|
||||
});
|
||||
@@ -141,6 +140,7 @@ final class DefaultSyncHttpGraphQlClientBuilder
|
||||
this.builderInitializer = builderInitializer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultSyncHttpGraphQlClientBuilder mutate() {
|
||||
DefaultSyncHttpGraphQlClientBuilder builder = new DefaultSyncHttpGraphQlClientBuilder(this.restClient);
|
||||
this.builderInitializer.accept(builder);
|
||||
|
||||
@@ -26,7 +26,6 @@ import org.springframework.util.Assert;
|
||||
* Default {@link GraphQlClient.Builder} with a given, externally, prepared transport.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class DefaultTransportGraphQlClientBuilder
|
||||
extends AbstractGraphQlClientBuilder<DefaultTransportGraphQlClientBuilder> {
|
||||
|
||||
@@ -20,7 +20,6 @@ import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@@ -37,7 +36,6 @@ import org.springframework.web.util.DefaultUriBuilderFactory;
|
||||
* {@code WebSocketGraphQlTransport}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class DefaultWebSocketGraphQlClientBuilder
|
||||
extends AbstractGraphQlClientBuilder<DefaultWebSocketGraphQlClientBuilder>
|
||||
@@ -130,14 +128,14 @@ final class DefaultWebSocketGraphQlClientBuilder
|
||||
private WebSocketGraphQlClientInterceptor getInterceptor() {
|
||||
|
||||
List<WebSocketGraphQlClientInterceptor> interceptors = getInterceptors().stream()
|
||||
.filter(interceptor -> interceptor instanceof WebSocketGraphQlClientInterceptor)
|
||||
.map(interceptor -> (WebSocketGraphQlClientInterceptor) interceptor)
|
||||
.filter((interceptor) -> interceptor instanceof WebSocketGraphQlClientInterceptor)
|
||||
.map((interceptor) -> (WebSocketGraphQlClientInterceptor) interceptor)
|
||||
.toList();
|
||||
|
||||
Assert.state(interceptors.size() <= 1,
|
||||
"Only a single interceptor of type WebSocketGraphQlClientInterceptor may be configured");
|
||||
|
||||
return (!interceptors.isEmpty() ? interceptors.get(0) : new WebSocketGraphQlClientInterceptor() {});
|
||||
return (!interceptors.isEmpty() ? interceptors.get(0) : new WebSocketGraphQlClientInterceptor() { });
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.client;
|
||||
|
||||
import java.util.HashMap;
|
||||
@@ -39,8 +40,8 @@ import org.springframework.util.Assert;
|
||||
* GraphQlClient client = ... ;
|
||||
* DgsGraphQlClient dgsClient = DgsGraphQlClient.create(client);
|
||||
*
|
||||
* List<Book> books = dgsClient.request(new BooksGraphQLQuery())
|
||||
* .projection(new BooksProjectionRoot<>().id().name())
|
||||
* List<Book> books = dgsClient.request(new BooksGraphQLQuery())
|
||||
* .projection(new BooksProjectionRoot<>().id().name())
|
||||
* .retrieveSync()
|
||||
* .toEntityList(Book.class);
|
||||
* </pre>
|
||||
@@ -67,6 +68,7 @@ public final class DgsGraphQlClient {
|
||||
|
||||
/**
|
||||
* Start defining a GraphQL request for the given {@link GraphQLQuery}.
|
||||
* @param query the GraphQL query
|
||||
*/
|
||||
public RequestSpec request(GraphQLQuery query) {
|
||||
return new RequestSpec(query);
|
||||
@@ -80,7 +82,7 @@ public final class DgsGraphQlClient {
|
||||
public static DgsGraphQlClient create(GraphQlClient client) {
|
||||
return new DgsGraphQlClient(client);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Declare options to gather input for a GraphQL request and execute it.
|
||||
@@ -105,6 +107,7 @@ public final class DgsGraphQlClient {
|
||||
|
||||
/**
|
||||
* Provide a {@link BaseProjectionNode} that defines the response selection set.
|
||||
* @param projectionNode the response selection set
|
||||
* @return ths same builder instance
|
||||
*/
|
||||
public RequestSpec projection(BaseProjectionNode projectionNode) {
|
||||
@@ -114,20 +117,23 @@ public final class DgsGraphQlClient {
|
||||
|
||||
/**
|
||||
* Configure {@link Coercing} for serialization of scalar types.
|
||||
* @param scalarType the scalar type
|
||||
* @param coercing the coercing function for this scalar
|
||||
* @return ths same builder instance
|
||||
*/
|
||||
public RequestSpec coercing(Class<?> scalarType, Coercing<?, ?> coercing) {
|
||||
this.coercingMap = (this.coercingMap != null ? this.coercingMap : new LinkedHashMap<>());
|
||||
this.coercingMap = (this.coercingMap != null) ? this.coercingMap : new LinkedHashMap<>();
|
||||
this.coercingMap.put(scalarType, coercing);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure {@link Coercing} for serialization of scalar types.
|
||||
* @param coercingMap the map of coercing function
|
||||
* @return ths same builder instance
|
||||
*/
|
||||
public RequestSpec coercing(Map<Class<?>, Coercing<?, ?>> coercingMap) {
|
||||
this.coercingMap = (this.coercingMap != null ? this.coercingMap : new LinkedHashMap<>());
|
||||
this.coercingMap = (this.coercingMap != null) ? this.coercingMap : new LinkedHashMap<>();
|
||||
this.coercingMap.putAll(coercingMap);
|
||||
return this;
|
||||
}
|
||||
@@ -136,10 +142,12 @@ public final class DgsGraphQlClient {
|
||||
* Set a client request attribute.
|
||||
* <p>This is purely for client side request processing, i.e. available
|
||||
* throughout the {@link GraphQlClientInterceptor} chain but not sent.
|
||||
* @param name the attribute name
|
||||
* @param value the attribute value
|
||||
* @return ths same builder instance
|
||||
*/
|
||||
public RequestSpec attribute(String name, Object value) {
|
||||
this.attributes = (this.attributes != null ? this.attributes : new HashMap<>());
|
||||
this.attributes = (this.attributes != null) ? this.attributes : new HashMap<>();
|
||||
this.attributes.put(name, value);
|
||||
return this;
|
||||
}
|
||||
@@ -147,10 +155,11 @@ public final class DgsGraphQlClient {
|
||||
/**
|
||||
* Manipulate the client request attributes. The map provided to the consumer
|
||||
* is "live", so the consumer can inspect and modify attributes accordingly.
|
||||
* @param attributesConsumer the consumer that will manipulate request attributes
|
||||
* @return ths same builder instance
|
||||
*/
|
||||
public RequestSpec attributes(Consumer<Map<String, Object>> attributesConsumer) {
|
||||
this.attributes = (this.attributes != null ? this.attributes : new HashMap<>());
|
||||
this.attributes = (this.attributes != null) ? this.attributes : new HashMap<>();
|
||||
attributesConsumer.accept(this.attributes);
|
||||
return this;
|
||||
}
|
||||
@@ -168,6 +177,7 @@ public final class DgsGraphQlClient {
|
||||
|
||||
/**
|
||||
* Variant of {@link #executeSync()} with explicit path relative to the "data" key.
|
||||
* @param path the JSON path relative to the "data" key
|
||||
*/
|
||||
public GraphQlClient.RetrieveSyncSpec retrieveSync(String path) {
|
||||
return initRequestSpec().retrieveSync(path);
|
||||
@@ -186,6 +196,7 @@ public final class DgsGraphQlClient {
|
||||
|
||||
/**
|
||||
* Variant of {@link #retrieve()} with explicit path relative to the "data" key.
|
||||
* @param path the JSON path relative to the "data" key
|
||||
*/
|
||||
public GraphQlClient.RetrieveSpec retrieve(String path) {
|
||||
return initRequestSpec().retrieve(path);
|
||||
@@ -237,15 +248,15 @@ public final class DgsGraphQlClient {
|
||||
Assert.state(this.projectionNode != null || this.coercingMap == null,
|
||||
"Coercing map provided without projection");
|
||||
|
||||
GraphQLQueryRequest request = (this.coercingMap != null ?
|
||||
GraphQLQueryRequest request = (this.coercingMap != null) ?
|
||||
new GraphQLQueryRequest(this.query, this.projectionNode, this.coercingMap) :
|
||||
new GraphQLQueryRequest(this.query, this.projectionNode));
|
||||
new GraphQLQueryRequest(this.query, this.projectionNode);
|
||||
|
||||
String operationName = (this.query.getName() != null ? this.query.getName() : null);
|
||||
String operationName = (this.query.getName() != null) ? this.query.getName() : null;
|
||||
|
||||
return graphQlClient.document(request.serialize())
|
||||
return DgsGraphQlClient.this.graphQlClient.document(request.serialize())
|
||||
.operationName(operationName)
|
||||
.attributes(map -> {
|
||||
.attributes((map) -> {
|
||||
if (this.attributes != null) {
|
||||
map.putAll(this.attributes);
|
||||
}
|
||||
|
||||
@@ -38,6 +38,9 @@ public class FieldAccessException extends GraphQlClientException {
|
||||
|
||||
/**
|
||||
* Constructor with the request and response, and the accessed field.
|
||||
* @param request the client request
|
||||
* @param response the client response
|
||||
* @param field the accessed field that caused the error
|
||||
*/
|
||||
public FieldAccessException(
|
||||
ClientGraphQlRequest request, ClientGraphQlResponse response, ClientResponseField field) {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.client;
|
||||
|
||||
import java.time.Duration;
|
||||
@@ -64,6 +65,7 @@ public interface GraphQlClient {
|
||||
* Variant of {@link #document(String)} that uses the given key to resolve
|
||||
* the GraphQL document from a file with the help of the configured
|
||||
* {@link Builder#documentSource(DocumentSource) DocumentSource}.
|
||||
* @param name the document name
|
||||
* @throws IllegalArgumentException if the content could not be loaded
|
||||
*/
|
||||
RequestSpec documentName(String name);
|
||||
@@ -90,7 +92,7 @@ public interface GraphQlClient {
|
||||
|
||||
/**
|
||||
* Base builder for creating and initializing a {@link GraphQlClient}.
|
||||
* @since 1.3
|
||||
* @param <B> the type of builder
|
||||
*/
|
||||
interface BaseBuilder<B extends BaseBuilder<B>> {
|
||||
|
||||
@@ -100,6 +102,7 @@ public interface GraphQlClient {
|
||||
* <p>By default, this is set to {@link ResourceDocumentSource} with
|
||||
* classpath location {@code "graphql-documents/"} and
|
||||
* {@link ResourceDocumentSource#FILE_EXTENSIONS} as extensions.
|
||||
* @param contentLoader the strategy for resolving documents by their names
|
||||
*/
|
||||
B documentSource(DocumentSource contentLoader);
|
||||
|
||||
@@ -125,7 +128,7 @@ public interface GraphQlClient {
|
||||
/**
|
||||
* Builder to create a {@link GraphQlClient} instance with a
|
||||
* synchronous execution chain and transport.
|
||||
* @since 1.3
|
||||
* @param <B> the type of builder
|
||||
* @see SyncGraphQlTransport
|
||||
*/
|
||||
interface SyncBuilder<B extends SyncBuilder<B>> extends BaseBuilder<B> {
|
||||
@@ -159,6 +162,7 @@ public interface GraphQlClient {
|
||||
/**
|
||||
* Builder to create a {@link GraphQlClient} with a non-blocking execution
|
||||
* chain and transport.
|
||||
* @param <B> the type of builder
|
||||
*/
|
||||
interface Builder<B extends Builder<B>> extends BaseBuilder<B> {
|
||||
|
||||
@@ -248,6 +252,7 @@ public interface GraphQlClient {
|
||||
* <pre>
|
||||
* client.document("..").executeSync()
|
||||
* </pre>
|
||||
* @param path the field path
|
||||
* @return a spec with decoding options
|
||||
* @throws FieldAccessException if the field has any field errors,
|
||||
* including errors at, above or below the field path.
|
||||
@@ -261,6 +266,7 @@ public interface GraphQlClient {
|
||||
* <pre>
|
||||
* client.document("..").execute().map(response -> ...)
|
||||
* </pre>
|
||||
* @param path the field path
|
||||
* @return a spec with decoding options
|
||||
* @throws FieldAccessException if the field has any field errors,
|
||||
* including errors at, above or below the field path.
|
||||
@@ -274,6 +280,7 @@ public interface GraphQlClient {
|
||||
* <pre>
|
||||
* client.document("..").executeSubscription().map(response -> ...)
|
||||
* </pre>
|
||||
* @param path the field path
|
||||
* @return a spec with decoding options
|
||||
*/
|
||||
RetrieveSubscriptionSpec retrieveSubscription(String path);
|
||||
@@ -318,12 +325,12 @@ public interface GraphQlClient {
|
||||
|
||||
/**
|
||||
* Declares options to decode a field in a single response.
|
||||
* @since 1.3
|
||||
*/
|
||||
interface RetrieveSyncSpec {
|
||||
|
||||
/**
|
||||
* Decode the field to an entity of the given type.
|
||||
* @param <D> the type to convert to
|
||||
* @param entityType the type to convert to
|
||||
* @return the entity or null if the field is {@code null} and has no errors.
|
||||
* @throws FieldAccessException in case of {@link ResponseField field
|
||||
@@ -335,18 +342,23 @@ public interface GraphQlClient {
|
||||
|
||||
/**
|
||||
* Variant of {@link #toEntity(Class)} with a {@link ParameterizedTypeReference}.
|
||||
* @param <D> the type to convert to
|
||||
* @param entityType the type to convert to
|
||||
*/
|
||||
@Nullable
|
||||
<D> D toEntity(ParameterizedTypeReference<D> entityType);
|
||||
|
||||
/**
|
||||
* Variant of {@link #toEntity(Class)} to decode to a List of entities.
|
||||
* @param <D> the type to convert to
|
||||
* @param elementType the type of elements in the list
|
||||
*/
|
||||
<D> List<D> toEntityList(Class<D> elementType);
|
||||
|
||||
/**
|
||||
* Variant of {@link #toEntityList(Class)} with a {@link ParameterizedTypeReference}.
|
||||
* @param <D> the type to convert to
|
||||
* @param elementType the type of elements in the list
|
||||
*/
|
||||
<D> List<D> toEntityList(ParameterizedTypeReference<D> elementType);
|
||||
|
||||
@@ -360,6 +372,7 @@ public interface GraphQlClient {
|
||||
|
||||
/**
|
||||
* Decode the field to an entity of the given type.
|
||||
* @param <D> the type to convert to
|
||||
* @param entityType the type to convert to
|
||||
* @return {@code Mono} with the decoded entity; completes with
|
||||
* {@link FieldAccessException} in case of {@link ResponseField field
|
||||
@@ -371,17 +384,22 @@ public interface GraphQlClient {
|
||||
|
||||
/**
|
||||
* Variant of {@link #toEntity(Class)} with a {@link ParameterizedTypeReference}.
|
||||
* @param <D> the entity type
|
||||
* @param entityType the type to convert to
|
||||
*/
|
||||
<D> Mono<D> toEntity(ParameterizedTypeReference<D> entityType);
|
||||
|
||||
/**
|
||||
* Variant of {@link #toEntity(Class)} to decode to a List of entities.
|
||||
* @param <D> the type to convert to
|
||||
* @param elementType the type of elements in the list
|
||||
*/
|
||||
<D> Mono<List<D>> toEntityList(Class<D> elementType);
|
||||
|
||||
/**
|
||||
* Variant of {@link #toEntityList(Class)} with a {@link ParameterizedTypeReference}.
|
||||
* @param <D> the type to convert to
|
||||
* @param elementType the type of elements in the list
|
||||
*/
|
||||
<D> Mono<List<D>> toEntityList(ParameterizedTypeReference<D> elementType);
|
||||
|
||||
@@ -395,6 +413,7 @@ public interface GraphQlClient {
|
||||
|
||||
/**
|
||||
* Decode the field to an entity of the given type.
|
||||
* @param <D> the type to convert to
|
||||
* @param entityType the type to convert to
|
||||
* @return {@code Mono} with the decoded entity; completes with
|
||||
* {@link FieldAccessException} in case of {@link ResponseField field
|
||||
@@ -406,20 +425,25 @@ public interface GraphQlClient {
|
||||
|
||||
/**
|
||||
* Variant of {@link #toEntity(Class)} with a {@link ParameterizedTypeReference}.
|
||||
* @param <D> the type to convert to
|
||||
* @param entityType the type to convert to
|
||||
*/
|
||||
<D> Flux<D> toEntity(ParameterizedTypeReference<D> entityType);
|
||||
|
||||
/**
|
||||
* Variant of {@link #toEntity(Class)} to decode each response to a List of entities.
|
||||
* @param <D> the type to convert to
|
||||
* @param elementType the type of elements in the list
|
||||
*/
|
||||
<D> Flux<List<D>> toEntityList(Class<D> elementType);
|
||||
|
||||
/**
|
||||
* Variant of {@link #toEntity(Class)} to decode each response to a List of entities.
|
||||
* @param <D> the type to convert to
|
||||
* @param elementType the type of elements in the list
|
||||
*/
|
||||
<D> Flux<List<D>> toEntityList(ParameterizedTypeReference<D> elementType);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,9 @@ public class GraphQlClientException extends NestedRuntimeException {
|
||||
|
||||
/**
|
||||
* Constructor with a message, optional cause, and the request details.
|
||||
* @param message the exception message to use
|
||||
* @param cause the original cause for the client exception
|
||||
* @param request the request that failed
|
||||
*/
|
||||
public GraphQlClientException(String message, @Nullable Throwable cause, GraphQlRequest request) {
|
||||
super(message, cause);
|
||||
|
||||
@@ -67,13 +67,13 @@ public interface GraphQlClientInterceptor {
|
||||
@Override
|
||||
public Mono<ClientGraphQlResponse> intercept(ClientGraphQlRequest request, Chain chain) {
|
||||
return GraphQlClientInterceptor.this.intercept(
|
||||
request, nextRequest -> interceptor.intercept(nextRequest, chain));
|
||||
request, (nextRequest) -> interceptor.intercept(nextRequest, chain));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ClientGraphQlResponse> interceptSubscription(ClientGraphQlRequest request, SubscriptionChain chain) {
|
||||
return GraphQlClientInterceptor.this.interceptSubscription(
|
||||
request, nextRequest -> interceptor.interceptSubscription(nextRequest, chain));
|
||||
request, (nextRequest) -> interceptor.interceptSubscription(nextRequest, chain));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ public interface GraphQlTransport {
|
||||
/**
|
||||
* Factory method to create {@link GraphQlResponse} from a GraphQL response
|
||||
* map for use in transport implementations.
|
||||
* @param responseMap the GraphQL response map
|
||||
*/
|
||||
static GraphQlResponse createResponse(Map<String, Object> responseMap) {
|
||||
return new ResponseMapGraphQlResponse(responseMap);
|
||||
|
||||
@@ -32,6 +32,8 @@ public class GraphQlTransportException extends GraphQlClientException {
|
||||
|
||||
/**
|
||||
* Constructor with a default message.
|
||||
* @param cause the original cause of the transport error
|
||||
* @param request the request that failed at the transport level
|
||||
*/
|
||||
public GraphQlTransportException(@Nullable Throwable cause, GraphQlRequest request) {
|
||||
super("GraphQlTransport error: " + cause.getMessage(), cause, request);
|
||||
@@ -39,6 +41,9 @@ public class GraphQlTransportException extends GraphQlClientException {
|
||||
|
||||
/**
|
||||
* Constructor with a given message.
|
||||
* @param message the exception message to use
|
||||
* @param cause the original cause of the transport error
|
||||
* @param request the request that failed at the transport level
|
||||
*/
|
||||
public GraphQlTransportException(String message, @Nullable Throwable cause, GraphQlRequest request) {
|
||||
super(message, cause, request);
|
||||
|
||||
@@ -36,6 +36,7 @@ public interface HttpGraphQlClient extends WebGraphQlClient {
|
||||
|
||||
/**
|
||||
* Create an {@link HttpGraphQlClient} that uses the given {@link WebClient}.
|
||||
* @param webClient the {@code WebClient} to use for sending HTTP requests
|
||||
*/
|
||||
static HttpGraphQlClient create(WebClient webClient) {
|
||||
return builder(webClient.mutate()).build();
|
||||
@@ -51,6 +52,7 @@ public interface HttpGraphQlClient extends WebGraphQlClient {
|
||||
/**
|
||||
* Variant of {@link #builder()} with a pre-configured {@code WebClient}
|
||||
* to mutate and customize further through the returned builder.
|
||||
* @param webClient the {@code WebClient} to use for sending HTTP requests
|
||||
*/
|
||||
static Builder<?> builder(WebClient webClient) {
|
||||
return builder(webClient.mutate());
|
||||
@@ -59,6 +61,7 @@ public interface HttpGraphQlClient extends WebGraphQlClient {
|
||||
/**
|
||||
* Variant of {@link #builder()} with a pre-configured {@code WebClient}
|
||||
* to mutate and customize further through the returned builder.
|
||||
* @param webClientBuilder the {@code WebClient.Builder} to use for building the HTTP client
|
||||
*/
|
||||
static Builder<?> builder(WebClient.Builder webClientBuilder) {
|
||||
return new DefaultHttpGraphQlClientBuilder(webClientBuilder);
|
||||
@@ -67,6 +70,7 @@ public interface HttpGraphQlClient extends WebGraphQlClient {
|
||||
|
||||
/**
|
||||
* Builder for the GraphQL over HTTP client.
|
||||
* @param <B> the builder type
|
||||
*/
|
||||
interface Builder<B extends Builder<B>> extends WebGraphQlClient.Builder<B> {
|
||||
|
||||
@@ -74,6 +78,7 @@ public interface HttpGraphQlClient extends WebGraphQlClient {
|
||||
* Customize the {@code WebClient} to use.
|
||||
* <p>Note that some properties of {@code WebClient.Builder} like the
|
||||
* base URL, headers, and codecs can be customized through this builder.
|
||||
* @param webClient the function for customizing the {@code WebClient.Builder} that's used to build the HTTP client
|
||||
* @see #url(String)
|
||||
* @see #header(String, String...)
|
||||
* @see #codecConfigurer(Consumer)
|
||||
|
||||
@@ -39,15 +39,14 @@ import org.springframework.web.reactive.function.client.WebClient;
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Brian Clozel
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class HttpGraphQlTransport implements GraphQlTransport {
|
||||
|
||||
private static final ParameterizedTypeReference<Map<String, Object>> MAP_TYPE =
|
||||
new ParameterizedTypeReference<Map<String, Object>>() {};
|
||||
new ParameterizedTypeReference<Map<String, Object>>() { };
|
||||
|
||||
private static final ParameterizedTypeReference<ServerSentEvent<Map<String, Object>>> SSE_TYPE =
|
||||
new ParameterizedTypeReference<ServerSentEvent<Map<String, Object>>>() {};
|
||||
new ParameterizedTypeReference<ServerSentEvent<Map<String, Object>>>() { };
|
||||
|
||||
// To be removed in favor of Framework's MediaType.APPLICATION_GRAPHQL_RESPONSE
|
||||
private static final MediaType APPLICATION_GRAPHQL_RESPONSE =
|
||||
@@ -69,7 +68,7 @@ final class HttpGraphQlTransport implements GraphQlTransport {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
webClient.mutate().defaultHeaders(headers::putAll);
|
||||
MediaType contentType = headers.getContentType();
|
||||
return (contentType != null ? contentType : MediaType.APPLICATION_JSON);
|
||||
return (contentType != null) ? contentType : MediaType.APPLICATION_JSON;
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +79,7 @@ final class HttpGraphQlTransport implements GraphQlTransport {
|
||||
.contentType(this.contentType)
|
||||
.accept(MediaType.APPLICATION_JSON, APPLICATION_GRAPHQL_RESPONSE, MediaType.APPLICATION_GRAPHQL)
|
||||
.bodyValue(request.toMap())
|
||||
.attributes(attributes -> {
|
||||
.attributes((attributes) -> {
|
||||
if (request instanceof ClientGraphQlRequest clientRequest) {
|
||||
attributes.putAll(clientRequest.getAttributes());
|
||||
}
|
||||
@@ -96,15 +95,15 @@ final class HttpGraphQlTransport implements GraphQlTransport {
|
||||
.contentType(this.contentType)
|
||||
.accept(MediaType.TEXT_EVENT_STREAM)
|
||||
.bodyValue(request.toMap())
|
||||
.attributes(attributes -> {
|
||||
.attributes((attributes) -> {
|
||||
if (request instanceof ClientGraphQlRequest clientRequest) {
|
||||
attributes.putAll(clientRequest.getAttributes());
|
||||
}
|
||||
})
|
||||
.retrieve()
|
||||
.bodyToFlux(SSE_TYPE)
|
||||
.takeWhile(event -> "next".equals(event.event()))
|
||||
.map(event -> new ResponseMapGraphQlResponse(event.data()));
|
||||
.takeWhile((event) -> "next".equals(event.event()))
|
||||
.map((event) -> new ResponseMapGraphQlResponse(event.data()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -54,14 +54,17 @@ import org.springframework.util.MimeType;
|
||||
* for JSON and adapt it to {@link Encoder} and {@link Decoder}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.3
|
||||
*/
|
||||
final class HttpMessageConverterDelegate {
|
||||
|
||||
private HttpMessageConverterDelegate() {
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static HttpMessageConverter<Object> findJsonConverter(List<HttpMessageConverter<?>> converters) {
|
||||
return (HttpMessageConverter<Object>) converters.stream()
|
||||
.filter(converter -> converter.canRead(Map.class, MediaType.APPLICATION_JSON))
|
||||
.filter((converter) -> converter.canRead(Map.class, MediaType.APPLICATION_JSON))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalArgumentException("No JSON HttpMessageConverter"));
|
||||
}
|
||||
@@ -79,14 +82,14 @@ final class HttpMessageConverterDelegate {
|
||||
if (mimeType instanceof MediaType mediaType) {
|
||||
return mediaType;
|
||||
}
|
||||
return (mimeType != null ? new MediaType(mimeType) : null);
|
||||
return (mimeType != null) ? new MediaType(mimeType) : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Partial Encoder implementation to encode a single value through an HttpMessageConverter.
|
||||
*/
|
||||
private static class HttpMessageConverterEncoder implements Encoder<Object> {
|
||||
private static final class HttpMessageConverterEncoder implements Encoder<Object> {
|
||||
|
||||
private final HttpMessageConverter<Object> converter;
|
||||
|
||||
@@ -140,7 +143,7 @@ final class HttpMessageConverterDelegate {
|
||||
/**
|
||||
* Partial Decoder implementation to decode a single buffer through an HttpMessageConverter.
|
||||
*/
|
||||
private static class HttpMessageConverterDecoder implements Decoder<Object> {
|
||||
private static final class HttpMessageConverterDecoder implements Decoder<Object> {
|
||||
|
||||
private final HttpMessageConverter<Object> converter;
|
||||
|
||||
@@ -196,7 +199,7 @@ final class HttpMessageConverterDelegate {
|
||||
}
|
||||
|
||||
|
||||
private static class HttpInputMessageAdapter extends ByteArrayInputStream implements HttpInputMessage {
|
||||
private static final class HttpInputMessageAdapter extends ByteArrayInputStream implements HttpInputMessage {
|
||||
|
||||
HttpInputMessageAdapter(DataBuffer buffer) {
|
||||
super(toBytes(buffer));
|
||||
@@ -222,7 +225,7 @@ final class HttpMessageConverterDelegate {
|
||||
}
|
||||
|
||||
|
||||
private static class HttpOutputMessageAdapter extends ByteArrayOutputStream implements HttpOutputMessage {
|
||||
private static final class HttpOutputMessageAdapter extends ByteArrayOutputStream implements HttpOutputMessage {
|
||||
|
||||
private static final HttpHeaders noOpHeaders = new HttpHeaders();
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ public interface HttpSyncGraphQlClient extends GraphQlClient {
|
||||
|
||||
/**
|
||||
* Create an {@link HttpSyncGraphQlClient} that uses the given {@link RestClient}.
|
||||
* @param client the {@code RestClient} to use for HTTP requests
|
||||
*/
|
||||
static HttpSyncGraphQlClient create(RestClient client) {
|
||||
return builder(client.mutate()).build();
|
||||
@@ -57,6 +58,7 @@ public interface HttpSyncGraphQlClient extends GraphQlClient {
|
||||
/**
|
||||
* Variant of {@link #builder()} with a pre-configured {@code RestClient}
|
||||
* to mutate and customize further through the returned builder.
|
||||
* @param client the {@code RestClient} to use for HTTP requests
|
||||
*/
|
||||
static Builder<?> builder(RestClient client) {
|
||||
return builder(client.mutate());
|
||||
@@ -65,6 +67,7 @@ public interface HttpSyncGraphQlClient extends GraphQlClient {
|
||||
/**
|
||||
* Variant of {@link #builder()} with a pre-configured {@code RestClient}
|
||||
* to mutate and customize further through the returned builder.
|
||||
* @param builder the {@code RestClient} builder to use for HTTP requests
|
||||
*/
|
||||
static Builder<?> builder(RestClient.Builder builder) {
|
||||
return new DefaultSyncHttpGraphQlClientBuilder(builder);
|
||||
@@ -73,6 +76,7 @@ public interface HttpSyncGraphQlClient extends GraphQlClient {
|
||||
|
||||
/**
|
||||
* Builder for the GraphQL over HTTP client with a blocking execution chain.
|
||||
* @param <B> the type of builder
|
||||
*/
|
||||
interface Builder<B extends Builder<B>> extends GraphQlClient.SyncBuilder<B> {
|
||||
|
||||
@@ -115,6 +119,7 @@ public interface HttpSyncGraphQlClient extends GraphQlClient {
|
||||
* Customize the underlying {@code RestClient}.
|
||||
* <p>Note that some properties of {@code RestClient.Builder} like the base URL,
|
||||
* headers, and message converters can be customized through this builder.
|
||||
* @param builderConsumer a consumer that customizes the {@code RestClient}.
|
||||
* @see #url(String)
|
||||
* @see #header(String, String...)
|
||||
* @see #messageConverters(Consumer)
|
||||
|
||||
@@ -32,11 +32,10 @@ import org.springframework.web.client.RestClient;
|
||||
* Transport for GraphQL over HTTP requests executed with {@link RestClient}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.3
|
||||
*/
|
||||
final class HttpSyncGraphQlTransport implements SyncGraphQlTransport {
|
||||
|
||||
private static final ParameterizedTypeReference<Map<String, Object>> MAP_TYPE = new ParameterizedTypeReference<>() {};
|
||||
private static final ParameterizedTypeReference<Map<String, Object>> MAP_TYPE = new ParameterizedTypeReference<>() { };
|
||||
|
||||
|
||||
private final RestClient restClient;
|
||||
@@ -54,7 +53,7 @@ final class HttpSyncGraphQlTransport implements SyncGraphQlTransport {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
webClient.mutate().defaultHeaders(headers::putAll);
|
||||
MediaType contentType = headers.getContentType();
|
||||
return (contentType != null ? contentType : MediaType.APPLICATION_JSON);
|
||||
return (contentType != null) ? contentType : MediaType.APPLICATION_JSON;
|
||||
}
|
||||
|
||||
|
||||
@@ -68,7 +67,7 @@ final class HttpSyncGraphQlTransport implements SyncGraphQlTransport {
|
||||
.retrieve()
|
||||
.body(MAP_TYPE);
|
||||
|
||||
return new ResponseMapGraphQlResponse(body != null ? body : Collections.emptyMap());
|
||||
return new ResponseMapGraphQlResponse((body != null) ? body : Collections.emptyMap());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@ public interface RSocketGraphQlClient extends GraphQlClient {
|
||||
|
||||
/**
|
||||
* Start with a given {@link #builder()}.
|
||||
* @param requesterBuilder the existing request builder
|
||||
*/
|
||||
static Builder<?> builder(RSocketRequester.Builder requesterBuilder) {
|
||||
return new DefaultRSocketGraphQlClientBuilder(requesterBuilder);
|
||||
@@ -78,6 +79,7 @@ public interface RSocketGraphQlClient extends GraphQlClient {
|
||||
|
||||
/**
|
||||
* Builder for the GraphQL over HTTP client.
|
||||
* @param <B> the builder type
|
||||
*/
|
||||
interface Builder<B extends Builder<B>> extends GraphQlClient.Builder<B> {
|
||||
|
||||
@@ -146,11 +148,12 @@ public interface RSocketGraphQlClient extends GraphQlClient {
|
||||
* <p>Note that some properties of {@code RSocketRequester.Builder} like the
|
||||
* data MimeType, and the underlying RSocket transport can be customized
|
||||
* through this builder.
|
||||
* @param requester the requester to be customized
|
||||
* @return the same builder instance
|
||||
* @see #dataMimeType(MimeType)
|
||||
* @see #tcp(String, int)
|
||||
* @see #webSocket(URI)
|
||||
* @see #clientTransport(ClientTransport)
|
||||
* @return the same builder instance
|
||||
*/
|
||||
B rsocketRequester(Consumer<RSocketRequester.Builder> requester);
|
||||
|
||||
|
||||
@@ -45,12 +45,11 @@ import org.springframework.util.Assert;
|
||||
* metadata extension.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class RSocketGraphQlTransport implements GraphQlTransport {
|
||||
|
||||
private static final ParameterizedTypeReference<Map<String, Object>> MAP_TYPE =
|
||||
new ParameterizedTypeReference<Map<String, Object>>() {};
|
||||
new ParameterizedTypeReference<Map<String, Object>>() { };
|
||||
|
||||
private static final ResolvableType LIST_TYPE = ResolvableType.forClass(List.class);
|
||||
|
||||
@@ -83,7 +82,7 @@ final class RSocketGraphQlTransport implements GraphQlTransport {
|
||||
public Flux<GraphQlResponse> executeSubscription(GraphQlRequest request) {
|
||||
return this.rsocketRequester.route(this.route).data(request.toMap())
|
||||
.retrieveFlux(MAP_TYPE)
|
||||
.onErrorResume(RejectedException.class, ex -> Flux.error(decodeErrors(request, ex)))
|
||||
.onErrorResume(RejectedException.class, (ex) -> Flux.error(decodeErrors(request, ex)))
|
||||
.map(ResponseMapGraphQlResponse::new);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ import org.springframework.util.ObjectUtils;
|
||||
* {@link GraphQlResponse} that wraps a deserialized the GraphQL response map.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class ResponseMapGraphQlResponse extends AbstractGraphQlResponse {
|
||||
|
||||
@@ -60,7 +59,7 @@ class ResponseMapGraphQlResponse extends AbstractGraphQlResponse {
|
||||
@SuppressWarnings("unchecked")
|
||||
private static List<ResponseError> wrapErrors(Map<String, Object> map) {
|
||||
List<Map<String, Object>> errors = (List<Map<String, Object>>) map.get("errors");
|
||||
errors = (errors != null ? errors : Collections.emptyList());
|
||||
errors = (errors != null) ? errors : Collections.emptyList();
|
||||
return errors.stream().map(MapResponseError::new).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@@ -134,7 +133,7 @@ class ResponseMapGraphQlResponse extends AbstractGraphQlResponse {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return locations.stream()
|
||||
.map(m -> new SourceLocation(getInt(m, "line"), getInt(m, "column"), (String) m.get("sourceName")))
|
||||
.map((m) -> new SourceLocation(getInt(m, "line"), getInt(m, "column"), (String) m.get("sourceName")))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@@ -155,7 +154,7 @@ class ResponseMapGraphQlResponse extends AbstractGraphQlResponse {
|
||||
return "";
|
||||
}
|
||||
return path.stream().reduce("",
|
||||
(s, o) -> s + (o instanceof Integer ? "[" + o + "]" : (s.isEmpty() ? o : "." + o)),
|
||||
(s, o) -> s + ((o instanceof Integer) ? "[" + o + "]" : (s.isEmpty() ? o : "." + o)),
|
||||
(s, s2) -> null);
|
||||
}
|
||||
|
||||
@@ -163,7 +162,7 @@ class ResponseMapGraphQlResponse extends AbstractGraphQlResponse {
|
||||
@Override
|
||||
@Nullable
|
||||
public String getMessage() {
|
||||
return (String) errorMap.get("message");
|
||||
return (String) this.errorMap.get("message");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -38,6 +38,8 @@ public class SubscriptionErrorException extends GraphQlTransportException {
|
||||
/**
|
||||
* Constructor with the request details and the errors listed in the payload
|
||||
* of the {@code "errors"} message.
|
||||
* @param request the request details
|
||||
* @param errors the errors listed in the payload
|
||||
*/
|
||||
public SubscriptionErrorException(GraphQlRequest request, List<ResponseError> errors) {
|
||||
super("GraphQL subscription completed with an \"error\" message, " +
|
||||
|
||||
@@ -50,7 +50,7 @@ public interface SyncGraphQlClientInterceptor {
|
||||
@Override
|
||||
public ClientGraphQlResponse intercept(ClientGraphQlRequest request, Chain chain) {
|
||||
return SyncGraphQlClientInterceptor.this.intercept(
|
||||
request, nextRequest -> interceptor.intercept(nextRequest, chain));
|
||||
request, (nextRequest) -> interceptor.intercept(nextRequest, chain));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ public interface WebGraphQlClient extends GraphQlClient {
|
||||
|
||||
/**
|
||||
* Base builder for GraphQL clients over a Web transport.
|
||||
* @param <B> the type of builder
|
||||
*/
|
||||
interface Builder<B extends Builder<B>> extends GraphQlClient.Builder<B> {
|
||||
|
||||
@@ -72,6 +73,7 @@ public interface WebGraphQlClient extends GraphQlClient {
|
||||
* Configure JSON encoders and decoders for use in the
|
||||
* {@link org.springframework.graphql.GraphQlResponse} to convert response
|
||||
* data to higher level objects.
|
||||
* @param codecsConsumer a callback that customizes the configured codecs
|
||||
*/
|
||||
B codecConfigurer(Consumer<CodecConfigurer> codecsConsumer);
|
||||
|
||||
|
||||
@@ -35,6 +35,9 @@ public class WebSocketDisconnectedException extends GraphQlTransportException {
|
||||
/**
|
||||
* Constructor with an explanation about the closure, along with the request
|
||||
* details and the status used to close the WebSocket session.
|
||||
* @param closeStatusMessage the message received when the connection was closed
|
||||
* @param request the ongoing request when the connection was closed
|
||||
* @param status the received close status
|
||||
*/
|
||||
public WebSocketDisconnectedException(String closeStatusMessage, GraphQlRequest request, CloseStatus status) {
|
||||
super(closeStatusMessage, null, request);
|
||||
|
||||
@@ -85,6 +85,7 @@ public interface WebSocketGraphQlClient extends WebGraphQlClient {
|
||||
|
||||
/**
|
||||
* Builder for a GraphQL over WebSocket client.
|
||||
* @param <B> the builder type
|
||||
*/
|
||||
interface Builder<B extends Builder<B>> extends WebGraphQlClient.Builder<B> {
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.client;
|
||||
|
||||
import java.net.URI;
|
||||
@@ -50,7 +51,6 @@ import org.springframework.web.reactive.socket.client.WebSocketClient;
|
||||
* {@link GraphQlTransport} for GraphQL over WebSocket via {@link WebSocketClient}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
* @see <a href="https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md">GraphQL over WebSocket protocol</a>
|
||||
*/
|
||||
final class WebSocketGraphQlTransport implements GraphQlTransport {
|
||||
@@ -78,7 +78,7 @@ final class WebSocketGraphQlTransport implements GraphQlTransport {
|
||||
Assert.notNull(interceptor, "WebSocketGraphQlClientInterceptor is required");
|
||||
|
||||
this.url = url;
|
||||
this.headers.putAll(headers != null ? headers : HttpHeaders.EMPTY);
|
||||
this.headers.putAll((headers != null) ? headers : HttpHeaders.EMPTY);
|
||||
this.webSocketClient = client;
|
||||
|
||||
this.graphQlSessionHandler = new GraphQlSessionHandler(codecConfigurer, interceptor);
|
||||
@@ -100,7 +100,9 @@ final class WebSocketGraphQlTransport implements GraphQlTransport {
|
||||
Mono<GraphQlSession> sessionMono = handler.getGraphQlSession();
|
||||
|
||||
client.execute(uri, headers, handler)
|
||||
.subscribe(aVoid -> {},
|
||||
.subscribe((aVoid) -> {
|
||||
|
||||
},
|
||||
handler::handleWebSocketSessionError,
|
||||
handler::handleWebSocketSessionClosed);
|
||||
|
||||
@@ -109,19 +111,19 @@ final class WebSocketGraphQlTransport implements GraphQlTransport {
|
||||
}
|
||||
|
||||
|
||||
public URI getUrl() {
|
||||
URI getUrl() {
|
||||
return this.url;
|
||||
}
|
||||
|
||||
public HttpHeaders getHeaders() {
|
||||
HttpHeaders getHeaders() {
|
||||
return this.headers;
|
||||
}
|
||||
|
||||
public WebSocketClient getWebSocketClient() {
|
||||
WebSocketClient getWebSocketClient() {
|
||||
return this.webSocketClient;
|
||||
}
|
||||
|
||||
public CodecConfigurer getCodecConfigurer() {
|
||||
CodecConfigurer getCodecConfigurer() {
|
||||
return this.graphQlSessionHandler.getCodecConfigurer();
|
||||
}
|
||||
|
||||
@@ -132,7 +134,7 @@ final class WebSocketGraphQlTransport implements GraphQlTransport {
|
||||
* @return {@code Mono} that completes when the WebSocket is connected and
|
||||
* ready to begin sending GraphQL requests
|
||||
*/
|
||||
public Mono<Void> start() {
|
||||
Mono<Void> start() {
|
||||
this.graphQlSessionHandler.setStopped(false);
|
||||
return this.graphQlSessionMono.then();
|
||||
}
|
||||
@@ -145,19 +147,19 @@ final class WebSocketGraphQlTransport implements GraphQlTransport {
|
||||
* call {@link #start()} to allow requests again.
|
||||
* @return {@code Mono} that completes when the underlying session is closed
|
||||
*/
|
||||
public Mono<Void> stop() {
|
||||
Mono<Void> stop() {
|
||||
this.graphQlSessionHandler.setStopped(true);
|
||||
return this.graphQlSessionMono.flatMap(GraphQlSession::close).onErrorResume(ex -> Mono.empty());
|
||||
return this.graphQlSessionMono.flatMap(GraphQlSession::close).onErrorResume((ex) -> Mono.empty());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<GraphQlResponse> execute(GraphQlRequest request) {
|
||||
return this.graphQlSessionMono.flatMap(session -> session.execute(request));
|
||||
return this.graphQlSessionMono.flatMap((session) -> session.execute(request));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<GraphQlResponse> executeSubscription(GraphQlRequest request) {
|
||||
return this.graphQlSessionMono.flatMapMany(session -> session.executeSubscription(request));
|
||||
return this.graphQlSessionMono.flatMapMany((session) -> session.executeSubscription(request));
|
||||
}
|
||||
|
||||
|
||||
@@ -189,7 +191,7 @@ final class WebSocketGraphQlTransport implements GraphQlTransport {
|
||||
}
|
||||
|
||||
|
||||
public CodecConfigurer getCodecConfigurer() {
|
||||
CodecConfigurer getCodecConfigurer() {
|
||||
return this.codecDelegate.getCodecConfigurer();
|
||||
}
|
||||
|
||||
@@ -205,7 +207,7 @@ final class WebSocketGraphQlTransport implements GraphQlTransport {
|
||||
* the "connection_init" and "connection_ack" messages are exchanged or
|
||||
* returns an error if it fails for any reason.
|
||||
*/
|
||||
public Mono<GraphQlSession> getGraphQlSession() {
|
||||
Mono<GraphQlSession> getGraphQlSession() {
|
||||
return this.graphQlSessionSink.asMono();
|
||||
}
|
||||
|
||||
@@ -213,14 +215,14 @@ final class WebSocketGraphQlTransport implements GraphQlTransport {
|
||||
* When the handler is marked "stopped", i.e. set to {@code true}, new
|
||||
* requests are rejected. When set to {@code true} they are allowed.
|
||||
*/
|
||||
public void setStopped(boolean stopped) {
|
||||
void setStopped(boolean stopped) {
|
||||
this.stopped.set(stopped);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the handler is marked {@link #setStopped(boolean) "stopped"}.
|
||||
*/
|
||||
public boolean isStopped() {
|
||||
boolean isStopped() {
|
||||
return this.stopped.get();
|
||||
}
|
||||
|
||||
@@ -241,10 +243,10 @@ final class WebSocketGraphQlTransport implements GraphQlTransport {
|
||||
|
||||
Mono<Void> sendCompletion =
|
||||
session.send(connectionInitMono.concatWith(graphQlSession.getRequestFlux())
|
||||
.map(message -> this.codecDelegate.encode(session, message)));
|
||||
.map((message) -> this.codecDelegate.encode(session, message)));
|
||||
|
||||
Mono<Void> receiveCompletion = session.receive()
|
||||
.flatMap(webSocketMessage -> {
|
||||
.flatMap((webSocketMessage) -> {
|
||||
if (sessionNotInitialized()) {
|
||||
try {
|
||||
GraphQlWebSocketMessage message = this.codecDelegate.decode(webSocketMessage);
|
||||
@@ -301,14 +303,14 @@ final class WebSocketGraphQlTransport implements GraphQlTransport {
|
||||
private void registerCloseStatusHandling(GraphQlSession graphQlSession, WebSocketSession session) {
|
||||
session.closeStatus()
|
||||
.defaultIfEmpty(CloseStatus.NO_STATUS_CODE)
|
||||
.doOnNext(closeStatus -> {
|
||||
.doOnNext((closeStatus) -> {
|
||||
String closeStatusMessage = initCloseStatusMessage(closeStatus, null, graphQlSession);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(closeStatusMessage);
|
||||
}
|
||||
graphQlSession.terminateRequests(closeStatusMessage, closeStatus);
|
||||
})
|
||||
.doOnError(cause -> {
|
||||
.doOnError((cause) -> {
|
||||
CloseStatus closeStatus = CloseStatus.NO_STATUS_CODE;
|
||||
String closeStatusMessage = initCloseStatusMessage(closeStatus, cause, graphQlSession);
|
||||
if (logger.isErrorEnabled()) {
|
||||
@@ -347,7 +349,7 @@ final class WebSocketGraphQlTransport implements GraphQlTransport {
|
||||
* with an error. The error is routed to subscribers of
|
||||
* {@link #getGraphQlSession()} which is necessary for connection issues.
|
||||
*/
|
||||
public void handleWebSocketSessionError(Throwable ex) {
|
||||
void handleWebSocketSessionError(Throwable ex) {
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Session handling error: " + ex.getMessage(), ex);
|
||||
@@ -364,7 +366,7 @@ final class WebSocketGraphQlTransport implements GraphQlTransport {
|
||||
* This must be called from code that calls the {@code WebSocketClient}
|
||||
* when execution completes.
|
||||
*/
|
||||
public void handleWebSocketSessionClosed() {
|
||||
void handleWebSocketSessionClosed() {
|
||||
this.graphQlSessionSink = Sinks.unsafe().one();
|
||||
}
|
||||
|
||||
@@ -396,16 +398,16 @@ final class WebSocketGraphQlTransport implements GraphQlTransport {
|
||||
/**
|
||||
* Return the {@code Flux} of GraphQL requests to send as WebSocket messages.
|
||||
*/
|
||||
public Flux<GraphQlWebSocketMessage> getRequestFlux() {
|
||||
Flux<GraphQlWebSocketMessage> getRequestFlux() {
|
||||
return this.requestSink.getRequestFlux();
|
||||
}
|
||||
|
||||
|
||||
// Outbound messages
|
||||
|
||||
public Mono<GraphQlResponse> execute(GraphQlRequest request) {
|
||||
Mono<GraphQlResponse> execute(GraphQlRequest request) {
|
||||
String id = String.valueOf(this.requestIndex.incrementAndGet());
|
||||
return Mono.<GraphQlResponse>create(sink -> {
|
||||
return Mono.<GraphQlResponse>create((sink) -> {
|
||||
SingleResponseRequestState state = new SingleResponseRequestState(request, sink);
|
||||
this.requestStateMap.put(id, state);
|
||||
try {
|
||||
@@ -419,9 +421,9 @@ final class WebSocketGraphQlTransport implements GraphQlTransport {
|
||||
}).doOnCancel(() -> this.requestStateMap.remove(id));
|
||||
}
|
||||
|
||||
public Flux<GraphQlResponse> executeSubscription(GraphQlRequest request) {
|
||||
Flux<GraphQlResponse> executeSubscription(GraphQlRequest request) {
|
||||
String id = String.valueOf(this.requestIndex.incrementAndGet());
|
||||
return Flux.<GraphQlResponse>create(sink -> {
|
||||
return Flux.<GraphQlResponse>create((sink) -> {
|
||||
SubscriptionRequestState state = new SubscriptionRequestState(request, sink);
|
||||
this.requestStateMap.put(id, state);
|
||||
try {
|
||||
@@ -452,7 +454,7 @@ final class WebSocketGraphQlTransport implements GraphQlTransport {
|
||||
}
|
||||
}
|
||||
|
||||
public void sendPong(@Nullable Map<String, Object> payload) {
|
||||
void sendPong(@Nullable Map<String, Object> payload) {
|
||||
GraphQlWebSocketMessage message = GraphQlWebSocketMessage.pong(payload);
|
||||
this.requestSink.sendRequest(message);
|
||||
}
|
||||
@@ -463,7 +465,7 @@ final class WebSocketGraphQlTransport implements GraphQlTransport {
|
||||
/**
|
||||
* Handle a "next" message and route to its recipient.
|
||||
*/
|
||||
public void handleNext(GraphQlWebSocketMessage message) {
|
||||
void handleNext(GraphQlWebSocketMessage message) {
|
||||
String id = message.getId();
|
||||
RequestState requestState = this.requestStateMap.get(id);
|
||||
if (requestState == null) {
|
||||
@@ -486,7 +488,7 @@ final class WebSocketGraphQlTransport implements GraphQlTransport {
|
||||
* Handle an "error" message, turning it into an {@link GraphQlResponse}
|
||||
* for single responses, or signaling an error for streams.
|
||||
*/
|
||||
public void handleError(GraphQlWebSocketMessage message) {
|
||||
void handleError(GraphQlWebSocketMessage message) {
|
||||
String id = message.getId();
|
||||
RequestState requestState = this.requestStateMap.remove(id);
|
||||
if (requestState == null) {
|
||||
@@ -512,7 +514,7 @@ final class WebSocketGraphQlTransport implements GraphQlTransport {
|
||||
/**
|
||||
* Handle a "complete" message.
|
||||
*/
|
||||
public void handleComplete(GraphQlWebSocketMessage message) {
|
||||
void handleComplete(GraphQlWebSocketMessage message) {
|
||||
String id = message.getId();
|
||||
RequestState requestState = this.requestStateMap.remove(id);
|
||||
if (requestState == null) {
|
||||
@@ -528,22 +530,22 @@ final class WebSocketGraphQlTransport implements GraphQlTransport {
|
||||
* Return a {@code Mono} that completes when the connection is closed
|
||||
* for any reason.
|
||||
*/
|
||||
public Mono<Void> notifyWhenClosed() {
|
||||
Mono<Void> notifyWhenClosed() {
|
||||
return this.connection.notifyWhenClosed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the underlying connection.
|
||||
*/
|
||||
public Mono<Void> close() {
|
||||
Mono<Void> close() {
|
||||
return this.connection.close(CloseStatus.GOING_AWAY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminate and clean all in-progress requests with the given error.
|
||||
*/
|
||||
public void terminateRequests(String message, CloseStatus status) {
|
||||
this.requestStateMap.values().forEach(info -> info.emitDisconnectError(message, status));
|
||||
void terminateRequests(String message, CloseStatus status) {
|
||||
this.requestStateMap.values().forEach((info) -> info.emitDisconnectError(message, status));
|
||||
this.requestStateMap.clear();
|
||||
}
|
||||
|
||||
@@ -595,21 +597,21 @@ final class WebSocketGraphQlTransport implements GraphQlTransport {
|
||||
/**
|
||||
* Holds the request {@code Flux} and associated {@link FluxSink}.
|
||||
*/
|
||||
private static class RequestSink {
|
||||
private static final class RequestSink {
|
||||
|
||||
@Nullable
|
||||
private FluxSink<GraphQlWebSocketMessage> requestSink;
|
||||
|
||||
private final Flux<GraphQlWebSocketMessage> requestFlux = Flux.create(sink -> {
|
||||
private final Flux<GraphQlWebSocketMessage> requestFlux = Flux.create((sink) -> {
|
||||
Assert.state(this.requestSink == null, "Expected single subscriber only for outbound messages");
|
||||
this.requestSink = sink;
|
||||
});
|
||||
|
||||
public Flux<GraphQlWebSocketMessage> getRequestFlux() {
|
||||
Flux<GraphQlWebSocketMessage> getRequestFlux() {
|
||||
return this.requestFlux;
|
||||
}
|
||||
|
||||
public void sendRequest(GraphQlWebSocketMessage message) {
|
||||
void sendRequest(GraphQlWebSocketMessage message) {
|
||||
Assert.state(this.requestSink != null, "Unexpected request before Flux is subscribed to");
|
||||
this.requestSink.next(message);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.data;
|
||||
|
||||
|
||||
@@ -39,8 +40,8 @@ import org.springframework.util.ObjectUtils;
|
||||
* object.
|
||||
* </ul>
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @param <T> the type of value contained
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.1.0
|
||||
* @see <a href="http://spec.graphql.org/October2021/#sec-Non-Null.Nullable-vs-Optional">Nullable vs Optional</a>
|
||||
*/
|
||||
@@ -115,6 +116,7 @@ public final class ArgumentValue<T> {
|
||||
/**
|
||||
* Static factory method for an argument value that was provided, even if
|
||||
* it was set to {@literal "null}.
|
||||
* @param <T> the type of value
|
||||
* @param value the value to hold in the instance
|
||||
*/
|
||||
public static <T> ArgumentValue<T> ofNullable(@Nullable T value) {
|
||||
@@ -123,6 +125,7 @@ public final class ArgumentValue<T> {
|
||||
|
||||
/**
|
||||
* Static factory method for an argument value that was omitted.
|
||||
* @param <T> the type of value
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> ArgumentValue<T> omitted() {
|
||||
|
||||
@@ -139,7 +139,7 @@ public class GraphQlArgumentBinder {
|
||||
DataFetchingEnvironment environment, @Nullable String name, ResolvableType targetType)
|
||||
throws BindException {
|
||||
|
||||
Object rawValue = (name != null ? environment.getArgument(name) : environment.getArguments());
|
||||
Object rawValue = (name != null) ? environment.getArgument(name) : environment.getArguments();
|
||||
boolean isOmitted = (name != null && !environment.getArguments().containsKey(name));
|
||||
|
||||
return bind(name, rawValue, isOmitted, targetType);
|
||||
@@ -148,6 +148,11 @@ public class GraphQlArgumentBinder {
|
||||
/**
|
||||
* Variant of {@link #bind(DataFetchingEnvironment, String, ResolvableType)}
|
||||
* with a pre-extracted raw value to bind from.
|
||||
* @param name the name of an argument, or {@code null} to use the full map
|
||||
* @param rawValue the raw argument value (Collection, Map, or scalar)
|
||||
* @param isOmitted {@code true} if the argument was omitted from the input
|
||||
* and {@code false} if it was provided, but possibly {@code null}
|
||||
* @param targetType the type of Object to create
|
||||
* @since 1.3
|
||||
*/
|
||||
@Nullable
|
||||
@@ -259,9 +264,9 @@ public class GraphQlArgumentBinder {
|
||||
|
||||
Constructor<?> constructor = BeanUtils.getResolvableConstructor(targetClass);
|
||||
|
||||
Object value = (constructor.getParameterCount() > 0 ?
|
||||
Object value = (constructor.getParameterCount() > 0) ?
|
||||
bindMapToObjectViaConstructor(rawMap, constructor, targetType, bindingResult) :
|
||||
bindMapToObjectViaSetters(rawMap, constructor, targetType, bindingResult));
|
||||
bindMapToObjectViaSetters(rawMap, constructor, targetType, bindingResult);
|
||||
|
||||
bindingResult.popNestedPath();
|
||||
|
||||
@@ -373,7 +378,7 @@ public class GraphQlArgumentBinder {
|
||||
Object value = null;
|
||||
try {
|
||||
TypeConverter converter =
|
||||
(this.typeConverter != null ? this.typeConverter : new SimpleTypeConverter());
|
||||
(this.typeConverter != null) ? this.typeConverter : new SimpleTypeConverter();
|
||||
|
||||
value = converter.convertIfNecessary(
|
||||
rawValue, (Class<?>) clazz, new TypeDescriptor(type, null, null));
|
||||
@@ -398,9 +403,9 @@ public class GraphQlArgumentBinder {
|
||||
}
|
||||
|
||||
private static String initObjectName(ResolvableType targetType) {
|
||||
return (targetType.getSource() instanceof MethodParameter methodParameter ?
|
||||
return (targetType.getSource() instanceof MethodParameter methodParameter) ?
|
||||
Conventions.getVariableNameForParameter(methodParameter) :
|
||||
ClassUtils.getShortNameAsProperty(targetType.resolve(Object.class)));
|
||||
ClassUtils.getShortNameAsProperty(targetType.resolve(Object.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -413,7 +418,7 @@ public class GraphQlArgumentBinder {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void rejectArgumentValue(
|
||||
void rejectArgumentValue(
|
||||
String field, @Nullable Object rawValue, String code, String defaultMessage) {
|
||||
|
||||
addError(new FieldError(
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.data;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
|
||||
@@ -44,7 +44,6 @@ import org.springframework.lang.Nullable;
|
||||
* {@link EntityHandlerMethod}s.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.3
|
||||
* @see com.apollographql.federation.graphqljava.SchemaTransformer#fetchEntities(DataFetcher)
|
||||
*/
|
||||
final class EntitiesDataFetcher implements DataFetcher<Mono<DataFetcherResult<List<Object>>>> {
|
||||
@@ -54,7 +53,7 @@ final class EntitiesDataFetcher implements DataFetcher<Mono<DataFetcherResult<Li
|
||||
private final HandlerDataFetcherExceptionResolver exceptionResolver;
|
||||
|
||||
|
||||
public EntitiesDataFetcher(
|
||||
EntitiesDataFetcher(
|
||||
Map<String, EntityHandlerMethod> handlerMethods, HandlerDataFetcherExceptionResolver resolver) {
|
||||
|
||||
this.handlerMethods = new LinkedHashMap<>(handlerMethods);
|
||||
@@ -90,15 +89,15 @@ final class EntitiesDataFetcher implements DataFetcher<Mono<DataFetcherResult<Li
|
||||
|
||||
return handlerMethod.getEntity(env, map, index)
|
||||
.switchIfEmpty(Mono.error(new RepresentationNotResolvedException(map, handlerMethod)))
|
||||
.onErrorResume(ex -> resolveException(ex, env, handlerMethod, index));
|
||||
.onErrorResume((ex) -> resolveException(ex, env, handlerMethod, index));
|
||||
}
|
||||
|
||||
private Mono<Object> resolveException(
|
||||
Throwable ex, DataFetchingEnvironment env, @Nullable EntityHandlerMethod handlerMethod, int index) {
|
||||
|
||||
Throwable theEx = (ex instanceof CompletionException ? ex.getCause() : ex);
|
||||
Throwable theEx = (ex instanceof CompletionException) ? ex.getCause() : ex;
|
||||
DataFetchingEnvironment theEnv = new EntityDataFetchingEnvironment(env, index);
|
||||
Object handler = (handlerMethod != null ? handlerMethod.getBean() : null);
|
||||
Object handler = (handlerMethod != null) ? handlerMethod.getBean() : null;
|
||||
|
||||
return this.exceptionResolver.resolveException(theEx, theEnv, handler)
|
||||
.map(ErrorContainer::new)
|
||||
@@ -108,8 +107,8 @@ final class EntitiesDataFetcher implements DataFetcher<Mono<DataFetcherResult<Li
|
||||
|
||||
private ErrorContainer createDefaultError(Throwable ex, DataFetchingEnvironment env) {
|
||||
|
||||
ErrorType errorType = (ex instanceof RepresentationException representationEx ?
|
||||
representationEx.getErrorType() : ErrorType.INTERNAL_ERROR);
|
||||
ErrorType errorType = (ex instanceof RepresentationException representationEx) ?
|
||||
representationEx.getErrorType() : ErrorType.INTERNAL_ERROR;
|
||||
|
||||
return new ErrorContainer(GraphqlErrorBuilder.newError(env)
|
||||
.errorType(errorType)
|
||||
@@ -134,7 +133,7 @@ final class EntitiesDataFetcher implements DataFetcher<Mono<DataFetcherResult<Li
|
||||
|
||||
private final ExecutionStepInfo executionStepInfo;
|
||||
|
||||
public EntityDataFetchingEnvironment(DataFetchingEnvironment env, int index) {
|
||||
EntityDataFetchingEnvironment(DataFetchingEnvironment env, int index) {
|
||||
super(env);
|
||||
this.executionStepInfo = ExecutionStepInfo.newExecutionStepInfo(env.getExecutionStepInfo())
|
||||
.path(env.getExecutionStepInfo().getPath().segment(index))
|
||||
|
||||
@@ -34,7 +34,6 @@ import org.springframework.validation.BindException;
|
||||
* the entity uniquely.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.3
|
||||
*/
|
||||
final class EntityArgumentMethodArgumentResolver extends ArgumentMethodArgumentResolver {
|
||||
|
||||
@@ -61,7 +60,7 @@ final class EntityArgumentMethodArgumentResolver extends ArgumentMethodArgumentR
|
||||
/**
|
||||
* Wrap the environment in order to also expose the entity representation map.
|
||||
*/
|
||||
public static DataFetchingEnvironment wrap(DataFetchingEnvironment env, Map<String, Object> representation) {
|
||||
static DataFetchingEnvironment wrap(DataFetchingEnvironment env, Map<String, Object> representation) {
|
||||
return new EntityDataFetchingEnvironment(env, representation);
|
||||
}
|
||||
|
||||
@@ -75,7 +74,7 @@ final class EntityArgumentMethodArgumentResolver extends ArgumentMethodArgumentR
|
||||
this.representation = representation;
|
||||
}
|
||||
|
||||
public Map<String, Object> getRepresentation() {
|
||||
Map<String, Object> getRepresentation() {
|
||||
return this.representation;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,11 +32,10 @@ import org.springframework.lang.Nullable;
|
||||
* Invokable controller method to fetch a federated entity.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.3
|
||||
*/
|
||||
final class EntityHandlerMethod extends DataFetcherHandlerMethodSupport {
|
||||
|
||||
public EntityHandlerMethod(
|
||||
EntityHandlerMethod(
|
||||
HandlerMethod handlerMethod, HandlerMethodArgumentResolverComposite resolvers,
|
||||
@Nullable Executor executor) {
|
||||
|
||||
@@ -44,7 +43,7 @@ final class EntityHandlerMethod extends DataFetcherHandlerMethodSupport {
|
||||
}
|
||||
|
||||
|
||||
public Mono<Object> getEntity(
|
||||
Mono<Object> getEntity(
|
||||
DataFetchingEnvironment environment, Map<String, Object> representation, int index) {
|
||||
|
||||
Object[] args;
|
||||
|
||||
@@ -75,6 +75,7 @@ public final class FederationSchemaFactory
|
||||
/**
|
||||
* Configure a resolver that helps to map Java to entity schema type names.
|
||||
* <p>By default this is {@link ClassNameTypeResolver}.
|
||||
* @param typeResolver the custom type resolver to use
|
||||
* @see SchemaTransformer#resolveEntityType(TypeResolver)
|
||||
*/
|
||||
public void setTypeResolver(@Nullable TypeResolver typeResolver) {
|
||||
@@ -86,7 +87,7 @@ public final class FederationSchemaFactory
|
||||
public void afterPropertiesSet() {
|
||||
super.afterPropertiesSet();
|
||||
|
||||
detectHandlerMethods().forEach(info ->
|
||||
detectHandlerMethods().forEach((info) ->
|
||||
this.handlerMethods.put(info.typeName(),
|
||||
new EntityHandlerMethod(info.handlerMethod(), getArgumentResolvers(), getExecutor())));
|
||||
|
||||
@@ -149,6 +150,8 @@ public final class FederationSchemaFactory
|
||||
* Create {@link GraphQLSchema} via {@link SchemaTransformer}, setting up
|
||||
* the "_entities" {@link DataFetcher} and {@link TypeResolver} for federated types.
|
||||
* <p>Use this to supply a {@link SchemaResourceBuilder#schemaFactory(BiFunction) schemaFactory}.
|
||||
* @param registry the existing type definition registry
|
||||
* @param wiring the existing runtime wiring
|
||||
*/
|
||||
public GraphQLSchema createGraphQLSchema(TypeDefinitionRegistry registry, RuntimeWiring wiring) {
|
||||
return createSchemaTransformer(registry, wiring).build();
|
||||
@@ -157,6 +160,8 @@ public final class FederationSchemaFactory
|
||||
/**
|
||||
* Alternative to {@link #createGraphQLSchema(TypeDefinitionRegistry, RuntimeWiring)}
|
||||
* that allows calling additional methods on {@link SchemaTransformer}.
|
||||
* @param registry the existing type definition registry
|
||||
* @param wiring the existing runtime wiring
|
||||
*/
|
||||
public SchemaTransformer createSchemaTransformer(TypeDefinitionRegistry registry, RuntimeWiring wiring) {
|
||||
Assert.state(this.typeResolver != null, "afterPropertiesSet not called");
|
||||
|
||||
@@ -55,7 +55,7 @@ public class RepresentationException extends RuntimeException {
|
||||
super(msg);
|
||||
this.representation = representation;
|
||||
this.handlerMethod = hm;
|
||||
this.errorType = (representation.get("__typename") == null ? ErrorType.BAD_REQUEST : ErrorType.INTERNAL_ERROR);
|
||||
this.errorType = (representation.get("__typename") == null) ? ErrorType.BAD_REQUEST : ErrorType.INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.data.method;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
@@ -77,6 +78,8 @@ public class HandlerMethod {
|
||||
|
||||
/**
|
||||
* Constructor with a handler instance and a method.
|
||||
* @param bean the handler instance
|
||||
* @param method the handler method
|
||||
*/
|
||||
public HandlerMethod(Object bean, Method method) {
|
||||
Assert.notNull(bean, "Bean is required");
|
||||
@@ -94,6 +97,9 @@ public class HandlerMethod {
|
||||
* Constructor with a bean name for the handler along with a {@code BeanFactory}
|
||||
* to allow {@link #createWithResolvedBean() resolving} the handler instance
|
||||
* later.
|
||||
* @param beanName the bean name
|
||||
* @param beanFactory the bean factory to use for bean resolution
|
||||
* @param method the handler method
|
||||
*/
|
||||
public HandlerMethod(String beanName, BeanFactory beanFactory, Method method) {
|
||||
Assert.hasText(beanName, "Bean name is required");
|
||||
@@ -115,6 +121,7 @@ public class HandlerMethod {
|
||||
|
||||
/**
|
||||
* Copy constructor for use from subclasses that accept more arguments.
|
||||
* @param handlerMethod the handler method
|
||||
*/
|
||||
protected HandlerMethod(HandlerMethod handlerMethod) {
|
||||
this(handlerMethod, handlerMethod.bean);
|
||||
@@ -191,6 +198,7 @@ public class HandlerMethod {
|
||||
|
||||
/**
|
||||
* Return the actual return value type.
|
||||
* @param returnValue the return value instance, can be {@code null}
|
||||
*/
|
||||
public MethodParameter getReturnValueType(@Nullable Object returnValue) {
|
||||
return new ReturnValueMethodParameter(returnValue);
|
||||
@@ -208,6 +216,7 @@ public class HandlerMethod {
|
||||
* if no annotation can be found on the given method itself.
|
||||
* <p>Also supports <em>merged</em> composed annotations with attribute
|
||||
* overrides as of Spring Framework 4.3.
|
||||
* @param <A> the annotation type
|
||||
* @param annotationType the type of annotation to introspect the method for
|
||||
* @return the annotation, or {@code null} if none found
|
||||
* @see AnnotatedElementUtils#findMergedAnnotation
|
||||
@@ -219,6 +228,7 @@ public class HandlerMethod {
|
||||
|
||||
/**
|
||||
* Return whether the parameter is declared with the given annotation type.
|
||||
* @param <A> the annotation type
|
||||
* @param annotationType the annotation type to look for
|
||||
* @see AnnotatedElementUtils#hasAnnotation
|
||||
*/
|
||||
@@ -331,6 +341,9 @@ public class HandlerMethod {
|
||||
* processing time may be a JDK dynamic proxy (lazy initialization, prototype
|
||||
* beans, and others). Endpoint classes that require proxying should prefer
|
||||
* class-based proxy mechanisms.
|
||||
* @param method the handler method
|
||||
* @param targetBean the bean instance
|
||||
* @param args the method arguments
|
||||
*/
|
||||
protected void assertTargetBean(Method method, Object targetBean, Object[] args) {
|
||||
Class<?> methodDeclaringClass = method.getDeclaringClass();
|
||||
@@ -347,9 +360,9 @@ public class HandlerMethod {
|
||||
protected String formatInvokeError(String text, Object[] args) {
|
||||
|
||||
String formattedArgs = IntStream.range(0, args.length)
|
||||
.mapToObj(i -> (args[i] != null ?
|
||||
.mapToObj((i) -> (args[i] != null) ?
|
||||
"[" + i + "] [type=" + args[i].getClass().getName() + "] [value=" + args[i] + "]" :
|
||||
"[" + i + "] [null]"))
|
||||
"[" + i + "] [null]")
|
||||
.collect(Collectors.joining(",\n", " ", " "));
|
||||
|
||||
return text + "\n" +
|
||||
@@ -401,21 +414,7 @@ public class HandlerMethod {
|
||||
if (index < ifcAnns.length) {
|
||||
Annotation[] paramAnns = ifcAnns[index];
|
||||
if (paramAnns.length > 0) {
|
||||
List<Annotation> merged = new ArrayList<>(anns.length + paramAnns.length);
|
||||
merged.addAll(Arrays.asList(anns));
|
||||
for (Annotation paramAnn : paramAnns) {
|
||||
boolean existingType = false;
|
||||
for (Annotation ann : anns) {
|
||||
if (ann.annotationType() == paramAnn.annotationType()) {
|
||||
existingType = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!existingType) {
|
||||
merged.add(adaptAnnotation(paramAnn));
|
||||
}
|
||||
}
|
||||
anns = merged.toArray(new Annotation[0]);
|
||||
anns = mergeAnnotations(anns, paramAnns);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -424,6 +423,25 @@ public class HandlerMethod {
|
||||
}
|
||||
return anns;
|
||||
}
|
||||
|
||||
private Annotation[] mergeAnnotations(Annotation[] anns, Annotation[] paramAnns) {
|
||||
List<Annotation> merged = new ArrayList<>(anns.length + paramAnns.length);
|
||||
merged.addAll(Arrays.asList(anns));
|
||||
for (Annotation paramAnn : paramAnns) {
|
||||
boolean existingType = false;
|
||||
for (Annotation ann : anns) {
|
||||
if (ann.annotationType() == paramAnn.annotationType()) {
|
||||
existingType = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!existingType) {
|
||||
merged.add(adaptAnnotation(paramAnn));
|
||||
}
|
||||
}
|
||||
anns = merged.toArray(new Annotation[0]);
|
||||
return anns;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -435,7 +453,7 @@ public class HandlerMethod {
|
||||
@Nullable
|
||||
private final Object returnValue;
|
||||
|
||||
public ReturnValueMethodParameter(@Nullable Object returnValue) {
|
||||
ReturnValueMethodParameter(@Nullable Object returnValue) {
|
||||
super(-1);
|
||||
this.returnValue = returnValue;
|
||||
}
|
||||
@@ -447,7 +465,7 @@ public class HandlerMethod {
|
||||
|
||||
@Override
|
||||
public Class<?> getParameterType() {
|
||||
return (this.returnValue != null ? this.returnValue.getClass() : super.getParameterType());
|
||||
return (this.returnValue != null) ? this.returnValue.getClass() : super.getParameterType();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.data.method;
|
||||
|
||||
import graphql.schema.DataFetchingEnvironment;
|
||||
@@ -35,20 +36,18 @@ public interface HandlerMethodArgumentResolver {
|
||||
|
||||
/**
|
||||
* Whether this resolver supports the given {@link MethodParameter}.
|
||||
* @param parameter the method parameter to check for support
|
||||
*/
|
||||
boolean supportsParameter(MethodParameter parameter);
|
||||
|
||||
/**
|
||||
* Resolve a method parameter to a value.
|
||||
*
|
||||
* @param parameter the method parameter to resolve. This parameter must
|
||||
* have previously checked via {@link #supportsParameter}.
|
||||
* @param environment the environment to use to resolve the value
|
||||
*
|
||||
* @return the resolved value, which may be {@code null} if not resolved;
|
||||
* the value may also be a {@link reactor.core.publisher.Mono} if it
|
||||
* requires asynchronous resolution.
|
||||
*
|
||||
* @throws Exception in case of errors with the preparation of argument values
|
||||
*/
|
||||
@Nullable
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.data.method;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -43,6 +44,7 @@ public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgu
|
||||
|
||||
/**
|
||||
* Add the given {@link HandlerMethodArgumentResolver}.
|
||||
* @param resolver the argument resolver
|
||||
*/
|
||||
public void addResolver(HandlerMethodArgumentResolver resolver) {
|
||||
this.argumentResolvers.add(resolver);
|
||||
@@ -84,10 +86,11 @@ public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgu
|
||||
/**
|
||||
* Find a registered {@link HandlerMethodArgumentResolver} that supports
|
||||
* the given method parameter.
|
||||
* @param parameter the method parameter
|
||||
*/
|
||||
@Nullable
|
||||
public HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
|
||||
return this.argumentResolverCache.computeIfAbsent(parameter, p -> {
|
||||
return this.argumentResolverCache.computeIfAbsent(parameter, (p) -> {
|
||||
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
|
||||
if (resolver.supportsParameter(parameter)) {
|
||||
return resolver;
|
||||
@@ -97,4 +100,4 @@ public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgu
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.data.method;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
@@ -25,7 +26,6 @@ import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import graphql.GraphQLContext;
|
||||
import io.micrometer.context.ContextSnapshot;
|
||||
import io.micrometer.context.ContextSnapshotFactory;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@@ -72,6 +72,7 @@ public abstract class InvocableHandlerMethodSupport extends HandlerMethod {
|
||||
|
||||
/**
|
||||
* Invoke the handler method with the given argument values.
|
||||
* @param graphQLContext the GraphQL context for this data fetching operation
|
||||
* @param argValues the values to use to invoke the method
|
||||
* @return the value returned from the method or a {@code Mono<Throwable>}
|
||||
* if the invocation fails.
|
||||
@@ -92,7 +93,7 @@ public abstract class InvocableHandlerMethodSupport extends HandlerMethod {
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
assertTargetBean(method, getBean(), argValues);
|
||||
String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
|
||||
String text = (ex.getMessage() != null) ? ex.getMessage() : "Illegal argument";
|
||||
return Mono.error(new IllegalStateException(formatInvokeError(text, argValues), ex));
|
||||
}
|
||||
catch (InvocationTargetException ex) {
|
||||
@@ -145,15 +146,16 @@ public abstract class InvocableHandlerMethodSupport extends HandlerMethod {
|
||||
/**
|
||||
* Use this method to resolve the arguments asynchronously. This is only
|
||||
* useful when at least one of the values is a {@link Mono}
|
||||
* @param args the arguments to be resolved asynchronously
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Mono<Object[]> toArgsMono(Object[] args) {
|
||||
List<Mono<Object>> monoList = new ArrayList<>();
|
||||
for (Object arg : args) {
|
||||
Mono<Object> argMono = (arg instanceof Mono ? (Mono<Object>) arg : Mono.justOrEmpty(arg));
|
||||
Mono<Object> argMono = ((arg instanceof Mono) ? (Mono<Object>) arg : Mono.justOrEmpty(arg));
|
||||
monoList.add(argMono.defaultIfEmpty(NO_VALUE));
|
||||
}
|
||||
return Mono.zip(monoList, values -> {
|
||||
return Mono.zip(monoList, (values) -> {
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
if (values[i] == NO_VALUE) {
|
||||
values[i] = null;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.data.method.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.data.method.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.data.method.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.data.method.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.data.method.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.data.method.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.data.method.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.data.method.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.data.method.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.data.method.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.data.method.annotation.support;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
@@ -105,13 +106,13 @@ public class AnnotatedControllerConfigurer
|
||||
|
||||
private static final ClassLoader classLoader = AnnotatedControllerConfigurer.class.getClassLoader();
|
||||
|
||||
private final static boolean springDataPresent = ClassUtils.isPresent(
|
||||
private static final boolean springDataPresent = ClassUtils.isPresent(
|
||||
"org.springframework.data.projection.SpelAwareProxyProjectionFactory", classLoader);
|
||||
|
||||
private final static boolean springSecurityPresent = ClassUtils.isPresent(
|
||||
private static final boolean springSecurityPresent = ClassUtils.isPresent(
|
||||
"org.springframework.security.core.context.SecurityContext", classLoader);
|
||||
|
||||
private final static boolean beanValidationPresent = ClassUtils.isPresent(
|
||||
private static final boolean beanValidationPresent = ClassUtils.isPresent(
|
||||
"jakarta.validation.executable.ExecutableValidator", classLoader);
|
||||
|
||||
|
||||
@@ -125,7 +126,6 @@ public class AnnotatedControllerConfigurer
|
||||
* Add a {@link HandlerMethodArgumentResolver} for custom controller method
|
||||
* arguments. Such custom resolvers are ordered after built-in resolvers
|
||||
* except for {@link SourceMethodArgumentResolver}, which is always last.
|
||||
*
|
||||
* @param resolver the resolver to add.
|
||||
* @since 1.2.0
|
||||
*/
|
||||
@@ -228,7 +228,7 @@ public class AnnotatedControllerConfigurer
|
||||
|
||||
@Override
|
||||
public void configure(RuntimeWiring.Builder runtimeWiringBuilder) {
|
||||
detectHandlerMethods().forEach(info -> {
|
||||
detectHandlerMethods().forEach((info) -> {
|
||||
DataFetcher<?> dataFetcher;
|
||||
if (!info.isBatchMapping()) {
|
||||
dataFetcher = new SchemaMappingDataFetcher(
|
||||
@@ -238,7 +238,7 @@ public class AnnotatedControllerConfigurer
|
||||
dataFetcher = registerBatchLoader(info);
|
||||
}
|
||||
FieldCoordinates coordinates = info.getCoordinates();
|
||||
runtimeWiringBuilder.type(coordinates.getTypeName(), typeBuilder ->
|
||||
runtimeWiringBuilder.type(coordinates.getTypeName(), (typeBuilder) ->
|
||||
typeBuilder.dataFetcher(coordinates.getFieldName(), dataFetcher));
|
||||
});
|
||||
}
|
||||
@@ -322,7 +322,7 @@ public class AnnotatedControllerConfigurer
|
||||
BatchLoaderRegistry registry = obtainApplicationContext().getBean(BatchLoaderRegistry.class);
|
||||
BatchLoaderRegistry.RegistrationSpec<Object, Object> registration = registry.forName(dataLoaderKey);
|
||||
if (info.getMaxBatchSize() > 0) {
|
||||
registration.withOptions(options -> options.setMaxBatchSize(info.getMaxBatchSize()));
|
||||
registration.withOptions((options) -> options.setMaxBatchSize(info.getMaxBatchSize()));
|
||||
}
|
||||
|
||||
HandlerMethod handlerMethod = info.getHandlerMethod();
|
||||
@@ -362,6 +362,7 @@ public class AnnotatedControllerConfigurer
|
||||
* Alternative to {@link #configure(RuntimeWiring.Builder)} that registers
|
||||
* data fetchers in a {@link GraphQLCodeRegistry.Builder}. This could be
|
||||
* used with programmatic creation of {@link graphql.schema.GraphQLSchema}.
|
||||
* @param codeRegistryBuilder the code registry
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public void configure(GraphQLCodeRegistry.Builder codeRegistryBuilder) {
|
||||
@@ -408,7 +409,7 @@ public class AnnotatedControllerConfigurer
|
||||
this.argumentResolvers = argumentResolvers;
|
||||
|
||||
this.methodValidationHelper =
|
||||
(helper != null ? helper.getValidationHelperFor(info.getHandlerMethod()) : null);
|
||||
(helper != null) ? helper.getValidationHelperFor(info.getHandlerMethod()) : null;
|
||||
|
||||
this.exceptionResolver = exceptionResolver;
|
||||
|
||||
@@ -429,12 +430,12 @@ public class AnnotatedControllerConfigurer
|
||||
@Override
|
||||
public Map<String, ResolvableType> getArguments() {
|
||||
|
||||
Predicate<MethodParameter> argumentPredicate = p ->
|
||||
Predicate<MethodParameter> argumentPredicate = (p) ->
|
||||
(p.getParameterAnnotation(Argument.class) != null || p.getParameterType() == ArgumentValue.class);
|
||||
|
||||
return Arrays.stream(this.mappingInfo.getHandlerMethod().getMethodParameters())
|
||||
.filter(argumentPredicate)
|
||||
.peek(p -> p.initParameterNameDiscovery(parameterNameDiscoverer))
|
||||
.peek((p) -> p.initParameterNameDiscovery(parameterNameDiscoverer))
|
||||
.collect(Collectors.toMap(
|
||||
ArgumentMethodArgumentResolver::getArgumentName,
|
||||
ResolvableType::forMethodParameter));
|
||||
@@ -443,7 +444,7 @@ public class AnnotatedControllerConfigurer
|
||||
/**
|
||||
* Return the {@link HandlerMethod} used to fetch data.
|
||||
*/
|
||||
public HandlerMethod getHandlerMethod() {
|
||||
HandlerMethod getHandlerMethod() {
|
||||
return this.mappingInfo.getHandlerMethod();
|
||||
}
|
||||
|
||||
@@ -469,13 +470,13 @@ public class AnnotatedControllerConfigurer
|
||||
DataFetchingEnvironment env, DataFetcherHandlerMethod handlerMethod, Object result) {
|
||||
|
||||
if (this.subscription && result instanceof Publisher<?> publisher) {
|
||||
result = Flux.from(publisher).onErrorResume(ex -> handleSubscriptionError(ex, env, handlerMethod));
|
||||
result = Flux.from(publisher).onErrorResume((ex) -> handleSubscriptionError(ex, env, handlerMethod));
|
||||
}
|
||||
else if (result instanceof Mono) {
|
||||
result = ((Mono<T>) result).onErrorResume(ex -> (Mono<T>) handleException(ex, env, handlerMethod));
|
||||
result = ((Mono<T>) result).onErrorResume((ex) -> (Mono<T>) handleException(ex, env, handlerMethod));
|
||||
}
|
||||
else if (result instanceof Flux<?>) {
|
||||
result = ((Flux<T>) result).onErrorResume(ex -> (Mono<T>) handleException(ex, env, handlerMethod));
|
||||
result = ((Flux<T>) result).onErrorResume((ex) -> (Mono<T>) handleException(ex, env, handlerMethod));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -484,7 +485,7 @@ public class AnnotatedControllerConfigurer
|
||||
Throwable ex, DataFetchingEnvironment env, DataFetcherHandlerMethod handlerMethod) {
|
||||
|
||||
return this.exceptionResolver.resolveException(ex, env, handlerMethod.getBean())
|
||||
.map(errors -> DataFetcherResult.newResult().errors(errors).build())
|
||||
.map((errors) -> DataFetcherResult.newResult().errors(errors).build())
|
||||
.switchIfEmpty(Mono.error(ex));
|
||||
}
|
||||
|
||||
@@ -493,7 +494,7 @@ public class AnnotatedControllerConfigurer
|
||||
Throwable ex, DataFetchingEnvironment env, DataFetcherHandlerMethod handlerMethod) {
|
||||
|
||||
return (Publisher<T>) this.exceptionResolver.resolveException(ex, env, handlerMethod.getBean())
|
||||
.flatMap(errors -> Mono.error(new SubscriptionPublisherException(errors, ex)))
|
||||
.flatMap((errors) -> Mono.error(new SubscriptionPublisherException(errors, ex)))
|
||||
.switchIfEmpty(Mono.error(ex));
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.data.method.annotation.support;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
@@ -54,13 +55,13 @@ import org.springframework.util.ClassUtils;
|
||||
* Convenient base for classes that find annotated controller method with argument
|
||||
* values resolved from a {@link graphql.schema.DataFetchingEnvironment}.
|
||||
*
|
||||
* @param <M> the type of mapping info prepared from a controller method
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.3
|
||||
* @param <M> the type of mapping info prepared from a controller method
|
||||
*/
|
||||
public abstract class AnnotatedControllerDetectionSupport<M> implements ApplicationContextAware, InitializingBean {
|
||||
|
||||
protected final static boolean springSecurityPresent = ClassUtils.isPresent(
|
||||
protected static final boolean springSecurityPresent = ClassUtils.isPresent(
|
||||
"org.springframework.security.core.context.SecurityContext",
|
||||
AnnotatedControllerDetectionSupport.class.getClassLoader());
|
||||
|
||||
@@ -102,6 +103,7 @@ public abstract class AnnotatedControllerDetectionSupport<M> implements Applicat
|
||||
* that assists in binding GraphQL arguments onto
|
||||
* {@link org.springframework.graphql.data.method.annotation.Argument @Argument}
|
||||
* annotated method parameters.
|
||||
* @param registrar the formatter registrar
|
||||
*/
|
||||
public void addFormatterRegistrar(FormatterRegistrar registrar) {
|
||||
registrar.registerFormatters(this.conversionService);
|
||||
@@ -116,6 +118,7 @@ public abstract class AnnotatedControllerDetectionSupport<M> implements Applicat
|
||||
* {@link org.springframework.graphql.data.method.annotation.Argument @Argument}
|
||||
* should falls back to direct field access in case the target object does
|
||||
* not use accessor methods.
|
||||
* @param fallBackOnDirectFieldAccess whether binding should fall back on direct field access
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public void setFallBackOnDirectFieldAccess(boolean fallBackOnDirectFieldAccess) {
|
||||
@@ -133,11 +136,9 @@ public abstract class AnnotatedControllerDetectionSupport<M> implements Applicat
|
||||
* exceptions from non-controller {@link DataFetcher}s since exceptions from
|
||||
* {@code @SchemaMapping} controller methods are handled automatically at
|
||||
* the point of invocation.
|
||||
*
|
||||
* @return a resolver instance that can be plugged into
|
||||
* {@link org.springframework.graphql.execution.GraphQlSource.Builder#exceptionResolvers(List)
|
||||
* GraphQlSource.Builder}
|
||||
*
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public HandlerDataFetcherExceptionResolver getExceptionResolver() {
|
||||
@@ -214,15 +215,15 @@ public abstract class AnnotatedControllerDetectionSupport<M> implements Applicat
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
|
||||
if (this.logger.isTraceEnabled()) {
|
||||
this.logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
|
||||
}
|
||||
}
|
||||
if (beanType == null || !AnnotatedElementUtils.hasAnnotation(beanType, Controller.class)) {
|
||||
continue;
|
||||
}
|
||||
Class<?> beanClass = context.getType(beanName);
|
||||
findHandlerMethods(beanName, beanClass).forEach(info -> registerHandlerMethod(info, results));
|
||||
findHandlerMethods(beanName, beanClass).forEach((info) -> registerHandlerMethod(info, results));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
@@ -240,8 +241,8 @@ public abstract class AnnotatedControllerDetectionSupport<M> implements Applicat
|
||||
|
||||
Collection<M> mappingInfos = map.values();
|
||||
|
||||
if (logger.isTraceEnabled() && !mappingInfos.isEmpty()) {
|
||||
logger.trace(formatMappings(userClass, mappingInfos));
|
||||
if (this.logger.isTraceEnabled() && !mappingInfos.isEmpty()) {
|
||||
this.logger.trace(formatMappings(userClass, mappingInfos));
|
||||
}
|
||||
|
||||
return mappingInfos;
|
||||
@@ -252,10 +253,10 @@ public abstract class AnnotatedControllerDetectionSupport<M> implements Applicat
|
||||
|
||||
private String formatMappings(Class<?> handlerType, Collection<M> infos) {
|
||||
String formattedType = Arrays.stream(ClassUtils.getPackageName(handlerType).split("\\."))
|
||||
.map(p -> p.substring(0, 1))
|
||||
.map((p) -> p.substring(0, 1))
|
||||
.collect(Collectors.joining(".", "", "." + handlerType.getSimpleName()));
|
||||
return infos.stream()
|
||||
.map(info -> {
|
||||
.map((info) -> {
|
||||
Method method = getHandlerMethod(info).getMethod();
|
||||
String methodParameters = Arrays.stream(method.getGenericParameterTypes())
|
||||
.map(Type::getTypeName)
|
||||
@@ -268,7 +269,7 @@ public abstract class AnnotatedControllerDetectionSupport<M> implements Applicat
|
||||
private void registerHandlerMethod(M info, Set<M> results) {
|
||||
Assert.state(this.exceptionResolver != null, "afterPropertiesSet not called");
|
||||
HandlerMethod handlerMethod = getHandlerMethod(info);
|
||||
M existing = results.stream().filter(o -> o.equals(info)).findFirst().orElse(null);
|
||||
M existing = results.stream().filter((o) -> o.equals(info)).findFirst().orElse(null);
|
||||
if (existing != null && !getHandlerMethod(existing).equals(handlerMethod)) {
|
||||
throw new IllegalStateException(
|
||||
"Ambiguous mapping. Cannot map '" + handlerMethod.getBean() + "' method \n" +
|
||||
@@ -281,9 +282,9 @@ public abstract class AnnotatedControllerDetectionSupport<M> implements Applicat
|
||||
|
||||
protected HandlerMethod createHandlerMethod(Method originalMethod, Object handler, Class<?> handlerType) {
|
||||
Method method = AopUtils.selectInvocableMethod(originalMethod, handlerType);
|
||||
return (handler instanceof String beanName ?
|
||||
return (handler instanceof String beanName) ?
|
||||
new HandlerMethod(beanName, obtainApplicationContext().getAutowireCapableBeanFactory(), method) :
|
||||
new HandlerMethod(handler, method));
|
||||
new HandlerMethod(handler, method);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.data.method.annotation.support;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
@@ -72,7 +73,6 @@ import org.springframework.web.method.ControllerAdviceBean;
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Brian Clozel
|
||||
* @since 1.2.0
|
||||
*/
|
||||
final class AnnotatedControllerExceptionResolver implements HandlerDataFetcherExceptionResolver {
|
||||
|
||||
@@ -98,9 +98,9 @@ final class AnnotatedControllerExceptionResolver implements HandlerDataFetcherEx
|
||||
* are validated to ensure they are within a range of supported types.
|
||||
* @param controllerType the controller type to register
|
||||
*/
|
||||
public void registerController(Class<?> controllerType) {
|
||||
void registerController(Class<?> controllerType) {
|
||||
this.controllerCache.computeIfAbsent(
|
||||
controllerType, type -> new MethodResolver(findExceptionHandlers(controllerType)));
|
||||
controllerType, (type) -> new MethodResolver(findExceptionHandlers(controllerType)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,7 +110,7 @@ final class AnnotatedControllerExceptionResolver implements HandlerDataFetcherEx
|
||||
* for use at runtime.
|
||||
* @param context the context to look into
|
||||
*/
|
||||
public void registerControllerAdvice(ApplicationContext context) {
|
||||
void registerControllerAdvice(ApplicationContext context) {
|
||||
Map<ControllerAdviceBean, MethodResolver> detectedControllerAdvice = new HashMap<>();
|
||||
for (ControllerAdviceBean bean : ControllerAdviceBean.findAnnotatedBeans(context)) {
|
||||
Class<?> beanType = bean.getBeanType();
|
||||
@@ -121,9 +121,8 @@ final class AnnotatedControllerExceptionResolver implements HandlerDataFetcherEx
|
||||
}
|
||||
}
|
||||
}
|
||||
detectedControllerAdvice.keySet().stream().sorted(OrderComparator.INSTANCE).forEach(bean -> {
|
||||
this.controllerAdviceCache.put(bean, detectedControllerAdvice.get(bean));
|
||||
});
|
||||
detectedControllerAdvice.keySet().stream().sorted(OrderComparator.INSTANCE)
|
||||
.forEach((bean) -> this.controllerAdviceCache.put(bean, detectedControllerAdvice.get(bean)));
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("@GraphQlException methods in ControllerAdvice beans: " +
|
||||
(this.controllerAdviceCache.isEmpty() ? "none" : this.controllerAdviceCache.size()));
|
||||
@@ -134,7 +133,7 @@ final class AnnotatedControllerExceptionResolver implements HandlerDataFetcherEx
|
||||
private static Map<Class<? extends Throwable>, Method> findExceptionHandlers(Class<?> handlerType) {
|
||||
|
||||
Map<Method, GraphQlExceptionHandler> handlerMap = MethodIntrospector.selectMethods(
|
||||
handlerType, (MethodIntrospector.MetadataLookup<GraphQlExceptionHandler>) method ->
|
||||
handlerType, (MethodIntrospector.MetadataLookup<GraphQlExceptionHandler>) (method) ->
|
||||
AnnotatedElementUtils.findMergedAnnotation(method, GraphQlExceptionHandler.class));
|
||||
|
||||
Map<Class<? extends Throwable>, Method> mappings = new HashMap<>(handlerMap.size());
|
||||
@@ -230,7 +229,7 @@ final class AnnotatedControllerExceptionResolver implements HandlerDataFetcherEx
|
||||
while (exToExpose != null) {
|
||||
exceptions.add(exToExpose);
|
||||
Throwable cause = exToExpose.getCause();
|
||||
exToExpose = (cause != exToExpose ? cause : null);
|
||||
exToExpose = (cause != exToExpose) ? cause : null;
|
||||
}
|
||||
Object[] arguments = new Object[exceptions.size() + 1];
|
||||
exceptions.toArray(arguments); // efficient arraycopy call in ArrayList
|
||||
@@ -278,7 +277,7 @@ final class AnnotatedControllerExceptionResolver implements HandlerDataFetcherEx
|
||||
* @return the exception handler to use, or {@code null} if no match
|
||||
*/
|
||||
@Nullable
|
||||
public MethodHolder resolveMethod(Throwable exception) {
|
||||
MethodHolder resolveMethod(Throwable exception) {
|
||||
MethodHolder method = resolveMethodByExceptionType(exception.getClass());
|
||||
if (method == null) {
|
||||
Throwable cause = exception.getCause();
|
||||
@@ -296,7 +295,7 @@ final class AnnotatedControllerExceptionResolver implements HandlerDataFetcherEx
|
||||
method = getMappedMethod(exceptionType);
|
||||
this.resolvedExceptionCache.put(exceptionType, method);
|
||||
}
|
||||
return (method != NO_MATCH ? method : null);
|
||||
return (method != NO_MATCH) ? method : null;
|
||||
}
|
||||
|
||||
private MethodHolder getMappedMethod(Class<? extends Throwable> exceptionType) {
|
||||
@@ -342,11 +341,11 @@ final class AnnotatedControllerExceptionResolver implements HandlerDataFetcherEx
|
||||
this.adapter = ReturnValueAdapter.createFor(this.returnType);
|
||||
}
|
||||
|
||||
public Method getMethod() {
|
||||
Method getMethod() {
|
||||
return this.method;
|
||||
}
|
||||
|
||||
public Mono<List<GraphQLError>> adapt(@Nullable Object result, Throwable ex) {
|
||||
Mono<List<GraphQLError>> adapt(@Nullable Object result, Throwable ex) {
|
||||
return this.adapter.adapt(result, this.returnType, ex);
|
||||
}
|
||||
|
||||
@@ -421,24 +420,24 @@ final class AnnotatedControllerExceptionResolver implements HandlerDataFetcherEx
|
||||
}
|
||||
|
||||
|
||||
/** Adapter for void */
|
||||
/* Adapter for void */
|
||||
ReturnValueAdapter forVoid = (result, returnType, ex) -> Mono.just(Collections.emptyList());
|
||||
|
||||
/** Adapter for a single GraphQLError */
|
||||
/* Adapter for a single GraphQLError */
|
||||
ReturnValueAdapter forSingleError = (result, returnType, ex) ->
|
||||
(result == null ?
|
||||
Mono.empty() :
|
||||
Mono.just(Collections.singletonList((GraphQLError) result)));
|
||||
(result != null) ?
|
||||
Mono.just(Collections.singletonList((GraphQLError) result)) :
|
||||
Mono.empty();
|
||||
|
||||
/** Adapter for a collection of GraphQLError's */
|
||||
/* Adapter for a collection of GraphQLError's */
|
||||
ReturnValueAdapter forCollection = (result, returnType, ex) ->
|
||||
(result == null ?
|
||||
Mono.empty() :
|
||||
Mono.just((result instanceof List ?
|
||||
(result != null) ?
|
||||
Mono.just((result instanceof List) ?
|
||||
(List<GraphQLError>) result :
|
||||
new ArrayList<>((Collection<GraphQLError>) result))));
|
||||
new ArrayList<>((Collection<GraphQLError>) result)) :
|
||||
Mono.empty();
|
||||
|
||||
/** Adapter for Object */
|
||||
/* Adapter for Object */
|
||||
ReturnValueAdapter forObject = (result, returnType, ex) -> {
|
||||
if (result == null) {
|
||||
return Mono.empty();
|
||||
@@ -458,15 +457,15 @@ final class AnnotatedControllerExceptionResolver implements HandlerDataFetcherEx
|
||||
}
|
||||
};
|
||||
|
||||
/** Adapter for {@code Mono<Void>} */
|
||||
/* Adapter for {@code Mono<Void>} */
|
||||
ReturnValueAdapter forMonoVoid = (result, returnType, ex) ->
|
||||
(result == null ? Mono.empty() : Mono.just(Collections.emptyList()));
|
||||
(result != null) ? Mono.just(Collections.emptyList()) : Mono.empty();
|
||||
|
||||
/** Adapter for a {@code Mono} wrapping any of the other synchronous return value types */
|
||||
/* Adapter for a {@code Mono} wrapping any of the other synchronous return value types */
|
||||
ReturnValueAdapter forMono = (result, returnType, ex) ->
|
||||
(result == null ?
|
||||
Mono.empty() :
|
||||
((Mono<?>) result).flatMap(o -> forObject.adapt(o, returnType, ex)).switchIfEmpty(Mono.error(ex)));
|
||||
(result != null) ?
|
||||
((Mono<?>) result).flatMap((o) -> forObject.adapt(o, returnType, ex)).switchIfEmpty(Mono.error(ex)) :
|
||||
Mono.empty();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.data.method.annotation.support;
|
||||
|
||||
import graphql.schema.DataFetchingEnvironment;
|
||||
@@ -83,6 +84,9 @@ public class ArgumentMethodArgumentResolver implements HandlerMethodArgumentReso
|
||||
|
||||
/**
|
||||
* Perform the binding with the configured {@link #getArgumentBinder() binder}.
|
||||
* @param environment for access to the arguments
|
||||
* @param name the name of an argument, or {@code null} to use the full map
|
||||
* @param targetType the type of Object to create
|
||||
* @since 1.3
|
||||
*/
|
||||
@Nullable
|
||||
|
||||
@@ -19,22 +19,24 @@ package org.springframework.graphql.data.method.annotation.support;
|
||||
import jakarta.validation.valueextraction.ExtractedValue;
|
||||
import jakarta.validation.valueextraction.UnwrapByDefault;
|
||||
import jakarta.validation.valueextraction.ValueExtractor;
|
||||
|
||||
import org.springframework.graphql.data.ArgumentValue;
|
||||
|
||||
/**
|
||||
* {@link ValueExtractor} that enables {@code @Valid} with {@link ArgumentValue},
|
||||
* and helps to extract the value from it.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.2.2
|
||||
*/
|
||||
@UnwrapByDefault
|
||||
public final class ArgumentValueValueExtractor implements ValueExtractor<ArgumentValue<@ExtractedValue ?>> {
|
||||
|
||||
@Override
|
||||
public void extractValues(ArgumentValue<?> argumentValue, ValueReceiver receiver) {
|
||||
if (!argumentValue.isOmitted()) {
|
||||
receiver.value(null, argumentValue.value());
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void extractValues(ArgumentValue<?> argumentValue, ValueReceiver receiver) {
|
||||
if (!argumentValue.isOmitted()) {
|
||||
receiver.value(null, argumentValue.value());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.data.method.annotation.support;
|
||||
|
||||
import graphql.schema.DataFetchingEnvironment;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.data.method.annotation.support;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
@@ -97,7 +98,7 @@ public class AuthenticationPrincipalArgumentResolver implements HandlerMethodArg
|
||||
@Override
|
||||
public Object resolveArgument(MethodParameter parameter, DataFetchingEnvironment environment) throws Exception {
|
||||
return getCurrentAuthentication(parameter)
|
||||
.mapNotNull(auth -> resolvePrincipal(parameter, auth.getPrincipal()))
|
||||
.mapNotNull((auth) -> resolvePrincipal(parameter, auth.getPrincipal()))
|
||||
.transform((argument) -> isPublisherOrMono(parameter) ? Mono.just(argument) : argument);
|
||||
}
|
||||
|
||||
@@ -109,7 +110,7 @@ public class AuthenticationPrincipalArgumentResolver implements HandlerMethodArg
|
||||
@SuppressWarnings("unchecked")
|
||||
private Mono<Authentication> getCurrentAuthentication(MethodParameter parameter) {
|
||||
Object value = PrincipalMethodArgumentResolver.resolveAuthentication(parameter);
|
||||
return (value instanceof Authentication auth ? Mono.just(auth) : (Mono<Authentication>) value);
|
||||
return (value instanceof Authentication auth) ? Mono.just(auth) : (Mono<Authentication>) value;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.data.method.annotation.support;
|
||||
|
||||
import java.security.Principal;
|
||||
@@ -50,7 +51,7 @@ import org.springframework.util.ClassUtils;
|
||||
*/
|
||||
public class BatchLoaderHandlerMethod extends InvocableHandlerMethodSupport {
|
||||
|
||||
private final static boolean springSecurityPresent = ClassUtils.isPresent(
|
||||
private static final boolean springSecurityPresent = ClassUtils.isPresent(
|
||||
"org.springframework.security.core.context.SecurityContext",
|
||||
AnnotatedControllerConfigurer.class.getClassLoader());
|
||||
|
||||
@@ -66,7 +67,6 @@ public class BatchLoaderHandlerMethod extends InvocableHandlerMethodSupport {
|
||||
/**
|
||||
* Invoke the underlying batch loader method with a collection of keys to
|
||||
* return a Map of key-value pairs.
|
||||
*
|
||||
* @param keys the keys for which to load values
|
||||
* @param environment the environment available to batch loaders
|
||||
* @param <K> the type of keys in the map
|
||||
@@ -80,7 +80,7 @@ public class BatchLoaderHandlerMethod extends InvocableHandlerMethodSupport {
|
||||
Object result = doInvoke(environment.getContext(), args);
|
||||
return toMonoMap(result);
|
||||
}
|
||||
return toArgsMono(args).flatMap(argValues -> {
|
||||
return toArgsMono(args).flatMap((argValues) -> {
|
||||
Object result = doInvoke(environment.getContext(), argValues);
|
||||
return toMonoMap(result);
|
||||
});
|
||||
@@ -89,7 +89,6 @@ public class BatchLoaderHandlerMethod extends InvocableHandlerMethodSupport {
|
||||
/**
|
||||
* Invoke the underlying batch loader method with a collection of input keys
|
||||
* to return a collection of matching values.
|
||||
*
|
||||
* @param keys the keys for which to load values
|
||||
* @param environment the environment available to batch loaders
|
||||
* @param <V> the type of values returned
|
||||
@@ -101,7 +100,7 @@ public class BatchLoaderHandlerMethod extends InvocableHandlerMethodSupport {
|
||||
Object result = doInvoke(environment.getContext(), args);
|
||||
return toFlux(result);
|
||||
}
|
||||
return toArgsMono(args).flatMapMany(resolvedArgs -> {
|
||||
return toArgsMono(args).flatMapMany((resolvedArgs) -> {
|
||||
Object result = doInvoke(environment.getContext(), resolvedArgs);
|
||||
return toFlux(result);
|
||||
});
|
||||
@@ -164,7 +163,7 @@ public class BatchLoaderHandlerMethod extends InvocableHandlerMethodSupport {
|
||||
}
|
||||
|
||||
private boolean doesNotHaveAsyncArgs(Object[] args) {
|
||||
return Arrays.stream(args).noneMatch(arg -> arg instanceof Mono);
|
||||
return Arrays.stream(args).noneMatch((arg) -> arg instanceof Mono);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -176,7 +175,7 @@ public class BatchLoaderHandlerMethod extends InvocableHandlerMethodSupport {
|
||||
return (Mono<Map<K, V>>) result;
|
||||
}
|
||||
else if (result instanceof CompletableFuture) {
|
||||
return Mono.fromFuture((CompletableFuture<? extends Map<K,V>>) result);
|
||||
return Mono.fromFuture((CompletableFuture<? extends Map<K, V>>) result);
|
||||
}
|
||||
return Mono.error(new IllegalStateException("Unexpected return value: " + result));
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.data.method.annotation.support;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
@@ -72,7 +73,7 @@ public class ContextValueMethodArgumentResolver implements HandlerMethodArgument
|
||||
@Nullable GraphQLContext graphQlContext) {
|
||||
|
||||
Class<?> parameterType = parameter.getParameterType();
|
||||
Object value = (graphQlContext != null ? graphQlContext.get(contextValueName) : null);
|
||||
Object value = (graphQlContext != null) ? graphQlContext.get(contextValueName) : null;
|
||||
|
||||
boolean isOptional = parameterType.equals(Optional.class);
|
||||
boolean isMono = parameterType.equals(Mono.class);
|
||||
@@ -85,14 +86,14 @@ public class ContextValueMethodArgumentResolver implements HandlerMethodArgument
|
||||
if (value == null) {
|
||||
value = Mono.empty();
|
||||
}
|
||||
else if (!( value instanceof Mono)) {
|
||||
else if (!(value instanceof Mono)) {
|
||||
value = Mono.just(value);
|
||||
}
|
||||
return Mono.just(value);
|
||||
}
|
||||
|
||||
if (isOptional) {
|
||||
return (value instanceof Optional ? value : Optional.ofNullable(value));
|
||||
return (value instanceof Optional) ? value : Optional.ofNullable(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
|
||||
@@ -13,9 +13,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.data.method.annotation.support;
|
||||
|
||||
import graphql.schema.DataFetchingEnvironment;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.graphql.data.method.HandlerMethodArgumentResolver;
|
||||
|
||||
@@ -27,14 +29,14 @@ import org.springframework.graphql.data.method.HandlerMethodArgumentResolver;
|
||||
*/
|
||||
public class ContinuationHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
|
||||
@Override
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
return "kotlin.coroutines.Continuation".equals(parameter.getParameterType().getName());
|
||||
}
|
||||
@Override
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
return "kotlin.coroutines.Continuation".equals(parameter.getParameterType().getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object resolveArgument(MethodParameter parameter, DataFetchingEnvironment environment) {
|
||||
return null;
|
||||
}
|
||||
@Override
|
||||
public Object resolveArgument(MethodParameter parameter, DataFetchingEnvironment environment) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,9 +13,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.data.method.annotation.support;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.BiConsumer;
|
||||
@@ -49,6 +51,7 @@ public class DataFetcherHandlerMethod extends DataFetcherHandlerMethodSupport {
|
||||
* @param handlerMethod the handler method
|
||||
* @param resolvers the argument resolvers
|
||||
* @param validationHelper to apply bean validation with
|
||||
* @param executor an {@link Executor} to use for {@link Callable} return values
|
||||
* @param subscription whether the field being fetched is of subscription type
|
||||
*/
|
||||
public DataFetcherHandlerMethod(
|
||||
@@ -58,7 +61,7 @@ public class DataFetcherHandlerMethod extends DataFetcherHandlerMethodSupport {
|
||||
|
||||
super(handlerMethod, resolvers, executor);
|
||||
Assert.isTrue(!resolvers.getResolvers().isEmpty(), "No argument resolvers");
|
||||
this.validationHelper = (validationHelper != null ? validationHelper : (controller, args) -> {});
|
||||
this.validationHelper = (validationHelper != null) ? validationHelper : (controller, args) -> { };
|
||||
this.subscription = subscription;
|
||||
}
|
||||
|
||||
@@ -72,9 +75,7 @@ public class DataFetcherHandlerMethod extends DataFetcherHandlerMethodSupport {
|
||||
* The {@code providedArgs} parameter however may supply argument values to
|
||||
* be used directly, i.e. without argument resolution. Provided argument
|
||||
* values are checked before argument resolvers.
|
||||
*
|
||||
* @param environment the environment to resolve arguments from
|
||||
*
|
||||
* @return the raw value returned by the invoked method, possibly a
|
||||
* {@code Mono} in case a method argument requires asynchronous resolution;
|
||||
* {@code Mono<Throwable>} is returned if invocation fails.
|
||||
@@ -87,6 +88,8 @@ public class DataFetcherHandlerMethod extends DataFetcherHandlerMethodSupport {
|
||||
/**
|
||||
* Variant of {@link #invoke(DataFetchingEnvironment)} that also accepts
|
||||
* "given" arguments, which are matched by type.
|
||||
* @param environment the data fetching environment
|
||||
* @param providedArgs additional arguments to be matched by their type
|
||||
* @since 1.2.0
|
||||
*/
|
||||
@Nullable
|
||||
@@ -99,17 +102,17 @@ public class DataFetcherHandlerMethod extends DataFetcherHandlerMethodSupport {
|
||||
return Mono.error(ex);
|
||||
}
|
||||
|
||||
if (Arrays.stream(args).noneMatch(arg -> arg instanceof Mono)) {
|
||||
if (Arrays.stream(args).noneMatch((arg) -> arg instanceof Mono)) {
|
||||
return validateAndInvoke(args, environment);
|
||||
}
|
||||
|
||||
return this.subscription ?
|
||||
toArgsMono(args).flatMapMany(argValues -> {
|
||||
toArgsMono(args).flatMapMany((argValues) -> {
|
||||
Object result = validateAndInvoke(argValues, environment);
|
||||
Assert.state(result instanceof Publisher, "Expected a Publisher from a Subscription response");
|
||||
return Flux.from((Publisher<?>) result);
|
||||
}) :
|
||||
toArgsMono(args).flatMap(argValues -> {
|
||||
toArgsMono(args).flatMap((argValues) -> {
|
||||
Object result = validateAndInvoke(argValues, environment);
|
||||
if (result instanceof Mono<?> mono) {
|
||||
return mono;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user