Replace GraphQLError with GraphQlResponseError
This allows exposing additional conveniences for inspecting errors. See gh-10
This commit is contained in:
@@ -19,13 +19,13 @@ package org.springframework.graphql.test.tester;
|
||||
import java.util.List;
|
||||
|
||||
import graphql.ExecutionResult;
|
||||
import graphql.GraphQLError;
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.graphql.GraphQlRequest;
|
||||
import org.springframework.graphql.GraphQlResponse;
|
||||
import org.springframework.graphql.GraphQlResponseError;
|
||||
import org.springframework.graphql.RequestOutput;
|
||||
import org.springframework.graphql.client.GraphQlTransport;
|
||||
import org.springframework.test.util.AssertionErrors;
|
||||
@@ -58,7 +58,7 @@ abstract class AbstractDirectTransport implements GraphQlTransport {
|
||||
Object data = output.getData();
|
||||
AssertionErrors.assertTrue("Not a Publisher: " + data, data instanceof Publisher);
|
||||
|
||||
List<GraphQLError> errors = output.getErrors();
|
||||
List<GraphQlResponseError> errors = output.getErrors();
|
||||
AssertionErrors.assertTrue("Subscription errors: " + errors, CollectionUtils.isEmpty(errors));
|
||||
|
||||
return Flux.from((Publisher<ExecutionResult>) data)
|
||||
|
||||
@@ -24,8 +24,8 @@ import com.jayway.jsonpath.Configuration;
|
||||
import com.jayway.jsonpath.spi.json.JacksonJsonProvider;
|
||||
import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider;
|
||||
import com.jayway.jsonpath.spi.mapper.MappingProvider;
|
||||
import graphql.GraphQLError;
|
||||
|
||||
import org.springframework.graphql.GraphQlResponseError;
|
||||
import org.springframework.graphql.client.AbstractGraphQlClientBuilder;
|
||||
import org.springframework.graphql.client.GraphQlTransport;
|
||||
import org.springframework.graphql.support.CachingDocumentSource;
|
||||
@@ -57,7 +57,7 @@ public abstract class AbstractGraphQlTesterBuilder<B extends AbstractGraphQlTest
|
||||
|
||||
|
||||
@Nullable
|
||||
private Predicate<GraphQLError> errorFilter;
|
||||
private Predicate<GraphQlResponseError> errorFilter;
|
||||
|
||||
private DocumentSource documentSource = new CachingDocumentSource(new ResourceDocumentSource());
|
||||
|
||||
@@ -67,7 +67,7 @@ public abstract class AbstractGraphQlTesterBuilder<B extends AbstractGraphQlTest
|
||||
|
||||
|
||||
@Override
|
||||
public B errorFilter(Predicate<GraphQLError> predicate) {
|
||||
public B errorFilter(Predicate<GraphQlResponseError> predicate) {
|
||||
this.errorFilter = (this.errorFilter != null ? errorFilter.and(predicate) : predicate);
|
||||
return self();
|
||||
}
|
||||
|
||||
@@ -31,12 +31,12 @@ import com.jayway.jsonpath.Configuration;
|
||||
import com.jayway.jsonpath.DocumentContext;
|
||||
import com.jayway.jsonpath.JsonPath;
|
||||
import com.jayway.jsonpath.TypeRef;
|
||||
import graphql.GraphQLError;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.graphql.GraphQlRequest;
|
||||
import org.springframework.graphql.GraphQlResponse;
|
||||
import org.springframework.graphql.GraphQlResponseError;
|
||||
import org.springframework.graphql.client.GraphQlTransport;
|
||||
import org.springframework.graphql.support.DocumentSource;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -61,7 +61,7 @@ final class DefaultGraphQlTester implements GraphQlTester {
|
||||
private final GraphQlTransport transport;
|
||||
|
||||
@Nullable
|
||||
private final Predicate<GraphQLError> errorFilter;
|
||||
private final Predicate<GraphQlResponseError> errorFilter;
|
||||
|
||||
private final Configuration jsonPathConfig;
|
||||
|
||||
@@ -76,7 +76,7 @@ final class DefaultGraphQlTester implements GraphQlTester {
|
||||
* Package private constructor for use from {@link AbstractGraphQlTesterBuilder}.
|
||||
*/
|
||||
DefaultGraphQlTester(
|
||||
GraphQlTransport transport, @Nullable Predicate<GraphQLError> errorFilter,
|
||||
GraphQlTransport transport, @Nullable Predicate<GraphQlResponseError> errorFilter,
|
||||
Configuration jsonPathConfig, DocumentSource documentSource, Duration timeout,
|
||||
Consumer<AbstractGraphQlTesterBuilder<?>> builderInitializer) {
|
||||
|
||||
@@ -209,15 +209,15 @@ final class DefaultGraphQlTester implements GraphQlTester {
|
||||
|
||||
private final Supplier<String> jsonContent;
|
||||
|
||||
private final List<GraphQLError> errors;
|
||||
private final List<GraphQlResponseError> errors;
|
||||
|
||||
private final List<GraphQLError> unexpectedErrors;
|
||||
private final List<GraphQlResponseError> unexpectedErrors;
|
||||
|
||||
private final Consumer<Runnable> assertDecorator;
|
||||
|
||||
|
||||
private ResponseDelegate(
|
||||
GraphQlResponse response, @Nullable Predicate<GraphQLError> errorFilter,
|
||||
GraphQlResponse response, @Nullable Predicate<GraphQlResponseError> errorFilter,
|
||||
Consumer<Runnable> assertDecorator, Configuration jsonPathConfig) {
|
||||
|
||||
this.jsonDoc = JsonPath.parse(response.toMap(), jsonPathConfig);
|
||||
@@ -253,9 +253,9 @@ final class DefaultGraphQlTester implements GraphQlTester {
|
||||
this.assertDecorator.accept(task);
|
||||
}
|
||||
|
||||
boolean filterErrors(Predicate<GraphQLError> predicate) {
|
||||
boolean filterErrors(Predicate<GraphQlResponseError> predicate) {
|
||||
boolean filtered = false;
|
||||
for (GraphQLError error : this.errors) {
|
||||
for (GraphQlResponseError error : this.errors) {
|
||||
if (predicate.test(error)) {
|
||||
this.unexpectedErrors.remove(error);
|
||||
filtered = true;
|
||||
@@ -264,12 +264,12 @@ final class DefaultGraphQlTester implements GraphQlTester {
|
||||
return filtered;
|
||||
}
|
||||
|
||||
void expectErrors(Predicate<GraphQLError> predicate) {
|
||||
void expectErrors(Predicate<GraphQlResponseError> predicate) {
|
||||
boolean filtered = filterErrors(predicate);
|
||||
this.assertDecorator.accept(() -> AssertionErrors.assertTrue("No matching errors.", filtered));
|
||||
}
|
||||
|
||||
void consumeErrors(Consumer<List<GraphQLError>> consumer) {
|
||||
void consumeErrors(Consumer<List<GraphQlResponseError>> consumer) {
|
||||
filterErrors(error -> true);
|
||||
consumer.accept(this.errors);
|
||||
}
|
||||
@@ -293,7 +293,7 @@ final class DefaultGraphQlTester implements GraphQlTester {
|
||||
private final ResponseDelegate delegate;
|
||||
|
||||
private DefaultResponse(
|
||||
GraphQlResponse response, @Nullable Predicate<GraphQLError> errorFilter,
|
||||
GraphQlResponse response, @Nullable Predicate<GraphQlResponseError> errorFilter,
|
||||
Consumer<Runnable> assertDecorator, Configuration jsonPathConfig) {
|
||||
|
||||
this.delegate = new ResponseDelegate(response, errorFilter, assertDecorator, jsonPathConfig);
|
||||
@@ -311,13 +311,13 @@ final class DefaultGraphQlTester implements GraphQlTester {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Errors filter(Predicate<GraphQLError> predicate) {
|
||||
public Errors filter(Predicate<GraphQlResponseError> predicate) {
|
||||
this.delegate.filterErrors(predicate);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Errors expect(Predicate<GraphQLError> predicate) {
|
||||
public Errors expect(Predicate<GraphQlResponseError> predicate) {
|
||||
this.delegate.expectErrors(predicate);
|
||||
return this;
|
||||
}
|
||||
@@ -329,7 +329,7 @@ final class DefaultGraphQlTester implements GraphQlTester {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Traversable satisfy(Consumer<List<GraphQLError>> consumer) {
|
||||
public Traversable satisfy(Consumer<List<GraphQlResponseError>> consumer) {
|
||||
this.delegate.consumeErrors(consumer);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -21,10 +21,10 @@ import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import graphql.GraphQLError;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.graphql.GraphQlResponseError;
|
||||
import org.springframework.graphql.client.GraphQlTransport;
|
||||
import org.springframework.graphql.support.DocumentSource;
|
||||
import org.springframework.graphql.support.ResourceDocumentSource;
|
||||
@@ -103,7 +103,7 @@ public interface GraphQlTester {
|
||||
* @param predicate the error filter to add
|
||||
* @return the same builder instance
|
||||
*/
|
||||
B errorFilter(Predicate<GraphQLError> predicate);
|
||||
B errorFilter(Predicate<GraphQlResponseError> predicate);
|
||||
|
||||
/**
|
||||
* Configure a {@link DocumentSource} for use with
|
||||
@@ -448,7 +448,7 @@ public interface GraphQlTester {
|
||||
* @param errorPredicate the error filter to add
|
||||
* @return the same spec to add more filters before {@link #verify()}
|
||||
*/
|
||||
Errors filter(Predicate<GraphQLError> errorPredicate);
|
||||
Errors filter(Predicate<GraphQlResponseError> errorPredicate);
|
||||
|
||||
/**
|
||||
* Use this to declare errors that are expected.
|
||||
@@ -461,7 +461,7 @@ public interface GraphQlTester {
|
||||
* @param errorPredicate the predicate for the expected error
|
||||
* @return the same spec to add more filters or expected errors
|
||||
*/
|
||||
Errors expect(Predicate<GraphQLError> errorPredicate);
|
||||
Errors expect(Predicate<GraphQlResponseError> errorPredicate);
|
||||
|
||||
/**
|
||||
* Verify there are either no errors or that there no unexpected errors that have
|
||||
@@ -477,7 +477,7 @@ public interface GraphQlTester {
|
||||
* @param errorsConsumer to inspect errors with
|
||||
* @return a spec to switch to a data path
|
||||
*/
|
||||
Traversable satisfy(Consumer<List<GraphQLError>> errorsConsumer);
|
||||
Traversable satisfy(Consumer<List<GraphQlResponseError>> errorsConsumer);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -20,8 +20,6 @@ package org.springframework.graphql;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import graphql.GraphQLError;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
@@ -61,7 +59,7 @@ public interface GraphQlResponse {
|
||||
* response is not {@link #isValid() valid} and/or "field errors" for a
|
||||
* partial response.
|
||||
*/
|
||||
List<GraphQLError> getErrors();
|
||||
List<GraphQlResponseError> getErrors();
|
||||
|
||||
/**
|
||||
* Return implementor specific, protocol extensions, if any.
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package org.springframework.graphql;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import graphql.ErrorClassification;
|
||||
import graphql.language.SourceLocation;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a GraphQL response error.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0
|
||||
*/
|
||||
public interface GraphQlResponseError {
|
||||
|
||||
/**
|
||||
* Return the message with a description of the error intended for the
|
||||
* developer as a guide to understand and correct the error.
|
||||
*/
|
||||
@Nullable
|
||||
String getMessage();
|
||||
|
||||
/**
|
||||
* Return a list of locations in the GraphQL document, if the error can be
|
||||
* associated to a particular point in the document. Each location has a
|
||||
* line and a column, both positive, starting from 1 and describing the
|
||||
* beginning of an associated syntax element.
|
||||
*/
|
||||
List<SourceLocation> getLocations();
|
||||
|
||||
/**
|
||||
* Return a classification for the error that is specific to GraphQL Java.
|
||||
* This is serialized under {@link #getExtensions() "extensions"} in the
|
||||
* response map.
|
||||
* @see graphql.ErrorType
|
||||
* @see org.springframework.graphql.execution.ErrorType
|
||||
*/
|
||||
@Nullable
|
||||
ErrorClassification getErrorType();
|
||||
|
||||
/**
|
||||
* Return the path to a response field which experienced the error,
|
||||
* if the error can be associated to a particular field in the result. This
|
||||
* allows a client to identify whether a {@code null} result is intentional
|
||||
* or caused by an error.
|
||||
* <p>This list contains path segments starting at the root of the response
|
||||
* and ending with the field associated with the error. Path segments that
|
||||
* represent fields are strings, and path segments that represent list
|
||||
* indices are 0-indexed integers. If the error happens in an aliased field,
|
||||
* the path uses the aliased name, since it represents a path in the
|
||||
* response, not in the request.
|
||||
*/
|
||||
@Nullable
|
||||
List<Object> getPath();
|
||||
|
||||
/**
|
||||
* Return a map with GraphQL Java specific error details such as the
|
||||
* {@link #getErrorType()}.
|
||||
*/
|
||||
@Nullable
|
||||
Map<String, Object> getExtensions();
|
||||
|
||||
}
|
||||
@@ -18,10 +18,13 @@ package org.springframework.graphql;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import graphql.ErrorClassification;
|
||||
import graphql.ExecutionInput;
|
||||
import graphql.ExecutionResult;
|
||||
import graphql.GraphQLError;
|
||||
import graphql.language.SourceLocation;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -55,9 +58,7 @@ public class RequestOutput implements GraphQlResponse {
|
||||
* Constructor to re-wrap from transport specific subclass.
|
||||
*/
|
||||
protected RequestOutput(RequestOutput requestOutput) {
|
||||
Assert.notNull(requestOutput, "RequestOutput is required.");
|
||||
this.input = requestOutput.getExecutionInput();
|
||||
this.result = requestOutput.result;
|
||||
this(requestOutput.getExecutionInput(), requestOutput.result);
|
||||
}
|
||||
|
||||
|
||||
@@ -84,8 +85,8 @@ public class RequestOutput implements GraphQlResponse {
|
||||
return this.result.getData();
|
||||
}
|
||||
|
||||
public List<GraphQLError> getErrors() {
|
||||
return this.result.getErrors();
|
||||
public List<GraphQlResponseError> getErrors() {
|
||||
return this.result.getErrors().stream().map(OutputError::new).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public Map<Object, Object> getExtensions() {
|
||||
@@ -102,4 +103,41 @@ public class RequestOutput implements GraphQlResponse {
|
||||
return this.result.toString();
|
||||
}
|
||||
|
||||
|
||||
private static class OutputError implements GraphQlResponseError {
|
||||
|
||||
private final GraphQLError delegate;
|
||||
|
||||
OutputError(GraphQLError delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return this.delegate.getMessage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SourceLocation> getLocations() {
|
||||
return this.delegate.getLocations();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ErrorClassification getErrorType() {
|
||||
return this.delegate.getErrorType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Object> getPath() {
|
||||
return this.delegate.getPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getExtensions() {
|
||||
return this.delegate.getExtensions();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -20,8 +20,6 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import graphql.GraphQLError;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.codec.Decoder;
|
||||
@@ -31,7 +29,9 @@ import org.springframework.core.io.buffer.DataBufferFactory;
|
||||
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
||||
import org.springframework.graphql.GraphQlRequest;
|
||||
import org.springframework.graphql.GraphQlResponse;
|
||||
import org.springframework.graphql.GraphQlResponseError;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.MimeType;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
|
||||
@@ -54,7 +54,7 @@ final class DefaultClientGraphQlResponse extends MapGraphQlResponse implements C
|
||||
DefaultClientGraphQlResponse(
|
||||
GraphQlRequest request, GraphQlResponse response, Encoder<?> encoder, Decoder<?> decoder) {
|
||||
|
||||
super(response.toMap());
|
||||
super(response);
|
||||
|
||||
this.request = request;
|
||||
this.encoder = encoder;
|
||||
@@ -72,7 +72,7 @@ final class DefaultClientGraphQlResponse extends MapGraphQlResponse implements C
|
||||
|
||||
List<Object> dataPath = parseFieldPath(path);
|
||||
Object value = getFieldValue(dataPath);
|
||||
List<GraphQLError> errors = getFieldErrors(dataPath);
|
||||
List<GraphQlResponseError> errors = getFieldErrors(dataPath);
|
||||
|
||||
return new DefaultField(path, dataPath, (value != NO_VALUE ? value : null), errors);
|
||||
}
|
||||
@@ -97,13 +97,13 @@ final class DefaultClientGraphQlResponse extends MapGraphQlResponse implements C
|
||||
|
||||
private final List<Object> parsedPath;
|
||||
|
||||
private final List<GraphQLError> errors;
|
||||
private final List<GraphQlResponseError> errors;
|
||||
|
||||
@Nullable
|
||||
private final Object value;
|
||||
|
||||
public DefaultField(
|
||||
String path, List<Object> parsedPath, @Nullable Object value, List<GraphQLError> errors) {
|
||||
String path, List<Object> parsedPath, @Nullable Object value, List<GraphQlResponseError> errors) {
|
||||
|
||||
this.path = path;
|
||||
this.parsedPath = parsedPath;
|
||||
@@ -128,8 +128,9 @@ final class DefaultClientGraphQlResponse extends MapGraphQlResponse implements C
|
||||
}
|
||||
|
||||
@Override
|
||||
public GraphQLError getError() {
|
||||
for (GraphQLError error : this.errors) {
|
||||
public GraphQlResponseError getError() {
|
||||
for (GraphQlResponseError error : this.errors) {
|
||||
Assert.notNull(error.getPath(), "Expected field error");
|
||||
if (error.getPath().size() <= this.parsedPath.size()) {
|
||||
return error;
|
||||
}
|
||||
@@ -138,7 +139,7 @@ final class DefaultClientGraphQlResponse extends MapGraphQlResponse implements C
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GraphQLError> getErrors() {
|
||||
public List<GraphQlResponseError> getErrors() {
|
||||
return this.errors;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,137 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
* 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.client;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import graphql.ErrorClassification;
|
||||
import graphql.GraphQLError;
|
||||
import graphql.GraphqlErrorHelper;
|
||||
import graphql.language.SourceLocation;
|
||||
|
||||
import org.springframework.graphql.execution.ErrorType;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link GraphQLError} that wraps a deserialized the GraphQL response map.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
final class MapGraphQlError implements GraphQLError {
|
||||
|
||||
private final Map<String, Object> errorMap;
|
||||
|
||||
private final List<SourceLocation> locations;
|
||||
|
||||
|
||||
MapGraphQlError(Map<String, Object> errorMap) {
|
||||
Assert.notNull(errorMap, "'errorMap' is required");
|
||||
this.errorMap = errorMap;
|
||||
this.locations = initLocations(errorMap);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static List<SourceLocation> initLocations(Map<String, Object> errorMap) {
|
||||
List<Map<String, Object>> locations = (List<Map<String, Object>>) errorMap.get("locations");
|
||||
if (locations == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return locations.stream()
|
||||
.map(map -> new SourceLocation(
|
||||
(int) map.getOrDefault("line", 0),
|
||||
(int) map.getOrDefault("column", 0),
|
||||
(String) map.get("sourceName")))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String getMessage() {
|
||||
return (String) errorMap.get("message");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SourceLocation> getLocations() {
|
||||
return this.locations;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public ErrorClassification getErrorType() {
|
||||
// Attempt the reverse of how errorType is serialized in GraphqlErrorHelper.toSpecification.
|
||||
// However, we can only do that for ErrorClassification enums that we know of.
|
||||
String value = (getExtensions() != null ? (String) getExtensions().get("classification") : null);
|
||||
if (value != null) {
|
||||
try {
|
||||
return graphql.ErrorType.valueOf(value);
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
// ignore
|
||||
}
|
||||
try {
|
||||
return ErrorType.valueOf(value);
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
@Nullable
|
||||
public List<Object> getPath() {
|
||||
return (List<Object>) this.errorMap.get("path");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
@Nullable
|
||||
public Map<String, Object> getExtensions() {
|
||||
return (Map<String, Object>) this.errorMap.get("extensions");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> toSpecification() {
|
||||
return GraphqlErrorHelper.toSpecification(this);
|
||||
}
|
||||
|
||||
@SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
return GraphqlErrorHelper.equals(this, other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return GraphqlErrorHelper.hashCode(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toSpecification().toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,13 +20,19 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import graphql.ErrorClassification;
|
||||
import graphql.GraphQLError;
|
||||
import graphql.language.SourceLocation;
|
||||
|
||||
import org.springframework.graphql.GraphQlResponse;
|
||||
import org.springframework.graphql.GraphQlResponseError;
|
||||
import org.springframework.graphql.execution.ErrorType;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -45,7 +51,7 @@ class MapGraphQlResponse implements GraphQlResponse {
|
||||
|
||||
private final Map<String, Object> responseMap;
|
||||
|
||||
private final List<GraphQLError> errors;
|
||||
private final List<GraphQlResponseError> errors;
|
||||
|
||||
|
||||
MapGraphQlResponse(Map<String, Object> responseMap) {
|
||||
@@ -54,17 +60,17 @@ class MapGraphQlResponse implements GraphQlResponse {
|
||||
this.errors = wrapErrors(responseMap);
|
||||
}
|
||||
|
||||
MapGraphQlResponse(GraphQlResponse response) {
|
||||
Assert.notNull(response, "'GraphQlResponse' is required");
|
||||
this.responseMap = response.toMap();
|
||||
this.errors = response.getErrors();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static List<GraphQLError> wrapErrors(Map<String, Object> responseMap) {
|
||||
List<Map<String, Object>> rawErrors = (List<Map<String, Object>>) responseMap.get("errors");
|
||||
if (CollectionUtils.isEmpty(rawErrors)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<GraphQLError> errors = new ArrayList<>(rawErrors.size());
|
||||
for (Map<String, Object> map : rawErrors) {
|
||||
errors.add(new MapGraphQlError(map));
|
||||
}
|
||||
return errors;
|
||||
private static List<GraphQlResponseError> wrapErrors(Map<String, Object> map) {
|
||||
List<Map<String, Object>> errors = (List<Map<String, Object>>) map.get("errors");
|
||||
errors = (errors != null ? errors : Collections.emptyList());
|
||||
return errors.stream().map(MapError::new).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +80,7 @@ class MapGraphQlResponse implements GraphQlResponse {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GraphQLError> getErrors() {
|
||||
public List<GraphQlResponseError> getErrors() {
|
||||
return this.errors;
|
||||
}
|
||||
|
||||
@@ -177,12 +183,12 @@ class MapGraphQlResponse implements GraphQlResponse {
|
||||
* @param fieldPath the field path to match
|
||||
* @return errors whose path starts with the dataPath
|
||||
*/
|
||||
protected List<GraphQLError> getFieldErrors(List<Object> fieldPath) {
|
||||
protected List<GraphQlResponseError> getFieldErrors(List<Object> fieldPath) {
|
||||
if (fieldPath.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<GraphQLError> fieldErrors = Collections.emptyList();
|
||||
for (GraphQLError error : this.errors) {
|
||||
List<GraphQlResponseError> fieldErrors = Collections.emptyList();
|
||||
for (GraphQlResponseError error : this.errors) {
|
||||
List<Object> errorPath = error.getPath();
|
||||
if (CollectionUtils.isEmpty(errorPath)) {
|
||||
continue;
|
||||
@@ -217,4 +223,115 @@ class MapGraphQlResponse implements GraphQlResponse {
|
||||
return this.responseMap.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@link GraphQLError} that wraps a deserialized the GraphQL response map.
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
private static final class MapError implements GraphQlResponseError {
|
||||
|
||||
private final Map<String, Object> errorMap;
|
||||
|
||||
private final List<SourceLocation> locations;
|
||||
|
||||
MapError(Map<String, Object> errorMap) {
|
||||
Assert.notNull(errorMap, "'errorMap' is required");
|
||||
this.errorMap = errorMap;
|
||||
this.locations = initLocations(errorMap);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static List<SourceLocation> initLocations(Map<String, Object> errorMap) {
|
||||
List<Map<String, Object>> locations = (List<Map<String, Object>>) errorMap.get("locations");
|
||||
if (locations == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return locations.stream()
|
||||
.map(map -> new SourceLocation(
|
||||
(int) map.getOrDefault("line", 0),
|
||||
(int) map.getOrDefault("column", 0),
|
||||
(String) map.get("sourceName")))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String getMessage() {
|
||||
return (String) errorMap.get("message");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SourceLocation> getLocations() {
|
||||
return this.locations;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public ErrorClassification getErrorType() {
|
||||
// Attempt the reverse of how errorType is serialized in GraphqlErrorHelper.toSpecification.
|
||||
// However, we can only do that for ErrorClassification enums that we know of.
|
||||
String value = (getExtensions() != null ? (String) getExtensions().get("classification") : null);
|
||||
if (value != null) {
|
||||
try {
|
||||
return graphql.ErrorType.valueOf(value);
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
// ignore
|
||||
}
|
||||
try {
|
||||
return ErrorType.valueOf(value);
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
@Nullable
|
||||
public List<Object> getPath() {
|
||||
return (List<Object>) this.errorMap.get("path");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
@Nullable
|
||||
public Map<String, Object> getExtensions() {
|
||||
return (Map<String, Object>) this.errorMap.get("extensions");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || this.getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
GraphQlResponseError other = (GraphQlResponseError) o;
|
||||
return (ObjectUtils.nullSafeEquals(getMessage(), other.getMessage()) &&
|
||||
ObjectUtils.nullSafeEquals(getLocations(), other.getLocations()) &&
|
||||
ObjectUtils.nullSafeEquals(getPath(), other.getPath()) &&
|
||||
getErrorType() == other.getErrorType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = 1;
|
||||
result = 31 * result + ObjectUtils.nullSafeHashCode(getMessage());
|
||||
result = 31 * result + ObjectUtils.nullSafeHashCode(getLocations());
|
||||
result = 31 * result + ObjectUtils.nullSafeHashCode(getPath());
|
||||
result = 31 * result + ObjectUtils.nullSafeHashCode(getErrorType());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.errorMap.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,9 +19,8 @@ package org.springframework.graphql.client;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import graphql.GraphQLError;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.graphql.GraphQlResponseError;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
@@ -72,7 +71,7 @@ public interface ResponseField {
|
||||
* error with the same path as the field path
|
||||
*/
|
||||
@Nullable
|
||||
GraphQLError getError();
|
||||
GraphQlResponseError getError();
|
||||
|
||||
/**
|
||||
* Return all field errors including errors above, at, and below this field.
|
||||
@@ -80,7 +79,7 @@ public interface ResponseField {
|
||||
* below. When the field does not have a value, there is only one error, and
|
||||
* it is the same as {@link #getError()}.
|
||||
*/
|
||||
List<GraphQLError> getErrors();
|
||||
List<GraphQlResponseError> getErrors();
|
||||
|
||||
/**
|
||||
* Decode the field to an entity of the given type.
|
||||
|
||||
@@ -18,9 +18,8 @@ package org.springframework.graphql.client;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import graphql.GraphQLError;
|
||||
|
||||
import org.springframework.graphql.GraphQlRequest;
|
||||
import org.springframework.graphql.GraphQlResponseError;
|
||||
|
||||
/**
|
||||
* WebSocket {@link GraphQlTransportException} raised when a subscription
|
||||
@@ -33,14 +32,14 @@ import org.springframework.graphql.GraphQlRequest;
|
||||
@SuppressWarnings("serial")
|
||||
public class SubscriptionErrorException extends GraphQlTransportException {
|
||||
|
||||
private final List<GraphQLError> errors;
|
||||
private final List<GraphQlResponseError> errors;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor with the request details and the errors listed in the payload
|
||||
* of the {@code "errors"} message.
|
||||
*/
|
||||
public SubscriptionErrorException(GraphQlRequest request, List<GraphQLError> errors) {
|
||||
public SubscriptionErrorException(GraphQlRequest request, List<GraphQlResponseError> errors) {
|
||||
super("GraphQL subscription completed with an \"error\" message, " +
|
||||
"with the following errors: " + errors, null, request);
|
||||
this.errors = errors;
|
||||
@@ -50,7 +49,7 @@ public class SubscriptionErrorException extends GraphQlTransportException {
|
||||
/**
|
||||
* Return the errors contained in the GraphQL over WebSocket "errors" message.
|
||||
*/
|
||||
public List<GraphQLError> getErrors() {
|
||||
public List<GraphQlResponseError> getErrors() {
|
||||
return this.errors;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import graphql.GraphQLError;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import reactor.core.Scannable;
|
||||
@@ -34,6 +33,7 @@ import reactor.core.publisher.Sinks;
|
||||
|
||||
import org.springframework.graphql.GraphQlRequest;
|
||||
import org.springframework.graphql.GraphQlResponse;
|
||||
import org.springframework.graphql.GraphQlResponseError;
|
||||
import org.springframework.graphql.web.support.GraphQlMessage;
|
||||
import org.springframework.graphql.web.support.GraphQlMessageType;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
@@ -514,8 +514,8 @@ final class WebSocketGraphQlTransport implements GraphQlTransport {
|
||||
emitResult = responseState.sink().tryEmitValue(response);
|
||||
}
|
||||
else {
|
||||
List<GraphQLError> graphQLErrors = response.getErrors();
|
||||
Exception ex = new SubscriptionErrorException(subscriptionState.request(), graphQLErrors);
|
||||
List<GraphQlResponseError> errors = response.getErrors();
|
||||
Exception ex = new SubscriptionErrorException(subscriptionState.request(), errors);
|
||||
emitResult = subscriptionState.sink().tryEmitError(ex);
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ import graphql.execution.ResultPath;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import org.springframework.graphql.GraphQlResponseError;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -140,9 +141,12 @@ public class MapGraphQlResponseTests {
|
||||
.map(GraphQLError::toSpecification).collect(Collectors.toList());
|
||||
|
||||
MapGraphQlResponse response = new MapGraphQlResponse(Collections.singletonMap("errors", errorList));
|
||||
List<GraphQLError> errors = response.getFieldErrors(path);
|
||||
List<GraphQlResponseError> errors = response.getFieldErrors(path);
|
||||
|
||||
assertThat(errors).containsExactly(error1, error2, error3);
|
||||
assertThat(errors).hasSize(3);
|
||||
assertThat(errors.get(0).getPath()).containsExactly("me");
|
||||
assertThat(errors.get(1).getPath()).containsExactly("me", "friends");
|
||||
assertThat(errors.get(2).getPath()).containsExactly("me", "friends", 0, "name");
|
||||
}
|
||||
|
||||
private GraphQLError createError(@Nullable String errorPath, String message) {
|
||||
@@ -150,8 +154,7 @@ public class MapGraphQlResponseTests {
|
||||
if (errorPath != null) {
|
||||
builder = builder.path(ResultPath.parse(errorPath));
|
||||
}
|
||||
Map<String, Object> errorMap = builder.build().toSpecification();
|
||||
return new MapGraphQlError(errorMap);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import graphql.GraphQLError;
|
||||
import graphql.GraphqlErrorBuilder;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
@@ -34,6 +33,7 @@ import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.graphql.GraphQlRequest;
|
||||
import org.springframework.graphql.GraphQlResponse;
|
||||
import org.springframework.graphql.GraphQlResponseError;
|
||||
import org.springframework.graphql.web.TestWebSocketClient;
|
||||
import org.springframework.graphql.web.TestWebSocketConnection;
|
||||
import org.springframework.graphql.web.support.GraphQlMessage;
|
||||
@@ -110,7 +110,7 @@ public class MockWebSocketGraphQlTransportTests {
|
||||
StepVerifier.create(this.transport.execute(request))
|
||||
.consumeNextWith(result -> {
|
||||
assertThat(result.isValid()).isFalse();
|
||||
assertThat(result.getErrors()).extracting(GraphQLError::getMessage).containsExactly("boo");
|
||||
assertThat(result.getErrors()).extracting(GraphQlResponseError::getMessage).containsExactly("boo");
|
||||
})
|
||||
.expectComplete()
|
||||
.verify(TIMEOUT);
|
||||
@@ -128,8 +128,8 @@ public class MockWebSocketGraphQlTransportTests {
|
||||
StepVerifier.create(this.transport.executeSubscription(request))
|
||||
.expectNext(this.response1)
|
||||
.expectErrorSatisfies(actualEx -> {
|
||||
List<GraphQLError> errorList = ((SubscriptionErrorException) actualEx).getErrors();
|
||||
assertThat(errorList).extracting(GraphQLError::getMessage).containsExactly("boo");
|
||||
List<GraphQlResponseError> errors = ((SubscriptionErrorException) actualEx).getErrors();
|
||||
assertThat(errors).extracting(GraphQlResponseError::getMessage).containsExactly("boo");
|
||||
})
|
||||
.verify(TIMEOUT);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user