diff --git a/spring-graphql/src/main/java/org/springframework/graphql/client/AbstractGraphQlClientBuilder.java b/spring-graphql/src/main/java/org/springframework/graphql/client/AbstractGraphQlClientBuilder.java index b1dc8ba9..a5d1a1fd 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/client/AbstractGraphQlClientBuilder.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/client/AbstractGraphQlClientBuilder.java @@ -99,8 +99,8 @@ public abstract class AbstractGraphQlClientBuilder transport - .execute(request) + Chain chain = request -> transport.execute(request) .map(response -> new DefaultClientGraphQlResponse(request, response, getEncoder(), getDecoder())); return this.interceptors.stream() diff --git a/spring-graphql/src/main/java/org/springframework/graphql/client/AbstractGraphQlClientSyncBuilder.java b/spring-graphql/src/main/java/org/springframework/graphql/client/AbstractGraphQlClientSyncBuilder.java index ce5b3c2e..5b7b85a9 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/client/AbstractGraphQlClientSyncBuilder.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/client/AbstractGraphQlClientSyncBuilder.java @@ -100,8 +100,8 @@ public abstract class AbstractGraphQlClientSyncBuilder Flux.error(new IllegalStateException("Subscriptions on supported")); + this.subscriptionChain = request -> Flux.error(new IllegalStateException("Subscriptions on supported")); this.blockingTimeout = blockingTimeout; } @@ -80,7 +80,7 @@ final class DefaultGraphQlClient implements GraphQlClient { this.documentSource = documentSource; this.blockingChain = adaptToBlockingChain(nonBlockingChain, blockingTimeout); this.nonBlockingChain = nonBlockingChain; - this.executeSubscriptionChain = subscriptionChain; + this.subscriptionChain = subscriptionChain; this.blockingTimeout = blockingTimeout; } @@ -217,7 +217,7 @@ final class DefaultGraphQlClient implements GraphQlClient { @Override public Flux executeSubscription() { - return initRequest().flatMapMany(request -> executeSubscriptionChain.next(request) + return initRequest().flatMapMany(request -> subscriptionChain.next(request) .onErrorResume( ex -> !(ex instanceof GraphQlClientException), ex -> Mono.error(new GraphQlTransportException(ex, request)))); diff --git a/spring-graphql/src/main/java/org/springframework/graphql/client/DefaultSyncHttpGraphQlClientBuilder.java b/spring-graphql/src/main/java/org/springframework/graphql/client/DefaultSyncHttpGraphQlClientBuilder.java index 61131998..df24f1b3 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/client/DefaultSyncHttpGraphQlClientBuilder.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/client/DefaultSyncHttpGraphQlClientBuilder.java @@ -111,15 +111,15 @@ final class DefaultSyncHttpGraphQlClientBuilder }); RestClient restClient = this.restClientBuilder.build(); - HttpSyncGraphQlTransport syncTransport = new HttpSyncGraphQlTransport(restClient); + HttpSyncGraphQlTransport transport = new HttpSyncGraphQlTransport(restClient); - GraphQlClient graphQlClient = super.buildGraphQlClient(syncTransport); + GraphQlClient graphQlClient = super.buildGraphQlClient(transport); return new DefaultHttpSyncGraphQlClient(graphQlClient, restClient, getBuilderInitializer()); } /** - * Default {@link HttpGraphQlClient} implementation. + * Default {@link HttpSyncGraphQlClient} implementation. */ private static class DefaultHttpSyncGraphQlClient extends AbstractDelegatingGraphQlClient implements HttpSyncGraphQlClient { diff --git a/spring-graphql/src/main/java/org/springframework/graphql/client/GraphQlClient.java b/spring-graphql/src/main/java/org/springframework/graphql/client/GraphQlClient.java index df78fd8a..99a0b943 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/client/GraphQlClient.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/client/GraphQlClient.java @@ -89,14 +89,14 @@ public interface GraphQlClient { /** - * Base builder to create a {@link GraphQlClient}. + * Base builder for creating and initializing a {@link GraphQlClient}. * @since 1.3 */ interface BaseBuilder> { /** - * Configure a {@link DocumentSource} for use with - * {@link #documentName(String)} for resolving a document by name. + * Configure a {@link DocumentSource} strategy to resolve a document by + * name. For use within {@link #documentName(String)}. *

By default, this is set to {@link ResourceDocumentSource} with * classpath location {@code "graphql-documents/"} and * {@link ResourceDocumentSource#FILE_EXTENSIONS} as extensions. @@ -107,8 +107,9 @@ public interface GraphQlClient { * Configure a timeout to use for blocking execution. *

By default this is not set, in which case the behavior depends on * connection and request timeout settings of the underlying transport. - * We recommend configuring timeout values directly on the underlying - * transport, which provides more control over such settings. + * We recommend configuring timeout values directly if possible on the + * underlying transport library such an HTTP client library as that can + * provide more control over such settings. * @param blockingTimeout the timeout to use */ B blockingTimeout(@Nullable Duration blockingTimeout); @@ -123,7 +124,7 @@ public interface GraphQlClient { /** * Builder to create a {@link GraphQlClient} instance with a - * synchronous transport and interceptors. + * synchronous execution chain and transport. * @since 1.3 * @see SyncGraphQlTransport */ @@ -156,8 +157,8 @@ public interface GraphQlClient { /** - * Builder to create {@link GraphQlClient} instances with a non-blocking - * {@link GraphQlTransport} and interceptors. + * Builder to create a {@link GraphQlClient} with a non-blocking execution + * chain and transport. */ interface Builder> extends BaseBuilder { @@ -242,10 +243,10 @@ public interface GraphQlClient { RequestSpec attributes(Consumer> attributesConsumer); /** - * Shortcut for {@link #execute()} with a field path to decode from. - *

If you want to decode the full data instead, use {@link #execute()}: + * Shortcut for {@link #executeSync()} with a field path to decode from. + *

If you want to decode the full data instead, use: *

-		 * client.document("..").execute().map(response -> response.toEntity(..))
+		 * client.document("..").executeSync()
 		 * 
* @return a spec with decoding options * @throws FieldAccessException if the field has any field errors, @@ -256,9 +257,9 @@ public interface GraphQlClient { /** * Shortcut for {@link #execute()} with a field path to decode from. - *

If you want to decode the full data instead, use {@link #execute()}: + *

If you want to decode the full data instead, use: *

-		 * client.document("..").execute().map(response -> response.toEntity(..))
+		 * client.document("..").execute().map(response -> ...)
 		 * 
* @return a spec with decoding options * @throws FieldAccessException if the field has any field errors, @@ -269,9 +270,9 @@ public interface GraphQlClient { /** * Shortcut for {@link #executeSubscription()} with a field path to * decode from for each result. - *

If you want to decode the full data, use {@link #executeSubscription()}: + *

If you want to decode the full data, use: *

-		 * client.document("..").executeSubscription().map(response -> response.toEntity(..))
+		 * client.document("..").executeSubscription().map(response -> ...)
 		 * 
* @return a spec with decoding options */ @@ -291,8 +292,8 @@ public interface GraphQlClient { * Execute request with a single response, e.g. "query" or "mutation", and * return a response for further options. * @return a {@code Mono} with a {@code ClientGraphQlResponse} for further - * decoding of the response. The {@code Mono} may end with a - * . + * decoding of the response. The {@code Mono} may end with an error due + * to transport level issues. */ Mono execute(); @@ -316,17 +317,17 @@ public interface GraphQlClient { /** - * Declares options to decode a field for a single response operation. + * 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 entityType the type to convert to - * @return {@code Mono} with the decoded entity; completes with - * {@link FieldAccessException} in case of {@link ResponseField field + * @return the entity or null if the field is {@code null} and has no errors. + * @throws FieldAccessException in case of {@link ResponseField field * errors} or an {@link GraphQlResponse#isValid() invalid} response; - * completes empty if the field is {@code null} but has no errors. * @see ResponseField#getErrors() */ @Nullable @@ -345,8 +346,7 @@ public interface GraphQlClient { List toEntityList(Class elementType); /** - * Variant of {@link #toEntity(Class)} to decode to a List of entities. - * @param elementType the type of elements in the list + * Variant of {@link #toEntityList(Class)} with a {@link ParameterizedTypeReference}. */ List toEntityList(ParameterizedTypeReference elementType); @@ -354,7 +354,7 @@ public interface GraphQlClient { /** - * Declares options to decode a field for a single response operation. + * Declares options to decode a field in a single response. */ interface RetrieveSpec { @@ -381,8 +381,7 @@ public interface GraphQlClient { Mono> toEntityList(Class elementType); /** - * Variant of {@link #toEntity(Class)} to decode to a List of entities. - * @param elementType the type of elements in the list + * Variant of {@link #toEntityList(Class)} with a {@link ParameterizedTypeReference}. */ Mono> toEntityList(ParameterizedTypeReference elementType); diff --git a/spring-graphql/src/main/java/org/springframework/graphql/client/GraphQlClientInterceptor.java b/spring-graphql/src/main/java/org/springframework/graphql/client/GraphQlClientInterceptor.java index a1b9597a..b6c246e1 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/client/GraphQlClientInterceptor.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/client/GraphQlClientInterceptor.java @@ -21,10 +21,12 @@ import reactor.core.publisher.Mono; /** - * Interceptor for {@link GraphQlClient} requests. + * Interceptor for {@link GraphQlClient} requests for use in a non-blocking + * execution chain with a non-blocking {@link GraphQlTransport}.. * * @author Rossen Stoyanchev * @since 1.0.0 + * @see GraphQlClient.Builder */ public interface GraphQlClientInterceptor { @@ -54,8 +56,8 @@ public interface GraphQlClientInterceptor { } /** - * Return a new {@link GraphQlClientInterceptor} that invokes the current - * interceptor first and then the one that is passed in. + * Return a new interceptor that invokes the current interceptor first and + * then the one that is passed in. * @param interceptor the interceptor to delegate to after "this" * @return the new {@code GraphQlClientInterceptor} */ @@ -78,7 +80,7 @@ public interface GraphQlClientInterceptor { /** - * Contract for delegation of single response requests to the rest of the chain. + * Contract to delegate to the rest of a non-blocking execution chain. */ interface Chain { diff --git a/spring-graphql/src/main/java/org/springframework/graphql/client/HttpMessageConverterDelegate.java b/spring-graphql/src/main/java/org/springframework/graphql/client/HttpMessageConverterDelegate.java index eb5e9f98..3b5422f7 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/client/HttpMessageConverterDelegate.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/client/HttpMessageConverterDelegate.java @@ -66,14 +66,6 @@ final class HttpMessageConverterDelegate { .orElseThrow(() -> new IllegalArgumentException("No JSON HttpMessageConverter")); } - @Nullable - private static MediaType toMediaType(@Nullable MimeType mimeType) { - if (mimeType instanceof MediaType mediaType) { - return mediaType; - } - return (mimeType != null ? new MediaType(mimeType) : null); - } - static HttpMessageConverterEncoder asEncoder(HttpMessageConverter converter) { return new HttpMessageConverterEncoder(converter); } @@ -82,7 +74,18 @@ final class HttpMessageConverterDelegate { return new HttpMessageConverterDecoder(converter); } + @Nullable + private static MediaType toMediaType(@Nullable MimeType mimeType) { + if (mimeType instanceof MediaType mediaType) { + return mediaType; + } + return (mimeType != null ? new MediaType(mimeType) : null); + } + + /** + * Partial Encoder implementation to encode a single value through an HttpMessageConverter. + */ private static class HttpMessageConverterEncoder implements Encoder { private final HttpMessageConverter converter; @@ -134,6 +137,9 @@ final class HttpMessageConverterDelegate { } + /** + * Partial Decoder implementation to decode a single buffer through an HttpMessageConverter. + */ private static class HttpMessageConverterDecoder implements Decoder { private final HttpMessageConverter converter; diff --git a/spring-graphql/src/main/java/org/springframework/graphql/client/HttpSyncGraphQlClient.java b/spring-graphql/src/main/java/org/springframework/graphql/client/HttpSyncGraphQlClient.java index b0bc0970..9b08751d 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/client/HttpSyncGraphQlClient.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/client/HttpSyncGraphQlClient.java @@ -26,10 +26,12 @@ import org.springframework.web.client.RestClient; /** - * GraphQL over HTTP client that uses {@link RestClient}. + * GraphQL over HTTP client with that uses {@link RestClient} in a blocking + * execution chain. * * @author Rossen Stoyanchev * @since 1.3 + * @see SyncGraphQlTransport */ public interface HttpSyncGraphQlClient extends GraphQlClient { @@ -70,24 +72,24 @@ public interface HttpSyncGraphQlClient extends GraphQlClient { /** - * Builder for the GraphQL over HTTP client. + * Builder for the GraphQL over HTTP client with a blocking execution chain. */ interface Builder> extends GraphQlClient.SyncBuilder { /** * Set the GraphQL endpoint URL as a String. - * @param url the url to send HTTP requests to or connect over WebSocket + * @param url the url to send HTTP requests to */ B url(String url); /** * Set the GraphQL endpoint URL. - * @param url the url to send HTTP requests to or connect over WebSocket + * @param url the url to send HTTP requests to */ B url(URI url); /** - * Add the given header to HTTP requests or to the WebSocket handshake request. + * Add the given header to HTTP requests. * @param name the header name * @param values the header values */ @@ -101,14 +103,16 @@ public interface HttpSyncGraphQlClient extends GraphQlClient { B headers(Consumer headersConsumer); /** - * Configure message converters for all JSON encoding and decoding needs. + * Configure message converters for JSON for use in the + * {@link org.springframework.graphql.GraphQlResponse} to convert response + * data to higher level objects. * @param configurer the configurer to apply * @return this builder */ B messageConverters(Consumer>> configurer); /** - * Customize the {@code RestClient} to use. + * Customize the underlying {@code RestClient}. *

Note that some properties of {@code RestClient.Builder} like the base URL, * headers, and message converters can be customized through this builder. * @see #url(String) @@ -118,7 +122,7 @@ public interface HttpSyncGraphQlClient extends GraphQlClient { B restClient(Consumer builderConsumer); /** - * Build the {@code RestClientGraphQlClient} instance. + * Build the {@code HttpSyncGraphQlClient} instance. */ @Override HttpSyncGraphQlClient build(); diff --git a/spring-graphql/src/main/java/org/springframework/graphql/client/HttpSyncGraphQlTransport.java b/spring-graphql/src/main/java/org/springframework/graphql/client/HttpSyncGraphQlTransport.java index abb44e17..9cb4370a 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/client/HttpSyncGraphQlTransport.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/client/HttpSyncGraphQlTransport.java @@ -29,10 +29,7 @@ import org.springframework.web.client.RestClient; /** - * Transport for executing GraphQL over HTTP requests via {@link RestClient}. - * - *

Supports only single-response requests over HTTP POST. For subscriptions, - * see {@link WebSocketGraphQlTransport} and {@link RSocketGraphQlTransport}. + * Transport for GraphQL over HTTP requests executed with {@link RestClient}. * * @author Rossen Stoyanchev * @since 1.3 @@ -63,12 +60,14 @@ final class HttpSyncGraphQlTransport implements SyncGraphQlTransport { @Override public GraphQlResponse execute(GraphQlRequest request) { + Map body = this.restClient.post() .contentType(this.contentType) .accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_GRAPHQL_RESPONSE) .body(request.toMap()) .retrieve() .body(MAP_TYPE); + return new ResponseMapGraphQlResponse(body != null ? body : Collections.emptyMap()); } diff --git a/spring-graphql/src/main/java/org/springframework/graphql/client/SyncGraphQlClientInterceptor.java b/spring-graphql/src/main/java/org/springframework/graphql/client/SyncGraphQlClientInterceptor.java index 75aee024..b6d14441 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/client/SyncGraphQlClientInterceptor.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/client/SyncGraphQlClientInterceptor.java @@ -17,7 +17,8 @@ package org.springframework.graphql.client; /** - * Interceptor of {@link GraphQlClient} requests. + * Interceptor of {@link GraphQlClient} requests for use in a blocking execution + * chain with a {@link SyncGraphQlTransport}. * * @author Rossen Stoyanchev * @since 1.3 @@ -27,19 +28,19 @@ public interface SyncGraphQlClientInterceptor { /** * Intercept a single response request (query and mutation operations), and * delegate to the rest of the chain including other interceptors followed - * by the {@link GraphQlTransport}. + * by the {@link SyncGraphQlTransport}. * @param request the request to perform * @param chain the rest of the chain to perform the request * @return the response - * @see GraphQlClient.RequestSpec#execute() + * @see GraphQlClient.RequestSpec#executeSync() */ default ClientGraphQlResponse intercept(ClientGraphQlRequest request, Chain chain) { return chain.next(request); } /** - * Return a new {@link SyncGraphQlClientInterceptor} that invokes the current - * interceptor first and then the one that is passed in. + * Return a new interceptor that invokes the current interceptor first and + * then the one that is passed in. * @param interceptor the interceptor to delegate to after "this" * @return the new interceptor instance */ @@ -56,7 +57,7 @@ public interface SyncGraphQlClientInterceptor { /** - * Contract to delegate to the rest of the chain. + * Contract to delegate to the rest of a blocking execution chain. */ interface Chain { @@ -66,6 +67,7 @@ public interface SyncGraphQlClientInterceptor { * @return the GraphQL response * @throws GraphQlTransportException in case of errors due to transport or * other issues related to encoding and decoding the request and response. + * @see GraphQlClient.RequestSpec#executeSync() */ ClientGraphQlResponse next(ClientGraphQlRequest request); diff --git a/spring-graphql/src/main/java/org/springframework/graphql/client/SyncGraphQlTransport.java b/spring-graphql/src/main/java/org/springframework/graphql/client/SyncGraphQlTransport.java index 7f93c131..0a83d544 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/client/SyncGraphQlTransport.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/client/SyncGraphQlTransport.java @@ -25,6 +25,7 @@ import org.springframework.graphql.GraphQlResponse; * * @author Rossen Stoyanchev * @since 1.3 + * @see GraphQlClient.SyncBuilder */ public interface SyncGraphQlTransport { diff --git a/spring-graphql/src/main/java/org/springframework/graphql/client/WebGraphQlClient.java b/spring-graphql/src/main/java/org/springframework/graphql/client/WebGraphQlClient.java index 534fdd6c..81365e53 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/client/WebGraphQlClient.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/client/WebGraphQlClient.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-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. @@ -69,8 +69,9 @@ public interface WebGraphQlClient extends GraphQlClient { B headers(Consumer headersConsumer); /** - * Configure the underlying {@code CodecConfigurer} to use for all JSON - * encoding and decoding needs. + * Configure JSON encoders and decoders for use in the + * {@link org.springframework.graphql.GraphQlResponse} to convert response + * data to higher level objects. */ B codecConfigurer(Consumer codecsConsumer); diff --git a/spring-graphql/src/test/java/org/springframework/graphql/client/GraphQlClientBuilderTests.java b/spring-graphql/src/test/java/org/springframework/graphql/client/GraphQlClientBuilderTests.java index 94350222..ac1eaf30 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/client/GraphQlClientBuilderTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/client/GraphQlClientBuilderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-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.