From c16521c907ae73578a4f5cc8f7b03949bccb85d9 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Tue, 6 Dec 2022 15:59:03 +0100 Subject: [PATCH] Move interception code snippets to Java classes This commit starts movint code snippets from the reference documentation to actual Java classes so that they're compiled and checked at build time. --- .../src/docs/asciidoc/index.adoc | 98 +------------------ .../docs/server/interception/web/Person.java | 20 ++++ .../web/RequestErrorInterceptor.java | 49 ++++++++++ .../web/RequestHeaderInterceptor.java | 48 +++++++++ .../web/ResponseHeaderInterceptor.java | 52 ++++++++++ .../rsocket/GraphQlRSocketController.java | 46 +++++++++ 6 files changed, 219 insertions(+), 94 deletions(-) create mode 100644 spring-graphql-docs/src/main/java/org/springframework/graphql/docs/server/interception/web/Person.java create mode 100644 spring-graphql-docs/src/main/java/org/springframework/graphql/docs/server/interception/web/RequestErrorInterceptor.java create mode 100644 spring-graphql-docs/src/main/java/org/springframework/graphql/docs/server/interception/web/RequestHeaderInterceptor.java create mode 100644 spring-graphql-docs/src/main/java/org/springframework/graphql/docs/server/interception/web/ResponseHeaderInterceptor.java create mode 100644 spring-graphql-docs/src/main/java/org/springframework/graphql/docs/server/transports/rsocket/GraphQlRSocketController.java diff --git a/spring-graphql-docs/src/docs/asciidoc/index.adoc b/spring-graphql-docs/src/docs/asciidoc/index.adoc index c44b002e..f712b26f 100644 --- a/spring-graphql-docs/src/docs/asciidoc/index.adoc +++ b/spring-graphql-docs/src/docs/asciidoc/index.adoc @@ -117,29 +117,7 @@ handled as `request-stream`. `GraphQlRSocketHandler` can be used a delegate from an `@Controller` that is mapped to the route for GraphQL requests. For example: -[source,java,indent=0,subs="verbatim,quotes"] ----- -@Controller -public class GraphQlRSocketController { - - private final GraphQlRSocketHandler handler; - - GraphQlRSocketController(GraphQlRSocketHandler handler) { - this.handler = handler; - } - - @MessageMapping("graphql") - public Mono> handle(Map payload) { - return this.handler.handle(payload); - } - - @MessageMapping("graphql") - public Flux> handleSubscription(Map payload) { - return this.handler.handleSubscription(payload); - } -} ----- - +include::code:GraphQlRSocketController[] @@ -166,58 +144,13 @@ incoming requests and do one of the following: For example, an interceptor can pass an HTTP request header to a `DataFetcher`: -[source,java,indent=0,subs="verbatim,quotes"] ----- -class HeaderInterceptor implements WebGraphQlInterceptor { <1> - - @Override - public Mono intercept(WebGraphQlRequest request, Chain chain) { - String value = request.getHeaders().getFirst("myHeader"); - request.configureExecutionInput((executionInput, builder) -> - builder.graphQLContext(Collections.singletonMap("myHeader", value)).build()); - return chain.next(request); - } -} - -@Controller -class MyController { <2> - - @QueryMapping - Person person(@ContextValue String myHeader) { - // ... - } -} ----- +include::code:RequestHeaderInterceptor[] <1> Interceptor adds HTTP request header value into GraphQLContext <2> Data controller method accesses the value Reversely, an interceptor can access values added to the `GraphQLContext` by a controller: -[source,java,indent=0,subs="verbatim,quotes"] ----- -@Controller -class MyController { - - @QueryMapping - Person person(GraphQLContext context) { <1> - context.put("cookieName", "123"); - } -} - -// Subsequent access from a WebGraphQlInterceptor - -class HeaderInterceptor implements WebGraphQlInterceptor { - - @Override - public Mono intercept(WebGraphQlRequest request, Chain chain) { <2> - return chain.next(request).doOnNext(response -> { - String value = response.getExecutionInput().getGraphQLContext().get("cookieName"); - ResponseCookie cookie = ResponseCookie.from("cookieName", value).build(); - response.getResponseHeaders().add(HttpHeaders.SET_COOKIE, cookie.toString()); - }); - } -} ----- +include::code:ResponseHeaderInterceptor[] <1> Controller adds value to the `GraphQLContext` <2> Interceptor uses the value to add an HTTP response header @@ -225,30 +158,7 @@ class HeaderInterceptor implements WebGraphQlInterceptor { request validation errors that are raised before execution begins and which cannot be handled with a `DataFetcherExceptionResolver`: -[source,java,indent=0,subs="verbatim,quotes"] ----- -static class RequestErrorInterceptor implements WebGraphQlInterceptor { - - @Override - public Mono intercept(WebGraphQlRequest request, Chain chain) { - return chain.next(request).map(response -> { - if (response.isValid()) { - return response; <1> - } - - List errors = response.getErrors().stream() <2> - .map(error -> { - GraphqlErrorBuilder builder = GraphqlErrorBuilder.newError(); - // ... - return builder.build(); - }) - .collect(Collectors.toList()); - - return response.transform(builder -> builder.errors(errors).build()); <3> - }); - } -} ----- +include::code:RequestErrorInterceptor[] <1> Return the same if `ExecutionResult` has a "data" key with non-null value <2> Check and transform the GraphQL errors <3> Update the `ExecutionResult` with the modified errors diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/server/interception/web/Person.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/server/interception/web/Person.java new file mode 100644 index 00000000..874db710 --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/server/interception/web/Person.java @@ -0,0 +1,20 @@ +/* + * 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.docs.server.interception.web; + +public record Person(String firstName, String lastName) { +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/server/interception/web/RequestErrorInterceptor.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/server/interception/web/RequestErrorInterceptor.java new file mode 100644 index 00000000..07967ad2 --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/server/interception/web/RequestErrorInterceptor.java @@ -0,0 +1,49 @@ +/* + * 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.docs.server.interception.web; + +import java.util.List; + +import graphql.GraphQLError; +import graphql.GraphqlErrorBuilder; +import reactor.core.publisher.Mono; + +import org.springframework.graphql.server.WebGraphQlInterceptor; +import org.springframework.graphql.server.WebGraphQlRequest; +import org.springframework.graphql.server.WebGraphQlResponse; + +class RequestErrorInterceptor implements WebGraphQlInterceptor { + + @Override + public Mono intercept(WebGraphQlRequest request, Chain chain) { + return chain.next(request).map(response -> { + if (response.isValid()) { + return response; // <1> + } + + List errors = response.getErrors().stream() // <2> + .map(error -> { + GraphqlErrorBuilder builder = GraphqlErrorBuilder.newError(); + // ... + return builder.build(); + }) + .toList(); + + return response.transform(builder -> builder.errors(errors).build()); // <3> + }); + } +} \ No newline at end of file diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/server/interception/web/RequestHeaderInterceptor.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/server/interception/web/RequestHeaderInterceptor.java new file mode 100644 index 00000000..3686f42d --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/server/interception/web/RequestHeaderInterceptor.java @@ -0,0 +1,48 @@ +/* + * 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.docs.server.interception.web; + +import java.util.Collections; + +import reactor.core.publisher.Mono; + +import org.springframework.graphql.data.method.annotation.ContextValue; +import org.springframework.graphql.data.method.annotation.QueryMapping; +import org.springframework.graphql.server.WebGraphQlInterceptor; +import org.springframework.graphql.server.WebGraphQlRequest; +import org.springframework.graphql.server.WebGraphQlResponse; +import org.springframework.stereotype.Controller; + +class RequestHeaderInterceptor implements WebGraphQlInterceptor { // <1> + + @Override + public Mono intercept(WebGraphQlRequest request, Chain chain) { + String value = request.getHeaders().getFirst("myHeader"); + request.configureExecutionInput((executionInput, builder) -> + builder.graphQLContext(Collections.singletonMap("myHeader", value)).build()); + return chain.next(request); + } +} + +@Controller +class MyContextValueController { // <2> + + @QueryMapping + Person person(@ContextValue String myHeader) { + /**/ return new Person("spring", "graphql"); + } +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/server/interception/web/ResponseHeaderInterceptor.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/server/interception/web/ResponseHeaderInterceptor.java new file mode 100644 index 00000000..0bd33c19 --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/server/interception/web/ResponseHeaderInterceptor.java @@ -0,0 +1,52 @@ +/* + * 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.docs.server.interception.web; + +import graphql.GraphQLContext; +import reactor.core.publisher.Mono; + +import org.springframework.graphql.data.method.annotation.QueryMapping; +import org.springframework.graphql.server.WebGraphQlInterceptor; +import org.springframework.graphql.server.WebGraphQlRequest; +import org.springframework.graphql.server.WebGraphQlResponse; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseCookie; +import org.springframework.stereotype.Controller; + +// Subsequent access from a WebGraphQlInterceptor + +class ResponseHeaderInterceptor implements WebGraphQlInterceptor { + + @Override + public Mono intercept(WebGraphQlRequest request, Chain chain) { // <2> + return chain.next(request).doOnNext(response -> { + String value = response.getExecutionInput().getGraphQLContext().get("cookieName"); + ResponseCookie cookie = ResponseCookie.from("cookieName", value).build(); + response.getResponseHeaders().add(HttpHeaders.SET_COOKIE, cookie.toString()); + }); + } +} + +@Controller +class MyCookieController { + + @QueryMapping + Person person(GraphQLContext context) { // <1> + context.put("cookieName", "123"); + /**/ return new Person("spring", "graphql"); + } +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/server/transports/rsocket/GraphQlRSocketController.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/server/transports/rsocket/GraphQlRSocketController.java new file mode 100644 index 00000000..349da8c4 --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/server/transports/rsocket/GraphQlRSocketController.java @@ -0,0 +1,46 @@ +/* + * 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.docs.server.transports.rsocket; + +import java.util.Map; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import org.springframework.graphql.server.GraphQlRSocketHandler; +import org.springframework.messaging.handler.annotation.MessageMapping; +import org.springframework.stereotype.Controller; + +@Controller +public class GraphQlRSocketController { + + private final GraphQlRSocketHandler handler; + + GraphQlRSocketController(GraphQlRSocketHandler handler) { + this.handler = handler; + } + + @MessageMapping("graphql") + public Mono> handle(Map payload) { + return this.handler.handle(payload); + } + + @MessageMapping("graphql") + public Flux> handleSubscription(Map payload) { + return this.handler.handleSubscription(payload); + } +} \ No newline at end of file