From fd7eebc0d97dc201fac11ba4af424071f5e49133 Mon Sep 17 00:00:00 2001 From: Phillip Kruger Date: Tue, 30 Jun 2026 11:05:14 +1000 Subject: [PATCH 1/2] Re-enable TCK Tests and implement spec features Bump microprofile-graphql to 2.1-SNAPSHOT and re-enable the TCK tests that were previously skipped, removing all override files that are no longer needed. Implement two features added to the spec: - @Source.value() support for custom field names (preferred over the deprecated @Source.name()) - JSON default values rendered as AST literals in the schema via a new DefaultValueAstHelper Migrate @Subscription from the SmallRye-specific annotation to org.eclipse.microprofile.graphql.Subscription with backward compatibility for the old annotation across server and client. Add Flow.Publisher support alongside org.reactivestreams.Publisher for subscription methods. Also fix the micrometer property name typo and remove stale commented-out client dependency block from the parent pom. Signed-off-by: Phillip Kruger --- .gitignore | 1 + .../typesafe/reflection/MethodInvocation.java | 19 ++- .../client/modelbuilder/Annotations.java | 1 + .../modelbuilder/helper/OperationModel.java | 16 +- .../smallrye/graphql/schema/Annotations.java | 1 + .../io/smallrye/graphql/schema/Classes.java | 4 +- .../graphql/schema/SchemaBuilder.java | 4 +- .../schema/creator/OperationCreator.java | 60 ++++++- pom.xml | 24 +-- .../io/smallrye/graphql/api/Subscription.java | 4 + .../smallrye/graphql/bootstrap/Bootstrap.java | 41 ++++- .../graphql/bootstrap/DataFetcherFactory.java | 10 ++ .../bootstrap/DefaultValueAstHelper.java | 105 ++++++++++++ .../datafetcher/PublisherDataFetcher.java | 24 ++- .../bootstrap/DefaultValueAstHelperTest.java | 160 ++++++++++++++++++ .../graphql/schema/DirectivesTestApi.java | 2 +- .../graphql/schema/SchemaTestBase.java | 4 +- .../rolesallowedschemas/RolesSchema1.java | 2 +- .../rolesallowedschemas/RolesSchema2.java | 2 +- .../rolesallowedschemas/RolesSchema3.java | 2 +- .../DynamicClientSubscriptionApi.java | 2 +- .../subscription/SubscriptionApi.java | 2 +- .../subscription/SubscriptionClientApi.java | 3 +- .../metrics/MicrometerMetricsTestCase.java | 2 +- .../SubscriptionFieldBatchingTest.java | 1 + ...ToBeCancelledOnWebSocketCloseTestCase.java | 2 +- server/tck/pom.xml | 22 ++- .../SmallRyeGraphQLArchiveProcessor.java | 4 +- .../test/apps/subscription/api/StocksApi.java | 9 +- .../resources/overrides/basicScalarTests.csv | 3 - .../createNewNullNamedHero/output2.json | 13 -- .../createNewUnnamedHero/output2.json | 13 -- .../invalidDataTypeValue/output3.json | 13 -- .../overrides/invalidEnumValue/output2.json | 13 -- .../output3.json | 16 -- .../output3.json | 16 -- .../invalidLocalDateTimeValue/output3.json | 16 -- .../invalidLocalDateValue/output3.json | 16 -- .../output3.json | 16 -- .../invalidLocalTimeValue/output3.json | 16 -- .../test/resources/overrides/schemaTests.csv | 2 - .../overrides/unknownField/output2.json | 13 -- .../overrides/unknownMutation/output2.json | 13 -- .../overrides/unknownQuery/output2.json | 13 -- .../resources/tests/subscriptionTests.csv | 5 +- 45 files changed, 464 insertions(+), 266 deletions(-) create mode 100644 server/implementation/src/main/java/io/smallrye/graphql/bootstrap/DefaultValueAstHelper.java create mode 100644 server/implementation/src/test/java/io/smallrye/graphql/bootstrap/DefaultValueAstHelperTest.java delete mode 100644 server/tck/src/test/resources/overrides/basicScalarTests.csv delete mode 100644 server/tck/src/test/resources/overrides/createNewNullNamedHero/output2.json delete mode 100644 server/tck/src/test/resources/overrides/createNewUnnamedHero/output2.json delete mode 100644 server/tck/src/test/resources/overrides/invalidDataTypeValue/output3.json delete mode 100644 server/tck/src/test/resources/overrides/invalidEnumValue/output2.json delete mode 100644 server/tck/src/test/resources/overrides/invalidLocalDateFormattedValue/output3.json delete mode 100644 server/tck/src/test/resources/overrides/invalidLocalDateTimeFormattedValue/output3.json delete mode 100644 server/tck/src/test/resources/overrides/invalidLocalDateTimeValue/output3.json delete mode 100644 server/tck/src/test/resources/overrides/invalidLocalDateValue/output3.json delete mode 100644 server/tck/src/test/resources/overrides/invalidLocalTimeFormattedValue/output3.json delete mode 100644 server/tck/src/test/resources/overrides/invalidLocalTimeValue/output3.json delete mode 100644 server/tck/src/test/resources/overrides/schemaTests.csv delete mode 100644 server/tck/src/test/resources/overrides/unknownField/output2.json delete mode 100644 server/tck/src/test/resources/overrides/unknownMutation/output2.json delete mode 100644 server/tck/src/test/resources/overrides/unknownQuery/output2.json diff --git a/.gitignore b/.gitignore index 49dee7762..5403f0327 100644 --- a/.gitignore +++ b/.gitignore @@ -81,6 +81,7 @@ client/shade/dependency-reduced-pom.xml # mkdocs output site + # agents .claude .playwright-mcp diff --git a/client/implementation/src/main/java/io/smallrye/graphql/client/impl/typesafe/reflection/MethodInvocation.java b/client/implementation/src/main/java/io/smallrye/graphql/client/impl/typesafe/reflection/MethodInvocation.java index 5f44dc2a4..b4419ee70 100644 --- a/client/implementation/src/main/java/io/smallrye/graphql/client/impl/typesafe/reflection/MethodInvocation.java +++ b/client/implementation/src/main/java/io/smallrye/graphql/client/impl/typesafe/reflection/MethodInvocation.java @@ -25,7 +25,6 @@ import org.eclipse.microprofile.graphql.Query; import io.smallrye.graphql.api.Namespace; -import io.smallrye.graphql.api.Subscription; import io.smallrye.graphql.client.core.OperationType; import io.smallrye.graphql.client.model.MethodKey; import io.smallrye.graphql.client.typesafe.api.Multiple; @@ -67,7 +66,8 @@ public OperationType getOperationType() { if (method.isAnnotationPresent(Mutation.class)) { return OperationType.MUTATION; } - if (method.isAnnotationPresent(Subscription.class)) { + if (method.isAnnotationPresent(org.eclipse.microprofile.graphql.Subscription.class) + || method.isAnnotationPresent(io.smallrye.graphql.api.Subscription.class)) { return OperationType.SUBSCRIPTION; } return OperationType.QUERY; @@ -99,9 +99,18 @@ private Optional mutationName() { } private Optional subscriptionName() { - Subscription annotation = method.getAnnotation(Subscription.class); - if (annotation != null && !annotation.value().isEmpty()) - return Optional.of(annotation.value()); + // Check MicroProfile annotation first (preferred) + org.eclipse.microprofile.graphql.Subscription mpAnnotation = method + .getAnnotation(org.eclipse.microprofile.graphql.Subscription.class); + if (mpAnnotation != null && !mpAnnotation.value().isEmpty()) + return Optional.of(mpAnnotation.value()); + + // Fall back to deprecated SmallRye annotation + io.smallrye.graphql.api.Subscription srAnnotation = method + .getAnnotation(io.smallrye.graphql.api.Subscription.class); + if (srAnnotation != null && !srAnnotation.value().isEmpty()) + return Optional.of(srAnnotation.value()); + return Optional.empty(); } diff --git a/client/model-builder/src/main/java/io/smallrye/graphql/client/modelbuilder/Annotations.java b/client/model-builder/src/main/java/io/smallrye/graphql/client/modelbuilder/Annotations.java index af76831e6..26262c906 100644 --- a/client/model-builder/src/main/java/io/smallrye/graphql/client/modelbuilder/Annotations.java +++ b/client/model-builder/src/main/java/io/smallrye/graphql/client/modelbuilder/Annotations.java @@ -542,6 +542,7 @@ private static Map getAnnotationsWithFilter(Type ty .createSimple("io.smallrye.graphql.client.typesafe.api.GraphQLClientApi"); public static final DotName QUERY = DotName.createSimple("org.eclipse.microprofile.graphql.Query"); public static final DotName MUTATION = DotName.createSimple("org.eclipse.microprofile.graphql.Mutation"); + public static final DotName SUBSCRIPTION = DotName.createSimple("org.eclipse.microprofile.graphql.Subscription"); public static final DotName INPUT = DotName.createSimple("org.eclipse.microprofile.graphql.Input"); public static final DotName TYPE = DotName.createSimple("org.eclipse.microprofile.graphql.Type"); public static final DotName INTERFACE = DotName.createSimple("org.eclipse.microprofile.graphql.Interface"); diff --git a/client/model-builder/src/main/java/io/smallrye/graphql/client/modelbuilder/helper/OperationModel.java b/client/model-builder/src/main/java/io/smallrye/graphql/client/modelbuilder/helper/OperationModel.java index 94336097a..fb185b0e7 100644 --- a/client/model-builder/src/main/java/io/smallrye/graphql/client/modelbuilder/helper/OperationModel.java +++ b/client/model-builder/src/main/java/io/smallrye/graphql/client/modelbuilder/helper/OperationModel.java @@ -6,6 +6,7 @@ import static io.smallrye.graphql.client.modelbuilder.Annotations.NAMESPACE; import static io.smallrye.graphql.client.modelbuilder.Annotations.QUERY; import static io.smallrye.graphql.client.modelbuilder.Annotations.SUBCRIPTION; +import static io.smallrye.graphql.client.modelbuilder.Annotations.SUBSCRIPTION; import static io.smallrye.graphql.client.modelbuilder.ScanningContext.getIndex; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; @@ -201,7 +202,7 @@ public OperationType getOperationType() { if (method.hasAnnotation(MUTATION)) { return OperationType.MUTATION; } - if (method.hasAnnotation(SUBCRIPTION)) { + if (method.hasAnnotation(SUBSCRIPTION) || method.hasAnnotation(SUBCRIPTION)) { return OperationType.SUBSCRIPTION; } return OperationType.QUERY; @@ -236,14 +237,23 @@ public Optional mutationName() { } /** - * Gets the name of the GraphQL subscription, considering any io.smallrye.graphql.api.Subscription annotation. + * Gets the name of the GraphQL subscription, considering any {@code @Subscription} annotation + * (either {@link org.eclipse.microprofile.graphql.Subscription} or + * {@link io.smallrye.graphql.api.Subscription}). * * @return An optional containing the subscription name if specified, otherwise empty. */ public Optional subscriptionName() { - Optional subscriptionAnnotation = getMethodAnnotation(SUBCRIPTION); + // Check MicroProfile annotation first (preferred) + Optional subscriptionAnnotation = getMethodAnnotation(SUBSCRIPTION); if (subscriptionAnnotation.isPresent() && subscriptionAnnotation.orElseThrow().value() != null) return Optional.of(subscriptionAnnotation.orElseThrow().value().asString()); + + // Fall back to deprecated SmallRye annotation + subscriptionAnnotation = getMethodAnnotation(SUBCRIPTION); + if (subscriptionAnnotation.isPresent() && subscriptionAnnotation.orElseThrow().value() != null) + return Optional.of(subscriptionAnnotation.orElseThrow().value().asString()); + return Optional.empty(); } diff --git a/common/schema-builder/src/main/java/io/smallrye/graphql/schema/Annotations.java b/common/schema-builder/src/main/java/io/smallrye/graphql/schema/Annotations.java index e513951c2..481080ae5 100644 --- a/common/schema-builder/src/main/java/io/smallrye/graphql/schema/Annotations.java +++ b/common/schema-builder/src/main/java/io/smallrye/graphql/schema/Annotations.java @@ -604,6 +604,7 @@ private static Map getAnnotationsWithFilter(org.jbo public static final DotName GRAPHQL_API = DotName.createSimple("org.eclipse.microprofile.graphql.GraphQLApi"); public static final DotName QUERY = DotName.createSimple("org.eclipse.microprofile.graphql.Query"); public static final DotName MUTATION = DotName.createSimple("org.eclipse.microprofile.graphql.Mutation"); + public static final DotName SUBSCRIPTION = DotName.createSimple("org.eclipse.microprofile.graphql.Subscription"); public static final DotName INPUT = DotName.createSimple("org.eclipse.microprofile.graphql.Input"); public static final DotName TYPE = DotName.createSimple("org.eclipse.microprofile.graphql.Type"); public static final DotName INTERFACE = DotName.createSimple("org.eclipse.microprofile.graphql.Interface"); diff --git a/common/schema-builder/src/main/java/io/smallrye/graphql/schema/Classes.java b/common/schema-builder/src/main/java/io/smallrye/graphql/schema/Classes.java index f6b6e3467..7343c9785 100644 --- a/common/schema-builder/src/main/java/io/smallrye/graphql/schema/Classes.java +++ b/common/schema-builder/src/main/java/io/smallrye/graphql/schema/Classes.java @@ -156,7 +156,8 @@ private static boolean isAsyncType(Type type) { || type.name().equals(COMPLETION_STAGE) || type.name().equals(UNI) || type.name().equals(MULTI) - || type.name().equals(PUBLISHER); + || type.name().equals(PUBLISHER) + || type.name().equals(FLOW_PUBLISHER); } /** @@ -267,6 +268,7 @@ public static boolean isUnwrappedType(Type type) { private static final DotName MULTI = DotName.createSimple("io.smallrye.mutiny.Multi"); @Deprecated private static final DotName PUBLISHER = DotName.createSimple("org.reactivestreams.Publisher"); + private static final DotName FLOW_PUBLISHER = DotName.createSimple("java.util.concurrent.Flow$Publisher"); public static final DotName SERIALIZABLE = DotName.createSimple(Serializable.class.getName()); public static final DotName OBJECT = DotName.createSimple(Object.class.getName()); diff --git a/common/schema-builder/src/main/java/io/smallrye/graphql/schema/SchemaBuilder.java b/common/schema-builder/src/main/java/io/smallrye/graphql/schema/SchemaBuilder.java index c57b113c7..b2823f13a 100644 --- a/common/schema-builder/src/main/java/io/smallrye/graphql/schema/SchemaBuilder.java +++ b/common/schema-builder/src/main/java/io/smallrye/graphql/schema/SchemaBuilder.java @@ -207,7 +207,7 @@ private void validateSubscriptions(Collection graphQLApiAnno List methods = getAllMethodsIncludingFromSuperClasses(apiClass); for (MethodInfo methodInfo : methods) { Annotations annotationsForMethod = Annotations.getAnnotationsForMethod(methodInfo); - if (annotationsForMethod.containsOneOfTheseAnnotations(Annotations.SUBCRIPTION)) { + if (annotationsForMethod.containsOneOfTheseAnnotations(Annotations.SUBSCRIPTION, Annotations.SUBCRIPTION)) { errors.add("class: " + apiClass.name().toString() + ", method: " + methodInfo.name()); } } @@ -450,7 +450,7 @@ private void addOperations(Schema schema, List methodInfoList) { } else if (annotationsForMethod.containsOneOfTheseAnnotations(Annotations.MUTATION)) { Operation mutation = operationCreator.createOperation(methodInfo, OperationType.MUTATION, null); schema.addMutation(mutation); - } else if (annotationsForMethod.containsOneOfTheseAnnotations(Annotations.SUBCRIPTION)) { + } else if (annotationsForMethod.containsOneOfTheseAnnotations(Annotations.SUBSCRIPTION, Annotations.SUBCRIPTION)) { Operation subscription = operationCreator.createOperation(methodInfo, OperationType.SUBSCRIPTION, null); schema.addSubscription(subscription); } diff --git a/common/schema-builder/src/main/java/io/smallrye/graphql/schema/creator/OperationCreator.java b/common/schema-builder/src/main/java/io/smallrye/graphql/schema/creator/OperationCreator.java index 3ac0d66fe..4551bb89a 100644 --- a/common/schema-builder/src/main/java/io/smallrye/graphql/schema/creator/OperationCreator.java +++ b/common/schema-builder/src/main/java/io/smallrye/graphql/schema/creator/OperationCreator.java @@ -79,7 +79,18 @@ public Operation createOperation(MethodInfo methodInfo, OperationType operationT Type fieldType = getReturnType(methodInfo); // Name - String name = getOperationName(methodInfo, operationType, annotationsForMethod); + // For source operations, find the @Source parameter to extract custom field name + Annotations sourceParameterAnnotations = null; + if (type != null) { // This is a source operation + for (short i = 0; i < methodInfo.parametersCount(); i++) { + Annotations paramAnnotations = Annotations.getAnnotationsForArgument(methodInfo, i); + if (paramAnnotations.containsOneOfTheseAnnotations(Annotations.SOURCE)) { + sourceParameterAnnotations = paramAnnotations; + break; + } + } + } + String name = getOperationName(methodInfo, operationType, annotationsForMethod, sourceParameterAnnotations); // Field Type validateFieldType(methodInfo, operationType); @@ -273,25 +284,56 @@ private static void validateFieldType(MethodInfo methodInfo, OperationType opera /** * Get the name from annotation(s) or default. - * This is for operations (query, mutation and source) * * @param methodInfo the java method - * @param operationType the type (query, mutation) + * @param operationType the type (query, mutation, subscription) * @param annotations the annotations on this method + * @param sourceParameterAnnotations annotations for the @Source parameter (null for top-level operations) * @return the operation name */ - private static String getOperationName(MethodInfo methodInfo, OperationType operationType, Annotations annotations) { + private static String getOperationName(MethodInfo methodInfo, OperationType operationType, Annotations annotations, + Annotations sourceParameterAnnotations) { + // For source operations, check the @Source annotation's value() and name() attributes first + if (sourceParameterAnnotations != null) { + Optional sourceAnnotation = sourceParameterAnnotations + .getOneOfTheseAnnotations(Annotations.SOURCE); + if (sourceAnnotation.isPresent()) { + AnnotationInstance source = sourceAnnotation.get(); + + // Try value() first (current preferred way per MicroProfile GraphQL 2.1) + if (source.value("value") != null) { + String valueAttr = source.value("value").asString(); + if (valueAttr != null && !valueAttr.isEmpty()) { + return valueAttr; + } + } + + // Fall back to name() (deprecated but still supported) + if (source.value("name") != null) { + String nameAttr = source.value("name").asString(); + if (nameAttr != null && !nameAttr.isEmpty()) { + return nameAttr; + } + } + } + } + DotName operationAnnotation = getOperationAnnotation(operationType); - // If the @Query or @Mutation annotation has a value, use that, else use name or jsonb property - return annotations.getOneOfTheseMethodAnnotationsValue( + // If the @Query, @Mutation, or @Subscription annotation has a value, use that, else use name or jsonb property + Optional name = annotations.getOneOfTheseMethodAnnotationsValue( operationAnnotation, Annotations.NAME, Annotations.JAKARTA_JSONB_PROPERTY, Annotations.JAVAX_JSONB_PROPERTY, - Annotations.JACKSON_PROPERTY) - .orElse(getDefaultExecutionTypeName(methodInfo, operationType)); + Annotations.JACKSON_PROPERTY); + + // For subscriptions, also check the deprecated SmallRye annotation + if (name.isEmpty() && operationType == OperationType.SUBSCRIPTION) { + name = annotations.getOneOfTheseMethodAnnotationsValue(Annotations.SUBCRIPTION); + } + return name.orElse(getDefaultExecutionTypeName(methodInfo, operationType)); } private static DotName getOperationAnnotation(OperationType operationType) { @@ -301,7 +343,7 @@ private static DotName getOperationAnnotation(OperationType operationType) { case MUTATION: return Annotations.MUTATION; case SUBSCRIPTION: - return Annotations.SUBCRIPTION; + return Annotations.SUBSCRIPTION; case RESOLVER: return Annotations.RESOLVER; default: diff --git a/pom.xml b/pom.xml index 297141b97..f1c1797c7 100644 --- a/pom.xml +++ b/pom.xml @@ -17,8 +17,7 @@ 3.1.1 - 2.0 - + 2.1-SNAPSHOT 1.3 3.5.3 3.17.2 @@ -108,31 +107,12 @@ - - org.eclipse.microprofile.graphql microprofile-graphql-tck ${version.eclipse.microprofile.graphql} - - - org.eclipse.microprofile.graphql - microprofile-graphql-server-tck - ${version.eclipse.microprofile.graphql} - - + org.eclipse.microprofile.config microprofile-config-api diff --git a/server/api/src/main/java/io/smallrye/graphql/api/Subscription.java b/server/api/src/main/java/io/smallrye/graphql/api/Subscription.java index 2e2854cac..6d363f9c0 100644 --- a/server/api/src/main/java/io/smallrye/graphql/api/Subscription.java +++ b/server/api/src/main/java/io/smallrye/graphql/api/Subscription.java @@ -46,6 +46,10 @@ * stockQuote(stockCode: string): [Stock] * } * + * + * @deprecated Use {@link org.eclipse.microprofile.graphql.Subscription} instead. + * This annotation has been added to the MicroProfile GraphQL specification + * and this SmallRye-specific version will be removed in a future release. */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) diff --git a/server/implementation/src/main/java/io/smallrye/graphql/bootstrap/Bootstrap.java b/server/implementation/src/main/java/io/smallrye/graphql/bootstrap/Bootstrap.java index dd8a7423b..a6659b60e 100644 --- a/server/implementation/src/main/java/io/smallrye/graphql/bootstrap/Bootstrap.java +++ b/server/implementation/src/main/java/io/smallrye/graphql/bootstrap/Bootstrap.java @@ -347,8 +347,12 @@ private void createGraphQLDirectiveType(DirectiveType directiveType) { .type(inputType); if (argumentType.hasDefaultValue()) { - argumentBuilder = argumentBuilder.defaultValueProgrammatic( - sanitizeDefaultValue(argumentType)); + Object sanitized = sanitizeDefaultValue(argumentType); + argumentBuilder = argumentBuilder.defaultValueProgrammatic(sanitized); + if (sanitized != null) { + argumentBuilder = argumentBuilder.defaultValueLiteral( + DefaultValueAstHelper.toAstValue(sanitized)); + } } directiveBuilder = directiveBuilder.argument(argumentBuilder.build()); @@ -772,7 +776,12 @@ private GraphQLDirective createGraphQLDirectiveFrom(DirectiveInstance directiveI .valueProgrammatic(entry.getValue()); if (argumentType.hasDefaultValue()) { - argumentBuilder = argumentBuilder.defaultValueProgrammatic(sanitizeDefaultValue(argumentType)); + Object sanitized = sanitizeDefaultValue(argumentType); + argumentBuilder = argumentBuilder.defaultValueProgrammatic(sanitized); + if (sanitized != null) { + argumentBuilder = argumentBuilder.defaultValueLiteral( + DefaultValueAstHelper.toAstValue(sanitized)); + } } directiveBuilder.argument(argumentBuilder.build()); @@ -796,7 +805,12 @@ private GraphQLDirective createGraphQLDirectiveFrom(DirectiveInstance directiveI .type(inputType); if (argumentType.hasDefaultValue()) { - argumentBuilder = argumentBuilder.defaultValueProgrammatic(sanitizeDefaultValue(argumentType)); + Object sanitized = sanitizeDefaultValue(argumentType); + argumentBuilder = argumentBuilder.defaultValueProgrammatic(sanitized); + if (sanitized != null) { + argumentBuilder = argumentBuilder.defaultValueLiteral( + DefaultValueAstHelper.toAstValue(sanitized)); + } } directiveBuilder.argument(argumentBuilder.build()); @@ -984,7 +998,13 @@ private GraphQLInputObjectField createGraphQLInputObjectFieldFromField(Field fie // Default value (on method) if (field.hasDefaultValue()) { - inputFieldBuilder = inputFieldBuilder.defaultValueProgrammatic(sanitizeDefaultValue(field)); + Object sanitized = sanitizeDefaultValue(field); + inputFieldBuilder = inputFieldBuilder.defaultValueProgrammatic(sanitized); + // Also set literal value for schema printing + if (sanitized != null) { + inputFieldBuilder = inputFieldBuilder.defaultValueLiteral( + DefaultValueAstHelper.toAstValue(sanitized)); + } } return inputFieldBuilder.build(); @@ -1135,7 +1155,13 @@ private GraphQLArgument createGraphQLArgument(Argument argument) { .description(argument.getDescription()); if (argument.hasDefaultValue()) { - argumentBuilder = argumentBuilder.defaultValueProgrammatic(sanitizeDefaultValue(argument)); + Object sanitized = sanitizeDefaultValue(argument); + argumentBuilder = argumentBuilder.defaultValueProgrammatic(sanitized); + // Also set literal value for schema printing + if (sanitized != null) { + argumentBuilder = argumentBuilder.defaultValueLiteral( + DefaultValueAstHelper.toAstValue(sanitized)); + } } GraphQLInputType graphQLInputType = createGraphQLInputType(argument); @@ -1171,7 +1197,8 @@ private Object sanitizeDefaultValue(Field field) { ReferenceType referenceType = reference.getType(); if (referenceType.equals(ReferenceType.INPUT) || referenceType.equals(ReferenceType.TYPE)) { - deserType = Map.class; + // Use LinkedHashMap to preserve field order from JSON + deserType = java.util.LinkedHashMap.class; } else { deserType = classloadingService.loadClass(field.getReference().getClassName()); } diff --git a/server/implementation/src/main/java/io/smallrye/graphql/bootstrap/DataFetcherFactory.java b/server/implementation/src/main/java/io/smallrye/graphql/bootstrap/DataFetcherFactory.java index 95dcdffcf..b0be58a8c 100644 --- a/server/implementation/src/main/java/io/smallrye/graphql/bootstrap/DataFetcherFactory.java +++ b/server/implementation/src/main/java/io/smallrye/graphql/bootstrap/DataFetcherFactory.java @@ -92,6 +92,8 @@ private V get(Operation operation, Type type) { return (V) getMultiDataFetcher(operation, type); } else if (isPublisher(operation)) { return (V) getPublisherDataFetcher(operation, type); + } else if (isFlowPublisher(operation)) { + return (V) getPublisherDataFetcher(operation, type); } else if (isWrapped(operation)) { return (V) getOtherWrappedDataFetcher(operation, type); } @@ -214,6 +216,14 @@ private boolean isPublisher(Field field) { return false; } + private boolean isFlowPublisher(Field field) { + if (field.hasWrapper()) { + String wrapperClassName = field.getWrapper().getWrapperClassName(); + return wrapperClassName.equals("java.util.concurrent.Flow$Publisher"); + } + return false; + } + private boolean isMutinyMulti(Field field) { if (field.hasWrapper()) { String wrapperClassName = field.getWrapper().getWrapperClassName(); diff --git a/server/implementation/src/main/java/io/smallrye/graphql/bootstrap/DefaultValueAstHelper.java b/server/implementation/src/main/java/io/smallrye/graphql/bootstrap/DefaultValueAstHelper.java new file mode 100644 index 000000000..38714f83c --- /dev/null +++ b/server/implementation/src/main/java/io/smallrye/graphql/bootstrap/DefaultValueAstHelper.java @@ -0,0 +1,105 @@ +package io.smallrye.graphql.bootstrap; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Collection; +import java.util.Map; + +import org.jboss.logging.Logger; + +import graphql.language.ArrayValue; +import graphql.language.BooleanValue; +import graphql.language.EnumValue; +import graphql.language.FloatValue; +import graphql.language.IntValue; +import graphql.language.NullValue; +import graphql.language.ObjectField; +import graphql.language.ObjectValue; +import graphql.language.StringValue; +import graphql.language.Value; + +/** + * Converts Java objects to GraphQL AST {@link Value} nodes for schema printing. + *

+ * graphql-java's {@code defaultValueProgrammatic()} handles runtime behavior but does not + * render default values in printed SDL or introspection; {@code defaultValueLiteral()} with + * an AST Value is needed for that. This helper bridges the two representations. + */ +public class DefaultValueAstHelper { + + private static final Logger LOG = Logger.getLogger(DefaultValueAstHelper.class); + + private DefaultValueAstHelper() { + } + + public static Value toAstValue(Object value) { + if (value == null) { + return NullValue.newNullValue().build(); + } + + if (value instanceof Map) { + return toObjectValue((Map) value); + } else if (value instanceof Collection) { + return toCollectionValue((Collection) value); + } else if (value.getClass().isArray()) { + return toArrayValue(value); + } else if (value instanceof String) { + return StringValue.newStringValue((String) value).build(); + } else if (value instanceof Boolean) { + return BooleanValue.newBooleanValue((Boolean) value).build(); + } else if (value instanceof Number) { + return toNumericValue((Number) value); + } else if (value instanceof Enum) { + return EnumValue.newEnumValue(((Enum) value).name()).build(); + } + + LOG.warnf("Unrecognized default value type for schema printing: %s", value.getClass().getName()); + return StringValue.newStringValue(value.toString()).build(); + } + + private static ObjectValue toObjectValue(Map map) { + ObjectValue.Builder builder = ObjectValue.newObjectValue(); + for (Map.Entry entry : map.entrySet()) { + builder.objectField(ObjectField.newObjectField() + .name(entry.getKey().toString()) + .value(toAstValue(entry.getValue())) + .build()); + } + return builder.build(); + } + + private static ArrayValue toCollectionValue(Collection collection) { + ArrayValue.Builder builder = ArrayValue.newArrayValue(); + for (Object item : collection) { + builder.value(toAstValue(item)); + } + return builder.build(); + } + + private static ArrayValue toArrayValue(Object array) { + ArrayValue.Builder builder = ArrayValue.newArrayValue(); + int length = java.lang.reflect.Array.getLength(array); + for (int i = 0; i < length; i++) { + builder.value(toAstValue(java.lang.reflect.Array.get(array, i))); + } + return builder.build(); + } + + private static Value toNumericValue(Number number) { + if (number instanceof BigInteger) { + return IntValue.newIntValue((BigInteger) number).build(); + } + if (number instanceof BigDecimal) { + BigDecimal bd = (BigDecimal) number; + if (bd.scale() <= 0) { + return IntValue.newIntValue(bd.toBigInteger()).build(); + } + return FloatValue.newFloatValue(bd).build(); + } + if (number instanceof Integer || number instanceof Long + || number instanceof Short || number instanceof Byte) { + return IntValue.newIntValue(BigInteger.valueOf(number.longValue())).build(); + } + return FloatValue.newFloatValue(BigDecimal.valueOf(number.doubleValue())).build(); + } +} diff --git a/server/implementation/src/main/java/io/smallrye/graphql/execution/datafetcher/PublisherDataFetcher.java b/server/implementation/src/main/java/io/smallrye/graphql/execution/datafetcher/PublisherDataFetcher.java index ead33a949..2c1a5deb4 100644 --- a/server/implementation/src/main/java/io/smallrye/graphql/execution/datafetcher/PublisherDataFetcher.java +++ b/server/implementation/src/main/java/io/smallrye/graphql/execution/datafetcher/PublisherDataFetcher.java @@ -1,6 +1,7 @@ package io.smallrye.graphql.execution.datafetcher; import java.util.List; +import java.util.concurrent.Flow; import org.reactivestreams.Publisher; @@ -26,13 +27,28 @@ public PublisherDataFetcher(Operation operation, Type type) { @Override protected Multi handleUserMethodCall(DataFetchingEnvironment dfe, final Object[] transformedArguments) throws Exception { - Publisher publisher = operationInvoker.invoke(transformedArguments); - return (Multi) Multi.createFrom().publisher(AdaptersToFlow.publisher(publisher)); + return toMulti(operationInvoker.invoke(transformedArguments)); } @Override protected Multi> handleUserBatchLoad(DataFetchingEnvironment dfe, final Object[] arguments) throws Exception { - Publisher> publisher = operationInvoker.invoke(arguments); - return (Multi>) Multi.createFrom().publisher(AdaptersToFlow.publisher(publisher)); + return toMulti(operationInvoker.invoke(arguments)); + } + + @SuppressWarnings("unchecked") + private Multi toMulti(Object result) { + if (result == null) { + throw new IllegalStateException( + "Subscription method " + operation.getName() + " returned null"); + } + if (result instanceof Flow.Publisher) { + return Multi.createFrom().publisher((Flow.Publisher) result); + } + if (result instanceof Publisher) { + return (Multi) Multi.createFrom().publisher(AdaptersToFlow.publisher((Publisher) result)); + } + throw new IllegalStateException( + "Subscription method " + operation.getName() + " returned " + result.getClass().getName() + + " which is neither Flow.Publisher nor org.reactivestreams.Publisher"); } } diff --git a/server/implementation/src/test/java/io/smallrye/graphql/bootstrap/DefaultValueAstHelperTest.java b/server/implementation/src/test/java/io/smallrye/graphql/bootstrap/DefaultValueAstHelperTest.java new file mode 100644 index 000000000..a58c3fd49 --- /dev/null +++ b/server/implementation/src/test/java/io/smallrye/graphql/bootstrap/DefaultValueAstHelperTest.java @@ -0,0 +1,160 @@ +package io.smallrye.graphql.bootstrap; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import graphql.language.ArrayValue; +import graphql.language.BooleanValue; +import graphql.language.EnumValue; +import graphql.language.FloatValue; +import graphql.language.IntValue; +import graphql.language.NullValue; +import graphql.language.ObjectValue; +import graphql.language.StringValue; +import graphql.language.Value; + +class DefaultValueAstHelperTest { + + @Test + void nullProducesNullValue() { + assertInstanceOf(NullValue.class, DefaultValueAstHelper.toAstValue(null)); + } + + @Test + void stringProducesStringValue() { + Value result = DefaultValueAstHelper.toAstValue("hello"); + assertInstanceOf(StringValue.class, result); + assertEquals("hello", ((StringValue) result).getValue()); + } + + @Test + void booleanProducesBooleanValue() { + Value t = DefaultValueAstHelper.toAstValue(true); + assertInstanceOf(BooleanValue.class, t); + assertEquals(true, ((BooleanValue) t).isValue()); + + Value f = DefaultValueAstHelper.toAstValue(false); + assertEquals(false, ((BooleanValue) f).isValue()); + } + + @Test + void integerProducesIntValue() { + Value result = DefaultValueAstHelper.toAstValue(42); + assertInstanceOf(IntValue.class, result); + assertEquals(BigInteger.valueOf(42), ((IntValue) result).getValue()); + } + + @Test + void longProducesIntValue() { + Value result = DefaultValueAstHelper.toAstValue(100L); + assertInstanceOf(IntValue.class, result); + assertEquals(BigInteger.valueOf(100), ((IntValue) result).getValue()); + } + + @Test + void bigIntegerProducesIntValue() { + BigInteger large = new BigInteger("99999999999999999999"); + Value result = DefaultValueAstHelper.toAstValue(large); + assertInstanceOf(IntValue.class, result); + assertEquals(large, ((IntValue) result).getValue()); + } + + @Test + void bigDecimalWholeNumberProducesIntValue() { + BigDecimal bd = new BigDecimal("1000"); + Value result = DefaultValueAstHelper.toAstValue(bd); + assertInstanceOf(IntValue.class, result); + assertEquals(BigInteger.valueOf(1000), ((IntValue) result).getValue()); + } + + @Test + void bigDecimalFractionalProducesFloatValue() { + BigDecimal bd = new BigDecimal("3.14"); + Value result = DefaultValueAstHelper.toAstValue(bd); + assertInstanceOf(FloatValue.class, result); + assertEquals(bd, ((FloatValue) result).getValue()); + } + + @Test + void bigDecimalHighPrecisionPreserved() { + BigDecimal bd = new BigDecimal("1.00000000000000001"); + Value result = DefaultValueAstHelper.toAstValue(bd); + assertInstanceOf(FloatValue.class, result); + assertEquals(bd, ((FloatValue) result).getValue()); + } + + @Test + void doubleProducesFloatValue() { + Value result = DefaultValueAstHelper.toAstValue(2.5); + assertInstanceOf(FloatValue.class, result); + } + + @Test + void enumProducesEnumValue() { + Value result = DefaultValueAstHelper.toAstValue(Thread.State.RUNNABLE); + assertInstanceOf(EnumValue.class, result); + assertEquals("RUNNABLE", ((EnumValue) result).getName()); + } + + @Test + void listProducesArrayValue() { + Value result = DefaultValueAstHelper.toAstValue(List.of("a", "b")); + assertInstanceOf(ArrayValue.class, result); + ArrayValue arr = (ArrayValue) result; + assertEquals(2, arr.getValues().size()); + assertEquals("a", ((StringValue) arr.getValues().get(0)).getValue()); + assertEquals("b", ((StringValue) arr.getValues().get(1)).getValue()); + } + + @Test + void setProducesArrayValue() { + LinkedHashSet set = new LinkedHashSet<>(Arrays.asList("x", "y")); + Value result = DefaultValueAstHelper.toAstValue(set); + assertInstanceOf(ArrayValue.class, result); + assertEquals(2, ((ArrayValue) result).getValues().size()); + } + + @Test + void javaArrayProducesArrayValue() { + Value result = DefaultValueAstHelper.toAstValue(new int[] { 1, 2, 3 }); + assertInstanceOf(ArrayValue.class, result); + ArrayValue arr = (ArrayValue) result; + assertEquals(3, arr.getValues().size()); + assertEquals(BigInteger.valueOf(1), ((IntValue) arr.getValues().get(0)).getValue()); + } + + @Test + void mapProducesObjectValue() { + Map map = new LinkedHashMap<>(); + map.put("id", new BigDecimal("1000")); + map.put("name", "Cape"); + Value result = DefaultValueAstHelper.toAstValue(map); + assertInstanceOf(ObjectValue.class, result); + ObjectValue obj = (ObjectValue) result; + assertEquals(2, obj.getObjectFields().size()); + assertEquals("id", obj.getObjectFields().get(0).getName()); + assertInstanceOf(IntValue.class, obj.getObjectFields().get(0).getValue()); + assertEquals("name", obj.getObjectFields().get(1).getName()); + assertInstanceOf(StringValue.class, obj.getObjectFields().get(1).getValue()); + } + + @Test + void nestedMapInList() { + Map inner = new LinkedHashMap<>(); + inner.put("key", "val"); + Value result = DefaultValueAstHelper.toAstValue(List.of(inner)); + assertInstanceOf(ArrayValue.class, result); + ArrayValue arr = (ArrayValue) result; + assertInstanceOf(ObjectValue.class, arr.getValues().get(0)); + } +} diff --git a/server/implementation/src/test/java/io/smallrye/graphql/schema/DirectivesTestApi.java b/server/implementation/src/test/java/io/smallrye/graphql/schema/DirectivesTestApi.java index 2fc1c47fd..22ed0cddf 100644 --- a/server/implementation/src/test/java/io/smallrye/graphql/schema/DirectivesTestApi.java +++ b/server/implementation/src/test/java/io/smallrye/graphql/schema/DirectivesTestApi.java @@ -5,8 +5,8 @@ import org.eclipse.microprofile.graphql.GraphQLApi; import org.eclipse.microprofile.graphql.Mutation; import org.eclipse.microprofile.graphql.Query; +import org.eclipse.microprofile.graphql.Subscription; -import io.smallrye.graphql.api.Subscription; import io.smallrye.mutiny.Multi; @GraphQLApi diff --git a/server/implementation/src/test/java/io/smallrye/graphql/schema/SchemaTestBase.java b/server/implementation/src/test/java/io/smallrye/graphql/schema/SchemaTestBase.java index 7e995e108..10c895456 100644 --- a/server/implementation/src/test/java/io/smallrye/graphql/schema/SchemaTestBase.java +++ b/server/implementation/src/test/java/io/smallrye/graphql/schema/SchemaTestBase.java @@ -11,6 +11,7 @@ import org.jboss.jandex.Indexer; import org.junit.jupiter.api.AfterEach; +import graphql.language.BooleanValue; import graphql.language.StringValue; import graphql.schema.GraphQLDirective; import graphql.schema.GraphQLSchema; @@ -36,7 +37,8 @@ protected void assertKeyDirective(GraphQLDirective graphQLDirective, String fiel ((StringValue) graphQLDirective.getArguments().get(0).toAppliedArgument().getArgumentValue().getValue()) .getValue()); assertEquals(resolvableValue, graphQLDirective.getArguments().get(1).toAppliedArgument().getArgumentValue().getValue()); - assertEquals(true, graphQLDirective.getArguments().get(1).getArgumentDefaultValue().getValue()); + assertEquals(true, + ((BooleanValue) graphQLDirective.getArguments().get(1).getArgumentDefaultValue().getValue()).isValue()); } protected GraphQLSchema createGraphQLSchema(Class... api) { diff --git a/server/implementation/src/test/java/io/smallrye/graphql/schema/rolesallowedschemas/RolesSchema1.java b/server/implementation/src/test/java/io/smallrye/graphql/schema/rolesallowedschemas/RolesSchema1.java index 906c2e40f..606919a3b 100644 --- a/server/implementation/src/test/java/io/smallrye/graphql/schema/rolesallowedschemas/RolesSchema1.java +++ b/server/implementation/src/test/java/io/smallrye/graphql/schema/rolesallowedschemas/RolesSchema1.java @@ -5,8 +5,8 @@ import org.eclipse.microprofile.graphql.GraphQLApi; import org.eclipse.microprofile.graphql.Mutation; import org.eclipse.microprofile.graphql.Query; +import org.eclipse.microprofile.graphql.Subscription; -import io.smallrye.graphql.api.Subscription; import io.smallrye.mutiny.Multi; import io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor; diff --git a/server/implementation/src/test/java/io/smallrye/graphql/schema/rolesallowedschemas/RolesSchema2.java b/server/implementation/src/test/java/io/smallrye/graphql/schema/rolesallowedschemas/RolesSchema2.java index f1d3eba38..bf43b8951 100644 --- a/server/implementation/src/test/java/io/smallrye/graphql/schema/rolesallowedschemas/RolesSchema2.java +++ b/server/implementation/src/test/java/io/smallrye/graphql/schema/rolesallowedschemas/RolesSchema2.java @@ -6,8 +6,8 @@ import org.eclipse.microprofile.graphql.Mutation; import org.eclipse.microprofile.graphql.Query; import org.eclipse.microprofile.graphql.Source; +import org.eclipse.microprofile.graphql.Subscription; -import io.smallrye.graphql.api.Subscription; import io.smallrye.mutiny.Multi; import io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor; diff --git a/server/implementation/src/test/java/io/smallrye/graphql/schema/rolesallowedschemas/RolesSchema3.java b/server/implementation/src/test/java/io/smallrye/graphql/schema/rolesallowedschemas/RolesSchema3.java index 278075b1c..b118a2ee0 100644 --- a/server/implementation/src/test/java/io/smallrye/graphql/schema/rolesallowedschemas/RolesSchema3.java +++ b/server/implementation/src/test/java/io/smallrye/graphql/schema/rolesallowedschemas/RolesSchema3.java @@ -4,8 +4,8 @@ import org.eclipse.microprofile.graphql.Mutation; import org.eclipse.microprofile.graphql.Query; import org.eclipse.microprofile.graphql.Source; +import org.eclipse.microprofile.graphql.Subscription; -import io.smallrye.graphql.api.Subscription; import io.smallrye.mutiny.Multi; import io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor; diff --git a/server/integration-tests/src/test/java/io/smallrye/graphql/tests/client/dynamic/subscription/DynamicClientSubscriptionApi.java b/server/integration-tests/src/test/java/io/smallrye/graphql/tests/client/dynamic/subscription/DynamicClientSubscriptionApi.java index 91b0ea900..8eb0d1f0e 100644 --- a/server/integration-tests/src/test/java/io/smallrye/graphql/tests/client/dynamic/subscription/DynamicClientSubscriptionApi.java +++ b/server/integration-tests/src/test/java/io/smallrye/graphql/tests/client/dynamic/subscription/DynamicClientSubscriptionApi.java @@ -2,8 +2,8 @@ import org.eclipse.microprofile.graphql.GraphQLApi; import org.eclipse.microprofile.graphql.Query; +import org.eclipse.microprofile.graphql.Subscription; -import io.smallrye.graphql.api.Subscription; import io.smallrye.mutiny.Multi; @GraphQLApi diff --git a/server/integration-tests/src/test/java/io/smallrye/graphql/tests/client/typesafe/subscription/SubscriptionApi.java b/server/integration-tests/src/test/java/io/smallrye/graphql/tests/client/typesafe/subscription/SubscriptionApi.java index 17a4393b8..2f67f68ce 100644 --- a/server/integration-tests/src/test/java/io/smallrye/graphql/tests/client/typesafe/subscription/SubscriptionApi.java +++ b/server/integration-tests/src/test/java/io/smallrye/graphql/tests/client/typesafe/subscription/SubscriptionApi.java @@ -3,8 +3,8 @@ import org.eclipse.microprofile.graphql.GraphQLApi; import org.eclipse.microprofile.graphql.Query; import org.eclipse.microprofile.graphql.Source; +import org.eclipse.microprofile.graphql.Subscription; -import io.smallrye.graphql.api.Subscription; import io.smallrye.mutiny.Multi; @GraphQLApi diff --git a/server/integration-tests/src/test/java/io/smallrye/graphql/tests/client/typesafe/subscription/SubscriptionClientApi.java b/server/integration-tests/src/test/java/io/smallrye/graphql/tests/client/typesafe/subscription/SubscriptionClientApi.java index 7677aa5ca..01d4f17f6 100644 --- a/server/integration-tests/src/test/java/io/smallrye/graphql/tests/client/typesafe/subscription/SubscriptionClientApi.java +++ b/server/integration-tests/src/test/java/io/smallrye/graphql/tests/client/typesafe/subscription/SubscriptionClientApi.java @@ -2,7 +2,8 @@ import java.io.Closeable; -import io.smallrye.graphql.api.Subscription; +import org.eclipse.microprofile.graphql.Subscription; + import io.smallrye.graphql.client.typesafe.api.GraphQLClientApi; import io.smallrye.mutiny.Multi; diff --git a/server/integration-tests/src/test/java/io/smallrye/graphql/tests/metrics/MicrometerMetricsTestCase.java b/server/integration-tests/src/test/java/io/smallrye/graphql/tests/metrics/MicrometerMetricsTestCase.java index c29f9fe28..84888f1f2 100644 --- a/server/integration-tests/src/test/java/io/smallrye/graphql/tests/metrics/MicrometerMetricsTestCase.java +++ b/server/integration-tests/src/test/java/io/smallrye/graphql/tests/metrics/MicrometerMetricsTestCase.java @@ -10,6 +10,7 @@ import org.eclipse.microprofile.graphql.GraphQLApi; import org.eclipse.microprofile.graphql.Query; +import org.eclipse.microprofile.graphql.Subscription; import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.junit5.ArquillianExtension; import org.jboss.arquillian.test.api.ArquillianResource; @@ -25,7 +26,6 @@ import io.micrometer.core.instrument.Metrics; import io.micrometer.core.instrument.Tags; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; -import io.smallrye.graphql.api.Subscription; import io.smallrye.graphql.client.Response; import io.smallrye.graphql.client.dynamic.api.DynamicGraphQLClient; import io.smallrye.graphql.client.vertx.dynamic.VertxDynamicGraphQLClientBuilder; diff --git a/server/integration-tests/src/test/java/io/smallrye/graphql/tests/subscription/SubscriptionFieldBatchingTest.java b/server/integration-tests/src/test/java/io/smallrye/graphql/tests/subscription/SubscriptionFieldBatchingTest.java index f583b0dd3..2ca0f134a 100644 --- a/server/integration-tests/src/test/java/io/smallrye/graphql/tests/subscription/SubscriptionFieldBatchingTest.java +++ b/server/integration-tests/src/test/java/io/smallrye/graphql/tests/subscription/SubscriptionFieldBatchingTest.java @@ -8,6 +8,7 @@ import org.eclipse.microprofile.graphql.GraphQLApi; import org.eclipse.microprofile.graphql.Source; +import org.eclipse.microprofile.graphql.Subscription; import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.junit5.ArquillianExtension; import org.jboss.arquillian.test.api.ArquillianResource; diff --git a/server/integration-tests/src/test/java/io/smallrye/graphql/tests/subscription/SubscriptionHasToBeCancelledOnWebSocketCloseTestCase.java b/server/integration-tests/src/test/java/io/smallrye/graphql/tests/subscription/SubscriptionHasToBeCancelledOnWebSocketCloseTestCase.java index 47f6e155c..7a0a62307 100644 --- a/server/integration-tests/src/test/java/io/smallrye/graphql/tests/subscription/SubscriptionHasToBeCancelledOnWebSocketCloseTestCase.java +++ b/server/integration-tests/src/test/java/io/smallrye/graphql/tests/subscription/SubscriptionHasToBeCancelledOnWebSocketCloseTestCase.java @@ -10,6 +10,7 @@ import org.eclipse.microprofile.graphql.GraphQLApi; import org.eclipse.microprofile.graphql.Query; +import org.eclipse.microprofile.graphql.Subscription; import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.junit5.ArquillianExtension; import org.jboss.arquillian.test.api.ArquillianResource; @@ -18,7 +19,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import io.smallrye.graphql.api.Subscription; import io.smallrye.graphql.client.Response; import io.smallrye.graphql.client.dynamic.api.DynamicGraphQLClient; import io.smallrye.graphql.client.vertx.dynamic.VertxDynamicGraphQLClient; diff --git a/server/tck/pom.xml b/server/tck/pom.xml index 26b19c5d4..f53a6e5e6 100644 --- a/server/tck/pom.xml +++ b/server/tck/pom.xml @@ -109,6 +109,21 @@ compile 7.12.0 + + + + io.vertx + vertx-core + ${version.vertx} + test + + + + + org.eclipse.jetty.ee9.websocket + jetty-ee9-websocket-jakarta-server + test + @@ -118,20 +133,15 @@ maven-surefire-plugin - org.eclipse.microprofile.graphql:microprofile-graphql-server-tck - org.eclipse.microprofile.graphql:microprofile-graphql-tck + org.eclipse.microprofile.graphql:microprofile-graphql-tck testng.xml - - - true ${project.basedir}/src/test/resources/logging.properties - true diff --git a/server/tck/src/test/java/io/smallrye/graphql/SmallRyeGraphQLArchiveProcessor.java b/server/tck/src/test/java/io/smallrye/graphql/SmallRyeGraphQLArchiveProcessor.java index 4e7d14d73..54c17c1dc 100644 --- a/server/tck/src/test/java/io/smallrye/graphql/SmallRyeGraphQLArchiveProcessor.java +++ b/server/tck/src/test/java/io/smallrye/graphql/SmallRyeGraphQLArchiveProcessor.java @@ -60,11 +60,11 @@ public void process(Archive applicationArchive, TestClass testClass) { WebArchive war = (WebArchive) applicationArchive; - // Exclude the TCK beans in the deployed app. The TCK jar also has a beans.xml which causes duplicated beans + // Exclude the TCK test infrastructure but keep the API classes as CDI beans war.addAsWebInfResource(new StringAsset( "\n" + " \n" + - " \n" + + " \n" + " \n" + ""), "beans.xml"); diff --git a/server/tck/src/test/java/io/smallrye/graphql/test/apps/subscription/api/StocksApi.java b/server/tck/src/test/java/io/smallrye/graphql/test/apps/subscription/api/StocksApi.java index 590de41c2..c60969fec 100644 --- a/server/tck/src/test/java/io/smallrye/graphql/test/apps/subscription/api/StocksApi.java +++ b/server/tck/src/test/java/io/smallrye/graphql/test/apps/subscription/api/StocksApi.java @@ -1,13 +1,14 @@ package io.smallrye.graphql.test.apps.subscription.api; import java.util.List; +import java.util.concurrent.Flow; import org.eclipse.microprofile.graphql.Description; import org.eclipse.microprofile.graphql.GraphQLApi; import org.eclipse.microprofile.graphql.Query; +import org.eclipse.microprofile.graphql.Subscription; import org.reactivestreams.Publisher; -import io.smallrye.graphql.api.Subscription; import io.smallrye.mutiny.Multi; import mutiny.zero.flow.adapters.AdaptersToFlow; @@ -45,6 +46,12 @@ public Multi arraySubscription() { return Multi.createFrom().empty(); } + @Subscription + @Description("Get stock quote changes using Flow.Publisher (MicroProfile spec requirement)") + public Flow.Publisher getStockQuoteFlow(String stockCode) { + return AdaptersToFlow.publisher(STOCK_TICKER_PUBLISHER.getPublisher()); + } + // TODO: Support other Publisher types ? } diff --git a/server/tck/src/test/resources/overrides/basicScalarTests.csv b/server/tck/src/test/resources/overrides/basicScalarTests.csv deleted file mode 100644 index 5f2526d80..000000000 --- a/server/tck/src/test/resources/overrides/basicScalarTests.csv +++ /dev/null @@ -1,3 +0,0 @@ -# Basic Scalar Types -23| type ScalarHolder | charArray: [String!] | Expecting a String Array Scalar (for Java Char[]) Type in type ScalarHolder -47| type Query | testCharArray: [String!] | Expecting a non null Stirng Array (for Java Char[]) Scalar Type in type Query \ No newline at end of file diff --git a/server/tck/src/test/resources/overrides/createNewNullNamedHero/output2.json b/server/tck/src/test/resources/overrides/createNewNullNamedHero/output2.json deleted file mode 100644 index f272245ba..000000000 --- a/server/tck/src/test/resources/overrides/createNewNullNamedHero/output2.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "errors": [ - { - "message": "Validation error (WrongType@[createNewHero]) : argument 'hero.name' with value 'NullValue{}' must not be null", - "locations": [ - { - "line": 2, - "column": 19 - } - ] - } - ] -} \ No newline at end of file diff --git a/server/tck/src/test/resources/overrides/createNewUnnamedHero/output2.json b/server/tck/src/test/resources/overrides/createNewUnnamedHero/output2.json deleted file mode 100644 index 60575b6ad..000000000 --- a/server/tck/src/test/resources/overrides/createNewUnnamedHero/output2.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "errors": [ - { - "message": "Validation error (WrongType@[createNewHero]) : argument 'hero' with value 'ObjectValue{objectFields=[ObjectField{name='realName', value=StringValue{value='John Smith'}}]}' is missing required fields '[name]'", - "locations": [ - { - "line": 2, - "column": 19 - } - ] - } - ] -} \ No newline at end of file diff --git a/server/tck/src/test/resources/overrides/invalidDataTypeValue/output3.json b/server/tck/src/test/resources/overrides/invalidDataTypeValue/output3.json deleted file mode 100644 index 2cc5307e6..000000000 --- a/server/tck/src/test/resources/overrides/invalidDataTypeValue/output3.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "errors": [ - { - "message": "Validation error (WrongType@[updateItemPowerLevel]) : argument 'powerLevel' with value 'StringValue{value='Unlimited'}' is not a valid 'Int' - SRGQL000022: Can not parse a number from [StringValue{value='Unlimited'}]", - "locations": [ - { - "line": 2, - "column": 37 - } - ] - } - ] -} \ No newline at end of file diff --git a/server/tck/src/test/resources/overrides/invalidEnumValue/output2.json b/server/tck/src/test/resources/overrides/invalidEnumValue/output2.json deleted file mode 100644 index c4a6ff809..000000000 --- a/server/tck/src/test/resources/overrides/invalidEnumValue/output2.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "errors": [ - { - "message": "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'}'", - "locations": [ - { - "line": 3, - "column": 19 - } - ] - } - ] -} \ No newline at end of file diff --git a/server/tck/src/test/resources/overrides/invalidLocalDateFormattedValue/output3.json b/server/tck/src/test/resources/overrides/invalidLocalDateFormattedValue/output3.json deleted file mode 100644 index 4fcff2375..000000000 --- a/server/tck/src/test/resources/overrides/invalidLocalDateFormattedValue/output3.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "errors": [ - { - "message": "argument 'date' with value 'StringValue{value='Today'}' is not a valid 'Date'", - "locations": [ - { - "line": 2, - "column": 49 - } - ] - } - ], - "data": { - "checkInWithCorrectDateFormat": null - } -} diff --git a/server/tck/src/test/resources/overrides/invalidLocalDateTimeFormattedValue/output3.json b/server/tck/src/test/resources/overrides/invalidLocalDateTimeFormattedValue/output3.json deleted file mode 100644 index 34be441fa..000000000 --- a/server/tck/src/test/resources/overrides/invalidLocalDateTimeFormattedValue/output3.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "errors": [ - { - "message": "argument 'dateTime' with value 'StringValue{value='Today'}' is not a valid 'DateTime'", - "locations": [ - { - "line": 2, - "column": 48 - } - ] - } - ], - "data": { - "battleWithCorrectDateFormat": null - } -} \ No newline at end of file diff --git a/server/tck/src/test/resources/overrides/invalidLocalDateTimeValue/output3.json b/server/tck/src/test/resources/overrides/invalidLocalDateTimeValue/output3.json deleted file mode 100644 index af2c0dc4e..000000000 --- a/server/tck/src/test/resources/overrides/invalidLocalDateTimeValue/output3.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "errors": [ - { - "message": "argument 'dateTime' with value 'StringValue{value='Today'}' is not a valid 'DateTime'", - "locations": [ - { - "line": 2, - "column": 27 - } - ] - } - ], - "data": { - "battle": null - } -} \ No newline at end of file diff --git a/server/tck/src/test/resources/overrides/invalidLocalDateValue/output3.json b/server/tck/src/test/resources/overrides/invalidLocalDateValue/output3.json deleted file mode 100644 index 49fac88d8..000000000 --- a/server/tck/src/test/resources/overrides/invalidLocalDateValue/output3.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "errors": [ - { - "message": "argument 'date' with value 'StringValue{value='Today'}' is not a valid 'Date'", - "locations": [ - { - "line": 2, - "column": 28 - } - ] - } - ], - "data": { - "checkIn": null - } -} diff --git a/server/tck/src/test/resources/overrides/invalidLocalTimeFormattedValue/output3.json b/server/tck/src/test/resources/overrides/invalidLocalTimeFormattedValue/output3.json deleted file mode 100644 index dbd808830..000000000 --- a/server/tck/src/test/resources/overrides/invalidLocalTimeFormattedValue/output3.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "errors": [ - { - "message": "argument 'time' with value 'StringValue{value='Today'}' is not a valid 'Time'", - "locations": [ - { - "line": 2, - "column": 57 - } - ] - } - ], - "data": { - "startPatrollingWithCorrectDateFormat": null - } -} diff --git a/server/tck/src/test/resources/overrides/invalidLocalTimeValue/output3.json b/server/tck/src/test/resources/overrides/invalidLocalTimeValue/output3.json deleted file mode 100644 index 78d6bfd37..000000000 --- a/server/tck/src/test/resources/overrides/invalidLocalTimeValue/output3.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "errors": [ - { - "message": "argument 'time' with value 'StringValue{value='Today'}' is not a valid 'Time'", - "locations": [ - { - "line": 2, - "column": 36 - } - ] - } - ], - "data": { - "startPatrolling": null - } -} \ No newline at end of file diff --git a/server/tck/src/test/resources/overrides/schemaTests.csv b/server/tck/src/test/resources/overrides/schemaTests.csv deleted file mode 100644 index 3447d7235..000000000 --- a/server/tck/src/test/resources/overrides/schemaTests.csv +++ /dev/null @@ -1,2 +0,0 @@ -# testJsonDefault -60|type Mutation | id : "1000" | Expecting an id in the default value for item for provisionHero \ No newline at end of file diff --git a/server/tck/src/test/resources/overrides/unknownField/output2.json b/server/tck/src/test/resources/overrides/unknownField/output2.json deleted file mode 100644 index c8eedf495..000000000 --- a/server/tck/src/test/resources/overrides/unknownField/output2.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "errors": [ - { - "message": "Validation error (FieldUndefined@[allHeroes/weaknesses]) : Field 'weaknesses' in type 'SuperHero' is undefined", - "locations": [ - { - "line": 4, - "column": 5 - } - ] - } - ] -} \ No newline at end of file diff --git a/server/tck/src/test/resources/overrides/unknownMutation/output2.json b/server/tck/src/test/resources/overrides/unknownMutation/output2.json deleted file mode 100644 index c1e1c0c82..000000000 --- a/server/tck/src/test/resources/overrides/unknownMutation/output2.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "errors": [ - { - "message": "Validation error (FieldUndefined@[createNewHeroCat]) : Field 'createNewHeroCat' in type 'Mutation' is undefined", - "locations": [ - { - "line": 2, - "column": 5 - } - ] - } - ] -} \ No newline at end of file diff --git a/server/tck/src/test/resources/overrides/unknownQuery/output2.json b/server/tck/src/test/resources/overrides/unknownQuery/output2.json deleted file mode 100644 index 55941f7c0..000000000 --- a/server/tck/src/test/resources/overrides/unknownQuery/output2.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "errors": [ - { - "message": "Validation error (FieldUndefined@[allHeroesWhoLikeIceCream]) : Field 'allHeroesWhoLikeIceCream' in type 'Query' is undefined", - "locations": [ - { - "line": 2, - "column": 3 - } - ] - } - ] -} diff --git a/server/tck/src/test/resources/tests/subscriptionTests.csv b/server/tck/src/test/resources/tests/subscriptionTests.csv index c04fe71cb..d2934187d 100644 --- a/server/tck/src/test/resources/tests/subscriptionTests.csv +++ b/server/tck/src/test/resources/tests/subscriptionTests.csv @@ -1,4 +1,5 @@ # testSubscriptions 1| type Subscription | arraySubscription: [String] | Java array in subscription should map to [String] in graphql -2| type Subscription | listSubscription: [String] | Java array in subscription should map to [String] in graphql -3| type Subscription | stockQuoteMulti(stockCode: String): Stock| Subscription must be able to take arguments \ No newline at end of file +2| type Subscription | listSubscription: [String] | Java List in subscription should map to [String] in graphql +3| type Subscription | stockQuoteMulti(stockCode: String): Stock| Subscription must be able to take arguments +4| type Subscription | stockQuoteFlow(stockCode: String): Stock | Flow.Publisher subscription (MicroProfile spec requirement) From a4a42d2a7afb222e0d7f90775a88f6d79316a232 Mon Sep 17 00:00:00 2001 From: Phillip Kruger Date: Mon, 20 Jul 2026 14:33:27 +1000 Subject: [PATCH 2/2] Remove JSON-P dependency, replace with Jackson Replace all jakarta.json (JSON-P) usage with Jackson across the server API, implementation, servlet, CDI, and test modules. Key changes: - Context API returns ObjectNode/ArrayNode instead of JsonObject/JsonArray - ErrorExtensionProvider returns JsonNode instead of JsonValue - ExecutionService accepts ObjectNode instead of JsonObject - WebSocket handlers and ExecutionServlet parse/build with Jackson - Remove bridge methods (getExecutionResultAsJsonPObject, etc.) - Delete JsonProviderHolder - Remove jakarta.json-api dependency from server modules - Update TestInterceptor to use Jackson (requires MP GraphQL TCK PR microprofile/microprofile-graphql#598) The only remaining jakarta.json import is CurrencyAdapter.java in schema-builder tests (JSON-B adapter boundary). --- CLAUDE.md | 2 +- .../graphql/client/modelbuilder/Scalars.java | 4 - .../graphql/schema/model/Scalars.java | 7 +- server/api/pom.xml | 6 +- .../java/io/smallrye/graphql/api/Context.java | 105 +++++++-------- .../graphql/api/ErrorExtensionProvider.java | 4 +- server/implementation-cdi/pom.xml | 5 - .../cdi/context/CDISmallRyeContext.java | 17 ++- .../graphql/execution/CdiExecutionTest.java | 10 +- server/implementation-servlet/pom.xml | 5 - .../graphql/servlet/ExecutionServlet.java | 82 ++++++------ server/implementation/pom.xml | 4 - .../smallrye/graphql/JsonProviderHolder.java | 11 -- .../graphql/execution/ExecutionResponse.java | 59 --------- .../graphql/execution/ExecutionService.java | 17 ++- .../execution/JsonObjectResponseWriter.java | 71 ++++------ .../execution/context/SmallRyeContext.java | 23 ++-- .../context/SmallRyeContextManager.java | 121 +++++++++--------- .../error/ErrorCodeExtensionProvider.java | 11 +- .../ExceptionNameErrorExtensionProvider.java | 11 +- .../error/ExecutionErrorsService.java | 25 +--- .../smallrye/graphql/json/JacksonCreator.java | 55 -------- .../AbstractGraphQLWebsocketHandler.java | 111 +++++++--------- .../GraphQLTransportWSSubprotocolHandler.java | 52 +++----- .../GraphQLWSSubprotocolHandler.java | 49 +++---- .../execution/CompletableFutureTest.java | 14 +- .../graphql/execution/ContextTest.java | 18 +-- .../execution/ExecutionGenericsTest.java | 28 ++-- .../graphql/execution/ExecutionTest.java | 54 ++++---- .../graphql/execution/ExecutionTestBase.java | 42 +++--- .../execution/ExecutionUnionsTest.java | 36 +++--- .../execution/ExperimentalNamespaceTest.java | 15 +-- .../execution/FederatedNamespaceTest.java | 37 +++--- .../execution/FederatedTracingTest.java | 17 ++- .../graphql/execution/GroupingTest.java | 10 +- .../graphql/execution/JsonbCreatorTest.java | 51 ++++---- .../graphql/execution/MappingTest.java | 108 +++++++--------- .../graphql/execution/MutinyTest.java | 28 ++-- .../graphql/execution/ResolverTest.java | 43 +++---- .../execution/TestErrorExtensionProvider.java | 9 +- .../graphql/tests/GraphQLAssured.java | 38 +++--- .../io/smallrye/graphql/TestInterceptor.java | 22 ++-- .../test/apps/context/api/ContextApi.java | 20 +-- .../graphql/test/apps/jsonp/api/JsonPApi.java | 28 ++-- .../graphql/test/apps/jsonp/api/Token.java | 4 +- 45 files changed, 595 insertions(+), 894 deletions(-) delete mode 100644 server/implementation/src/main/java/io/smallrye/graphql/JsonProviderHolder.java diff --git a/CLAUDE.md b/CLAUDE.md index 3279d857e..0052041b8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -118,6 +118,6 @@ ui/graphiql/ -> GraphiQL UI component - Jakarta namespace only — the build enforces a ban on `javax.*` dependencies via maven-enforcer-plugin - Logging uses `jboss-logging` with message localization (`SmallRyeGraphQLServerLogging`, `SmallRyeGraphQLServerMessages`) -- JSON binding uses JSON-B (Yasson implementation), not Jackson +- JSON processing uses Jackson 3.x (tools.jackson). JSON-B annotations are supported via the jackson-jsonb-compat module - The `.mvn/` directory is gitignored and may contain local Maven settings (repo path, mirrors) - The graphql-java library (currently v21.1) is the underlying GraphQL execution engine diff --git a/client/model-builder/src/main/java/io/smallrye/graphql/client/modelbuilder/Scalars.java b/client/model-builder/src/main/java/io/smallrye/graphql/client/modelbuilder/Scalars.java index 611c14045..4374c3244 100644 --- a/client/model-builder/src/main/java/io/smallrye/graphql/client/modelbuilder/Scalars.java +++ b/client/model-builder/src/main/java/io/smallrye/graphql/client/modelbuilder/Scalars.java @@ -72,10 +72,6 @@ public static boolean isStringScalar(String className) { populateScalar(URL.class.getName(), STRING); populateScalar(URI.class.getName(), STRING); populateScalar("org.bson.types.ObjectId", STRING); - populateScalar("javax.json.JsonObject", STRING); - populateScalar("javax.json.JsonArray", STRING); - populateScalar("jakarta.json.JsonObject", STRING); - populateScalar("jakarta.json.JsonArray", STRING); // Boolean populateScalar(Boolean.class.getName(), BOOLEAN); diff --git a/common/schema-model/src/main/java/io/smallrye/graphql/schema/model/Scalars.java b/common/schema-model/src/main/java/io/smallrye/graphql/schema/model/Scalars.java index fb6e8ba85..e27925cab 100644 --- a/common/schema-model/src/main/java/io/smallrye/graphql/schema/model/Scalars.java +++ b/common/schema-model/src/main/java/io/smallrye/graphql/schema/model/Scalars.java @@ -121,10 +121,9 @@ public static void addObject() { } public static void addJson() { - populateScalar("jakarta.json.JsonValue", JSON, Object.class.getName()); - populateScalar("jakarta.json.JsonObject", JSON, Object.class.getName()); populateScalar("tools.jackson.databind.JsonNode", JSON, Object.class.getName()); populateScalar("tools.jackson.databind.node.ObjectNode", JSON, Object.class.getName()); + populateScalar("tools.jackson.databind.node.ArrayNode", JSON, Object.class.getName()); } static { @@ -138,10 +137,6 @@ public static void addJson() { populateScalar(URL.class.getName(), STRING, String.class.getName()); populateScalar(URI.class.getName(), STRING, String.class.getName()); populateScalar("org.bson.types.ObjectId", STRING, String.class.getName()); - populateScalar("javax.json.JsonObject", STRING, String.class.getName()); - populateScalar("javax.json.JsonArray", STRING, String.class.getName()); - populateScalar("jakarta.json.JsonObject", STRING, String.class.getName()); - populateScalar("jakarta.json.JsonArray", STRING, String.class.getName()); // Boolean populateScalar(Boolean.class.getName(), BOOLEAN); diff --git a/server/api/pom.xml b/server/api/pom.xml index 3392190fd..45fe4d62f 100644 --- a/server/api/pom.xml +++ b/server/api/pom.xml @@ -45,10 +45,10 @@ smallrye-common-annotation - + - jakarta.json - jakarta.json-api + tools.jackson.core + jackson-databind provided diff --git a/server/api/src/main/java/io/smallrye/graphql/api/Context.java b/server/api/src/main/java/io/smallrye/graphql/api/Context.java index 917ffebe9..6adadb689 100644 --- a/server/api/src/main/java/io/smallrye/graphql/api/Context.java +++ b/server/api/src/main/java/io/smallrye/graphql/api/Context.java @@ -18,18 +18,15 @@ import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Set; - -import jakarta.json.JsonArray; -import jakarta.json.JsonNumber; -import jakarta.json.JsonObject; -import jakarta.json.JsonString; -import jakarta.json.JsonValue; import io.smallrye.common.annotation.Experimental; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.node.ArrayNode; +import tools.jackson.databind.node.ObjectNode; /** * Holing context for the current request @@ -48,9 +45,9 @@ public interface Context { * Get the full body of the request. * This includes the query, variables and operation name * - * @return JsonObject + * @return ObjectNode */ - public JsonObject getRequest(); + public ObjectNode getRequest(); /** * Check if there is a request set @@ -58,7 +55,7 @@ public interface Context { * @return */ default boolean hasRequest() { - JsonObject request = getRequest(); + ObjectNode request = getRequest(); return request != null; } @@ -69,7 +66,8 @@ default boolean hasRequest() { * @return raw string query */ default String getQuery() { - return getRequest().getString(QUERY, null); + JsonNode node = getRequest().get(QUERY); + return (node != null && !node.isNull()) ? node.asText() : null; } /** @@ -96,11 +94,11 @@ default boolean hasOperationName() { * @return */ default Optional> getVariables() { - if (getRequest().containsKey(VARIABLES) + if (getRequest().has(VARIABLES) && getRequest().get(VARIABLES) != null - && !getRequest().get(VARIABLES).getValueType().equals(JsonValue.ValueType.NULL)) { - JsonValue jsonValue = getRequest().get(VARIABLES); - return VariablesParser.toMap(jsonValue); + && !getRequest().get(VARIABLES).isNull()) { + JsonNode node = getRequest().get(VARIABLES); + return VariablesParser.toMap(node); } return Optional.empty(); } @@ -197,14 +195,14 @@ default boolean hasSource() { * * @return JsonArray of fields selected */ - public JsonArray getSelectedFields(); + public ArrayNode getSelectedFields(); /** * Return the fields and source fields in the request * * @return JsonArray of fields selected */ - public JsonArray getSelectedAndSourceFields(); + public ArrayNode getSelectedAndSourceFields(); /** * Return the current type (Query, Mutation ext) @@ -245,61 +243,44 @@ default boolean hasSource() { */ class VariablesParser { - public static Optional> toMap(JsonValue jsonValue) { - if (null != jsonValue - && !JsonValue.NULL.equals(jsonValue) - && !JsonValue.EMPTY_JSON_OBJECT.equals(jsonValue) - && !JsonValue.EMPTY_JSON_ARRAY.equals(jsonValue)) { - return Optional.of(toMap(jsonValue.asJsonObject())); + public static Optional> toMap(JsonNode node) { + if (node != null && !node.isNull() && node.isObject() && !node.isEmpty()) { + return Optional.of(toMap((ObjectNode) node)); } return Optional.empty(); } - private static Map toMap(JsonObject jo) { + private static Map toMap(ObjectNode obj) { Map ro = new HashMap<>(); - if (jo != null) { - Set> entrySet = jo.entrySet(); - for (Map.Entry es : entrySet) { - ro.put(es.getKey(), toObject(es.getValue())); + if (obj != null) { + Iterator> fields = obj.properties().iterator(); + while (fields.hasNext()) { + Map.Entry entry = fields.next(); + ro.put(entry.getKey(), toObject(entry.getValue())); } } return ro; } - private static Object toObject(JsonValue jsonValue) { - Object ret = null; - JsonValue.ValueType typ = jsonValue.getValueType(); - if (null != typ) - switch (typ) { - case NUMBER: - ret = ((JsonNumber) jsonValue).bigDecimalValue(); - break; - case STRING: - ret = ((JsonString) jsonValue).getString(); - break; - case FALSE: - ret = Boolean.FALSE; - break; - case TRUE: - ret = Boolean.TRUE; - break; - case ARRAY: - JsonArray arr = (JsonArray) jsonValue; - List vals = new ArrayList<>(); - int sz = arr.size(); - for (int i = 0; i < sz; i++) { - JsonValue v = arr.get(i); - vals.add(toObject(v)); - } - ret = vals; - break; - case OBJECT: - ret = toMap((JsonObject) jsonValue); - break; - default: - break; + private static Object toObject(JsonNode node) { + if (node == null || node.isNull()) { + return null; + } else if (node.isNumber()) { + return node.decimalValue(); + } else if (node.isTextual()) { + return node.asText(); + } else if (node.isBoolean()) { + return node.booleanValue(); + } else if (node.isArray()) { + List vals = new ArrayList<>(); + for (int i = 0; i < node.size(); i++) { + vals.add(toObject(node.get(i))); } - return ret; + return vals; + } else if (node.isObject()) { + return toMap((ObjectNode) node); + } + return null; } } -} \ No newline at end of file +} diff --git a/server/api/src/main/java/io/smallrye/graphql/api/ErrorExtensionProvider.java b/server/api/src/main/java/io/smallrye/graphql/api/ErrorExtensionProvider.java index f0f11033c..fcf4ab66a 100644 --- a/server/api/src/main/java/io/smallrye/graphql/api/ErrorExtensionProvider.java +++ b/server/api/src/main/java/io/smallrye/graphql/api/ErrorExtensionProvider.java @@ -1,6 +1,6 @@ package io.smallrye.graphql.api; -import jakarta.json.JsonValue; +import tools.jackson.databind.JsonNode; /** * To add you own GraphQL error extension fields, you can add your own implementations @@ -9,5 +9,5 @@ public interface ErrorExtensionProvider { String getKey(); - JsonValue mapValueFrom(Throwable exception); + JsonNode mapValueFrom(Throwable exception); } diff --git a/server/implementation-cdi/pom.xml b/server/implementation-cdi/pom.xml index 450feb975..96e1b6f4d 100644 --- a/server/implementation-cdi/pom.xml +++ b/server/implementation-cdi/pom.xml @@ -60,11 +60,6 @@ provided - - jakarta.json - jakarta.json-api - - org.junit.jupiter diff --git a/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/context/CDISmallRyeContext.java b/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/context/CDISmallRyeContext.java index f2238a625..3146de311 100644 --- a/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/context/CDISmallRyeContext.java +++ b/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/context/CDISmallRyeContext.java @@ -4,9 +4,6 @@ import java.util.Map; import java.util.Optional; -import jakarta.json.JsonArray; -import jakarta.json.JsonObject; - import graphql.ExecutionInput; import graphql.schema.DataFetchingEnvironment; import io.smallrye.graphql.execution.QueryCache; @@ -14,6 +11,8 @@ import io.smallrye.graphql.execution.context.SmallRyeContext; import io.smallrye.graphql.execution.context.SmallRyeContextManager; import io.smallrye.graphql.schema.model.Field; +import tools.jackson.databind.node.ArrayNode; +import tools.jackson.databind.node.ObjectNode; /** * Class that serves as a proxy decorator for obtaining the current SmallRyeContext. It always calls @@ -105,22 +104,22 @@ public String getOperationType() { } @Override - public void setSelectedAndSourceFields(JsonArray selectedAndSourceFields) { + public void setSelectedAndSourceFields(ArrayNode selectedAndSourceFields) { SmallRyeContextManager.getCurrentSmallRyeContext().setSelectedAndSourceFields(selectedAndSourceFields); } @Override - public JsonArray getSelectedAndSourceFields() { + public ArrayNode getSelectedAndSourceFields() { return SmallRyeContextManager.getCurrentSmallRyeContext().getSelectedAndSourceFields(); } @Override - public void setSelectedFields(JsonArray selectedFields) { + public void setSelectedFields(ArrayNode selectedFields) { SmallRyeContextManager.getCurrentSmallRyeContext().setSelectedFields(selectedFields); } @Override - public JsonArray getSelectedFields() { + public ArrayNode getSelectedFields() { return SmallRyeContextManager.getCurrentSmallRyeContext().getSelectedFields(); } @@ -185,12 +184,12 @@ public String getExecutionId() { } @Override - public void setRequest(JsonObject request) { + public void setRequest(ObjectNode request) { SmallRyeContextManager.getCurrentSmallRyeContext().setRequest(request); } @Override - public JsonObject getRequest() { + public ObjectNode getRequest() { return SmallRyeContextManager.getCurrentSmallRyeContext().getRequest(); } diff --git a/server/implementation-cdi/src/test/java/io/smallrye/graphql/execution/CdiExecutionTest.java b/server/implementation-cdi/src/test/java/io/smallrye/graphql/execution/CdiExecutionTest.java index 8b8aa0b69..04d36d55c 100644 --- a/server/implementation-cdi/src/test/java/io/smallrye/graphql/execution/CdiExecutionTest.java +++ b/server/implementation-cdi/src/test/java/io/smallrye/graphql/execution/CdiExecutionTest.java @@ -1,6 +1,5 @@ package io.smallrye.graphql.execution; -import static io.smallrye.graphql.JsonProviderHolder.JSON_PROVIDER; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -9,8 +8,6 @@ import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.RequestScoped; import jakarta.inject.Inject; -import jakarta.json.JsonObject; -import jakarta.json.JsonObjectBuilder; import org.eclipse.microprofile.graphql.tck.apps.basic.api.ScalarTestApi; import org.eclipse.microprofile.graphql.tck.apps.superhero.api.HeroFinder; @@ -27,6 +24,7 @@ import io.smallrye.graphql.schema.model.Schema; import io.smallrye.graphql.spi.LookupService; import tools.jackson.databind.node.ArrayNode; +import tools.jackson.databind.node.JsonNodeFactory; import tools.jackson.databind.node.ObjectNode; /** @@ -394,10 +392,8 @@ private ArrayNode executeAndGetError(String graphQL) { return (ArrayNode) result.getExecutionResultAsJsonObject().get(ERRORS); } - private JsonObject toJsonObject(String graphQL) { - JsonObjectBuilder builder = JSON_PROVIDER.createObjectBuilder(); - builder.add("query", graphQL); - return builder.build(); + private ObjectNode toJsonObject(String graphQL) { + return JsonNodeFactory.instance.objectNode().put("query", graphQL); } private static final String DATA = "data"; diff --git a/server/implementation-servlet/pom.xml b/server/implementation-servlet/pom.xml index 0ca67f243..2c560fc75 100644 --- a/server/implementation-servlet/pom.xml +++ b/server/implementation-servlet/pom.xml @@ -57,11 +57,6 @@ jakarta.annotation-api provided - - jakarta.json - jakarta.json-api - provided - tools.jackson.core jackson-databind diff --git a/server/implementation-servlet/src/main/java/io/smallrye/graphql/servlet/ExecutionServlet.java b/server/implementation-servlet/src/main/java/io/smallrye/graphql/servlet/ExecutionServlet.java index 2a87a3bad..0b9c8c84c 100644 --- a/server/implementation-servlet/src/main/java/io/smallrye/graphql/servlet/ExecutionServlet.java +++ b/server/implementation-servlet/src/main/java/io/smallrye/graphql/servlet/ExecutionServlet.java @@ -1,26 +1,20 @@ package io.smallrye.graphql.servlet; -import static io.smallrye.graphql.JsonProviderHolder.JSON_PROVIDER; - import java.io.BufferedReader; import java.io.IOException; -import java.io.StringReader; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; import jakarta.inject.Inject; -import jakarta.json.JsonObject; -import jakarta.json.JsonObjectBuilder; -import jakarta.json.JsonReader; -import jakarta.json.JsonReaderFactory; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; @@ -28,6 +22,11 @@ import io.smallrye.graphql.execution.ExecutionService; import io.smallrye.graphql.spi.config.Config; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; +import tools.jackson.databind.node.JsonNodeFactory; +import tools.jackson.databind.node.ObjectNode; /** * Executing the GraphQL request @@ -38,7 +37,8 @@ public class ExecutionServlet extends HttpServlet { private static final long serialVersionUID = -2859915918802356120L; - private static final JsonReaderFactory jsonReaderFactory = JSON_PROVIDER.createReaderFactory(null); + private static final ObjectMapper OBJECT_MAPPER = JsonMapper.builder().build(); + private static final JsonNodeFactory NODE_FACTORY = JsonNodeFactory.instance; @Inject ExecutionService executionService; @@ -56,7 +56,7 @@ public ExecutionServlet(ExecutionService executionService) { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { try { if (config.isAllowGet()) { - JsonObject jsonObject = getJsonObjectFromQueryParameters(request); + ObjectNode jsonObject = getObjectNodeFromQueryParameters(request); executionService.executeSync(jsonObject, getMetaData(request), new HttpServletResponseWriter(response)); } else { response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "GET Queries is not enabled"); @@ -71,16 +71,14 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException { try { Map metaData = getMetaData(request); - JsonObject jsonObjectFromBody = getJsonObjectFromBody(request); + ObjectNode objectNodeFromBody = getObjectNodeFromBody(request); if (request.getQueryString() != null && !request.getQueryString().isEmpty() && config.isAllowPostWithQueryParameters()) { - JsonObject jsonObjectFromQueryParameters = getJsonObjectFromQueryParameters(request); - JsonObject mergedJsonObject = JSON_PROVIDER.createMergePatch(jsonObjectFromQueryParameters) - .apply(jsonObjectFromBody) - .asJsonObject(); - executionService.executeSync(mergedJsonObject, metaData, new HttpServletResponseWriter(response)); + ObjectNode objectNodeFromQueryParameters = getObjectNodeFromQueryParameters(request); + ObjectNode mergedNode = mergeNodes(objectNodeFromBody, objectNodeFromQueryParameters); + executionService.executeSync(mergedNode, metaData, new HttpServletResponseWriter(response)); } else { - executionService.executeSync(jsonObjectFromBody, metaData, new HttpServletResponseWriter(response)); + executionService.executeSync(objectNodeFromBody, metaData, new HttpServletResponseWriter(response)); } } catch (IOException ex) { SmallRyeGraphQLServletLogging.log.ioException(ex); @@ -88,62 +86,72 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) } } - private JsonObject getJsonObjectFromQueryParameters(HttpServletRequest request) throws UnsupportedEncodingException { - JsonObjectBuilder input = JSON_PROVIDER.createObjectBuilder(); + private ObjectNode getObjectNodeFromQueryParameters(HttpServletRequest request) throws UnsupportedEncodingException { + ObjectNode input = NODE_FACTORY.objectNode(); // Query String query = request.getParameter(QUERY); if (query != null && !query.isEmpty()) { - input.add(QUERY, URLDecoder.decode(query, StandardCharsets.UTF_8)); + input.put(QUERY, URLDecoder.decode(query, StandardCharsets.UTF_8)); } // OperationName String operationName = request.getParameter(OPERATION_NAME); if (operationName != null && !operationName.isEmpty()) { - input.add(OPERATION_NAME, URLDecoder.decode(query, StandardCharsets.UTF_8)); + input.put(OPERATION_NAME, URLDecoder.decode(operationName, StandardCharsets.UTF_8)); } // Variables String variables = request.getParameter(VARIABLES); if (variables != null && !variables.isEmpty()) { - JsonObject jsonObject = toJsonObject(URLDecoder.decode(variables, StandardCharsets.UTF_8)); - input.add(VARIABLES, jsonObject); + ObjectNode jsonObject = toObjectNode(URLDecoder.decode(variables, StandardCharsets.UTF_8)); + input.set(VARIABLES, jsonObject); } // Extensions String extensions = request.getParameter(EXTENSIONS); if (extensions != null && !extensions.isEmpty()) { - JsonObject jsonObject = toJsonObject(URLDecoder.decode(extensions, StandardCharsets.UTF_8)); - input.add(EXTENSIONS, jsonObject); + ObjectNode jsonObject = toObjectNode(URLDecoder.decode(extensions, StandardCharsets.UTF_8)); + input.set(EXTENSIONS, jsonObject); } - return input.build(); + return input; } - private JsonObject getJsonObjectFromBody(HttpServletRequest request) throws IOException { + private ObjectNode getObjectNodeFromBody(HttpServletRequest request) throws IOException { String contentType = request.getContentType(); try (BufferedReader bufferedReader = request.getReader()) { - // If the content type is application/graphql, the query is in the body if (contentType != null && contentType.startsWith(APPLICATION_GRAPHQL)) { String query = bufferedReader.lines().collect(Collectors.joining("\n")); - JsonObjectBuilder input = JSON_PROVIDER.createObjectBuilder(); - input.add(QUERY, query); - return input.build(); - // Else we expect a Json in the content + return NODE_FACTORY.objectNode().put(QUERY, query); } else { - try (JsonReader jsonReader = jsonReaderFactory.createReader(bufferedReader)) { - return jsonReader.readObject(); - } + return (ObjectNode) OBJECT_MAPPER.readTree(bufferedReader); } } } - private static JsonObject toJsonObject(String jsonString) { + private static ObjectNode toObjectNode(String jsonString) { if (jsonString == null || jsonString.isEmpty()) { return null; } - try (JsonReader jsonReader = JSON_PROVIDER.createReader(new StringReader(jsonString))) { - return jsonReader.readObject(); + return (ObjectNode) OBJECT_MAPPER.readTree(jsonString); + } + + /** + * RFC 7386 JSON Merge Patch: fields from patch override fields in target; + * null-valued fields in patch remove the field from target. + */ + private static ObjectNode mergeNodes(ObjectNode target, ObjectNode patch) { + ObjectNode result = target.deepCopy(); + Iterator> fields = patch.properties().iterator(); + while (fields.hasNext()) { + Map.Entry entry = fields.next(); + if (entry.getValue().isNull()) { + result.remove(entry.getKey()); + } else { + result.set(entry.getKey(), entry.getValue()); + } } + return result; } protected Map getMetaData(HttpServletRequest request) { diff --git a/server/implementation/pom.xml b/server/implementation/pom.xml index 9d5147492..65ea3f003 100644 --- a/server/implementation/pom.xml +++ b/server/implementation/pom.xml @@ -116,10 +116,6 @@ test ${version.mockito} - - jakarta.json - jakarta.json-api - org.eclipse yasson diff --git a/server/implementation/src/main/java/io/smallrye/graphql/JsonProviderHolder.java b/server/implementation/src/main/java/io/smallrye/graphql/JsonProviderHolder.java deleted file mode 100644 index 614aed938..000000000 --- a/server/implementation/src/main/java/io/smallrye/graphql/JsonProviderHolder.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.smallrye.graphql; - -import jakarta.json.spi.JsonProvider; - -// A central place to get the JsonProvider to avoid calling `JsonProvider.provider()` many times -// due to associated performance costs. -public class JsonProviderHolder { - - public static final JsonProvider JSON_PROVIDER = JsonProvider.provider(); - -} diff --git a/server/implementation/src/main/java/io/smallrye/graphql/execution/ExecutionResponse.java b/server/implementation/src/main/java/io/smallrye/graphql/execution/ExecutionResponse.java index f65d36f5a..8af2af7ce 100644 --- a/server/implementation/src/main/java/io/smallrye/graphql/execution/ExecutionResponse.java +++ b/server/implementation/src/main/java/io/smallrye/graphql/execution/ExecutionResponse.java @@ -1,6 +1,5 @@ package io.smallrye.graphql.execution; -import java.io.StringReader; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; @@ -11,10 +10,6 @@ import java.util.Stack; import java.util.stream.Collectors; -import jakarta.json.JsonObject; -import jakarta.json.JsonReader; -import jakarta.json.spi.JsonProvider; - import com.fasterxml.jackson.annotation.JsonInclude; import graphql.ExecutionResult; @@ -83,20 +78,6 @@ public ObjectNode getExecutionResultAsJsonObject() { return returnObject; } - /** - * Bridge method: converts the Jackson ObjectNode to a JSON-P JsonObject. - * This is needed for callers that still use JSON-P types (websocket handlers, JsonObjectResponseWriter). - * Will be removed when those callers are migrated to Jackson. - */ - public JsonObject getExecutionResultAsJsonPObject() { - String jsonString = getExecutionResultAsString(); - JsonProvider jsonProvider = JsonProvider.provider(); - try (StringReader sr = new StringReader(jsonString); - JsonReader reader = jsonProvider.createReader(sr)) { - return reader.readObject(); - } - } - public String getExecutionResultAsString() { try { return OBJECT_MAPPER.writeValueAsString(getExecutionResultAsJsonObject()); @@ -223,8 +204,6 @@ private JsonNode toJsonNode(Object pojo) { return NODE_FACTORY.numberNode((BigInteger) pojo); } else if (pojo instanceof Enum) { return NODE_FACTORY.textNode(((Enum) pojo).name()); - } else if (pojo instanceof jakarta.json.JsonValue) { - return jsonPValueToJsonNode((jakarta.json.JsonValue) pojo); } else { try { return OBJECT_MAPPER.valueToTree(pojo); @@ -246,44 +225,6 @@ private void popFromThePathBuffer() { } } - private static JsonNode jsonPValueToJsonNode(jakarta.json.JsonValue jsonPValue) { - switch (jsonPValue.getValueType()) { - case OBJECT: - ObjectNode objectNode = NODE_FACTORY.objectNode(); - jakarta.json.JsonObject jsonPObject = jsonPValue.asJsonObject(); - for (String key : jsonPObject.keySet()) { - objectNode.set(key, jsonPValueToJsonNode(jsonPObject.get(key))); - } - return objectNode; - case ARRAY: - ArrayNode arrayNode = NODE_FACTORY.arrayNode(); - for (jakarta.json.JsonValue item : jsonPValue.asJsonArray()) { - arrayNode.add(jsonPValueToJsonNode(item)); - } - return arrayNode; - case STRING: - return NODE_FACTORY.textNode(((jakarta.json.JsonString) jsonPValue).getString()); - case NUMBER: - jakarta.json.JsonNumber jsonPNumber = (jakarta.json.JsonNumber) jsonPValue; - if (jsonPNumber.isIntegral()) { - try { - return NODE_FACTORY.numberNode(jsonPNumber.longValueExact()); - } catch (ArithmeticException e) { - return NODE_FACTORY.numberNode(jsonPNumber.bigIntegerValue()); - } - } - return NODE_FACTORY.numberNode(jsonPNumber.bigDecimalValue()); - case TRUE: - return NODE_FACTORY.booleanNode(true); - case FALSE: - return NODE_FACTORY.booleanNode(false); - case NULL: - return NODE_FACTORY.nullNode(); - default: - return NODE_FACTORY.nullNode(); - } - } - private static JsonNode excludeNullFields(JsonNode jsonNode) { if (jsonNode instanceof ObjectNode) { ObjectNode objectNode = (ObjectNode) jsonNode; diff --git a/server/implementation/src/main/java/io/smallrye/graphql/execution/ExecutionService.java b/server/implementation/src/main/java/io/smallrye/graphql/execution/ExecutionService.java index 5096cc92b..4cabf0869 100644 --- a/server/implementation/src/main/java/io/smallrye/graphql/execution/ExecutionService.java +++ b/server/implementation/src/main/java/io/smallrye/graphql/execution/ExecutionService.java @@ -14,8 +14,6 @@ import java.util.concurrent.SubmissionPublisher; import java.util.concurrent.atomic.AtomicLong; -import jakarta.json.JsonObject; - import org.dataloader.BatchLoaderWithContext; import org.dataloader.CacheKey; import org.dataloader.DataLoader; @@ -56,6 +54,7 @@ import io.smallrye.graphql.spi.config.Config; import io.smallrye.graphql.spi.config.LogPayloadOption; import io.smallrye.mutiny.Uni; +import tools.jackson.databind.node.ObjectNode; /** * Executing the GraphQL request @@ -118,7 +117,7 @@ public ExecutionService(GraphQLSchema graphQLSchema, Schema schema, Optional(), writer); } - public void executeSync(JsonObject jsonInput, Map context, ExecutionResponseWriter writer) { + public void executeSync(ObjectNode jsonInput, Map context, ExecutionResponseWriter writer) { execute(jsonInput, context, writer, false); } - public void executeAsync(JsonObject jsonInput, ExecutionResponseWriter writer) { + public void executeAsync(ObjectNode jsonInput, ExecutionResponseWriter writer) { executeAsync(jsonInput, new HashMap<>(), writer); } - public void executeAsync(JsonObject jsonInput, Map context, ExecutionResponseWriter writer) { + public void executeAsync(ObjectNode jsonInput, Map context, ExecutionResponseWriter writer) { execute(jsonInput, context, writer, true); } - public void execute(JsonObject jsonInput, ExecutionResponseWriter writer, boolean async) { + public void execute(ObjectNode jsonInput, ExecutionResponseWriter writer, boolean async) { execute(jsonInput, new HashMap<>(), writer, async); } - public void execute(JsonObject jsonInput, Map context, ExecutionResponseWriter writer, boolean async) { + public void execute(ObjectNode jsonInput, Map context, ExecutionResponseWriter writer, boolean async) { SmallRyeContext smallRyeContext = SmallRyeContextManager.fromInitialRequest(jsonInput); // ExecutionId diff --git a/server/implementation/src/main/java/io/smallrye/graphql/execution/JsonObjectResponseWriter.java b/server/implementation/src/main/java/io/smallrye/graphql/execution/JsonObjectResponseWriter.java index 66af6f8d4..21824d5a6 100644 --- a/server/implementation/src/main/java/io/smallrye/graphql/execution/JsonObjectResponseWriter.java +++ b/server/implementation/src/main/java/io/smallrye/graphql/execution/JsonObjectResponseWriter.java @@ -1,20 +1,14 @@ package io.smallrye.graphql.execution; -import static io.smallrye.graphql.JsonProviderHolder.JSON_PROVIDER; - -import java.io.IOException; -import java.io.StringWriter; -import java.util.HashMap; -import java.util.Map; - -import jakarta.json.JsonObject; -import jakarta.json.JsonObjectBuilder; -import jakarta.json.JsonWriter; -import jakarta.json.JsonWriterFactory; -import jakarta.json.stream.JsonGenerator; - import org.jboss.logging.Logger; +import tools.jackson.core.JacksonException; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.SerializationFeature; +import tools.jackson.databind.json.JsonMapper; +import tools.jackson.databind.node.JsonNodeFactory; +import tools.jackson.databind.node.ObjectNode; + /** * A default implementation for Execution Response Writer * @@ -23,15 +17,20 @@ public class JsonObjectResponseWriter implements ExecutionResponseWriter { protected static final Logger LOG = Logger.getLogger(JsonObjectResponseWriter.class.getName()); + private static final ObjectMapper PRETTY_MAPPER = JsonMapper.builder() + .enable(SerializationFeature.INDENT_OUTPUT) + .build(); + private static final JsonNodeFactory NODE_FACTORY = JsonNodeFactory.instance; + private ExecutionResponse executionResponse = null; private Throwable throwable = null; - private final JsonObject input; + private final ObjectNode input; JsonObjectResponseWriter(String graphQL) { - this.input = toJsonObject(graphQL); + this.input = NODE_FACTORY.objectNode().put("query", graphQL); } - JsonObjectResponseWriter(JsonObject input) { + JsonObjectResponseWriter(ObjectNode input) { this.input = input; } @@ -46,55 +45,37 @@ public void fail(Throwable t) { } public void logInput() { - String prettyInput = getPrettyJson(input); - LOG.info(prettyInput); + LOG.info(getPrettyJson(input)); } public void logOutput() { if (executionResponse != null) { - String prettyData = getPrettyJson(executionResponse.getExecutionResultAsJsonPObject()); - LOG.info(prettyData); + LOG.info(getPrettyJson(executionResponse.getExecutionResultAsJsonObject())); } else if (throwable != null) { LOG.error("ERROR", throwable); } } - public JsonObject getInput() { + public ObjectNode getInput() { return this.input; } - public JsonObject getOutput() { + public ObjectNode getOutput() { if (this.executionResponse != null) { - return this.executionResponse.getExecutionResultAsJsonPObject(); + return this.executionResponse.getExecutionResultAsJsonObject(); } - return JsonObject.EMPTY_JSON_OBJECT; + return NODE_FACTORY.objectNode(); } public ExecutionResponse getExecutionResponse() { return this.executionResponse; } - private String getPrettyJson(JsonObject jsonObject) { - - JsonWriterFactory writerFactory = JSON_PROVIDER.createWriterFactory(JSON_PROPERTIES); - - try (StringWriter sw = new StringWriter(); - JsonWriter jsonWriter = writerFactory.createWriter(sw)) { - jsonWriter.writeObject(jsonObject); - return sw.toString(); - } catch (IOException ex) { - throw new RuntimeException(ex); + private String getPrettyJson(ObjectNode node) { + try { + return PRETTY_MAPPER.writeValueAsString(node); + } catch (JacksonException e) { + throw new RuntimeException(e); } } - - private JsonObject toJsonObject(String graphQL) { - JsonObjectBuilder builder = JSON_PROVIDER.createObjectBuilder(); - builder.add("query", graphQL); - return builder.build(); - } - - private static final Map JSON_PROPERTIES = new HashMap<>(1); - static { - JSON_PROPERTIES.put(JsonGenerator.PRETTY_PRINTING, true); - } } diff --git a/server/implementation/src/main/java/io/smallrye/graphql/execution/context/SmallRyeContext.java b/server/implementation/src/main/java/io/smallrye/graphql/execution/context/SmallRyeContext.java index 422fdcfc0..b0f3b24ec 100644 --- a/server/implementation/src/main/java/io/smallrye/graphql/execution/context/SmallRyeContext.java +++ b/server/implementation/src/main/java/io/smallrye/graphql/execution/context/SmallRyeContext.java @@ -7,9 +7,6 @@ import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; -import jakarta.json.JsonArray; -import jakarta.json.JsonObject; - import graphql.ExecutionInput; import graphql.ExecutionResult; import graphql.language.Document; @@ -17,6 +14,8 @@ import io.smallrye.graphql.api.Context; import io.smallrye.graphql.execution.QueryCache; import io.smallrye.graphql.schema.model.Field; +import tools.jackson.databind.node.ArrayNode; +import tools.jackson.databind.node.ObjectNode; /** * Implements the Context from MicroProfile API. @@ -37,15 +36,15 @@ public class SmallRyeContext implements Context { private final String createdBy; private String fetchId; - private JsonObject request; + private ObjectNode request; private String executionId; private Field field; private String fieldName; private Map arguments; private Object source; private String path; - private JsonArray selectedFields; - private JsonArray selectedAndSourceFields; + private ArrayNode selectedFields; + private ArrayNode selectedAndSourceFields; private String operationType; private List requestedOperationTypes; private String parentTypeName; @@ -135,11 +134,11 @@ public SmallRyeContext(String createdBy) { } @Override - public JsonObject getRequest() { + public ObjectNode getRequest() { return this.request; } - public void setRequest(JsonObject request) { + public void setRequest(ObjectNode request) { this.request = request; } @@ -197,20 +196,20 @@ public void setPath(String path) { } @Override - public JsonArray getSelectedFields() { + public ArrayNode getSelectedFields() { return this.selectedFields; } - public void setSelectedFields(JsonArray selectedFields) { + public void setSelectedFields(ArrayNode selectedFields) { this.selectedFields = selectedFields; } @Override - public JsonArray getSelectedAndSourceFields() { + public ArrayNode getSelectedAndSourceFields() { return selectedAndSourceFields; } - public void setSelectedAndSourceFields(JsonArray selectedAndSourceFields) { + public void setSelectedAndSourceFields(ArrayNode selectedAndSourceFields) { this.selectedAndSourceFields = selectedAndSourceFields; } diff --git a/server/implementation/src/main/java/io/smallrye/graphql/execution/context/SmallRyeContextManager.java b/server/implementation/src/main/java/io/smallrye/graphql/execution/context/SmallRyeContextManager.java index 33552bffc..68fa84777 100644 --- a/server/implementation/src/main/java/io/smallrye/graphql/execution/context/SmallRyeContextManager.java +++ b/server/implementation/src/main/java/io/smallrye/graphql/execution/context/SmallRyeContextManager.java @@ -1,22 +1,14 @@ package io.smallrye.graphql.execution.context; -import static io.smallrye.graphql.JsonProviderHolder.JSON_PROVIDER; - import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; -import jakarta.json.JsonArray; -import jakarta.json.JsonArrayBuilder; -import jakarta.json.JsonBuilderFactory; -import jakarta.json.JsonObject; -import jakarta.json.JsonObjectBuilder; -import jakarta.json.JsonValue; - import graphql.ExecutionInput; import graphql.GraphQLContext; import graphql.language.Document; @@ -36,6 +28,10 @@ import io.smallrye.graphql.schema.model.Field; import io.smallrye.graphql.schema.model.Operation; import io.smallrye.graphql.schema.model.Type; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.node.ArrayNode; +import tools.jackson.databind.node.JsonNodeFactory; +import tools.jackson.databind.node.ObjectNode; /** * Adds methods to make the life cycle of the context easy to implement @@ -43,7 +39,7 @@ * @author Phillip Kruger (phillip.kruger@redhat.com) */ public class SmallRyeContextManager { - private static final JsonBuilderFactory jsonbuilder = JSON_PROVIDER.createBuilderFactory(null); + private static final JsonNodeFactory nodeFactory = JsonNodeFactory.instance; private static final InheritableThreadLocal current = new InheritableThreadLocal<>(); public static final String CONTEXT = "context"; @@ -72,7 +68,7 @@ public static void clearCurrentSmallRyeContext() { * @param request the original request * @return the initial context */ - public static SmallRyeContext fromInitialRequest(JsonObject request) { + public static SmallRyeContext fromInitialRequest(ObjectNode request) { SmallRyeContext smallRyeContext = getCurrentSmallRyeContext(); if (smallRyeContext == null) { @@ -165,42 +161,42 @@ private static String getOperationTypeFromDefinition(OperationDefinition definit return definition.getOperation().toString(); } - private static JsonArray buildSelectedFields(Type type, DataFetchingEnvironment dfe, Field field, + private static ArrayNode buildSelectedFields(Type type, DataFetchingEnvironment dfe, Field field, boolean includeSourceFields) { DataFetchingFieldSelectionSet selectionSet = dfe.getSelectionSet(); Set fields = new LinkedHashSet<>(selectionSet.getFields()); - return toJsonArrayBuilder(type, fields, field, includeSourceFields).build(); + return toArrayNode(type, fields, field, includeSourceFields); } - private static JsonArrayBuilder toJsonArrayBuilder(Type type, Set fields, Field field, + private static ArrayNode toArrayNode(Type type, Set fields, Field field, boolean includeSourceFields) { - JsonArrayBuilder builder = jsonbuilder.createArrayBuilder(); + ArrayNode arrayNode = nodeFactory.arrayNode(); for (SelectedField selectedField : fields) { if (!isFlattenScalar(selectedField)) { if (includeSourceFields || !isSourceField(type, selectedField)) { if (isScalar(selectedField)) { - builder = builder.add(selectedField.getName()); + arrayNode.add(selectedField.getName()); } else { - builder = builder.add(toJsonObjectBuilder(type, selectedField, field, includeSourceFields)); + arrayNode.add(toObjectNode(type, selectedField, field, includeSourceFields)); } } } } - return builder; + return arrayNode; } private static boolean isFlattenScalar(SelectedField field) { return field.getQualifiedName().contains("/"); } - private static JsonObjectBuilder toJsonObjectBuilder(Type type, SelectedField selectedField, Field field, + private static ObjectNode toObjectNode(Type type, SelectedField selectedField, Field field, boolean includeSourceFields) { - JsonObjectBuilder builder = jsonbuilder.createObjectBuilder(); + ObjectNode objectNode = nodeFactory.objectNode(); Set fields = new LinkedHashSet<>(selectedField.getSelectionSet().getFields()); - builder = builder.add(selectedField.getName(), - toJsonArrayBuilder(type, fields, field, includeSourceFields)); - return builder; + objectNode.set(selectedField.getName(), + toArrayNode(type, fields, field, includeSourceFields)); + return objectNode; } private static boolean isSourceField(Type type, SelectedField selectedField) { @@ -269,12 +265,12 @@ private static List getRequestedOperationTypes(DocumentSupplier document return allRequestedTypes; } - private static String getOperationName(JsonObject request) { - if (request.containsKey(Context.OPERATION_NAME) + private static String getOperationName(ObjectNode request) { + if (request.has(Context.OPERATION_NAME) && request.get(Context.OPERATION_NAME) != null - && !request.get(Context.OPERATION_NAME).getValueType().equals(JsonValue.ValueType.NULL)) { + && !request.get(Context.OPERATION_NAME).isNull()) { - return request.getString(Context.OPERATION_NAME); + return request.get(Context.OPERATION_NAME).asText(); } return null; } @@ -291,51 +287,50 @@ private static String getOperationName(DataFetchingEnvironment dataFetchingEnvir private static final String EXTENSIONS = "extensions"; - private static Map getExtensions(JsonObject request) { - if (request.containsKey(EXTENSIONS) + private static Map getExtensions(ObjectNode request) { + if (request.has(EXTENSIONS) && request.get(EXTENSIONS) != null - && !request.get(EXTENSIONS).getValueType().equals(JsonValue.ValueType.NULL)) { - JsonObject extensionsJson = request.getJsonObject(EXTENSIONS); + && !request.get(EXTENSIONS).isNull()) { + ObjectNode extensionsJson = (ObjectNode) request.get(EXTENSIONS); Map result = new HashMap<>(); - for (Map.Entry entry : extensionsJson.entrySet()) { - result.put(entry.getKey(), convertJsonValue(entry.getValue())); + Iterator> fields = extensionsJson.properties().iterator(); + while (fields.hasNext()) { + Map.Entry entry = fields.next(); + result.put(entry.getKey(), convertJsonNode(entry.getValue())); } return result; } return null; } - private static Object convertJsonValue(JsonValue value) { - switch (value.getValueType()) { - case STRING: - return ((jakarta.json.JsonString) value).getString(); - case NUMBER: - jakarta.json.JsonNumber number = (jakarta.json.JsonNumber) value; - if (number.isIntegral()) { - return number.longValueExact(); - } - return number.doubleValue(); - case TRUE: - return Boolean.TRUE; - case FALSE: - return Boolean.FALSE; - case NULL: - return null; - case OBJECT: - Map map = new HashMap<>(); - for (Map.Entry entry : value.asJsonObject().entrySet()) { - map.put(entry.getKey(), convertJsonValue(entry.getValue())); - } - return map; - case ARRAY: - List list = new java.util.ArrayList<>(); - for (JsonValue item : value.asJsonArray()) { - list.add(convertJsonValue(item)); - } - return list; - default: - return value.toString(); + private static Object convertJsonNode(JsonNode node) { + if (node == null || node.isNull()) { + return null; + } else if (node.isTextual()) { + return node.asText(); + } else if (node.isNumber()) { + if (node.isIntegralNumber()) { + return node.longValue(); + } + return node.doubleValue(); + } else if (node.isBoolean()) { + return node.booleanValue(); + } else if (node.isObject()) { + Map map = new HashMap<>(); + Iterator> fields = node.properties().iterator(); + while (fields.hasNext()) { + Map.Entry entry = fields.next(); + map.put(entry.getKey(), convertJsonNode(entry.getValue())); + } + return map; + } else if (node.isArray()) { + List list = new ArrayList<>(); + for (JsonNode item : node) { + list.add(convertJsonNode(item)); + } + return list; } + return node.toString(); } } diff --git a/server/implementation/src/main/java/io/smallrye/graphql/execution/error/ErrorCodeExtensionProvider.java b/server/implementation/src/main/java/io/smallrye/graphql/execution/error/ErrorCodeExtensionProvider.java index a2365da3d..b8ddaea55 100644 --- a/server/implementation/src/main/java/io/smallrye/graphql/execution/error/ErrorCodeExtensionProvider.java +++ b/server/implementation/src/main/java/io/smallrye/graphql/execution/error/ErrorCodeExtensionProvider.java @@ -2,25 +2,22 @@ import static java.util.Locale.ROOT; -import jakarta.json.JsonValue; -import jakarta.json.spi.JsonProvider; - import io.smallrye.graphql.api.ErrorExtensionProvider; import io.smallrye.graphql.schema.model.ErrorInfo; import io.smallrye.graphql.spi.config.Config; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.node.JsonNodeFactory; public class ErrorCodeExtensionProvider implements ErrorExtensionProvider { - private static final JsonProvider JSON_PROVIDER = JsonProvider.provider(); - @Override public String getKey() { return Config.ERROR_EXTENSION_CODE; } @Override - public JsonValue mapValueFrom(Throwable exception) { - return JSON_PROVIDER.createValue(errorCode(exception)); + public JsonNode mapValueFrom(Throwable exception) { + return JsonNodeFactory.instance.textNode(errorCode(exception)); } private String errorCode(Throwable exception) { diff --git a/server/implementation/src/main/java/io/smallrye/graphql/execution/error/ExceptionNameErrorExtensionProvider.java b/server/implementation/src/main/java/io/smallrye/graphql/execution/error/ExceptionNameErrorExtensionProvider.java index edb477cd3..6434bfd58 100644 --- a/server/implementation/src/main/java/io/smallrye/graphql/execution/error/ExceptionNameErrorExtensionProvider.java +++ b/server/implementation/src/main/java/io/smallrye/graphql/execution/error/ExceptionNameErrorExtensionProvider.java @@ -1,22 +1,19 @@ package io.smallrye.graphql.execution.error; -import jakarta.json.JsonString; -import jakarta.json.spi.JsonProvider; - import io.smallrye.graphql.api.ErrorExtensionProvider; import io.smallrye.graphql.spi.config.Config; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.node.JsonNodeFactory; public class ExceptionNameErrorExtensionProvider implements ErrorExtensionProvider { - private static final JsonProvider JSON_PROVIDER = JsonProvider.provider(); - @Override public String getKey() { return Config.ERROR_EXTENSION_EXCEPTION; } @Override - public JsonString mapValueFrom(Throwable exception) { - return JSON_PROVIDER.createValue(exception.getClass().getName()); + public JsonNode mapValueFrom(Throwable exception) { + return JsonNodeFactory.instance.textNode(exception.getClass().getName()); } } diff --git a/server/implementation/src/main/java/io/smallrye/graphql/execution/error/ExecutionErrorsService.java b/server/implementation/src/main/java/io/smallrye/graphql/execution/error/ExecutionErrorsService.java index 53e0e0d1d..1913ddc3d 100644 --- a/server/implementation/src/main/java/io/smallrye/graphql/execution/error/ExecutionErrorsService.java +++ b/server/implementation/src/main/java/io/smallrye/graphql/execution/error/ExecutionErrorsService.java @@ -4,8 +4,6 @@ import java.util.Map; import java.util.Optional; -import jakarta.json.JsonValue; - import graphql.ExceptionWhileDataFetching; import graphql.GraphQLError; import graphql.validation.ValidationError; @@ -87,30 +85,11 @@ private Optional getDataFetchingExtensions(ExceptionWhileDataFetchin private void addErrorExtensions(ObjectNode objectNode, Throwable exception) { errorExtensionProviders.get() .forEach(provider -> { - JsonValue jsonValue = provider.mapValueFrom(exception); - // Convert jakarta.json.JsonValue to Jackson JsonNode - JsonNode jacksonNode = convertJsonValueToJsonNode(jsonValue); + JsonNode jacksonNode = provider.mapValueFrom(exception); addKeyValue(objectNode, provider.getKey(), jacksonNode); }); } - /** - * Bridge method: converts a jakarta.json.JsonValue to a Jackson JsonNode. - * This is needed because ErrorExtensionProvider (in server/api) returns JSON-P types. - */ - private JsonNode convertJsonValueToJsonNode(JsonValue jsonValue) { - if (jsonValue == null) { - return NODE_FACTORY.nullNode(); - } - try { - // Use the JSON-P toString() which produces valid JSON, then parse with Jackson - return OBJECT_MAPPER.readTree(jsonValue.toString()); - } catch (Exception e) { - // fallback: treat as string - return NODE_FACTORY.textNode(jsonValue.toString()); - } - } - private void populateCustomExtensions(ObjectNode objectNode, Map extensions) { if (extensions != null) { for (Map.Entry entry : extensions.entrySet()) { @@ -120,8 +99,6 @@ private void populateCustomExtensions(ObjectNode objectNode, Map Object value = entry.getValue(); if (value instanceof JsonNode) { addKeyValue(objectNode, entry.getKey(), (JsonNode) value); - } else if (value instanceof JsonValue) { - addKeyValue(objectNode, entry.getKey(), convertJsonValueToJsonNode((JsonValue) value)); } else if (value instanceof Map) { addKeyValue(objectNode, entry.getKey(), OBJECT_MAPPER.valueToTree(value)); } else { diff --git a/server/implementation/src/main/java/io/smallrye/graphql/json/JacksonCreator.java b/server/implementation/src/main/java/io/smallrye/graphql/json/JacksonCreator.java index 7c3115800..73cacd15f 100644 --- a/server/implementation/src/main/java/io/smallrye/graphql/json/JacksonCreator.java +++ b/server/implementation/src/main/java/io/smallrye/graphql/json/JacksonCreator.java @@ -194,33 +194,6 @@ public CustomFloatScalar deserialize(JsonParser p, DeserializationContext ctxt) } }); - // jakarta.json.JsonObject deserializer — needed for JSON scalar type support - module.addDeserializer(jakarta.json.JsonObject.class, new ValueDeserializer() { - @Override - public jakarta.json.JsonObject deserialize(JsonParser p, DeserializationContext ctxt) { - JsonNode node = p.readValueAsTree(); - return (jakarta.json.JsonObject) jacksonNodeToJsonPValue(node); - } - }); - - // jakarta.json.JsonArray deserializer - module.addDeserializer(jakarta.json.JsonArray.class, new ValueDeserializer() { - @Override - public jakarta.json.JsonArray deserialize(JsonParser p, DeserializationContext ctxt) { - JsonNode node = p.readValueAsTree(); - return (jakarta.json.JsonArray) jacksonNodeToJsonPValue(node); - } - }); - - // jakarta.json.JsonValue deserializer - module.addDeserializer(jakarta.json.JsonValue.class, new ValueDeserializer() { - @Override - public jakarta.json.JsonValue deserialize(JsonParser p, DeserializationContext ctxt) { - JsonNode node = p.readValueAsTree(); - return jacksonNodeToJsonPValue(node); - } - }); - return module; } @@ -270,32 +243,4 @@ private static boolean isComplexKeyType(Class type) { && !name.startsWith("tools.jackson."); } - private static jakarta.json.JsonValue jacksonNodeToJsonPValue(JsonNode node) { - if (node == null || node.isNull()) { - return jakarta.json.JsonValue.NULL; - } - if (node.isObject()) { - jakarta.json.JsonObjectBuilder builder = jakarta.json.Json.createObjectBuilder(); - node.properties().forEach(entry -> builder.add(entry.getKey(), jacksonNodeToJsonPValue(entry.getValue()))); - return builder.build(); - } - if (node.isArray()) { - jakarta.json.JsonArrayBuilder builder = jakarta.json.Json.createArrayBuilder(); - node.forEach(item -> builder.add(jacksonNodeToJsonPValue(item))); - return builder.build(); - } - if (node.isTextual()) { - return jakarta.json.Json.createValue(node.asText()); - } - if (node.isBoolean()) { - return node.asBoolean() ? jakarta.json.JsonValue.TRUE : jakarta.json.JsonValue.FALSE; - } - if (node.isIntegralNumber()) { - return jakarta.json.Json.createValue(node.longValue()); - } - if (node.isFloatingPointNumber()) { - return jakarta.json.Json.createValue(node.decimalValue()); - } - return jakarta.json.JsonValue.NULL; - } } diff --git a/server/implementation/src/main/java/io/smallrye/graphql/websocket/AbstractGraphQLWebsocketHandler.java b/server/implementation/src/main/java/io/smallrye/graphql/websocket/AbstractGraphQLWebsocketHandler.java index 5aef37965..963985c5b 100644 --- a/server/implementation/src/main/java/io/smallrye/graphql/websocket/AbstractGraphQLWebsocketHandler.java +++ b/server/implementation/src/main/java/io/smallrye/graphql/websocket/AbstractGraphQLWebsocketHandler.java @@ -1,21 +1,12 @@ package io.smallrye.graphql.websocket; -import static io.smallrye.graphql.JsonProviderHolder.JSON_PROVIDER; - import java.io.IOException; -import java.io.StringReader; import java.time.Duration; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; -import jakarta.json.JsonBuilderFactory; -import jakarta.json.JsonObject; -import jakarta.json.JsonReader; -import jakarta.json.JsonReaderFactory; -import jakarta.json.stream.JsonParsingException; - import org.jboss.logging.Logger; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; @@ -31,12 +22,17 @@ import io.smallrye.graphql.spi.LookupService; import io.smallrye.mutiny.Multi; import io.smallrye.mutiny.subscription.Cancellable; +import tools.jackson.core.JacksonException; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; +import tools.jackson.databind.node.JsonNodeFactory; +import tools.jackson.databind.node.ObjectNode; public abstract class AbstractGraphQLWebsocketHandler implements GraphQLWebsocketHandler { // TODO: Replace with prepared log messages protected static final Logger LOG = Logger.getLogger(GraphQLWebsocketHandler.class.getName()); - private static final JsonReaderFactory jsonReaderFactory = JSON_PROVIDER.createReaderFactory(null); - private static final JsonBuilderFactory jsonBuilderFactory = JSON_PROVIDER.createBuilderFactory(null); + protected static final ObjectMapper OBJECT_MAPPER = JsonMapper.builder().build(); + protected static final JsonNodeFactory NODE_FACTORY = JsonNodeFactory.instance; protected final ExecutionService executionService = LookupService.get().getInstance(ExecutionService.class).get(); protected final GraphQLWebSocketSession session; @@ -53,7 +49,7 @@ public AbstractGraphQLWebsocketHandler(GraphQLWebSocketSession session, String d this.dataMessageTypeName = dataMessageTypeName; this.context = context; this.connectionInitialized = new AtomicBoolean(false); - this.connectionAckMessage = createConnectionAckMessage().toString(); + this.connectionAckMessage = writeValueAsString(createConnectionAckMessage()); this.activeOperations = new ConcurrentHashMap<>(); this.keepAliveSender = Multi.createFrom().ticks() .startingAfter(Duration.ofSeconds(10)) @@ -66,7 +62,7 @@ public void onMessage(String text) { if (LOG.isTraceEnabled()) { LOG.trace("<<< " + text); } - onMessage(getMessageAsJsonObject(text)); + onMessage(getMessageAsObjectNode(text)); } @Override @@ -101,10 +97,10 @@ protected void sendConnectionAckMessage() throws IOException { } } - protected void onOperationRequest(JsonObject message) { - String operationId = message.getString("id"); + protected void onOperationRequest(ObjectNode message) { + String operationId = message.get("id").asText(); if (validSubscription(operationId)) { - JsonObject payload = message.getJsonObject("payload"); + ObjectNode payload = (ObjectNode) message.get("payload"); executionService.executeAsync(payload, context, new ExecutionResponseWriter() { @Override @@ -133,22 +129,14 @@ public void write(ExecutionResponse executionResponse) { if (executionResult != null) { try { if (!executionResult.isDataPresent()) { - // this means a validation error sendErrorMessage(operationId, executionResponse); } else { Object data = executionResponse.getExecutionResult().getData(); if (data instanceof Map) { - // this means the operation is a query or mutation - // only send the response if the operation hasn't been cancelled sendSingleMessage(operationId, executionResponse); } else if (data instanceof Publisher) { - // this means the operation is a subscription sendStreamingMessage(operationId, executionResponse); } else if (data == null) { - // if isDataPresent() == true && but data == null, - // then this is probably a subscription, but the subscription - // method threw an exception instead of returning - // a failed Multi sendErrorMessage(operationId, executionResponse); } else { logUnknownResult(executionResult); @@ -163,16 +151,15 @@ public void write(ExecutionResponse executionResponse) { } } - private JsonObject createConnectionAckMessage() { - return JSON_PROVIDER.createObjectBuilder() - .add("type", "connection_ack") - .build(); + private ObjectNode createConnectionAckMessage() { + return NODE_FACTORY.objectNode() + .put("type", "connection_ack"); } - private JsonObject getMessageAsJsonObject(String text) { + private ObjectNode getMessageAsObjectNode(String text) { try { return parseIncomingMessage(text); - } catch (JsonParsingException ex) { + } catch (JacksonException ex) { session.close((short) 4400, ex.getMessage()); return null; } catch (NullPointerException | IllegalArgumentException ex) { @@ -182,25 +169,22 @@ private JsonObject getMessageAsJsonObject(String text) { } // TODO: we need more validation on the incoming messages (correct fields and types etc) - private JsonObject parseIncomingMessage(String message) { - try (JsonReader jsonReader = jsonReaderFactory.createReader(new StringReader(message))) { - return jsonReader.readObject(); - } + private ObjectNode parseIncomingMessage(String message) { + return (ObjectNode) OBJECT_MAPPER.readTree(message); } - private JsonObject createCompleteMessage(String operationId) { - return jsonBuilderFactory.createObjectBuilder() - .add("type", "complete") - .add("id", operationId) - .build(); + private ObjectNode createCompleteMessage(String operationId) { + return NODE_FACTORY.objectNode() + .put("type", "complete") + .put("id", operationId); } - private JsonObject createDataMessage(String operationId, JsonObject payload) { - return jsonBuilderFactory.createObjectBuilder() - .add("type", this.dataMessageTypeName) - .add("id", operationId) - .add("payload", payload) - .build(); + private ObjectNode createDataMessage(String operationId, ObjectNode payload) { + ObjectNode msg = NODE_FACTORY.objectNode() + .put("type", this.dataMessageTypeName) + .put("id", operationId); + msg.set("payload", payload); + return msg; } private void logUnknownResult(ExecutionResult executionResult) { @@ -211,10 +195,9 @@ private void logUnknownResult(ExecutionResult executionResult) { private void sendSingleMessage(String operationId, ExecutionResponse executionResponse) throws IOException { if (activeOperations.remove(operationId) != null) { session.sendMessage( - createDataMessage(operationId, - executionResponse.getExecutionResultAsJsonPObject()) - .toString()); - session.sendMessage(createCompleteMessage(operationId).toString()); + writeValueAsString(createDataMessage(operationId, + executionResponse.getExecutionResultAsJsonObject()))); + session.sendMessage(writeValueAsString(createCompleteMessage(operationId))); } } @@ -223,8 +206,6 @@ private void sendStreamingMessage(String operationId, ExecutionResponse executio Publisher stream = executionResponse.getExecutionResult() .getData(); if (stream != null) { - // this is actually a subscription, so replace the `activeOperation` entry - // with the actual subscriber activeOperations.put(operationId, subscriber); stream.subscribe(subscriber); } @@ -239,8 +220,8 @@ private void sendKeepAlive() { } } - protected void sendCancelMessage(JsonObject message) { - String opId = message.getString("id"); + protected void sendCancelMessage(ObjectNode message) { + String opId = message.get("id").asText(); boolean cancelled = cancelOperation(opId); if (cancelled) { if (LOG.isDebugEnabled()) { @@ -255,8 +236,6 @@ protected void sendCancelMessage(JsonObject message) { } } - // cancel the operation with this id, returns true if it actually cancels an operation, - // false if no such operation is active private boolean cancelOperation(String opId) { Subscriber subscriber = activeOperations.remove(opId); if (subscriber != null) { @@ -281,7 +260,15 @@ private boolean validSubscription(String operationId) { return true; } - protected abstract void onMessage(JsonObject message); + protected static String writeValueAsString(ObjectNode node) { + try { + return OBJECT_MAPPER.writeValueAsString(node); + } catch (JacksonException e) { + throw new RuntimeException(e); + } + } + + protected abstract void onMessage(ObjectNode message); protected abstract void sendErrorMessage(String operationId, ExecutionResponse executionResponse) throws IOException; @@ -293,9 +280,6 @@ public Map getContext() { return context; } - /** - * The middleman that subscribes to an execution result and forwards its events to the websocket channel. - */ private class SubscriptionSubscriber implements Subscriber { private final AtomicReference subscription = new AtomicReference<>(); @@ -319,9 +303,8 @@ public void onNext(ExecutionResult executionResult) { ExecutionResponse executionResponse = new ExecutionResponse(executionResult); try { session.sendMessage( - createDataMessage(operationId, - executionResponse.getExecutionResultAsJsonPObject()) - .toString()); + writeValueAsString(createDataMessage(operationId, + executionResponse.getExecutionResultAsJsonObject()))); } catch (IOException e) { LOG.warn(e); } @@ -331,7 +314,6 @@ public void onNext(ExecutionResult executionResult) { @Override public void onError(Throwable t) { - // TODO: I'm not sure if/when this can happen. Even if the operation's root fails, it goes into `onNext`. t.printStackTrace(); } @@ -341,7 +323,7 @@ public void onComplete() { LOG.trace("Subscription with id " + operationId + " completed"); } try { - session.sendMessage(createCompleteMessage(operationId).toString()); + session.sendMessage(writeValueAsString(createCompleteMessage(operationId))); } catch (IOException e) { LOG.warn(e); } @@ -356,7 +338,6 @@ public void cancel() { } } - // dummy value to put into the `activeOperations` map for single-result operations private static final Subscriber SINGLE_RESULT_MARKER = new Subscriber() { @Override public void onSubscribe(Subscription s) { diff --git a/server/implementation/src/main/java/io/smallrye/graphql/websocket/graphqltransportws/GraphQLTransportWSSubprotocolHandler.java b/server/implementation/src/main/java/io/smallrye/graphql/websocket/graphqltransportws/GraphQLTransportWSSubprotocolHandler.java index 3d236f86f..e5aaa82e0 100644 --- a/server/implementation/src/main/java/io/smallrye/graphql/websocket/graphqltransportws/GraphQLTransportWSSubprotocolHandler.java +++ b/server/implementation/src/main/java/io/smallrye/graphql/websocket/graphqltransportws/GraphQLTransportWSSubprotocolHandler.java @@ -1,34 +1,28 @@ package io.smallrye.graphql.websocket.graphqltransportws; -import static io.smallrye.graphql.JsonProviderHolder.JSON_PROVIDER; - import java.io.IOException; import java.util.Collections; import java.util.Map; -import jakarta.json.JsonArray; -import jakarta.json.JsonBuilderFactory; -import jakarta.json.JsonObject; - import io.smallrye.graphql.execution.ExecutionResponse; import io.smallrye.graphql.execution.ExecutionService; import io.smallrye.graphql.websocket.AbstractGraphQLWebsocketHandler; import io.smallrye.graphql.websocket.GraphQLWebSocketSession; +import tools.jackson.databind.node.ArrayNode; +import tools.jackson.databind.node.ObjectNode; /** * Websocket subprotocol handler that implements the `graphql-transport-ws` subprotocol. */ public class GraphQLTransportWSSubprotocolHandler extends AbstractGraphQLWebsocketHandler { - private static final JsonBuilderFactory jsonBuilderFactory = JSON_PROVIDER.createBuilderFactory(null); - private final String pingMessage; private final String pongMessage; public GraphQLTransportWSSubprotocolHandler(GraphQLWebSocketSession session, Map context) { super(session, "next", context); - this.pongMessage = createPongMessage().toString(); - this.pingMessage = createPingMessage().toString(); + this.pongMessage = writeValueAsString(NODE_FACTORY.objectNode().put("type", "pong")); + this.pingMessage = writeValueAsString(NODE_FACTORY.objectNode().put("type", "ping")); } @Deprecated @@ -37,7 +31,7 @@ public GraphQLTransportWSSubprotocolHandler(GraphQLWebSocketSession session, Exe } @Override - protected void onMessage(JsonObject message) { + protected void onMessage(ObjectNode message) { if (message != null) { MessageType messageType = getMessageType(message); try { @@ -45,7 +39,8 @@ protected void onMessage(JsonObject message) { case CONNECTION_INIT: if (message.get("payload") != null) { getContext().put("init-payload", - Collections.unmodifiableMap((Map) message.get("payload"))); + Collections.unmodifiableMap((Map) OBJECT_MAPPER + .treeToValue(message.get("payload"), Map.class))); } sendConnectionAckMessage(); break; @@ -71,8 +66,8 @@ protected void onMessage(JsonObject message) { } } - private MessageType getMessageType(JsonObject message) { - return MessageType.fromString(message.getString("type")); + private MessageType getMessageType(ObjectNode message) { + return MessageType.fromString(message.get("type").asText()); } @Override @@ -82,16 +77,13 @@ protected void closeDueToConnectionNotInitialized() { @Override protected void sendErrorMessage(String operationId, ExecutionResponse executionResponse) throws IOException { - session.sendMessage(createErrorMessage(operationId, - executionResponse.getExecutionResultAsJsonPObject().getJsonArray("errors")).toString()); - } - - private JsonObject createErrorMessage(String operationId, JsonArray errors) { - return jsonBuilderFactory.createObjectBuilder() - .add("id", operationId) - .add("type", "error") - .add("payload", errors) - .build(); + ObjectNode result = executionResponse.getExecutionResultAsJsonObject(); + ArrayNode errors = (ArrayNode) result.get("errors"); + ObjectNode errorMessage = NODE_FACTORY.objectNode() + .put("id", operationId) + .put("type", "error"); + errorMessage.set("payload", errors); + session.sendMessage(writeValueAsString(errorMessage)); } private void sendPongMessage() throws IOException { @@ -103,16 +95,4 @@ protected String getPingMessage() { return pingMessage; } - private JsonObject createPongMessage() { - return jsonBuilderFactory.createObjectBuilder() - .add("type", "pong") - .build(); - } - - private JsonObject createPingMessage() { - return jsonBuilderFactory.createObjectBuilder() - .add("type", "ping") - .build(); - } - } diff --git a/server/implementation/src/main/java/io/smallrye/graphql/websocket/graphqlws/GraphQLWSSubprotocolHandler.java b/server/implementation/src/main/java/io/smallrye/graphql/websocket/graphqlws/GraphQLWSSubprotocolHandler.java index 30f462ec2..c996b3d0e 100644 --- a/server/implementation/src/main/java/io/smallrye/graphql/websocket/graphqlws/GraphQLWSSubprotocolHandler.java +++ b/server/implementation/src/main/java/io/smallrye/graphql/websocket/graphqlws/GraphQLWSSubprotocolHandler.java @@ -1,31 +1,27 @@ package io.smallrye.graphql.websocket.graphqlws; -import static io.smallrye.graphql.JsonProviderHolder.JSON_PROVIDER; - import java.io.IOException; import java.util.Collections; import java.util.Map; -import jakarta.json.JsonBuilderFactory; -import jakarta.json.JsonObject; - import io.smallrye.graphql.execution.ExecutionResponse; import io.smallrye.graphql.execution.ExecutionService; import io.smallrye.graphql.websocket.AbstractGraphQLWebsocketHandler; import io.smallrye.graphql.websocket.GraphQLWebSocketSession; +import tools.jackson.databind.node.ArrayNode; +import tools.jackson.databind.node.ObjectNode; /** * Websocket subprotocol handler that implements the `graphql-ws` subprotocol. */ public class GraphQLWSSubprotocolHandler extends AbstractGraphQLWebsocketHandler { - private static final JsonBuilderFactory jsonBuilderFactory = JSON_PROVIDER.createBuilderFactory(null); - private final String pingMessage; public GraphQLWSSubprotocolHandler(GraphQLWebSocketSession session, Map context) { super(session, MessageType.GQL_DATA.asString(), context); - pingMessage = createPingMessage().toString(); + pingMessage = writeValueAsString( + NODE_FACTORY.objectNode().put("type", MessageType.GQL_CONNECTION_KEEP_ALIVE.asString())); } @Deprecated @@ -34,7 +30,7 @@ public GraphQLWSSubprotocolHandler(GraphQLWebSocketSession session, ExecutionSer } @Override - protected void onMessage(JsonObject message) { + protected void onMessage(ObjectNode message) { if (message != null) { MessageType messageType = getMessageType(message); try { @@ -42,7 +38,8 @@ protected void onMessage(JsonObject message) { case GQL_CONNECTION_INIT: if (message.get("payload") != null) { getContext().put("init-payload", - Collections.unmodifiableMap((Map) message.get("payload"))); + Collections.unmodifiableMap((Map) OBJECT_MAPPER + .treeToValue(message.get("payload"), Map.class))); } sendConnectionAckMessage(); break; @@ -59,8 +56,8 @@ protected void onMessage(JsonObject message) { } } - private MessageType getMessageType(JsonObject message) { - return MessageType.fromString(message.getString("type")); + private MessageType getMessageType(ObjectNode message) { + return MessageType.fromString(message.get("type").asText()); } @Override @@ -75,25 +72,15 @@ protected String getPingMessage() { @Override protected void sendErrorMessage(String operationId, ExecutionResponse executionResponse) throws IOException { - session.sendMessage(createErrorMessage(operationId, - // TODO: the message should have a single error, but executionresult contains an array of errors? what do? - executionResponse.getExecutionResultAsJsonPObject().getJsonArray("errors").get(0) - .asJsonObject()) - .toString()); - } - - private JsonObject createErrorMessage(String operationId, JsonObject error) { - return jsonBuilderFactory.createObjectBuilder() - .add("id", operationId) - .add("type", MessageType.GQL_ERROR.asString()) - .add("payload", error) - .build(); - } - - private JsonObject createPingMessage() { - return jsonBuilderFactory.createObjectBuilder() - .add("type", MessageType.GQL_CONNECTION_KEEP_ALIVE.asString()) - .build(); + ObjectNode result = executionResponse.getExecutionResultAsJsonObject(); + ArrayNode errors = (ArrayNode) result.get("errors"); + // TODO: the message should have a single error, but executionresult contains an array of errors? what do? + ObjectNode error = (ObjectNode) errors.get(0); + ObjectNode errorMessage = NODE_FACTORY.objectNode() + .put("id", operationId) + .put("type", MessageType.GQL_ERROR.asString()); + errorMessage.set("payload", error); + session.sendMessage(writeValueAsString(errorMessage)); } } diff --git a/server/implementation/src/test/java/io/smallrye/graphql/execution/CompletableFutureTest.java b/server/implementation/src/test/java/io/smallrye/graphql/execution/CompletableFutureTest.java index 38110b3a3..d77f9fe0f 100644 --- a/server/implementation/src/test/java/io/smallrye/graphql/execution/CompletableFutureTest.java +++ b/server/implementation/src/test/java/io/smallrye/graphql/execution/CompletableFutureTest.java @@ -3,12 +3,12 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; -import jakarta.json.JsonObject; -import jakarta.json.JsonValue; - import org.jboss.jandex.IndexView; import org.junit.jupiter.api.Test; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.node.ObjectNode; + /** * Test a basic async endpoint * @@ -22,16 +22,16 @@ protected IndexView getIndex() { @Test public void testBasicQuery() { - JsonObject data = executeAndGetData(TEST_QUERY); + ObjectNode data = executeAndGetData(TEST_QUERY); - JsonValue jsonValue = data.get("book"); + JsonNode jsonValue = data.get("book"); assertNotNull(jsonValue); - JsonObject book = jsonValue.asJsonObject(); + ObjectNode book = (ObjectNode) jsonValue; assertNotNull(book); - assertFalse(book.isNull("title"), "title should not be null"); + assertFalse(book.has("title") && book.get("title").isNull(), "title should not be null"); } private static final String TEST_QUERY = "{\n" + diff --git a/server/implementation/src/test/java/io/smallrye/graphql/execution/ContextTest.java b/server/implementation/src/test/java/io/smallrye/graphql/execution/ContextTest.java index b50409cbd..2a56e076f 100644 --- a/server/implementation/src/test/java/io/smallrye/graphql/execution/ContextTest.java +++ b/server/implementation/src/test/java/io/smallrye/graphql/execution/ContextTest.java @@ -4,12 +4,11 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; -import jakarta.json.JsonObject; - import org.junit.jupiter.api.Test; import graphql.language.Document; import io.smallrye.graphql.execution.event.TestEventingService; +import tools.jackson.databind.node.ObjectNode; /** * Test the context @@ -20,25 +19,26 @@ public class ContextTest extends ExecutionTestBase { @Test public void testContext() { - JsonObject data = executeAndGetData(TEST_QUERY); + ObjectNode data = executeAndGetData(TEST_QUERY); - JsonObject testObject = data.getJsonObject("testContext"); + ObjectNode testObject = (ObjectNode) data.get("testContext"); assertNotNull(testObject); - assertFalse(testObject.isNull("executionId"), "executionId should not be null"); + assertFalse(testObject.has("executionId") && testObject.get("executionId").isNull(), + "executionId should not be null"); - assertFalse(testObject.isNull("path"), "path should not be null"); - assertEquals("/testContext", testObject.getString("path")); + assertFalse(testObject.has("path") && testObject.get("path").isNull(), "path should not be null"); + assertEquals("/testContext", testObject.get("path").asText()); - assertFalse(testObject.isNull("query"), "query should not be null"); + assertFalse(testObject.has("query") && testObject.get("query").isNull(), "query should not be null"); } @Test public void testUnwrapDocument() { TestEventingService.reset(); - JsonObject data = executeAndGetData(TEST_QUERY); + ObjectNode data = executeAndGetData(TEST_QUERY); Document document = TestEventingService.beforeExecuteContext.unwrap(Document.class); assertNotNull(document, "unwrapped document was null"); diff --git a/server/implementation/src/test/java/io/smallrye/graphql/execution/ExecutionGenericsTest.java b/server/implementation/src/test/java/io/smallrye/graphql/execution/ExecutionGenericsTest.java index 528e615f2..038b6c3d3 100644 --- a/server/implementation/src/test/java/io/smallrye/graphql/execution/ExecutionGenericsTest.java +++ b/server/implementation/src/test/java/io/smallrye/graphql/execution/ExecutionGenericsTest.java @@ -4,10 +4,10 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; -import jakarta.json.JsonObject; - import org.junit.jupiter.api.Test; +import tools.jackson.databind.node.ObjectNode; + /** * Test against generics * @@ -17,33 +17,33 @@ public class ExecutionGenericsTest extends ExecutionTestBase { @Test public void testQueryGeneric1() { - JsonObject data = executeAndGetData(TEST_QUERY_GENERIC_1); + ObjectNode data = executeAndGetData(TEST_QUERY_GENERIC_1); - JsonObject testObject = data.getJsonObject("generic1"); + ObjectNode testObject = (ObjectNode) data.get("generic1"); assertNotNull(testObject); - assertFalse(testObject.isNull("name"), "name should not be null"); - assertEquals("my name", testObject.getString("name")); + assertFalse(testObject.has("name") && testObject.get("name").isNull(), "name should not be null"); + assertEquals("my name", testObject.get("name").asText()); - assertFalse(testObject.isNull("param1"), "param1 should not be null"); - assertEquals("my param 1", testObject.getString("param1")); + assertFalse(testObject.has("param1") && testObject.get("param1").isNull(), "param1 should not be null"); + assertEquals("my param 1", testObject.get("param1").asText()); } @Test public void testQueryGeneric2() { - JsonObject data = executeAndGetData(TEST_QUERY_GENERIC_2); + ObjectNode data = executeAndGetData(TEST_QUERY_GENERIC_2); - JsonObject testObject = data.getJsonObject("generic2"); + ObjectNode testObject = (ObjectNode) data.get("generic2"); assertNotNull(testObject); - assertFalse(testObject.isNull("name"), "name should not be null"); - assertEquals("my name", testObject.getString("name")); + assertFalse(testObject.has("name") && testObject.get("name").isNull(), "name should not be null"); + assertEquals("my name", testObject.get("name").asText()); - assertFalse(testObject.isNull("param1"), "param1 should not be null"); - assertEquals(22, testObject.getInt("param1")); + assertFalse(testObject.has("param1") && testObject.get("param1").isNull(), "param1 should not be null"); + assertEquals(22, testObject.get("param1").asInt()); } diff --git a/server/implementation/src/test/java/io/smallrye/graphql/execution/ExecutionTest.java b/server/implementation/src/test/java/io/smallrye/graphql/execution/ExecutionTest.java index f8406f7ab..aef173379 100644 --- a/server/implementation/src/test/java/io/smallrye/graphql/execution/ExecutionTest.java +++ b/server/implementation/src/test/java/io/smallrye/graphql/execution/ExecutionTest.java @@ -2,12 +2,12 @@ import static org.junit.jupiter.api.Assertions.*; -import jakarta.json.*; - import org.assertj.core.api.AutoCloseableSoftAssertions; import org.junit.jupiter.api.Test; import io.smallrye.graphql.test.TestSourceConfiguration; +import tools.jackson.databind.node.ArrayNode; +import tools.jackson.databind.node.ObjectNode; /** * Test a basic query @@ -18,57 +18,59 @@ public class ExecutionTest extends ExecutionTestBase { @Test public void testBasicQuery() { - JsonObject data = executeAndGetData(TEST_QUERY); + ObjectNode data = executeAndGetData(TEST_QUERY); - JsonObject testObject = data.getJsonObject("testObject"); + ObjectNode testObject = (ObjectNode) data.get("testObject"); assertNotNull(testObject); - assertFalse(testObject.isNull("name"), "name should not be null"); - assertEquals("Phillip", testObject.getString("name")); + assertFalse(testObject.has("name") && testObject.get("name").isNull(), "name should not be null"); + assertEquals("Phillip", testObject.get("name").asText()); - assertFalse(testObject.isNull("id"), "id should not be null"); + assertFalse(testObject.has("id") && testObject.get("id").isNull(), "id should not be null"); // Testing source - assertFalse(testObject.isNull("timestamp"), "timestamp should not be null"); - assertFalse(testObject.get("timestamp").asJsonObject().isNull("value"), "timestamp value should not be null"); + assertFalse(testObject.has("timestamp") && testObject.get("timestamp").isNull(), "timestamp should not be null"); + assertFalse(((ObjectNode) testObject.get("timestamp")).has("value") + && ((ObjectNode) testObject.get("timestamp")).get("value").isNull(), "timestamp value should not be null"); } @Test public void testBasicListQuery() { - JsonObject data = executeAndGetData(TEST_LIST_QUERY); + ObjectNode data = executeAndGetData(TEST_LIST_QUERY); - JsonArray testObjects = data.getJsonArray("testObjects"); + ArrayNode testObjects = (ArrayNode) data.get("testObjects"); assertNotNull(testObjects); assertEquals(2, testObjects.size()); - JsonObject testObject = testObjects.getJsonObject(0); + ObjectNode testObject = (ObjectNode) testObjects.get(0); assertNotNull(testObject); - assertFalse(testObject.isNull("name"), "name should not be null"); - assertEquals("Phillip", testObject.getString("name")); + assertFalse(testObject.has("name") && testObject.get("name").isNull(), "name should not be null"); + assertEquals("Phillip", testObject.get("name").asText()); - assertFalse(testObject.isNull("id"), "id should not be null"); + assertFalse(testObject.has("id") && testObject.get("id").isNull(), "id should not be null"); // Testing batch - assertFalse(testObject.isNull("timestamp"), "timestamp should not be null"); - assertFalse(testObject.get("timestamp").asJsonObject().isNull("value"), "timestamp value should not be null"); + assertFalse(testObject.has("timestamp") && testObject.get("timestamp").isNull(), "timestamp should not be null"); + assertFalse(((ObjectNode) testObject.get("timestamp")).has("value") + && ((ObjectNode) testObject.get("timestamp")).get("value").isNull(), "timestamp value should not be null"); } @Test public void testBatchSourceConfigurationQuery() { - JsonObject data = executeAndGetData(TEST_BATCH_SOURCE_CONFIGURATION_QUERY); + ObjectNode data = executeAndGetData(TEST_BATCH_SOURCE_CONFIGURATION_QUERY); - Boolean active1 = data.getJsonArray("objectsWithConfig1").get(0).asJsonObject().getJsonObject("configuredSources") - .getJsonObject("configuration").getBoolean("active"); - Boolean active2 = data.getJsonArray("objectsWithConfig2").get(0).asJsonObject().getJsonObject("configuredSources") - .getJsonObject("configuration").getBoolean("active"); + Boolean active1 = ((ObjectNode) ((ObjectNode) ((ObjectNode) ((ArrayNode) data.get("objectsWithConfig1")).get(0)) + .get("configuredSources")).get("configuration")).get("active").asBoolean(); + Boolean active2 = ((ObjectNode) ((ObjectNode) ((ObjectNode) ((ArrayNode) data.get("objectsWithConfig2")).get(0)) + .get("configuredSources")).get("configuration")).get("active").asBoolean(); - var state1 = TestSourceConfiguration.TestSourceState.valueOf(data.getJsonArray("objectsWithConfig1").get(0) - .asJsonObject().getJsonObject("configuredSources").getJsonObject("configuration").getString("state")); - var state2 = TestSourceConfiguration.TestSourceState.valueOf(data.getJsonArray("objectsWithConfig2").get(0) - .asJsonObject().getJsonObject("configuredSources").getJsonObject("configuration").getString("state")); + var state1 = TestSourceConfiguration.TestSourceState.valueOf(((ObjectNode) ((ObjectNode) ((ObjectNode) ((ArrayNode) data + .get("objectsWithConfig1")).get(0)).get("configuredSources")).get("configuration")).get("state").asText()); + var state2 = TestSourceConfiguration.TestSourceState.valueOf(((ObjectNode) ((ObjectNode) ((ObjectNode) ((ArrayNode) data + .get("objectsWithConfig2")).get(0)).get("configuredSources")).get("configuration")).get("state").asText()); try (AutoCloseableSoftAssertions softly = new AutoCloseableSoftAssertions()) { softly.assertThat(active1).isNotEqualTo(active2); diff --git a/server/implementation/src/test/java/io/smallrye/graphql/execution/ExecutionTestBase.java b/server/implementation/src/test/java/io/smallrye/graphql/execution/ExecutionTestBase.java index 76bf2dbf9..f35abf605 100644 --- a/server/implementation/src/test/java/io/smallrye/graphql/execution/ExecutionTestBase.java +++ b/server/implementation/src/test/java/io/smallrye/graphql/execution/ExecutionTestBase.java @@ -2,10 +2,6 @@ import java.util.Map; -import jakarta.json.JsonArray; -import jakarta.json.JsonObject; -import jakarta.json.JsonValue; - import org.jboss.jandex.IndexView; import org.jboss.logging.Logger; import org.junit.jupiter.api.BeforeEach; @@ -14,6 +10,10 @@ import io.smallrye.graphql.bootstrap.Bootstrap; import io.smallrye.graphql.schema.SchemaBuilder; import io.smallrye.graphql.schema.model.Schema; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.node.ArrayNode; +import tools.jackson.databind.node.JsonNodeFactory; +import tools.jackson.databind.node.ObjectNode; /** * Base class for execution tests @@ -43,46 +43,46 @@ protected IndexView getIndex() { return Indexer.getAllTestIndex(); } - protected JsonObject executeAndGetData(String graphQL) { + protected ObjectNode executeAndGetData(String graphQL) { return executeAndGetData(graphQL, null); } - protected JsonObject executeAndGetData(String graphQL, Map context) { - JsonObject result = executeAndGetResult(graphQL, context); - JsonValue value = result.get(DATA); + protected ObjectNode executeAndGetData(String graphQL, Map context) { + ObjectNode result = executeAndGetResult(graphQL, context); + JsonNode value = result.get(DATA); if (value != null) { - return result.getJsonObject(DATA); + return (ObjectNode) result.get(DATA); } - return JsonObject.EMPTY_JSON_OBJECT; + return JsonNodeFactory.instance.objectNode(); } - protected JsonArray executeAndGetErrors(String graphQL) { + protected ArrayNode executeAndGetErrors(String graphQL) { return executeAndGetErrors(graphQL, null); } - protected JsonArray executeAndGetErrors(String graphQL, Map context) { - JsonObject result = executeAndGetResult(graphQL, context); - JsonValue value = result.get(ERRORS); + protected ArrayNode executeAndGetErrors(String graphQL, Map context) { + ObjectNode result = executeAndGetResult(graphQL, context); + JsonNode value = result.get(ERRORS); if (value != null) { - return result.getJsonArray(ERRORS); + return (ArrayNode) result.get(ERRORS); } return null; } - protected JsonObject executeAndGetExtensions(String graphQL) { + protected ObjectNode executeAndGetExtensions(String graphQL) { return executeAndGetExtensions(graphQL, null); } - protected JsonObject executeAndGetExtensions(String graphQL, Map context) { - JsonObject result = executeAndGetResult(graphQL, context); - JsonValue value = result.get(EXTENSIONS); + protected ObjectNode executeAndGetExtensions(String graphQL, Map context) { + ObjectNode result = executeAndGetResult(graphQL, context); + JsonNode value = result.get(EXTENSIONS); if (value != null) { - return result.getJsonObject(EXTENSIONS); + return (ObjectNode) result.get(EXTENSIONS); } return null; } - protected JsonObject executeAndGetResult(String graphQL, Map context) { + protected ObjectNode executeAndGetResult(String graphQL, Map context) { JsonObjectResponseWriter jsonObjectResponseWriter = new JsonObjectResponseWriter(graphQL); jsonObjectResponseWriter.logInput(); if (context == null) { diff --git a/server/implementation/src/test/java/io/smallrye/graphql/execution/ExecutionUnionsTest.java b/server/implementation/src/test/java/io/smallrye/graphql/execution/ExecutionUnionsTest.java index f36a24a30..f16448d3c 100644 --- a/server/implementation/src/test/java/io/smallrye/graphql/execution/ExecutionUnionsTest.java +++ b/server/implementation/src/test/java/io/smallrye/graphql/execution/ExecutionUnionsTest.java @@ -2,10 +2,10 @@ import static org.junit.jupiter.api.Assertions.*; -import jakarta.json.JsonObject; - import org.junit.jupiter.api.Test; +import tools.jackson.databind.node.ObjectNode; + /** * Test against unions * @@ -15,46 +15,46 @@ public class ExecutionUnionsTest extends ExecutionTestBase { @Test public void testBasicUnion() { - JsonObject data = executeAndGetData(TEST_BASIC_UNION); - JsonObject testObject = data.getJsonObject("basicUnion"); + ObjectNode data = executeAndGetData(TEST_BASIC_UNION); + ObjectNode testObject = (ObjectNode) data.get("basicUnion"); assertNotNull(testObject); - assertFalse(testObject.isNull("name"), "name should not be null"); - assertEquals("my name", testObject.getString("name")); + assertFalse(testObject.has("name") && testObject.get("name").isNull(), "name should not be null"); + assertEquals("my name", testObject.get("name").asText()); } @Test public void testUnionOfInterfacesReturningDirectImplementer() { - JsonObject data = executeAndGetData(TEST_NESTED_INTERFACE_DIRECT_IMPL); - JsonObject testObject = data.getJsonObject("unionOfInterfacesDirectImplementor"); + ObjectNode data = executeAndGetData(TEST_NESTED_INTERFACE_DIRECT_IMPL); + ObjectNode testObject = (ObjectNode) data.get("unionOfInterfacesDirectImplementor"); assertNotNull(testObject); - assertFalse(testObject.isNull("message"), "message should not be null"); - assertEquals("im in many unions", testObject.getString("message")); + assertFalse(testObject.has("message") && testObject.get("message").isNull(), "message should not be null"); + assertEquals("im in many unions", testObject.get("message").asText()); } @Test public void testUnionOfInterfacesReturningNestedInterfaceImpl1() { - JsonObject data = executeAndGetData(TEST_NESTED_INTERFACE_1); - JsonObject testObject = data.getJsonObject("unionOfInterfacesNestedInterface1"); + ObjectNode data = executeAndGetData(TEST_NESTED_INTERFACE_1); + ObjectNode testObject = (ObjectNode) data.get("unionOfInterfacesNestedInterface1"); assertNotNull(testObject); - assertFalse(testObject.isNull("name"), "name should not be null"); - assertEquals("my name", testObject.getString("name")); + assertFalse(testObject.has("name") && testObject.get("name").isNull(), "name should not be null"); + assertEquals("my name", testObject.get("name").asText()); } @Test public void testUnionOfInterfacesReturningNestedInterfaceImpl2() { - JsonObject data = executeAndGetData(TEST_NESTED_INTERFACE_2); - JsonObject testObject = data.getJsonObject("unionOfInterfacesNestedInterface2"); + ObjectNode data = executeAndGetData(TEST_NESTED_INTERFACE_2); + ObjectNode testObject = (ObjectNode) data.get("unionOfInterfacesNestedInterface2"); assertNotNull(testObject); - assertFalse(testObject.isNull("color"), "color should not be null"); - assertEquals("purple", testObject.getString("color")); + assertFalse(testObject.has("color") && testObject.get("color").isNull(), "color should not be null"); + assertEquals("purple", testObject.get("color").asText()); } private static final String TEST_BASIC_UNION = "{\n" + diff --git a/server/implementation/src/test/java/io/smallrye/graphql/execution/ExperimentalNamespaceTest.java b/server/implementation/src/test/java/io/smallrye/graphql/execution/ExperimentalNamespaceTest.java index 4c75555db..64cedb8e6 100644 --- a/server/implementation/src/test/java/io/smallrye/graphql/execution/ExperimentalNamespaceTest.java +++ b/server/implementation/src/test/java/io/smallrye/graphql/execution/ExperimentalNamespaceTest.java @@ -7,8 +7,6 @@ import java.io.InputStream; import java.util.stream.Stream; -import jakarta.json.JsonObject; - import org.jboss.jandex.IndexView; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -19,6 +17,7 @@ import io.smallrye.graphql.schema.model.Schema; import io.smallrye.graphql.test.namespace.ExperimentalNamespaceApi; import io.smallrye.graphql.test.namespace.ExperimentalNamespaceWithErrorApi; +import tools.jackson.databind.node.ObjectNode; /** * Test for Federated namespaces @@ -53,7 +52,7 @@ private static GraphQLSchema createGraphQLSchema(IndexView index) { return graphQLSchema; } - private static JsonObject executeAndGetResult(String graphQL) { + private static ObjectNode executeAndGetResult(String graphQL) { JsonObjectResponseWriter jsonObjectResponseWriter = new JsonObjectResponseWriter(graphQL); jsonObjectResponseWriter.logInput(); executionService.executeSync(jsonObjectResponseWriter.getInput(), jsonObjectResponseWriter); @@ -69,13 +68,13 @@ public void experimentalNamespaceTest() { Schema schema = SchemaBuilder.build(index); executionService = new ExecutionService(graphQLSchema, schema); - JsonObject jsonObject = executeAndGetResult(NAMESPACED_QUERY); + ObjectNode jsonObject = executeAndGetResult(NAMESPACED_QUERY); assertNotNull(jsonObject); - String result = jsonObject.getJsonObject("data") - .getJsonObject("admin") - .getJsonObject("users") - .getString("find"); + String result = ((ObjectNode) ((ObjectNode) ((ObjectNode) jsonObject.get("data")) + .get("admin")) + .get("users")) + .get("find").asText(); assertEquals(result, "AdminUsersFind"); } diff --git a/server/implementation/src/test/java/io/smallrye/graphql/execution/FederatedNamespaceTest.java b/server/implementation/src/test/java/io/smallrye/graphql/execution/FederatedNamespaceTest.java index 7d6d8e1ec..50ff95ed4 100644 --- a/server/implementation/src/test/java/io/smallrye/graphql/execution/FederatedNamespaceTest.java +++ b/server/implementation/src/test/java/io/smallrye/graphql/execution/FederatedNamespaceTest.java @@ -8,10 +8,6 @@ import java.util.function.BiFunction; import java.util.stream.Stream; -import jakarta.json.JsonObject; -import jakarta.json.JsonString; -import jakarta.json.JsonValue; - import org.jboss.jandex.IndexView; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -33,6 +29,9 @@ import io.smallrye.graphql.test.namespace.SourceNamespaceTestApi; import io.smallrye.graphql.test.namespace.UnamedModel; import io.smallrye.graphql.test.namespace.UnnamedTestApi; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.node.ArrayNode; +import tools.jackson.databind.node.ObjectNode; /** * Test for Federated namespaces @@ -92,7 +91,7 @@ private static GraphQLSchema createGraphQLSchema(IndexView index) { return graphQLSchema; } - private static JsonObject executeAndGetResult(String graphQL) { + private static ObjectNode executeAndGetResult(String graphQL) { JsonObjectResponseWriter jsonObjectResponseWriter = new JsonObjectResponseWriter(graphQL); jsonObjectResponseWriter.logInput(); executionService.executeSync(jsonObjectResponseWriter.getInput(), jsonObjectResponseWriter); @@ -101,14 +100,14 @@ private static JsonObject executeAndGetResult(String graphQL) { } private void test(String type, String id) { - JsonObject jsonObject = executeAndGetResult(TEST_QUERY.apply(type, id)); + ObjectNode jsonObject = executeAndGetResult(TEST_QUERY.apply(type, id)); assertNotNull(jsonObject); - JsonValue jsonValue = jsonObject.getJsonObject("data") - .getJsonArray("_entities") - .getJsonObject(0) + JsonNode jsonValue = ((ObjectNode) ((ArrayNode) ((ObjectNode) jsonObject.get("data")) + .get("_entities")) + .get(0)) .get("value"); - String value = ((JsonString) jsonValue).getString(); + String value = jsonValue.asText(); assertEquals(value, id); } @@ -131,23 +130,23 @@ public void findEntityWithSourceNamespace() { public void findEntityWithWithGroupedKeyAndNamespace() { String id = "grouped_key"; - JsonObject jsonObject = executeAndGetResult(GROUPED_KEY_QUERY.apply( + ObjectNode jsonObject = executeAndGetResult(GROUPED_KEY_QUERY.apply( NamedNamespaceWIthGroupingKeyModel.class.getSimpleName(), id)); assertNotNull(jsonObject); - JsonValue jsonValue = jsonObject.getJsonObject("data") - .getJsonArray("_entities") - .getJsonObject(0) + JsonNode jsonValue = ((ObjectNode) ((ArrayNode) ((ObjectNode) jsonObject.get("data")) + .get("_entities")) + .get(0)) .get("value"); - String value = ((JsonString) jsonValue).getString(); + String value = jsonValue.asText(); assertEquals(value, id); - jsonValue = jsonObject.getJsonObject("data") - .getJsonArray("_entities") - .getJsonObject(0) + jsonValue = ((ObjectNode) ((ArrayNode) ((ObjectNode) jsonObject.get("data")) + .get("_entities")) + .get(0)) .get("anotherId"); - String anotherId = ((JsonString) jsonValue).getString(); + String anotherId = jsonValue.asText(); assertEquals(anotherId, "otherKey_" + id); } diff --git a/server/implementation/src/test/java/io/smallrye/graphql/execution/FederatedTracingTest.java b/server/implementation/src/test/java/io/smallrye/graphql/execution/FederatedTracingTest.java index ccd2aaf1a..3c8813edb 100644 --- a/server/implementation/src/test/java/io/smallrye/graphql/execution/FederatedTracingTest.java +++ b/server/implementation/src/test/java/io/smallrye/graphql/execution/FederatedTracingTest.java @@ -11,13 +11,12 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import jakarta.json.JsonObject; -import jakarta.json.JsonString; - import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import io.smallrye.graphql.spi.config.Config; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.node.ObjectNode; /** * Test for Federated tracing @@ -39,7 +38,7 @@ public void testFederatedTracingEnabledWithFederationDisabled() { Map metaData = createMetaData(ftKey); - JsonObject extensions = executeAndGetExtensions(TEST_QUERY, metaData); + ObjectNode extensions = executeAndGetExtensions(TEST_QUERY, metaData); assertNull(extensions); } @@ -50,13 +49,13 @@ public void testFederatedTracingEnabledWithFederationEnabled() { Map metaData = createMetaData(ftKey); - JsonObject extensions = executeAndGetExtensions(TEST_QUERY, metaData); + ObjectNode extensions = executeAndGetExtensions(TEST_QUERY, metaData); assertNotNull(extensions); - assertTrue(extensions.containsKey(ftKey)); + assertTrue(extensions.has(ftKey)); - JsonString ftValue = extensions.getJsonString(ftKey); + JsonNode ftValue = extensions.get(ftKey); assertNotNull(ftValue); - assertFalse(ftValue.getString().isEmpty()); + assertFalse(ftValue.asText().isEmpty()); } @Test @@ -66,7 +65,7 @@ public void testFederatedTracingDisabledWithFederationEnabled() { Map metaData = createMetaData(ftKey); - JsonObject extensions = executeAndGetExtensions(TEST_QUERY, metaData); + ObjectNode extensions = executeAndGetExtensions(TEST_QUERY, metaData); assertNull(extensions); } diff --git a/server/implementation/src/test/java/io/smallrye/graphql/execution/GroupingTest.java b/server/implementation/src/test/java/io/smallrye/graphql/execution/GroupingTest.java index c1d843338..48f95161e 100644 --- a/server/implementation/src/test/java/io/smallrye/graphql/execution/GroupingTest.java +++ b/server/implementation/src/test/java/io/smallrye/graphql/execution/GroupingTest.java @@ -3,11 +3,11 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; -import jakarta.json.JsonObject; - import org.jboss.jandex.IndexView; import org.junit.jupiter.api.Test; +import tools.jackson.databind.node.ObjectNode; + /** * Test a basic grouping * @@ -21,13 +21,13 @@ protected IndexView getIndex() { @Test public void testBasicQuery() { - JsonObject data = executeAndGetData(TEST_QUERY); + ObjectNode data = executeAndGetData(TEST_QUERY); - JsonObject books = data.getJsonObject("books"); + ObjectNode books = (ObjectNode) data.get("books"); assertNotNull(books); - assertFalse(books.isNull("book"), "book should not be null"); + assertFalse(books.has("book") && books.get("book").isNull(), "book should not be null"); } private static final String TEST_QUERY = "{\n" + diff --git a/server/implementation/src/test/java/io/smallrye/graphql/execution/JsonbCreatorTest.java b/server/implementation/src/test/java/io/smallrye/graphql/execution/JsonbCreatorTest.java index 151cca4af..ddb8871e8 100644 --- a/server/implementation/src/test/java/io/smallrye/graphql/execution/JsonbCreatorTest.java +++ b/server/implementation/src/test/java/io/smallrye/graphql/execution/JsonbCreatorTest.java @@ -2,13 +2,11 @@ import static org.junit.jupiter.api.Assertions.*; -import jakarta.json.JsonObject; -import jakarta.json.JsonValue; - import org.jboss.jandex.IndexView; import org.junit.jupiter.api.Test; import io.smallrye.graphql.test.jsonbCreator.CreatorApi; +import tools.jackson.databind.node.ObjectNode; public class JsonbCreatorTest extends ExecutionTestBase { @@ -18,97 +16,97 @@ protected IndexView getIndex() { @Test public void testWithValue() { - JsonObject data = executeAndGetData("{\n" + ObjectNode data = executeAndGetData("{\n" + " withJsonbCreator(input: {field: \"A\"}) {\n" + " field\n" + " }\n" + "}"); - JsonObject testObject = data.getJsonObject("withJsonbCreator"); + ObjectNode testObject = (ObjectNode) data.get("withJsonbCreator"); assertNotNull(testObject); - String field = testObject.getJsonString("field").getString(); + String field = testObject.get("field").asText(); assertEquals("A", field); } @Test public void testWithStaticFactory() { - JsonObject data = executeAndGetData("{\n" + ObjectNode data = executeAndGetData("{\n" + " withStaticFactory(input: {field: \"A\"}) {\n" + " field\n" + " }\n" + "}"); - JsonObject testObject = data.getJsonObject("withStaticFactory"); + ObjectNode testObject = (ObjectNode) data.get("withStaticFactory"); assertNotNull(testObject); - String field = testObject.getJsonString("field").getString(); + String field = testObject.get("field").asText(); assertEquals("A", field); } @Test public void testWithMissingValue() { - JsonObject data = executeAndGetData("{\n" + ObjectNode data = executeAndGetData("{\n" + " withJsonbCreator(input: {}) {\n" + " field\n" + " }\n" + "}"); - JsonObject testObject = data.getJsonObject("withJsonbCreator"); + ObjectNode testObject = (ObjectNode) data.get("withJsonbCreator"); assertNotNull(testObject); - assertEquals(JsonValue.ValueType.NULL, testObject.get("field").getValueType()); + assertTrue(testObject.get("field").isNull()); } @Test public void testWithTransformation() { - JsonObject data = executeAndGetData("{\n" + ObjectNode data = executeAndGetData("{\n" + " creatorWithTransformation(input: {field: \"1\"}) {\n" + " field\n" + " }\n" + "}"); - JsonObject testObject = data.getJsonObject("creatorWithTransformation"); + ObjectNode testObject = (ObjectNode) data.get("creatorWithTransformation"); assertNotNull(testObject); - String field = testObject.getJsonString("field").getString(); + String field = testObject.get("field").asText(); assertEquals("1", field); } @Test public void testWithDefault() { - JsonObject data = executeAndGetData("{\n" + ObjectNode data = executeAndGetData("{\n" + " creatorWithFieldDefault(input: {}) {\n" + " field\n" + " }\n" + "}"); - JsonObject testObject = data.getJsonObject("creatorWithFieldDefault"); + ObjectNode testObject = (ObjectNode) data.get("creatorWithFieldDefault"); assertNotNull(testObject); - String field = testObject.getJsonString("field").getString(); + String field = testObject.get("field").asText(); assertEquals("Some value", field); } @Test public void testWithParameterDefault() { - JsonObject data = executeAndGetData("{\n" + ObjectNode data = executeAndGetData("{\n" + " creatorWithParameterDefault(input: {}) {\n" + " field\n" + " }\n" + "}"); - JsonObject testObject = data.getJsonObject("creatorWithParameterDefault"); + ObjectNode testObject = (ObjectNode) data.get("creatorWithParameterDefault"); assertNotNull(testObject); - String field = testObject.getJsonString("field").getString(); + String field = testObject.get("field").asText(); assertEquals("Some value", field); } @Test public void testWithMultipleParameters() { - JsonObject data = executeAndGetData("{\n" + ObjectNode data = executeAndGetData("{\n" + " creatorWithMultipleParameters(input: {string: \"Foobar\", localDate:\"2021-01-01\"}) {\n" + " string\n" + " integer\n" @@ -116,16 +114,15 @@ public void testWithMultipleParameters() { + " }\n" + "}"); - JsonObject testObject = data.getJsonObject("creatorWithMultipleParameters"); + ObjectNode testObject = (ObjectNode) data.get("creatorWithMultipleParameters"); assertNotNull(testObject); - String string = testObject.getJsonString("string").getString(); + String string = testObject.get("string").asText(); assertEquals("Foobar", string); - JsonValue integer = testObject.get("integer"); - assertEquals(JsonValue.ValueType.NULL, integer.getValueType()); + assertTrue(testObject.has("integer") && testObject.get("integer").isNull()); - String localDate = testObject.getJsonString("localDate").getString(); + String localDate = testObject.get("localDate").asText(); assertEquals("2021-01-01", localDate); } } diff --git a/server/implementation/src/test/java/io/smallrye/graphql/execution/MappingTest.java b/server/implementation/src/test/java/io/smallrye/graphql/execution/MappingTest.java index 532c273ba..63ba9ae3d 100644 --- a/server/implementation/src/test/java/io/smallrye/graphql/execution/MappingTest.java +++ b/server/implementation/src/test/java/io/smallrye/graphql/execution/MappingTest.java @@ -9,44 +9,42 @@ import java.util.List; import java.util.Map; -import jakarta.json.Json; -import jakarta.json.JsonObject; - import org.junit.jupiter.api.Test; import io.smallrye.graphql.api.Context; +import tools.jackson.databind.node.JsonNodeFactory; +import tools.jackson.databind.node.ObjectNode; public class MappingTest { + private static final JsonNodeFactory NF = JsonNodeFactory.instance; + @Test public void toMap_string() { Map expected = Collections.singletonMap("firstName", "John"); - JsonObject jo = Json.createObjectBuilder() - .add("firstName", "John") - .build(); - assertEquals(expected, Context.VariablesParser.toMap(toInput(jo).getJsonObject("variables")).get()); + ObjectNode jo = NF.objectNode().put("firstName", "John"); + assertEquals(expected, Context.VariablesParser.toMap(toInput(jo).get("variables")).get()); } @Test public void toMap_boolean() { Map expected = Collections.singletonMap("certified", true); - JsonObject jo = Json.createObjectBuilder().add("certified", true).build(); - assertEquals(expected, Context.VariablesParser.toMap(toInput(jo).getJsonObject("variables")).get()); + ObjectNode jo = NF.objectNode().put("certified", true); + assertEquals(expected, Context.VariablesParser.toMap(toInput(jo).get("variables")).get()); expected = Collections.singletonMap("refurbished", false); - jo = Json.createObjectBuilder().add("refurbished", false).build(); - assertEquals(expected, Context.VariablesParser.toMap(toInput(jo).getJsonObject("variables")).get()); + jo = NF.objectNode().put("refurbished", false); + assertEquals(expected, Context.VariablesParser.toMap(toInput(jo).get("variables")).get()); } @Test public void toMap_numbers() { - JsonObject jo = Json.createObjectBuilder() - .add("weight", 17.003) - .add("block_count", 1025) - .add("bigNum", 1234567890987654321L) - .add("float", 0.00000023f) - .build(); - Map returned = Context.VariablesParser.toMap(toInput(jo).getJsonObject("variables")).get(); + ObjectNode jo = NF.objectNode() + .put("weight", 17.003) + .put("block_count", 1025) + .put("bigNum", 1234567890987654321L) + .put("float", 0.00000023f); + Map returned = Context.VariablesParser.toMap(toInput(jo).get("variables")).get(); assertEquals(4, returned.size()); assertEquals(17.003, ((BigDecimal) returned.get("weight")).doubleValue(), 0.001); assertEquals(1025, ((BigDecimal) returned.get("block_count")).intValue()); @@ -69,55 +67,35 @@ public void toMap_objects() { childMap2.put("state", "AL"); childMap2.put("zip", "99992"); - JsonObject jo = Json.createObjectBuilder() - .add("customer", Json.createObjectBuilder() - .add("name", "Joe Busy") - .add("address", Json.createObjectBuilder() - .add("street_num", "1003") - .add("street_name", "Elm Boulevard") - .add("city", "Nowhere") - .add("state", "AL") - .add("zip", "99992") - .build()) - .add("acct", 12345) - .build()) - .build(); - assertEquals(expected, Context.VariablesParser.toMap(toInput(jo).getJsonObject("variables")).get()); + ObjectNode address = NF.objectNode() + .put("street_num", "1003") + .put("street_name", "Elm Boulevard") + .put("city", "Nowhere") + .put("state", "AL") + .put("zip", "99992"); + ObjectNode customer = NF.objectNode() + .put("name", "Joe Busy"); + customer.set("address", address); + customer.put("acct", 12345); + ObjectNode jo = NF.objectNode(); + jo.set("customer", customer); + + assertEquals(expected, Context.VariablesParser.toMap(toInput(jo).get("variables")).get()); } @SuppressWarnings("unchecked") @Test public void toMap_array() { - JsonObject jo = Json.createObjectBuilder() - .add("names", Json.createArrayBuilder() - .add("bob") - .add("tom") - .add("dick") - .build()) - .add("games", Json.createArrayBuilder() - .add("basketball") - .add("hockey") - .add("rugby") - .add("baseball") - .build()) - .add("numbers", Json.createArrayBuilder() - .add(3.14) - .add(17) - .add(20.003f) - .add(98765432123456789L) - .build()) - .add("mixed", Json.createArrayBuilder() - .add(65535) - .add("fred") - .add(true) - .add(Json.createObjectBuilder() - .add("name", "Widget") - .add("length", 29) - .build()) - .build()) - .add("empty", Json.createArrayBuilder().build()) - .build(); - Map returned = Context.VariablesParser.toMap(toInput(jo).getJsonObject("variables")).get(); + ObjectNode jo = NF.objectNode(); + jo.set("names", NF.arrayNode().add("bob").add("tom").add("dick")); + jo.set("games", NF.arrayNode().add("basketball").add("hockey").add("rugby").add("baseball")); + jo.set("numbers", NF.arrayNode().add(3.14).add(17).add(20.003f).add(98765432123456789L)); + + ObjectNode widget = NF.objectNode().put("name", "Widget").put("length", 29); + jo.set("mixed", NF.arrayNode().add(65535).add("fred").add(true).add(widget)); + jo.set("empty", NF.arrayNode()); + + Map returned = Context.VariablesParser.toMap(toInput(jo).get("variables")).get(); assertEquals(5, returned.size()); assertEquals(Arrays.asList("bob", "tom", "dick"), returned.get("names")); assertEquals(Arrays.asList("basketball", "hockey", "rugby", "baseball"), returned.get("games")); @@ -142,7 +120,9 @@ public void toMap_array() { assertEquals(Collections.emptyList(), returned.get("empty")); } - private JsonObject toInput(JsonObject jo) { - return Json.createObjectBuilder().add("variables", jo).build(); + private ObjectNode toInput(ObjectNode jo) { + ObjectNode input = NF.objectNode(); + input.set("variables", jo); + return input; } } diff --git a/server/implementation/src/test/java/io/smallrye/graphql/execution/MutinyTest.java b/server/implementation/src/test/java/io/smallrye/graphql/execution/MutinyTest.java index 0e92f107f..b74fa7b2b 100644 --- a/server/implementation/src/test/java/io/smallrye/graphql/execution/MutinyTest.java +++ b/server/implementation/src/test/java/io/smallrye/graphql/execution/MutinyTest.java @@ -4,14 +4,13 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; -import jakarta.json.JsonArray; -import jakarta.json.JsonObject; -import jakarta.json.JsonValue; - import org.jboss.jandex.IndexView; import org.junit.jupiter.api.Test; import io.smallrye.graphql.test.mutiny.CustomException; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.node.ArrayNode; +import tools.jackson.databind.node.ObjectNode; public class MutinyTest extends ExecutionTestBase { @@ -21,29 +20,30 @@ protected IndexView getIndex() { @Test public void testBasicQuery() { - JsonObject data = executeAndGetData(TEST_QUERY); + ObjectNode data = executeAndGetData(TEST_QUERY); - JsonValue jsonValue = data.get("book"); + JsonNode jsonValue = data.get("book"); assertNotNull(jsonValue); - JsonObject book = jsonValue.asJsonObject(); + ObjectNode book = (ObjectNode) jsonValue; assertNotNull(book); - assertFalse(book.isNull("title"), "title should not be null"); + assertFalse(book.has("title") && book.get("title").isNull(), "title should not be null"); } @Test public void testFailureQuery() { - JsonArray errors = executeAndGetErrors(FAILURE_TEST_QUERY); + ArrayNode errors = executeAndGetErrors(FAILURE_TEST_QUERY); assertNotNull(errors); assertEquals(errors.size(), 1); - var extensions = errors.get(0).asJsonObject().getJsonObject("extensions"); - assertEquals("custom-error", extensions.getString("code"), "error code"); - assertEquals(CustomException.class.getName(), extensions.getString("exception"), "exception"); - assertEquals("DataFetchingException", extensions.getString("classification"), "classification"); - assertEquals(CustomException.class.getSimpleName().length(), extensions.getInt("test-extension"), "test extension"); + ObjectNode extensions = (ObjectNode) ((ObjectNode) errors.get(0)).get("extensions"); + assertEquals("custom-error", extensions.get("code").asText(), "error code"); + assertEquals(CustomException.class.getName(), extensions.get("exception").asText(), "exception"); + assertEquals("DataFetchingException", extensions.get("classification").asText(), "classification"); + assertEquals(CustomException.class.getSimpleName().length(), extensions.get("test-extension").asInt(), + "test extension"); } private static final String TEST_QUERY = "{\n" + diff --git a/server/implementation/src/test/java/io/smallrye/graphql/execution/ResolverTest.java b/server/implementation/src/test/java/io/smallrye/graphql/execution/ResolverTest.java index 278f0585f..04e4f61a5 100644 --- a/server/implementation/src/test/java/io/smallrye/graphql/execution/ResolverTest.java +++ b/server/implementation/src/test/java/io/smallrye/graphql/execution/ResolverTest.java @@ -8,10 +8,6 @@ import java.io.InputStream; import java.util.stream.Stream; -import jakarta.json.JsonObject; -import jakarta.json.JsonString; -import jakarta.json.JsonValue; - import org.jboss.jandex.IndexView; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -29,6 +25,9 @@ import io.smallrye.graphql.spi.config.Config; import io.smallrye.graphql.test.resolver.ExtendedApi; import io.smallrye.graphql.test.resolver.ExtendedType; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.node.ArrayNode; +import tools.jackson.databind.node.ObjectNode; /** * Test for Federated namespaces @@ -84,7 +83,7 @@ private static GraphQLSchema createGraphQLSchema(IndexView index) { return graphQLSchema; } - private static JsonObject executeAndGetResult(String graphQL) { + private static ObjectNode executeAndGetResult(String graphQL) { JsonObjectResponseWriter jsonObjectResponseWriter = new JsonObjectResponseWriter(graphQL); jsonObjectResponseWriter.logInput(); executionService.executeSync(jsonObjectResponseWriter.getInput(), jsonObjectResponseWriter); @@ -94,38 +93,38 @@ private static JsonObject executeAndGetResult(String graphQL) { @Test public void findByIdTest() { - JsonObject jsonObject = executeAndGetResult(TEST_ID_QUERY); + ObjectNode jsonObject = executeAndGetResult(TEST_ID_QUERY); assertNotNull(jsonObject); - JsonValue jsonValue = jsonObject.getJsonObject("data") - .getJsonArray("_entities") - .getJsonObject(0) + JsonNode jsonValue = ((ArrayNode) ((ObjectNode) jsonObject.get("data")) + .get("_entities")) + .get(0) .get("id"); - assertEquals(((JsonString) jsonValue).getString(), "id"); + assertEquals(jsonValue.asText(), "id"); - jsonValue = jsonObject.getJsonObject("data") - .getJsonArray("_entities") - .getJsonObject(0) + jsonValue = ((ArrayNode) ((ObjectNode) jsonObject.get("data")) + .get("_entities")) + .get(0) .get("value"); assertNull(jsonValue); } @Test public void extendsTest() { - JsonObject jsonObject = executeAndGetResult(TEST_ID_NAME_KEY_QUERY); + ObjectNode jsonObject = executeAndGetResult(TEST_ID_NAME_KEY_QUERY); assertNotNull(jsonObject); - JsonValue jsonValue = jsonObject.getJsonObject("data") - .getJsonArray("_entities") - .getJsonObject(0) + JsonNode jsonValue = ((ArrayNode) ((ObjectNode) jsonObject.get("data")) + .get("_entities")) + .get(0) .get("id"); - assertEquals(((JsonString) jsonValue).getString(), "id"); + assertEquals(jsonValue.asText(), "id"); - jsonValue = jsonObject.getJsonObject("data") - .getJsonArray("_entities") - .getJsonObject(0) + jsonValue = ((ArrayNode) ((ObjectNode) jsonObject.get("data")) + .get("_entities")) + .get(0) .get("value"); - assertEquals(((JsonString) jsonValue).getString(), "idnamekey"); + assertEquals(jsonValue.asText(), "idnamekey"); } private static final String TEST_ID_QUERY = "query {\n" + diff --git a/server/implementation/src/test/java/io/smallrye/graphql/execution/TestErrorExtensionProvider.java b/server/implementation/src/test/java/io/smallrye/graphql/execution/TestErrorExtensionProvider.java index d33305d8c..49944b3f9 100644 --- a/server/implementation/src/test/java/io/smallrye/graphql/execution/TestErrorExtensionProvider.java +++ b/server/implementation/src/test/java/io/smallrye/graphql/execution/TestErrorExtensionProvider.java @@ -1,9 +1,8 @@ package io.smallrye.graphql.execution; -import jakarta.json.Json; -import jakarta.json.JsonNumber; - import io.smallrye.graphql.api.ErrorExtensionProvider; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.node.JsonNodeFactory; public class TestErrorExtensionProvider implements ErrorExtensionProvider { @Override @@ -12,7 +11,7 @@ public String getKey() { } @Override - public JsonNumber mapValueFrom(Throwable exception) { - return Json.createValue(exception.getClass().getSimpleName().length()); + public JsonNode mapValueFrom(Throwable exception) { + return JsonNodeFactory.instance.numberNode(exception.getClass().getSimpleName().length()); } } diff --git a/server/integration-tests/src/test/java/io/smallrye/graphql/tests/GraphQLAssured.java b/server/integration-tests/src/test/java/io/smallrye/graphql/tests/GraphQLAssured.java index f051e0104..7b9a8b032 100644 --- a/server/integration-tests/src/test/java/io/smallrye/graphql/tests/GraphQLAssured.java +++ b/server/integration-tests/src/test/java/io/smallrye/graphql/tests/GraphQLAssured.java @@ -1,18 +1,17 @@ package io.smallrye.graphql.tests; -import java.io.StringReader; import java.net.URL; import java.util.Map; -import jakarta.json.Json; -import jakarta.json.JsonObject; -import jakarta.json.JsonObjectBuilder; -import jakarta.json.JsonReader; - import io.restassured.RestAssured; import io.restassured.config.EncoderConfig; import io.restassured.http.ContentType; import io.restassured.specification.RequestSpecification; +import tools.jackson.core.JacksonException; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; +import tools.jackson.databind.node.JsonNodeFactory; +import tools.jackson.databind.node.ObjectNode; /** * Using RestAssured for GraphQL @@ -23,6 +22,8 @@ public class GraphQLAssured { private static final String MEDIATYPE_JSON = "application/json"; private static final String MEDIATYPE_GRAPHQL = "application/graphql"; + private static final ObjectMapper OBJECT_MAPPER = JsonMapper.builder().build(); + private static final JsonNodeFactory NODE_FACTORY = JsonNodeFactory.instance; protected URL testingURL; @@ -88,27 +89,28 @@ public String postAsGraphQL(String request, Map queryparams) { } private String getPayload(String query, String variables) { - JsonObject jsonObject = createRequestBody(query, variables); - return jsonObject.toString(); + ObjectNode jsonObject = createRequestBody(query, variables); + try { + return OBJECT_MAPPER.writeValueAsString(jsonObject); + } catch (JacksonException e) { + throw new RuntimeException(e); + } } - private JsonObject createRequestBody(String graphQL, String variables) { - // Create the request - - JsonObject vjo = Json.createObjectBuilder().build(); + private ObjectNode createRequestBody(String graphQL, String variables) { + ObjectNode vjo = NODE_FACTORY.objectNode(); if (variables != null && !variables.isEmpty()) { - try (JsonReader jsonReader = Json.createReader(new StringReader(variables))) { - vjo = jsonReader.readObject(); - } + vjo = (ObjectNode) OBJECT_MAPPER.readTree(variables); } - JsonObjectBuilder job = Json.createObjectBuilder(); + ObjectNode job = NODE_FACTORY.objectNode(); if (graphQL != null && !graphQL.isEmpty()) { - job.add(QUERY, graphQL); + job.put(QUERY, graphQL); } - return job.add(VARIABLES, vjo).build(); + job.set(VARIABLES, vjo); + return job; } private static final String QUERY = "query"; diff --git a/server/tck/src/test/java/io/smallrye/graphql/TestInterceptor.java b/server/tck/src/test/java/io/smallrye/graphql/TestInterceptor.java index e6771bb43..eeb4a73cc 100644 --- a/server/tck/src/test/java/io/smallrye/graphql/TestInterceptor.java +++ b/server/tck/src/test/java/io/smallrye/graphql/TestInterceptor.java @@ -1,7 +1,6 @@ package io.smallrye.graphql; import java.io.IOException; -import java.io.StringReader; import java.nio.file.DirectoryStream; import java.nio.file.FileVisitResult; import java.nio.file.FileVisitor; @@ -20,16 +19,15 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import jakarta.json.Json; -import jakarta.json.JsonObject; -import jakarta.json.JsonReader; - import org.eclipse.microprofile.graphql.tck.dynamic.ExecutionDynamicTest; import org.eclipse.microprofile.graphql.tck.dynamic.SchemaDynamicValidityTest; import org.jboss.logging.Logger; import org.testng.ITestResult; import org.testng.TestListenerAdapter; +import tools.jackson.core.type.TypeReference; +import tools.jackson.databind.ObjectMapper; + /** * This allows us to override the input and output of the spec's TCK. * Sometimes this is needed when the spec should be updated but can not yet @@ -183,7 +181,7 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) } case "variables.json": { String content = getFileContent(file); - testData.setVariables(toJsonObject(content)); + testData.setVariables(toVariablesMap(content)); break; } case "test.properties": { @@ -291,12 +289,18 @@ private static boolean isHeader(String line) { return line.trim().startsWith(COMMENT); } - private JsonObject toJsonObject(String jsonString) { + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final TypeReference> MAP_TYPE = new TypeReference<>() { + }; + + private Map toVariablesMap(String jsonString) { if (jsonString == null || jsonString.isEmpty()) { return null; } - try (JsonReader jsonReader = Json.createReader(new StringReader(jsonString))) { - return jsonReader.readObject(); + try { + return OBJECT_MAPPER.readValue(jsonString, MAP_TYPE); + } catch (Exception e) { + throw new RuntimeException(e); } } diff --git a/server/tck/src/test/java/io/smallrye/graphql/test/apps/context/api/ContextApi.java b/server/tck/src/test/java/io/smallrye/graphql/test/apps/context/api/ContextApi.java index f93baf30f..9abefa1e5 100644 --- a/server/tck/src/test/java/io/smallrye/graphql/test/apps/context/api/ContextApi.java +++ b/server/tck/src/test/java/io/smallrye/graphql/test/apps/context/api/ContextApi.java @@ -1,14 +1,9 @@ package io.smallrye.graphql.test.apps.context.api; -import java.io.IOException; -import java.io.StringWriter; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import jakarta.inject.Inject; -import jakarta.json.Json; -import jakarta.json.JsonArray; -import jakarta.json.JsonWriter; import org.eclipse.microprofile.context.ThreadContext; import org.eclipse.microprofile.graphql.DefaultValue; @@ -17,6 +12,8 @@ import org.eclipse.microprofile.graphql.Source; import io.smallrye.graphql.api.Context; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.node.ArrayNode; @GraphQLApi public class ContextApi { @@ -89,14 +86,11 @@ public CompletionStage asyncOperationType(@Source Pojo pojo) { threadContext.currentContextExecutor())); } - private String toString(JsonArray jsonArray) { - try (StringWriter sw = new StringWriter(); - JsonWriter writer = Json.createWriter(sw)) { - writer.writeArray(jsonArray); - writer.close(); - return sw.toString(); - } catch (IOException ex) { - throw new RuntimeException(ex); + private String toString(ArrayNode arrayNode) { + try { + return new ObjectMapper().writeValueAsString(arrayNode); + } catch (tools.jackson.core.JacksonException e) { + throw new RuntimeException(e); } } } diff --git a/server/tck/src/test/java/io/smallrye/graphql/test/apps/jsonp/api/JsonPApi.java b/server/tck/src/test/java/io/smallrye/graphql/test/apps/jsonp/api/JsonPApi.java index 929256c01..38f1e13c9 100644 --- a/server/tck/src/test/java/io/smallrye/graphql/test/apps/jsonp/api/JsonPApi.java +++ b/server/tck/src/test/java/io/smallrye/graphql/test/apps/jsonp/api/JsonPApi.java @@ -1,30 +1,28 @@ package io.smallrye.graphql.test.apps.jsonp.api; -import jakarta.json.Json; -import jakarta.json.JsonArray; -import jakarta.json.JsonArrayBuilder; -import jakarta.json.JsonObject; -import jakarta.json.JsonObjectBuilder; - import org.eclipse.microprofile.graphql.GraphQLApi; import org.eclipse.microprofile.graphql.Query; +import tools.jackson.databind.node.ArrayNode; +import tools.jackson.databind.node.JsonNodeFactory; +import tools.jackson.databind.node.ObjectNode; + @GraphQLApi public class JsonPApi { @Query("jsonpObject") - public JsonObject getRawToken() { - JsonObjectBuilder builder = Json.createObjectBuilder(); - builder.add("foo", "bar"); - return builder.build(); + public ObjectNode getRawToken() { + ObjectNode node = JsonNodeFactory.instance.objectNode(); + node.put("foo", "bar"); + return node; } @Query("jsonpArray") - public JsonArray getRawTokens() { - JsonArrayBuilder builder = Json.createArrayBuilder(); - builder.add("foo"); - builder.add("bar"); - return builder.build(); + public ArrayNode getRawTokens() { + ArrayNode array = JsonNodeFactory.instance.arrayNode(); + array.add("foo"); + array.add("bar"); + return array; } @Query("jsonpField") diff --git a/server/tck/src/test/java/io/smallrye/graphql/test/apps/jsonp/api/Token.java b/server/tck/src/test/java/io/smallrye/graphql/test/apps/jsonp/api/Token.java index c46fb8339..d60a6aad2 100644 --- a/server/tck/src/test/java/io/smallrye/graphql/test/apps/jsonp/api/Token.java +++ b/server/tck/src/test/java/io/smallrye/graphql/test/apps/jsonp/api/Token.java @@ -1,8 +1,8 @@ package io.smallrye.graphql.test.apps.jsonp.api; -import jakarta.json.JsonObject; +import tools.jackson.databind.node.ObjectNode; public class Token { public String name; - public JsonObject value; + public ObjectNode value; }