Polishing

See gh-771
This commit is contained in:
rstoyanchev
2024-01-29 16:27:18 +00:00
parent a38fac0bb1
commit 08d831aed3
13 changed files with 86 additions and 73 deletions

View File

@@ -99,8 +99,8 @@ public abstract class AbstractGraphQlClientBuilder<B extends AbstractGraphQlClie
}
@Override
public B documentSource(DocumentSource contentLoader) {
this.documentSource = contentLoader;
public B documentSource(DocumentSource documentSource) {
this.documentSource = documentSource;
return self();
}
@@ -195,8 +195,7 @@ public abstract class AbstractGraphQlClientBuilder<B extends AbstractGraphQlClie
private Chain createExecuteChain(GraphQlTransport transport) {
Chain chain = request -> transport
.execute(request)
Chain chain = request -> transport.execute(request)
.map(response -> new DefaultClientGraphQlResponse(request, response, getEncoder(), getDecoder()));
return this.interceptors.stream()

View File

@@ -100,8 +100,8 @@ public abstract class AbstractGraphQlClientSyncBuilder<B extends AbstractGraphQl
}
@Override
public B documentSource(DocumentSource contentLoader) {
this.documentSource = contentLoader;
public B documentSource(DocumentSource documentSource) {
this.documentSource = documentSource;
return self();
}

View File

@@ -46,7 +46,7 @@ final class DefaultGraphQlClient implements GraphQlClient {
private final GraphQlClientInterceptor.Chain nonBlockingChain;
private final GraphQlClientInterceptor.SubscriptionChain executeSubscriptionChain;
private final GraphQlClientInterceptor.SubscriptionChain subscriptionChain;
@Nullable
private final Duration blockingTimeout;
@@ -63,7 +63,7 @@ final class DefaultGraphQlClient implements GraphQlClient {
this.documentSource = documentSource;
this.blockingChain = blockingChain;
this.nonBlockingChain = adaptToNonBlockingChain(blockingChain, scheduler);
this.executeSubscriptionChain = request -> 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<ClientGraphQlResponse> 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))));

View File

@@ -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 {

View File

@@ -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<B extends BaseBuilder<B>> {
/**
* 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)}.
* <p>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.
* <p>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<B extends Builder<B>> extends BaseBuilder<B> {
@@ -242,10 +243,10 @@ public interface GraphQlClient {
RequestSpec attributes(Consumer<Map<String, Object>> attributesConsumer);
/**
* Shortcut for {@link #execute()} with a field path to decode from.
* <p>If you want to decode the full data instead, use {@link #execute()}:
* Shortcut for {@link #executeSync()} with a field path to decode from.
* <p>If you want to decode the full data instead, use:
* <pre>
* client.document("..").execute().map(response -> response.toEntity(..))
* client.document("..").executeSync()
* </pre>
* @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.
* <p>If you want to decode the full data instead, use {@link #execute()}:
* <p>If you want to decode the full data instead, use:
* <pre>
* client.document("..").execute().map(response -> response.toEntity(..))
* client.document("..").execute().map(response -> ...)
* </pre>
* @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.
* <p>If you want to decode the full data, use {@link #executeSubscription()}:
* <p>If you want to decode the full data, use:
* <pre>
* client.document("..").executeSubscription().map(response -> response.toEntity(..))
* client.document("..").executeSubscription().map(response -> ...)
* </pre>
* @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<ClientGraphQlResponse> 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 {
<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
* Variant of {@link #toEntityList(Class)} with a {@link ParameterizedTypeReference}.
*/
<D> List<D> toEntityList(ParameterizedTypeReference<D> 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 {
<D> Mono<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
* Variant of {@link #toEntityList(Class)} with a {@link ParameterizedTypeReference}.
*/
<D> Mono<List<D>> toEntityList(ParameterizedTypeReference<D> elementType);

View File

@@ -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 {

View File

@@ -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<Object> 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<Object> {
private final HttpMessageConverter<Object> 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<Object> {
private final HttpMessageConverter<Object> converter;

View File

@@ -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<B extends Builder<B>> extends GraphQlClient.SyncBuilder<B> {
/**
* 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<HttpHeaders> 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<List<HttpMessageConverter<?>>> configurer);
/**
* Customize the {@code RestClient} to use.
* 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.
* @see #url(String)
@@ -118,7 +122,7 @@ public interface HttpSyncGraphQlClient extends GraphQlClient {
B restClient(Consumer<RestClient.Builder> builderConsumer);
/**
* Build the {@code RestClientGraphQlClient} instance.
* Build the {@code HttpSyncGraphQlClient} instance.
*/
@Override
HttpSyncGraphQlClient build();

View File

@@ -29,10 +29,7 @@ import org.springframework.web.client.RestClient;
/**
* Transport for executing GraphQL over HTTP requests via {@link RestClient}.
*
* <p>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<String, Object> 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());
}

View File

@@ -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);

View File

@@ -25,6 +25,7 @@ import org.springframework.graphql.GraphQlResponse;
*
* @author Rossen Stoyanchev
* @since 1.3
* @see GraphQlClient.SyncBuilder
*/
public interface SyncGraphQlTransport {

View File

@@ -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<HttpHeaders> 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<CodecConfigurer> codecsConsumer);

View File

@@ -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.