Skip to content

Commit 175673d

Browse files
jord1erstoyanchev
authored andcommitted
Convert websocket error message payload to array
As expected in graphql-ws protocol See gh-200
1 parent 6d37d9b commit 175673d

File tree

4 files changed

+118
-2
lines changed

4 files changed

+118
-2
lines changed

spring-graphql/src/main/java/org/springframework/graphql/web/webflux/GraphQlWebSocketHandler.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,10 @@ private Flux<WebSocketMessage> handleWebOutput(WebSocketSession session, String
245245
.message(ex.getMessage())
246246
.build()
247247
.toSpecification();
248-
return Mono.just(encode(session, id, MessageType.ERROR, errorMap));
248+
249+
// Payload needs to be an array
250+
// see: https://github.com/enisdenjo/graphql-ws/blob/master/docs/interfaces/common.ErrorMessage.md#payload
251+
return Mono.just(encode(session, id, MessageType.ERROR, Collections.singletonList(errorMap)));
249252
});
250253
}
251254

spring-graphql/src/main/java/org/springframework/graphql/web/webmvc/GraphQlWebSocketHandler.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,10 @@ private Flux<TextMessage> handleWebOutput(WebSocketSession session, String id, W
259259
String message = ex.getMessage();
260260
Map<String, Object> errorMap = GraphqlErrorBuilder.newError().errorType(errorType).message(message).build()
261261
.toSpecification();
262-
return Mono.just(encode(id, MessageType.ERROR, errorMap));
262+
263+
// Payload needs to be an array
264+
// see: https://github.com/enisdenjo/graphql-ws/blob/master/docs/interfaces/common.ErrorMessage.md#payload
265+
return Mono.just(encode(id, MessageType.ERROR, Collections.singletonList(errorMap)));
263266
});
264267
}
265268

spring-graphql/src/test/java/org/springframework/graphql/web/webflux/GraphQlWebSocketHandlerTests.java

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626

2727
import org.assertj.core.api.InstanceOfAssertFactories;
2828
import org.junit.jupiter.api.Test;
29+
import org.springframework.graphql.GraphQlSetup;
30+
import org.springframework.graphql.web.WebGraphQlHandler;
2931
import reactor.core.publisher.Flux;
3032
import reactor.core.publisher.Mono;
3133
import reactor.core.publisher.Sinks;
@@ -245,6 +247,60 @@ void clientCompletion() {
245247
.verifyTimeout(Duration.ofMillis(500));
246248
}
247249

250+
@Test
251+
void errorMessagePayloadIsCorrectArray() {
252+
final String GREETING_QUERY = "{" +
253+
"\"id\":\"" + SUBSCRIPTION_ID + "\"," +
254+
"\"type\":\"subscribe\"," +
255+
"\"payload\":{\"query\": \"" +
256+
" subscription TestTypenameSubscription {" +
257+
" greeting" +
258+
" }\"}" +
259+
"}";
260+
261+
WebGraphQlHandler initHandler = GraphQlSetup.schemaContent("" +
262+
"type Subscription { greeting: String! }" +
263+
"type Query { greetingUnused: String! }")
264+
.subscriptionFetcher("greeting", env -> Flux.just("a", null, "b"))
265+
.webInterceptor()
266+
.toWebGraphQlHandler();
267+
268+
GraphQlWebSocketHandler handler = new GraphQlWebSocketHandler(
269+
initHandler,
270+
ServerCodecConfigurer.create(),
271+
Duration.ofSeconds(60));
272+
273+
TestWebSocketSession session = new TestWebSocketSession(Flux.just(
274+
toWebSocketMessage("{\"type\":\"connection_init\"}"),
275+
toWebSocketMessage(GREETING_QUERY)));
276+
handler.handle(session).block();
277+
278+
StepVerifier.create(session.getOutput())
279+
.consumeNextWith((message) -> assertMessageType(message, "connection_ack"))
280+
.consumeNextWith((message) -> assertThat(decode(message))
281+
.hasSize(3)
282+
.containsEntry("id", SUBSCRIPTION_ID)
283+
.containsEntry("type", "next")
284+
.extractingByKey("payload", as(InstanceOfAssertFactories.map(String.class, Object.class)))
285+
.extractingByKey("data", as(InstanceOfAssertFactories.map(String.class, Object.class)))
286+
.containsEntry("greeting", "a"))
287+
.consumeNextWith((message) -> assertThat(decode(message))
288+
.hasSize(3)
289+
.containsEntry("id", SUBSCRIPTION_ID)
290+
.containsEntry("type", "error")
291+
.hasEntrySatisfying("payload", payload -> assertThat(payload)
292+
.asList()
293+
.hasSize(1)
294+
.allSatisfy(theError -> assertThat(theError)
295+
.asInstanceOf(InstanceOfAssertFactories.map(String.class, Object.class))
296+
.hasSize(3)
297+
.hasEntrySatisfying("locations", loc -> assertThat(loc).asList().isEmpty())
298+
.hasEntrySatisfying("message", msg -> assertThat(msg).asString().contains("null"))
299+
.extractingByKey("extensions", as(InstanceOfAssertFactories.map(String.class, Object.class)))
300+
.containsEntry("classification", "DataFetchingException"))))
301+
.verifyComplete();
302+
}
303+
248304
private TestWebSocketSession handle(Flux<WebSocketMessage> input, WebInterceptor... interceptors) {
249305
GraphQlWebSocketHandler handler = new GraphQlWebSocketHandler(
250306
initHandler(interceptors),

spring-graphql/src/test/java/org/springframework/graphql/web/webmvc/GraphQlWebSocketHandlerTests.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@
2929

3030
import org.assertj.core.api.InstanceOfAssertFactories;
3131
import org.junit.jupiter.api.Test;
32+
import org.springframework.graphql.GraphQlSetup;
33+
import org.springframework.graphql.web.WebGraphQlHandler;
34+
import reactor.core.publisher.Flux;
3235
import reactor.core.publisher.Mono;
3336
import reactor.test.StepVerifier;
3437

@@ -249,6 +252,57 @@ void clientCompletion() throws Exception {
249252
.verifyTimeout(Duration.ofMillis(500));
250253
}
251254

255+
@Test
256+
void errorMessagePayloadIsCorrectArray() throws Exception {
257+
final String GREETING_QUERY = "{" +
258+
"\"id\":\"" + SUBSCRIPTION_ID + "\"," +
259+
"\"type\":\"subscribe\"," +
260+
"\"payload\":{\"query\": \"" +
261+
" subscription TestTypenameSubscription {" +
262+
" greeting" +
263+
" }\"}" +
264+
"}";
265+
266+
WebGraphQlHandler initHandler = GraphQlSetup.schemaContent("" +
267+
"type Subscription { greeting: String! }" +
268+
"type Query { greetingUnused: String! }")
269+
.subscriptionFetcher("greeting", env -> Flux.just("a", null, "b"))
270+
.webInterceptor()
271+
.toWebGraphQlHandler();
272+
273+
GraphQlWebSocketHandler handler = new GraphQlWebSocketHandler(initHandler, converter, Duration.ofSeconds(60));
274+
275+
handle(handler,
276+
new TextMessage("{\"type\":\"connection_init\"}"),
277+
new TextMessage(GREETING_QUERY));
278+
279+
StepVerifier.create(this.session.getOutput())
280+
.consumeNextWith((message) -> assertMessageType(message, "connection_ack"))
281+
.consumeNextWith((message) -> assertThat(decode(message))
282+
.hasSize(3)
283+
.containsEntry("id", SUBSCRIPTION_ID)
284+
.containsEntry("type", "next")
285+
.extractingByKey("payload", as(InstanceOfAssertFactories.map(String.class, Object.class)))
286+
.extractingByKey("data", as(InstanceOfAssertFactories.map(String.class, Object.class)))
287+
.containsEntry("greeting", "a"))
288+
.consumeNextWith((message) -> assertThat(decode(message))
289+
.hasSize(3)
290+
.containsEntry("id", SUBSCRIPTION_ID)
291+
.containsEntry("type", "error")
292+
.hasEntrySatisfying("payload", payload -> assertThat(payload)
293+
.asList()
294+
.hasSize(1)
295+
.allSatisfy(theError -> assertThat(theError)
296+
.asInstanceOf(InstanceOfAssertFactories.map(String.class, Object.class))
297+
.hasSize(3)
298+
.hasEntrySatisfying("locations", loc -> assertThat(loc).asList().isEmpty())
299+
.hasEntrySatisfying("message", msg -> assertThat(msg).asString().contains("null"))
300+
.extractingByKey("extensions", as(InstanceOfAssertFactories.map(String.class, Object.class)))
301+
.containsEntry("classification", "DataFetchingException"))))
302+
.then(this.session::close)
303+
.verifyComplete();
304+
}
305+
252306
private void handle(GraphQlWebSocketHandler handler, TextMessage... textMessages) throws Exception {
253307
handler.afterConnectionEstablished(this.session);
254308
for (TextMessage message : textMessages) {

0 commit comments

Comments
 (0)