Skip to content

Commit 92547de

Browse files
committed
Add client side interception
Closes gh-332
1 parent bb4ebf6 commit 92547de

21 files changed

+482
-107
lines changed

spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/AbstractDirectGraphQlTransport.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@
2828
import org.springframework.graphql.GraphQlRequest;
2929
import org.springframework.graphql.GraphQlResponse;
3030
import org.springframework.graphql.ResponseError;
31+
import org.springframework.graphql.client.GraphQlTransport;
3132
import org.springframework.graphql.support.DefaultExecutionGraphQlRequest;
3233
import org.springframework.graphql.support.DefaultExecutionGraphQlResponse;
33-
import org.springframework.graphql.client.GraphQlTransport;
3434
import org.springframework.test.util.AssertionErrors;
3535
import org.springframework.util.AlternativeJdkIdGenerator;
3636
import org.springframework.util.CollectionUtils;

spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/WebTestClientTransport.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public Mono<GraphQlResponse> execute(GraphQlRequest request) {
6666
.getResponseBody();
6767

6868
responseMap = (responseMap != null ? responseMap : Collections.emptyMap());
69-
GraphQlResponse response = GraphQlTransport.wrapResponseMap(responseMap);
69+
GraphQlResponse response = GraphQlTransport.createResponse(responseMap);
7070
return Mono.just(response);
7171
}
7272

spring-graphql/src/main/java/org/springframework/graphql/client/AbstractGraphQlClientBuilder.java

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,15 @@
1616

1717
package org.springframework.graphql.client;
1818

19+
import java.util.ArrayList;
20+
import java.util.Arrays;
21+
import java.util.List;
1922
import java.util.function.Consumer;
2023

2124
import org.springframework.core.codec.Decoder;
2225
import org.springframework.core.codec.Encoder;
26+
import org.springframework.graphql.client.GraphQlClientInterceptor.Chain;
27+
import org.springframework.graphql.client.GraphQlClientInterceptor.SubscriptionChain;
2328
import org.springframework.graphql.support.CachingDocumentSource;
2429
import org.springframework.graphql.support.DocumentSource;
2530
import org.springframework.graphql.support.ResourceDocumentSource;
@@ -49,6 +54,8 @@ public abstract class AbstractGraphQlClientBuilder<B extends AbstractGraphQlClie
4954
"com.fasterxml.jackson.databind.ObjectMapper", AbstractGraphQlClientBuilder.class.getClassLoader());
5055

5156

57+
private final List<GraphQlClientInterceptor> interceptors = new ArrayList<>();
58+
5259
private DocumentSource documentSource = new CachingDocumentSource(new ResourceDocumentSource());
5360

5461
@Nullable
@@ -67,6 +74,18 @@ protected AbstractGraphQlClientBuilder() {
6774
}
6875

6976

77+
@Override
78+
public B interceptor(GraphQlClientInterceptor... interceptors) {
79+
this.interceptors.addAll(Arrays.asList(interceptors));
80+
return self();
81+
}
82+
83+
@Override
84+
public B interceptors(Consumer<List<GraphQlClientInterceptor>> interceptorsConsumer) {
85+
interceptorsConsumer.accept(this.interceptors);
86+
return self();
87+
}
88+
7089
@Override
7190
public B documentSource(DocumentSource contentLoader) {
7291
this.documentSource = contentLoader;
@@ -104,25 +123,48 @@ protected GraphQlClient buildGraphQlClient(GraphQlTransport transport) {
104123
}
105124

106125
return new DefaultGraphQlClient(
107-
this.documentSource, transport, getJsonEncoder(), getJsonDecoder(), getBuilderInitializer());
126+
this.documentSource, createExecuteChain(transport), createExecuteSubscriptionChain(transport));
108127
}
109128

110129
/**
111130
* Return a {@code Consumer} to initialize new builders from "this" builder.
112131
*/
113132
protected Consumer<AbstractGraphQlClientBuilder<?>> getBuilderInitializer() {
114133
return builder -> {
134+
builder.interceptors(interceptorList -> interceptorList.addAll(interceptors));
115135
builder.documentSource(documentSource);
116-
builder.setJsonCodecs(getJsonEncoder(), getJsonDecoder());
136+
builder.setJsonCodecs(getEncoder(), getDecoder());
117137
};
118138
}
119139

120-
private Encoder<?> getJsonEncoder() {
140+
private Chain createExecuteChain(GraphQlTransport transport) {
141+
142+
Chain chain = request -> transport.execute(request).map(response ->
143+
new DefaultClientGraphQlResponse(request, response, getEncoder(), getDecoder()));
144+
145+
return this.interceptors.stream()
146+
.reduce(GraphQlClientInterceptor::andThen)
147+
.map(interceptor -> (Chain) (request) -> interceptor.intercept(request, chain))
148+
.orElse(chain);
149+
}
150+
151+
private SubscriptionChain createExecuteSubscriptionChain(GraphQlTransport transport) {
152+
153+
SubscriptionChain chain = request -> transport.executeSubscription(request)
154+
.map(response -> new DefaultClientGraphQlResponse(request, response, getEncoder(), getDecoder()));
155+
156+
return this.interceptors.stream()
157+
.reduce(GraphQlClientInterceptor::andThen)
158+
.map(interceptor -> (SubscriptionChain) (request) -> interceptor.interceptSubscription(request, chain))
159+
.orElse(chain);
160+
}
161+
162+
private Encoder<?> getEncoder() {
121163
Assert.notNull(this.jsonEncoder, "jsonEncoder has not been set");
122164
return this.jsonEncoder;
123165
}
124166

125-
private Decoder<?> getJsonDecoder() {
167+
private Decoder<?> getDecoder() {
126168
Assert.notNull(this.jsonDecoder, "jsonDecoder has not been set");
127169
return this.jsonDecoder;
128170
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2020-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.graphql.client;
18+
19+
import java.util.Map;
20+
21+
import org.springframework.graphql.GraphQlRequest;
22+
23+
24+
/**
25+
* {@link GraphQlRequest} for client side use.
26+
*
27+
* @author Rossen Stoyanchev
28+
* @since 1.0.0
29+
*/
30+
public interface ClientGraphQlRequest extends GraphQlRequest {
31+
32+
/**
33+
* Return the client request attributes.
34+
* <p>The attributes purely for client side request processing, i.e. available
35+
* throughout the {@link GraphQlClientInterceptor} chain, but not sent.
36+
*/
37+
Map<String, Object> getAttributes();
38+
39+
}
40+

spring-graphql/src/main/java/org/springframework/graphql/client/ClientGraphQlResponse.java

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919

2020
import org.springframework.core.ParameterizedTypeReference;
21-
import org.springframework.graphql.GraphQlRequest;
2221
import org.springframework.graphql.GraphQlResponse;
2322

2423
/**
@@ -30,11 +29,6 @@
3029
*/
3130
public interface ClientGraphQlResponse extends GraphQlResponse {
3231

33-
/**
34-
* Return the request for the response.
35-
*/
36-
GraphQlRequest getRequest();
37-
3832
/**
3933
* {@inheritDoc}
4034
*/
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright 2020-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.graphql.client;
18+
19+
20+
import java.util.Map;
21+
import java.util.concurrent.ConcurrentHashMap;
22+
23+
import org.springframework.graphql.support.DefaultGraphQlRequest;
24+
import org.springframework.lang.Nullable;
25+
26+
/**
27+
* Default implementation of {@link ClientGraphQlRequest}.
28+
*
29+
* @author Rossen Stoyanchev
30+
* @since 1.0.0
31+
*/
32+
final class DefaultClientGraphQlRequest extends DefaultGraphQlRequest implements ClientGraphQlRequest {
33+
34+
private final Map<String, Object> attributes = new ConcurrentHashMap<>();
35+
36+
37+
DefaultClientGraphQlRequest(
38+
String document, @Nullable String operationName, Map<String, Object> variables,
39+
Map<String, Object> attributes) {
40+
41+
super(document, operationName, variables);
42+
this.attributes.putAll(attributes);
43+
}
44+
45+
46+
@Override
47+
public Map<String, Object> getAttributes() {
48+
return this.attributes;
49+
}
50+
51+
}

spring-graphql/src/main/java/org/springframework/graphql/client/DefaultClientGraphQlResponse.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import org.springframework.core.ParameterizedTypeReference;
2020
import org.springframework.core.codec.Decoder;
2121
import org.springframework.core.codec.Encoder;
22-
import org.springframework.graphql.GraphQlRequest;
2322
import org.springframework.graphql.GraphQlResponse;
2423

2524

@@ -29,17 +28,17 @@
2928
* @author Rossen Stoyanchev
3029
* @since 1.0.0
3130
*/
32-
final class DefaultClientGraphQlResponse extends MapGraphQlResponse implements ClientGraphQlResponse {
31+
final class DefaultClientGraphQlResponse extends ResponseMapGraphQlResponse implements ClientGraphQlResponse {
3332

34-
private final GraphQlRequest request;
33+
private final ClientGraphQlRequest request;
3534

3635
private final Encoder<?> encoder;
3736

3837
private final Decoder<?> decoder;
3938

4039

4140
DefaultClientGraphQlResponse(
42-
GraphQlRequest request, GraphQlResponse response, Encoder<?> encoder, Decoder<?> decoder) {
41+
ClientGraphQlRequest request, GraphQlResponse response, Encoder<?> encoder, Decoder<?> decoder) {
4342

4443
super(response);
4544

@@ -49,8 +48,7 @@ final class DefaultClientGraphQlResponse extends MapGraphQlResponse implements C
4948
}
5049

5150

52-
@Override
53-
public GraphQlRequest getRequest() {
51+
ClientGraphQlRequest getRequest() {
5452
return this.request;
5553
}
5654

spring-graphql/src/main/java/org/springframework/graphql/client/DefaultClientResponseField.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ public <D> List<D> toEntityList(ParameterizedTypeReference<D> elementType) {
107107
@SuppressWarnings({"unchecked", "ConstantConditions"})
108108
private <T> T toEntity(ResolvableType targetType) {
109109
if (!hasValue()) {
110-
throw new FieldAccessException(this.response, this);
110+
throw new FieldAccessException(this.response.getRequest(), this.response, this);
111111
}
112112

113113
DataBufferFactory bufferFactory = DefaultDataBufferFactory.sharedInstance;

0 commit comments

Comments
 (0)