From 0951f879dcc189db700e2c8fb0edf2ba62e398ec Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Tue, 5 Jul 2022 14:32:45 +0100 Subject: [PATCH 1/2] Improve error messages in SourceMethodArgumentResolver Closes gh-429 --- .../support/SourceMethodArgumentResolver.java | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/SourceMethodArgumentResolver.java b/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/SourceMethodArgumentResolver.java index dbd414a8..83c0be24 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/SourceMethodArgumentResolver.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/SourceMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-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. @@ -26,8 +26,8 @@ import graphql.schema.DataFetchingEnvironment; import org.springframework.core.MethodParameter; import org.springframework.graphql.data.method.HandlerMethodArgumentResolver; -import org.springframework.util.Assert; import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; /** * Resolver for the source/parent of a field, obtained via @@ -66,10 +66,21 @@ public class SourceMethodArgumentResolver implements HandlerMethodArgumentResolv @Override public Object resolveArgument(MethodParameter parameter, DataFetchingEnvironment environment) { Object source = environment.getSource(); - Assert.isInstanceOf(parameter.getParameterType(), source, - "The declared parameter of type '" + parameter.getParameterType() + "' " + - "does not match the type of the source Object '" + source.getClass() + "'."); + if (source == null) { + throw new IllegalStateException(formatArgumentError(parameter, + " was not recognized by any resolver and there is no source/parent either. " + + "Please, refer to the documentation for the full list of supported parameters.")); + } + if (!parameter.getParameterType().isInstance(source)) { + throw new IllegalStateException(formatArgumentError(parameter, + " does not match the source Object type '" + source.getClass() + "'.")); + } return source; } + private static String formatArgumentError(MethodParameter param, String message) { + return "Parameter [" + param.getParameterIndex() + "] in " + + param.getExecutable().toGenericString() + (StringUtils.hasText(message) ? ": " + message : ""); + } + } From 65cd5bfb49f8c13f1f65a135f0c10147cdc5b8fb Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Tue, 5 Jul 2022 15:12:48 +0100 Subject: [PATCH 2/2] Update docs on web interception Closes gh-428 --- .../src/docs/asciidoc/index.adoc | 59 ++++++++++--------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/spring-graphql-docs/src/docs/asciidoc/index.adoc b/spring-graphql-docs/src/docs/asciidoc/index.adoc index 3fb89387..b0fac93d 100644 --- a/spring-graphql-docs/src/docs/asciidoc/index.adoc +++ b/spring-graphql-docs/src/docs/asciidoc/index.adoc @@ -154,15 +154,12 @@ public class GraphQlRSocketController { [[server-interception]] === Interception -Spring MVC and Spring WebFlux transport handlers, for both <> and -<>, all delegate to the same `WebGraphQlInterceptor` chain, followed by -the `ExecutionGraphQlService` that invokes the GraphQL Java engine. You can use this to -intercept GraphQL requests over any Web transport. +Transport handlers for <> and <> delegate to a +`WebGraphQlInterceptor` chain with an `ExecutionGraphQlService` at the end which calls +the GraphQL Java engine. Use this to access HTTP request details and customize the +`ExecutionInput` for GraphQL Java. -A `WebGraphQlInterceptor` exposes the details of the underlying transport (HTTP or -WebSocket handshake) request and allows customizing the `graphql.ExecutionInput` -that is prepared for GraphQL Java. For example, to extract an HTTP header and make it -available to data fetchers through the `GraphQLContext`: +For example, to extract HTTP request values and pass them to data fetchers: [source,java,indent=0,subs="verbatim,quotes"] ---- @@ -170,16 +167,26 @@ class HeaderInterceptor implements WebGraphQlInterceptor { @Override public Mono intercept(WebGraphQlRequest request, Chain chain) { - List headerValue = request.getHeaders().get("myHeader"); + List values = request.getHeaders().get("headerName"); request.configureExecutionInput((executionInput, builder) -> - builder.graphQLContext(Collections.singletonMap("myHeader", headerValue)).build()); + builder.graphQLContext(Collections.singletonMap("headerName", values)).build()); return chain.next(request); } } + +// Subsequent access from a controller + +@Controller +class MyController { + + @QueryMapping + Person person(@ContextValue String myHeader) { + // ... + } +} ---- -A `DataFetcher` can then access this value, e.g. from an -<> method: +Or reversely, add values to the `GraphQLContext` and use them to update the HTTP response: [source,java,indent=0,subs="verbatim,quotes"] ---- @@ -187,34 +194,28 @@ A `DataFetcher` can then access this value, e.g. from an class MyController { @QueryMapping - Person person(@ContextValue String myHeader) { - // ... + Person person(GraphQLContext context) { + context.put("cookieName", "123"); } } ----- +// Subsequent access from a WebGraphQlInterceptor -Interceptors can also customize HTTP response headers, or inspect and/or transform the -`graphql.ExecutionResult` from GraphQL Java: - -[source,java,indent=0,subs="verbatim,quotes"] ----- -class MyInterceptor implements WebGraphQlInterceptor { +class HeaderInterceptor implements WebGraphQlInterceptor { @Override public Mono intercept(WebGraphQlRequest request, Chain chain) { - return chain.next(request) - .map(response -> { - Object data = response.getData(); - Object updatedData = ... ; - return response.transform(builder -> builder.data(updatedData)); - }); + 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()); + }); } } ---- -`WebGraphQlHandler` has a builder to create the `WebGraphQlInterceptor` chain. The Boot -starter uses this, see Boot's section on +The `WebGraphQlInterceptor` chain can be updated through the `WebGraphQlHandler` builder, +and the Boot starter uses this, see Boot's section on {spring-boot-ref-docs}/web.html#web.graphql.web-endpoints[Web Endpoints]. The <> transport handler delegates to a similar `GraphQlInterceptor`