diff --git a/dependencies/pom.xml b/dependencies/pom.xml index 4508463f025..53548034d1a 100644 --- a/dependencies/pom.xml +++ b/dependencies/pom.xml @@ -58,8 +58,8 @@ 2.3.3 3.21.7 22.3.0 - 18.6 - 18.3 + 22.1 + 22.0 2.9.0 1.60.0 32.0.1-jre diff --git a/graphql/server/src/main/java/io/helidon/graphql/server/InvocationHandlerImpl.java b/graphql/server/src/main/java/io/helidon/graphql/server/InvocationHandlerImpl.java index ba8a81b37ec..52901a9d1dc 100644 --- a/graphql/server/src/main/java/io/helidon/graphql/server/InvocationHandlerImpl.java +++ b/graphql/server/src/main/java/io/helidon/graphql/server/InvocationHandlerImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2024 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package io.helidon.graphql.server; +import java.lang.System.Logger.Level; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -25,8 +26,8 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.logging.Level; -import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import graphql.ExceptionWhileDataFetching; import graphql.ExecutionInput; @@ -49,7 +50,12 @@ import static io.helidon.graphql.server.GraphQlConstants.PATH; class InvocationHandlerImpl implements InvocationHandler { - private static final Logger LOGGER = Logger.getLogger(InvocationHandlerImpl.class.getName()); + private static final System.Logger LOGGER = System.getLogger(InvocationHandlerImpl.class.getName()); + private static final Pattern VALIDATION_ERROR_PATTERN = Pattern.compile("Validation error \\((\\w+)@\\[(.*)]\\) : (.*$)"); + private static final Pattern ERROR_ENUM_REPLACE_PATTERN = + Pattern.compile("Literal value not in allowable values for enum '.*?' - ('.*\\{.*}')"); + private static final Pattern INVALID_TYPE_REPLACE_PATTERN = + Pattern.compile("- Expected an AST type of ('.*?') but it was a ('.*?') (@.*?)$"); private final String defaultErrorMessage; private final Set exceptionDenySet = new HashSet<>(); @@ -76,7 +82,7 @@ public Map execute(String query, String operationName, Map result = new HashMap<>(); addError(result, e, e.getMessage()); return result; @@ -202,7 +208,71 @@ private void addError(Map resultMap, path = listPath.get(0).toString(); } - addErrorPayload(resultMap, error.getMessage(), path, line, column, error.getExtensions()); + addErrorPayload(resultMap, fixMessage(error.getMessage()), path, line, column, error.getExtensions()); + } + + // this works around https://github.com/eclipse/microprofile-graphql/issues/520 + // once the TCK is fixed, we can remove this work-around + // this is depending on version of GraphQL (heavily), and any change in error message format + // will result in wrong responses + private String fixMessage(String message) { + if (!message.startsWith("Validation error")) { + return message; + } + /* + What we get (first) and what we want (second) + Validation error (FieldUndefined@[allHeroes/weaknesses]) : Field 'weaknesses' in type 'SuperHero' is undefined + Validation error of type FieldUndefined: Field 'weaknesses' in type 'SuperHero' is undefined @ 'allHeroes/weaknesses' + */ + /* + 1: type of error (FieldUndefined) + 2: path (allHeroes/weaknesses) + 3: Message (Field 'weaknesses' in type 'SuperHero' is undefined) + */ + Matcher matcher = VALIDATION_ERROR_PATTERN.matcher(message); + if (matcher.matches()) { + String fixedMessage = "Validation error of type " + matcher.group(1) + + ": " + matcher.group(3) + + " @ '" + matcher.group(2) + "'"; + + if (fixedMessage.contains("Literal value not in allowable values for enum")) { + /* + What we get (first) and what we want (second) + Validation error (WrongType@[createNewHero]) : argument 'hero.tshirtSize' with value 'EnumValue{name='XLTall'}' + is not a valid 'ShirtSize' - Literal value not in allowable values for enum 'ShirtSize' + - 'EnumValue{name='XLTall'}' + Validation error of type WrongType: argument 'hero.tshirtSize' with value 'EnumValue{name='XLTall'}' is not a valid + 'ShirtSize' - Expected enum literal value not in allowable values + - 'EnumValue{name='XLTall'}'. @ 'createNewHero' + */ + // enum failures + return ERROR_ENUM_REPLACE_PATTERN.matcher(fixedMessage) + .replaceAll("Expected enum literal value not in allowable values - $1."); + } + + /* + actual graphql message + fixed graphql message + expected graphql message + Validation error (WrongType@[updateItemPowerLevel]) : argument 'powerLevel' with value + 'StringValue{value='Unlimited'}' is not a valid 'Int' - + Expected an AST type of 'IntValue' but it was a 'StringValue' + Validation error of type WrongType: argument 'powerLevel' with value 'StringValue{value='Unlimited'}' + is not a valid 'Int' - Expected an AST type of 'IntValue' but it + was a 'StringValue' @ 'updateItemPowerLevel' + Validation error of type WrongType: argument 'powerLevel' with value 'StringValue{value='Unlimited'}' + is not a valid 'Int' - Expected AST type 'IntValue' but + was 'StringValue'. @ 'updateItemPowerLevel + */ + if (fixedMessage.contains("type WrongType")) { + return INVALID_TYPE_REPLACE_PATTERN.matcher(fixedMessage) + .replaceAll("- Expected AST type $1 but was $2. $3"); + } + + return fixedMessage; + } else { + return message; + } } @SuppressWarnings("unchecked") diff --git a/microprofile/graphql/server/src/main/java/io/helidon/microprofile/graphql/server/DataFetcherUtils.java b/microprofile/graphql/server/src/main/java/io/helidon/microprofile/graphql/server/DataFetcherUtils.java index 5403faaa3d0..c9b1c07c58e 100644 --- a/microprofile/graphql/server/src/main/java/io/helidon/microprofile/graphql/server/DataFetcherUtils.java +++ b/microprofile/graphql/server/src/main/java/io/helidon/microprofile/graphql/server/DataFetcherUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2024 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,6 +41,7 @@ import java.util.Set; import java.util.TreeSet; import java.util.UUID; +import java.util.function.Supplier; import java.util.logging.Logger; import io.helidon.graphql.server.ExecutionContext; @@ -48,6 +49,7 @@ import graphql.GraphQLException; import graphql.schema.DataFetcher; import graphql.schema.DataFetchingEnvironment; +import graphql.schema.GraphQLFieldDefinition; import graphql.schema.PropertyDataFetcher; import graphql.schema.PropertyDataFetcherHelper; import jakarta.enterprise.inject.spi.CDI; @@ -192,7 +194,7 @@ static DataFetcher> newMapValuesDataFetcher(String property // retrieve the map and return the collection of V Map map = (Map) PropertyDataFetcherHelper - .getPropertyValue(propertyName, source, environment.getFieldType(), environment); + .getPropertyValue(propertyName, source, environment.getFieldType(), () -> environment); return map.values(); }; } @@ -452,7 +454,7 @@ protected static Object parseArgumentValue(Class originalType, String argumen * An implementation of a {@link PropertyDataFetcher} which returns a formatted number. */ @SuppressWarnings("rawtypes") - static class NumberFormattingDataFetcher extends PropertyDataFetcher { + static class NumberFormattingDataFetcher extends PropertyDataFetcher { /** * {@link NumberFormat} to format with. @@ -482,6 +484,12 @@ static class NumberFormattingDataFetcher extends PropertyDataFetcher { isScalar = SchemaGeneratorHelper.getScalar(type) != null; } + @Override + public Object get(GraphQLFieldDefinition fieldDefinition, Object source, + Supplier environmentSupplier) throws Exception { + return formatNumber(super.get(environmentSupplier.get()), isScalar, numberFormat); + } + @Override public Object get(DataFetchingEnvironment environment) { return formatNumber(super.get(environment), isScalar, numberFormat); @@ -492,7 +500,7 @@ public Object get(DataFetchingEnvironment environment) { * An implementation of a {@link PropertyDataFetcher} which returns a formatted date. */ @SuppressWarnings({ "rawtypes" }) - static class DateFormattingDataFetcher extends PropertyDataFetcher { + static class DateFormattingDataFetcher extends PropertyDataFetcher { /** * {@link DateTimeFormatter} to format with. @@ -519,11 +527,14 @@ static class DateFormattingDataFetcher extends PropertyDataFetcher { // must be java.util.Date simpleDateFormat = new SimpleDateFormat(valueFormat); } - System.err.println("DateFormattingDataFetcher: propertyName=" + propertyName - + ", type=" + type - + ", valueFormat=" + valueFormat - + ", dateTimeFormatter=" + dateTimeFormatter - + ", simpleDateFormat=" + simpleDateFormat); + } + + @Override + public Object get(GraphQLFieldDefinition fieldDefinition, Object source, + Supplier environmentSupplier) throws Exception { + return dateTimeFormatter != null + ? formatDate(super.get(environmentSupplier.get()), dateTimeFormatter) + : formatDate(super.get(environmentSupplier.get()), simpleDateFormat); } @Override diff --git a/microprofile/tests/tck/tck-graphql/src/test/resources/arquillian.xml b/microprofile/tests/tck/tck-graphql/src/test/resources/arquillian.xml index 00570795b3a..8371d1d27d1 100644 --- a/microprofile/tests/tck/tck-graphql/src/test/resources/arquillian.xml +++ b/microprofile/tests/tck/tck-graphql/src/test/resources/arquillian.xml @@ -1,7 +1,7 @@