diff --git a/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/AbstractDirectGraphQlTransport.java b/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/AbstractDirectGraphQlTransport.java
index 188b31d1..7672bfda 100644
--- a/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/AbstractDirectGraphQlTransport.java
+++ b/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/AbstractDirectGraphQlTransport.java
@@ -28,9 +28,9 @@ import org.springframework.graphql.ExecutionGraphQlResponse;
import org.springframework.graphql.GraphQlRequest;
import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.ResponseError;
+import org.springframework.graphql.client.GraphQlTransport;
import org.springframework.graphql.support.DefaultExecutionGraphQlRequest;
import org.springframework.graphql.support.DefaultExecutionGraphQlResponse;
-import org.springframework.graphql.client.GraphQlTransport;
import org.springframework.test.util.AssertionErrors;
import org.springframework.util.AlternativeJdkIdGenerator;
import org.springframework.util.CollectionUtils;
diff --git a/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/WebTestClientTransport.java b/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/WebTestClientTransport.java
index 03d7a4ad..90469759 100644
--- a/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/WebTestClientTransport.java
+++ b/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/WebTestClientTransport.java
@@ -66,7 +66,7 @@ final class WebTestClientTransport implements GraphQlTransport {
.getResponseBody();
responseMap = (responseMap != null ? responseMap : Collections.emptyMap());
- GraphQlResponse response = GraphQlTransport.wrapResponseMap(responseMap);
+ GraphQlResponse response = GraphQlTransport.createResponse(responseMap);
return Mono.just(response);
}
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 ee5d4630..a0959cc2 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
@@ -16,10 +16,15 @@
package org.springframework.graphql.client;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
import java.util.function.Consumer;
import org.springframework.core.codec.Decoder;
import org.springframework.core.codec.Encoder;
+import org.springframework.graphql.client.GraphQlClientInterceptor.Chain;
+import org.springframework.graphql.client.GraphQlClientInterceptor.SubscriptionChain;
import org.springframework.graphql.support.CachingDocumentSource;
import org.springframework.graphql.support.DocumentSource;
import org.springframework.graphql.support.ResourceDocumentSource;
@@ -49,6 +54,8 @@ public abstract class AbstractGraphQlClientBuilder interceptors = new ArrayList<>();
+
private DocumentSource documentSource = new CachingDocumentSource(new ResourceDocumentSource());
@Nullable
@@ -67,6 +74,18 @@ public abstract class AbstractGraphQlClientBuilder> interceptorsConsumer) {
+ interceptorsConsumer.accept(this.interceptors);
+ return self();
+ }
+
@Override
public B documentSource(DocumentSource contentLoader) {
this.documentSource = contentLoader;
@@ -104,7 +123,7 @@ public abstract class AbstractGraphQlClientBuilder> getBuilderInitializer() {
return builder -> {
+ builder.interceptors(interceptorList -> interceptorList.addAll(interceptors));
builder.documentSource(documentSource);
- builder.setJsonCodecs(getJsonEncoder(), getJsonDecoder());
+ builder.setJsonCodecs(getEncoder(), getDecoder());
};
}
- private Encoder> getJsonEncoder() {
+ private Chain createExecuteChain(GraphQlTransport transport) {
+
+ Chain chain = request -> transport.execute(request).map(response ->
+ new DefaultClientGraphQlResponse(request, response, getEncoder(), getDecoder()));
+
+ return this.interceptors.stream()
+ .reduce(GraphQlClientInterceptor::andThen)
+ .map(interceptor -> (Chain) (request) -> interceptor.intercept(request, chain))
+ .orElse(chain);
+ }
+
+ private SubscriptionChain createExecuteSubscriptionChain(GraphQlTransport transport) {
+
+ SubscriptionChain chain = request -> transport.executeSubscription(request)
+ .map(response -> new DefaultClientGraphQlResponse(request, response, getEncoder(), getDecoder()));
+
+ return this.interceptors.stream()
+ .reduce(GraphQlClientInterceptor::andThen)
+ .map(interceptor -> (SubscriptionChain) (request) -> interceptor.interceptSubscription(request, chain))
+ .orElse(chain);
+ }
+
+ private Encoder> getEncoder() {
Assert.notNull(this.jsonEncoder, "jsonEncoder has not been set");
return this.jsonEncoder;
}
- private Decoder> getJsonDecoder() {
+ private Decoder> getDecoder() {
Assert.notNull(this.jsonDecoder, "jsonDecoder has not been set");
return this.jsonDecoder;
}
diff --git a/spring-graphql/src/main/java/org/springframework/graphql/client/ClientGraphQlRequest.java b/spring-graphql/src/main/java/org/springframework/graphql/client/ClientGraphQlRequest.java
new file mode 100644
index 00000000..66f5e982
--- /dev/null
+++ b/spring-graphql/src/main/java/org/springframework/graphql/client/ClientGraphQlRequest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2020-2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.client;
+
+import java.util.Map;
+
+import org.springframework.graphql.GraphQlRequest;
+
+
+/**
+ * {@link GraphQlRequest} for client side use.
+ *
+ * @author Rossen Stoyanchev
+ * @since 1.0.0
+ */
+public interface ClientGraphQlRequest extends GraphQlRequest {
+
+ /**
+ * Return the client request attributes.
+ *
The attributes purely for client side request processing, i.e. available
+ * throughout the {@link GraphQlClientInterceptor} chain, but not sent.
+ */
+ Map getAttributes();
+
+}
+
diff --git a/spring-graphql/src/main/java/org/springframework/graphql/client/ClientGraphQlResponse.java b/spring-graphql/src/main/java/org/springframework/graphql/client/ClientGraphQlResponse.java
index b986f260..a43c29c2 100644
--- a/spring-graphql/src/main/java/org/springframework/graphql/client/ClientGraphQlResponse.java
+++ b/spring-graphql/src/main/java/org/springframework/graphql/client/ClientGraphQlResponse.java
@@ -18,7 +18,6 @@ package org.springframework.graphql.client;
import org.springframework.core.ParameterizedTypeReference;
-import org.springframework.graphql.GraphQlRequest;
import org.springframework.graphql.GraphQlResponse;
/**
@@ -30,11 +29,6 @@ import org.springframework.graphql.GraphQlResponse;
*/
public interface ClientGraphQlResponse extends GraphQlResponse {
- /**
- * Return the request for the response.
- */
- GraphQlRequest getRequest();
-
/**
* {@inheritDoc}
*/
diff --git a/spring-graphql/src/main/java/org/springframework/graphql/client/DefaultClientGraphQlRequest.java b/spring-graphql/src/main/java/org/springframework/graphql/client/DefaultClientGraphQlRequest.java
new file mode 100644
index 00000000..2a9d4d5a
--- /dev/null
+++ b/spring-graphql/src/main/java/org/springframework/graphql/client/DefaultClientGraphQlRequest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2020-2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.client;
+
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.springframework.graphql.support.DefaultGraphQlRequest;
+import org.springframework.lang.Nullable;
+
+/**
+ * Default implementation of {@link ClientGraphQlRequest}.
+ *
+ * @author Rossen Stoyanchev
+ * @since 1.0.0
+ */
+final class DefaultClientGraphQlRequest extends DefaultGraphQlRequest implements ClientGraphQlRequest {
+
+ private final Map attributes = new ConcurrentHashMap<>();
+
+
+ DefaultClientGraphQlRequest(
+ String document, @Nullable String operationName, Map variables,
+ Map attributes) {
+
+ super(document, operationName, variables);
+ this.attributes.putAll(attributes);
+ }
+
+
+ @Override
+ public Map getAttributes() {
+ return this.attributes;
+ }
+
+}
diff --git a/spring-graphql/src/main/java/org/springframework/graphql/client/DefaultClientGraphQlResponse.java b/spring-graphql/src/main/java/org/springframework/graphql/client/DefaultClientGraphQlResponse.java
index 76807ae4..5450f971 100644
--- a/spring-graphql/src/main/java/org/springframework/graphql/client/DefaultClientGraphQlResponse.java
+++ b/spring-graphql/src/main/java/org/springframework/graphql/client/DefaultClientGraphQlResponse.java
@@ -19,7 +19,6 @@ package org.springframework.graphql.client;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.codec.Decoder;
import org.springframework.core.codec.Encoder;
-import org.springframework.graphql.GraphQlRequest;
import org.springframework.graphql.GraphQlResponse;
@@ -29,9 +28,9 @@ import org.springframework.graphql.GraphQlResponse;
* @author Rossen Stoyanchev
* @since 1.0.0
*/
-final class DefaultClientGraphQlResponse extends MapGraphQlResponse implements ClientGraphQlResponse {
+final class DefaultClientGraphQlResponse extends ResponseMapGraphQlResponse implements ClientGraphQlResponse {
- private final GraphQlRequest request;
+ private final ClientGraphQlRequest request;
private final Encoder> encoder;
@@ -39,7 +38,7 @@ final class DefaultClientGraphQlResponse extends MapGraphQlResponse implements C
DefaultClientGraphQlResponse(
- GraphQlRequest request, GraphQlResponse response, Encoder> encoder, Decoder> decoder) {
+ ClientGraphQlRequest request, GraphQlResponse response, Encoder> encoder, Decoder> decoder) {
super(response);
@@ -49,8 +48,7 @@ final class DefaultClientGraphQlResponse extends MapGraphQlResponse implements C
}
- @Override
- public GraphQlRequest getRequest() {
+ ClientGraphQlRequest getRequest() {
return this.request;
}
diff --git a/spring-graphql/src/main/java/org/springframework/graphql/client/DefaultClientResponseField.java b/spring-graphql/src/main/java/org/springframework/graphql/client/DefaultClientResponseField.java
index 337417f2..20ed2a06 100644
--- a/spring-graphql/src/main/java/org/springframework/graphql/client/DefaultClientResponseField.java
+++ b/spring-graphql/src/main/java/org/springframework/graphql/client/DefaultClientResponseField.java
@@ -107,7 +107,7 @@ final class DefaultClientResponseField implements ClientResponseField {
@SuppressWarnings({"unchecked", "ConstantConditions"})
private T toEntity(ResolvableType targetType) {
if (!hasValue()) {
- throw new FieldAccessException(this.response, this);
+ throw new FieldAccessException(this.response.getRequest(), this.response, this);
}
DataBufferFactory bufferFactory = DefaultDataBufferFactory.sharedInstance;
diff --git a/spring-graphql/src/main/java/org/springframework/graphql/client/DefaultGraphQlClient.java b/spring-graphql/src/main/java/org/springframework/graphql/client/DefaultGraphQlClient.java
index 6a5a9db9..c501346a 100644
--- a/spring-graphql/src/main/java/org/springframework/graphql/client/DefaultGraphQlClient.java
+++ b/spring-graphql/src/main/java/org/springframework/graphql/client/DefaultGraphQlClient.java
@@ -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.Collections;
@@ -25,11 +26,6 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.core.ParameterizedTypeReference;
-import org.springframework.core.codec.Decoder;
-import org.springframework.core.codec.Encoder;
-import org.springframework.graphql.support.DefaultGraphQlRequest;
-import org.springframework.graphql.GraphQlRequest;
-import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.support.DocumentSource;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -44,31 +40,22 @@ final class DefaultGraphQlClient implements GraphQlClient {
private final DocumentSource documentSource;
- private final GraphQlTransport transport;
+ private final GraphQlClientInterceptor.Chain executeChain;
- private final Encoder> jsonEncoder;
-
- private final Decoder> jsonDecoder;
-
- private final Consumer> builderInitializer;
+ private final GraphQlClientInterceptor.SubscriptionChain executeSubscriptionChain;
DefaultGraphQlClient(
- DocumentSource documentSource, GraphQlTransport transport,
- Encoder> jsonEncoder, Decoder> jsonDecoder,
- Consumer> builderInitializer) {
+ DocumentSource documentSource, GraphQlClientInterceptor.Chain executeChain,
+ GraphQlClientInterceptor.SubscriptionChain executeSubscriptionChain) {
Assert.notNull(documentSource, "DocumentSource is required");
- Assert.notNull(transport, "GraphQlTransport is required");
- Assert.notNull(jsonEncoder, "'jsonEncoder' is required");
- Assert.notNull(jsonEncoder, "'jsonDecoder' is required");
- Assert.notNull(builderInitializer, "`builderInitializer` is required");
+ Assert.notNull(executeChain, "GraphQlClientInterceptor.Chain is required");
+ Assert.notNull(executeSubscriptionChain, "GraphQlClientInterceptor.SubscriptionChain is required");
this.documentSource = documentSource;
- this.transport = transport;
- this.jsonEncoder = jsonEncoder;
- this.jsonDecoder = jsonDecoder;
- this.builderInitializer = builderInitializer;
+ this.executeChain = executeChain;
+ this.executeSubscriptionChain = executeSubscriptionChain;
}
@@ -82,31 +69,14 @@ final class DefaultGraphQlClient implements GraphQlClient {
return new DefaultRequestSpec(this.documentSource.getDocument(name));
}
- @Override
- public Builder mutate() {
- Builder builder = new Builder(this.transport);
- this.builderInitializer.accept(builder);
- return builder;
- }
-
-
/**
- * Default {@link GraphQlClient.Builder} with a given transport.
+ * The default client is unaware of transport details, and doesn't implement
+ * this method. It should always be wrapped via with a transport specific
+ * {@link AbstractDelegatingGraphQlClient} that implements mutation.
*/
- static final class Builder extends AbstractGraphQlClientBuilder {
-
- private final GraphQlTransport transport;
-
- Builder(GraphQlTransport transport) {
- Assert.notNull(transport, "GraphQlTransport is required");
- this.transport = transport;
- }
-
- @Override
- public GraphQlClient build() {
- return super.buildGraphQlClient(this.transport);
- }
-
+ @Override
+ public Builder> mutate() {
+ throw new UnsupportedOperationException();
}
@@ -122,6 +92,8 @@ final class DefaultGraphQlClient implements GraphQlClient {
private final Map variables = new LinkedHashMap<>();
+ private final Map attributes = new LinkedHashMap<>();
+
DefaultRequestSpec(Mono documentMono) {
Assert.notNull(documentMono, "'document' is required");
this.documentMono = documentMono;
@@ -145,6 +117,18 @@ final class DefaultGraphQlClient implements GraphQlClient {
return this;
}
+ @Override
+ public RequestSpec attribute(String name, Object value) {
+ this.attributes.put(name, value);
+ return this;
+ }
+
+ @Override
+ public RequestSpec attributes(Consumer