Skip to content

Commit 6fc326e

Browse files
committed
implement expressions to handle for various RequestInput parameters
issue spring-projects#3501
1 parent 05e369c commit 6fc326e

File tree

3 files changed

+180
-15
lines changed

3 files changed

+180
-15
lines changed

spring-integration-graphql/src/main/java/org/springframework/integration/graphql/outbound/GraphQlMessageHandler.java

+96-6
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,21 @@
1616

1717
package org.springframework.integration.graphql.outbound;
1818

19+
import java.util.Collections;
20+
import java.util.Map;
21+
22+
import org.springframework.expression.Expression;
23+
import org.springframework.expression.common.LiteralExpression;
24+
import org.springframework.expression.spel.support.StandardEvaluationContext;
1925
import org.springframework.graphql.GraphQlService;
2026
import org.springframework.graphql.RequestInput;
27+
import org.springframework.integration.expression.ExpressionUtils;
28+
import org.springframework.integration.expression.SupplierExpression;
2129
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
2230
import org.springframework.messaging.Message;
2331
import org.springframework.util.Assert;
2432

25-
import graphql.ExecutionResult;
26-
import reactor.core.publisher.Mono;
33+
import graphql.com.google.common.base.Strings;
2734

2835
/**
2936
* A {@link org.springframework.messaging.MessageHandler} capable of fielding GraphQL Query, Mutation and Subscription requests.
@@ -35,25 +42,108 @@ public class GraphQlMessageHandler extends AbstractReplyProducingMessageHandler
3542

3643
private final GraphQlService graphQlService;
3744

45+
private StandardEvaluationContext evaluationContext;
46+
47+
private Expression queryExpression;
48+
49+
private Expression operationNameExpression = new SupplierExpression<>(() -> null);
50+
51+
private Expression variablesExpression = new SupplierExpression<>(() -> Collections.emptyMap());
52+
3853
public GraphQlMessageHandler(final GraphQlService graphQlService) {
3954
Assert.notNull(graphQlService, "'graphQlService' must not be null");
4055

56+
this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(getBeanFactory());
4157
this.graphQlService = graphQlService;
4258
setAsync(true);
4359
}
4460

61+
/**
62+
* Specify a GraphQL Query.
63+
* @param query the GraphQL query to use.
64+
*/
65+
public void setQuery(String query) {
66+
setQueryExpression(new LiteralExpression(query));
67+
}
68+
69+
/**
70+
* Specify a SpEL expression to evaluate a GraphQL Query
71+
* @param queryExpression the expression to evaluate a GraphQL query.
72+
*/
73+
public void setQueryExpression(Expression queryExpression) {
74+
Assert.notNull(queryExpression, "'queryExpression' must not be null");
75+
this.queryExpression = queryExpression;
76+
}
77+
78+
/**
79+
* Set a GraphQL Operation Name to execute.
80+
* @param operationName the GraphQL Operation Name to use.
81+
*/
82+
public void setOperationName(String operationName) {
83+
setOperationNameTypeExpression(new LiteralExpression(operationName));
84+
}
85+
86+
/**
87+
* Set a SpEL expression to evaluate a GraphQL Operation Name to execute.
88+
* @param operationNameExpression the expression to use.
89+
*/
90+
public void setOperationNameTypeExpression(Expression operationNameExpression) {
91+
Assert.notNull(operationNameExpression, "'operationNameExpression' must not be null");
92+
this.operationNameExpression = operationNameExpression;
93+
}
94+
95+
/**
96+
* Specify variables for the GraphQL Query to execute.
97+
* @param variables the GraphQL variables to use.
98+
*/
99+
public void setVariables(Map<String, Object> variables) {
100+
setVariablesExpression(new SupplierExpression<>(() -> variables));
101+
}
102+
103+
/**
104+
* Set a SpEL expression to evaluate Variables for GraphQL Query to execute.
105+
* @param variablesExpression the expression to use.
106+
*/
107+
public void setVariablesExpression(Expression variablesExpression) {
108+
Assert.notNull(variablesExpression, "'variablesExpression' must not be null");
109+
this.variablesExpression = variablesExpression;
110+
}
111+
45112
@Override
46113
protected Object handleRequestMessage(Message<?> requestMessage) {
47114

48115
if (requestMessage.getPayload() instanceof RequestInput) {
49116

50-
Mono<ExecutionResult> result = this.graphQlService
117+
return this.graphQlService
51118
.execute((RequestInput) requestMessage.getPayload());
52-
53-
return result;
119+
}
120+
else if (requestMessage.getPayload() instanceof String && !Strings.isNullOrEmpty((String) requestMessage.getPayload())) {
121+
String query = evaluateQueryExpression(requestMessage);
122+
String operationName = evaluateOperationNameExpression(requestMessage);
123+
Map<String, Object> variables = evaluateVariablesExpression(requestMessage);
124+
return this.graphQlService
125+
.execute(new RequestInput(query, operationName, variables));
54126
}
55127
else {
56-
throw new IllegalArgumentException("Message payload needs to be 'org.springframework.graphql.RequestInput'");
128+
throw new IllegalArgumentException("Message payload does not meet criteria to construct a 'org.springframework.graphql.RequestInput'");
57129
}
58130
}
131+
132+
String evaluateQueryExpression(Message<?> message) {
133+
String query = this.queryExpression.getValue(this.evaluationContext, message, String.class);
134+
Assert.notNull(query, "'queryExpression' must not evaluate to null");
135+
return query;
136+
}
137+
138+
String evaluateOperationNameExpression(Message<?> message) {
139+
Assert.notNull(this.operationNameExpression, "'operationNameExpression' must not be null when 'query' mode is used");
140+
return this.operationNameExpression.getValue(this.evaluationContext, message, String.class);
141+
}
142+
143+
@SuppressWarnings("unchecked")
144+
Map<String, Object> evaluateVariablesExpression(Message<?> message) {
145+
Assert.notNull(this.variablesExpression, "'variablesExpression' must not be null when 'query' mode is used");
146+
return this.variablesExpression.getValue(this.evaluationContext, message, Map.class);
147+
}
148+
59149
}

spring-integration-graphql/src/test/java/org/springframework/integration/graphql/outbound/GraphQlMessageHandlerTests.java

+83-9
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
* @since 6.0
6969
*/
7070
@SpringJUnitConfig
71-
@DirtiesContext
71+
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
7272
public class GraphQlMessageHandlerTests {
7373

7474
@Autowired
@@ -80,6 +80,9 @@ public class GraphQlMessageHandlerTests {
8080
@Autowired
8181
private PollableChannel errorChannel;
8282

83+
@Autowired
84+
private GraphQlMessageHandler graphQlMessageHandler;
85+
8386
@Autowired
8487
private UpdateRepository updateRepository;
8588

@@ -97,8 +100,7 @@ void testHandleMessageForQuery() {
97100
Map<String, Object> data = result.getData();
98101
Map<String, Object> testQuery = (Map<String, Object>) data.get("testQuery");
99102
assertThat(testQuery.get("id")).isEqualTo("test-data");
100-
}
101-
)
103+
})
102104
.thenCancel()
103105
.verifyLater();
104106

@@ -111,6 +113,69 @@ void testHandleMessageForQuery() {
111113
verifier.verify(Duration.ofSeconds(10));
112114
}
113115

116+
@Test
117+
@SuppressWarnings("unchecked")
118+
void testHandleMessageForQueryWithQueryProvided() {
119+
120+
String fakeQuery = "{ testQuery { id } }";
121+
this.graphQlMessageHandler.setQuery(fakeQuery);
122+
123+
StepVerifier.create(
124+
Mono.from((Mono<ExecutionResult>)this.graphQlMessageHandler.handleRequestMessage(MessageBuilder.withPayload(fakeQuery).build()))
125+
)
126+
.consumeNextWith(result -> {
127+
assertThat(result).isInstanceOf(ExecutionResultImpl.class);
128+
Map<String, Object> data = result.getData();
129+
Map<String, Object> testQuery = (Map<String, Object>) data.get("testQuery");
130+
assertThat(testQuery.get("id")).isEqualTo("test-data");
131+
})
132+
.expectComplete()
133+
.verify();
134+
}
135+
136+
@Test
137+
@SuppressWarnings("unchecked")
138+
void testHandleMessageForQueryWithQueryAndOperationProvided() {
139+
140+
String fakeQuery = "query FriendlyName { testQuery { id } }";
141+
this.graphQlMessageHandler.setQuery(fakeQuery);
142+
this.graphQlMessageHandler.setOperationName("FriendlyName");
143+
144+
StepVerifier.create(
145+
Mono.from((Mono<ExecutionResult>)this.graphQlMessageHandler.handleRequestMessage(MessageBuilder.withPayload(fakeQuery).build()))
146+
)
147+
.consumeNextWith(result -> {
148+
assertThat(result).isInstanceOf(ExecutionResultImpl.class);
149+
Map<String, Object> data = result.getData();
150+
Map<String, Object> testQuery = (Map<String, Object>) data.get("testQuery");
151+
assertThat(testQuery.get("id")).isEqualTo("test-data");
152+
})
153+
.expectComplete()
154+
.verify();
155+
}
156+
157+
@Test
158+
@SuppressWarnings("unchecked")
159+
void testHandleMessageForQueryWithQueryAndOperationNameAndVariablesProvided() {
160+
161+
String fakeQuery = "query FriendlyName($id: String) { testQueryById(id: $id) { id } }";
162+
this.graphQlMessageHandler.setQuery(fakeQuery);
163+
this.graphQlMessageHandler.setOperationName("FriendlyName");
164+
this.graphQlMessageHandler.setVariables(Map.of("$id", "test-data"));
165+
166+
StepVerifier.create(
167+
Mono.from((Mono<ExecutionResult>)this.graphQlMessageHandler.handleRequestMessage(MessageBuilder.withPayload(fakeQuery).build()))
168+
)
169+
.consumeNextWith(result -> {
170+
assertThat(result).isInstanceOf(ExecutionResultImpl.class);
171+
Map<String, Object> data = result.getData();
172+
Map<String, Object> testQuery = (Map<String, Object>) data.get("testQueryById");
173+
assertThat(testQuery.get("id")).isEqualTo("test-data");
174+
})
175+
.expectComplete()
176+
.verify();
177+
}
178+
114179
@Test
115180
@SuppressWarnings("unchecked")
116181
void testHandleMessageForMutation() {
@@ -191,7 +256,7 @@ void testHandleMessageForQueryWithInvalidPayload() {
191256

192257
this.inputChannel.send(
193258
MessageBuilder
194-
.withPayload("{ testQuery { id } }")
259+
.withPayload(new Object())
195260
.build()
196261
);
197262

@@ -202,7 +267,7 @@ void testHandleMessageForQueryWithInvalidPayload() {
202267
.isInstanceOf(MessageHandlingException.class)
203268
.satisfies((ex) -> assertThat((Exception) ex)
204269
.hasMessageContaining(
205-
"Message payload needs to be 'org.springframework.graphql.RequestInput'"));
270+
"Message payload does not meet criteria to construct a 'org.springframework.graphql.RequestInput'"));
206271

207272
}
208273

@@ -213,7 +278,7 @@ void testHandleMessageForMutationWithInvalidPayload() {
213278

214279
this.inputChannel.send(
215280
MessageBuilder
216-
.withPayload("mutation { update(id: \"" + fakeId + "\") { id } }")
281+
.withPayload(new Object())
217282
.build()
218283
);
219284

@@ -224,7 +289,7 @@ void testHandleMessageForMutationWithInvalidPayload() {
224289
.isInstanceOf(MessageHandlingException.class)
225290
.satisfies((ex) -> assertThat((Exception) ex)
226291
.hasMessageContaining(
227-
"Message payload needs to be 'org.springframework.graphql.RequestInput'"));
292+
"Message payload does not meet criteria to construct a 'org.springframework.graphql.RequestInput'"));
228293

229294
}
230295

@@ -233,7 +298,7 @@ void testHandleMessageForSubscriptionWithInvalidPayload() {
233298

234299
this.inputChannel.send(
235300
MessageBuilder
236-
.withPayload("subscription { results { id } }")
301+
.withPayload(new Object())
237302
.build()
238303
);
239304

@@ -244,10 +309,14 @@ void testHandleMessageForSubscriptionWithInvalidPayload() {
244309
.isInstanceOf(MessageHandlingException.class)
245310
.satisfies((ex) -> assertThat((Exception) ex)
246311
.hasMessageContaining(
247-
"Message payload needs to be 'org.springframework.graphql.RequestInput'"));
312+
"Message payload does not meet criteria to construct a 'org.springframework.graphql.RequestInput'"));
248313

249314
}
250315

316+
private static <T> T waitFor(Mono<T> mono) {
317+
return mono.block(Duration.ofSeconds(10));
318+
}
319+
251320
@Controller
252321
static class GraphQlController {
253322

@@ -262,6 +331,11 @@ public Mono<QueryResult> testQuery() {
262331
return Mono.just(new QueryResult("test-data"));
263332
}
264333

334+
@QueryMapping
335+
public Mono<QueryResult> testQueryById(@Argument String id) {
336+
return Mono.just(new QueryResult("test-data"));
337+
}
338+
265339
@MutationMapping
266340
public Mono<Update> update(@Argument String id) {
267341
return this.updateRepository.save(new Update(id));

spring-integration-graphql/src/test/resources/graphql/test-schema.graphqls

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
type Query {
22
testQuery: QueryResult
3+
testQueryById(id: String): QueryResult
34
}
45

56
type Mutation {

0 commit comments

Comments
 (0)