From 089733df7c07356eace53b7162b923b93d42157f Mon Sep 17 00:00:00 2001 From: Marek Skacelik Date: Fri, 11 Oct 2024 14:06:17 +0200 Subject: [PATCH] fixed adaptXXX issue --- .../graphql/schema/model/Scalars.java | 2 +- .../smallrye/graphql/bootstrap/Bootstrap.java | 7 +- .../datafetcher/helper/ArgumentHelper.java | 4 +- .../smallrye/graphql/schema/SchemaTest.java | 116 +++++++++++++++ .../graphql/tests/adapting/AdaptTest.java | 135 ++++++++++++++++++ 5 files changed, 260 insertions(+), 4 deletions(-) create mode 100644 server/integration-tests/src/test/java/io/smallrye/graphql/tests/adapting/AdaptTest.java 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 f16ada8b3..6d5b6cd9a 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 @@ -214,7 +214,7 @@ private static void populateScalar(String className, String scalarName, String e scalarMap.put(className, reference); // looking up by name - scalarNameMap.put(scalarName, reference); + scalarNameMap.putIfAbsent(scalarName, reference); //Currently, each scalar is formatted as String formattedScalarMap.put(className, new Reference.Builder() 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 7bd2cd619..7f5880fb3 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 @@ -989,7 +989,7 @@ private GraphQLInputObjectField createGraphQLInputObjectFieldFromField(Field fie private GraphQLInputType createGraphQLInputType(Field field) { - GraphQLInputType graphQLInputType = getGraphQLInputType(field.getReference()); + GraphQLInputType graphQLInputType = referenceGraphQLInputType(field); Wrapper wrapper = dataFetcherFactory.unwrap(field, false); // Field can have a wrapper, like List if (wrapper != null && wrapper.isCollectionOrArrayOrMap()) { @@ -1047,6 +1047,11 @@ private GraphQLOutputType createGraphQLOutputType(Field field, boolean isBatch) return graphQLOutputType; } + private GraphQLInputType referenceGraphQLInputType(Field field) { + Reference reference = getCorrectFieldReference(field); + return getGraphQLInputType(reference); + } + private GraphQLOutputType referenceGraphQLOutputType(Field field) { Reference reference = getCorrectFieldReference(field); ReferenceType type = reference.getType(); diff --git a/server/implementation/src/main/java/io/smallrye/graphql/execution/datafetcher/helper/ArgumentHelper.java b/server/implementation/src/main/java/io/smallrye/graphql/execution/datafetcher/helper/ArgumentHelper.java index 2303522c1..64972e3cd 100644 --- a/server/implementation/src/main/java/io/smallrye/graphql/execution/datafetcher/helper/ArgumentHelper.java +++ b/server/implementation/src/main/java/io/smallrye/graphql/execution/datafetcher/helper/ArgumentHelper.java @@ -166,7 +166,7 @@ private Object adaptInputTo(Field field, Object object) { if (methodName != null && !methodName.isEmpty()) { Class mappingClass = classloadingService.loadClass(field.getReference().getClassName()); try { - if (methodName.equals(CONTRUCTOR_METHOD_NAME)) { + if (methodName.equals(CONSTRUCTOR_METHOD_NAME)) { // Try with contructor Constructor constructor = mappingClass.getConstructor(object.getClass()); return constructor.newInstance(object); @@ -503,5 +503,5 @@ public Type[] getActualTypeArguments() { } } - private static final String CONTRUCTOR_METHOD_NAME = ""; + private static final String CONSTRUCTOR_METHOD_NAME = ""; } diff --git a/server/implementation/src/test/java/io/smallrye/graphql/schema/SchemaTest.java b/server/implementation/src/test/java/io/smallrye/graphql/schema/SchemaTest.java index 2c8d4e7a9..e75766344 100644 --- a/server/implementation/src/test/java/io/smallrye/graphql/schema/SchemaTest.java +++ b/server/implementation/src/test/java/io/smallrye/graphql/schema/SchemaTest.java @@ -23,6 +23,8 @@ import java.util.stream.Collectors; import jakarta.annotation.security.RolesAllowed; +import jakarta.json.bind.adapter.JsonbAdapter; +import jakarta.json.bind.annotation.JsonbTypeAdapter; import org.eclipse.microprofile.graphql.GraphQLApi; import org.eclipse.microprofile.graphql.NonNull; @@ -35,12 +37,17 @@ import graphql.schema.GraphQLDirective; import graphql.schema.GraphQLEnumType; import graphql.schema.GraphQLFieldDefinition; +import graphql.schema.GraphQLInputObjectField; import graphql.schema.GraphQLInputObjectType; import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLSchema; import graphql.schema.GraphQLUnionType; +import io.smallrye.graphql.api.AdaptToScalar; +import io.smallrye.graphql.api.AdaptWith; +import io.smallrye.graphql.api.Adapter; import io.smallrye.graphql.api.Directive; import io.smallrye.graphql.api.OneOf; +import io.smallrye.graphql.api.Scalar; import io.smallrye.graphql.api.federation.Key; import io.smallrye.graphql.api.federation.Key.Keys; import io.smallrye.graphql.execution.SchemaPrinter; @@ -515,6 +522,115 @@ void nonNullWrapperTest() { } + public static class ModelA { + @AdaptToScalar(Scalar.String.class) + private ModelB modelB; + + @AdaptWith(CustomAdapter.class) + public Collection someField; + + @JsonbTypeAdapter(CustomJsonbTypeAdapter.class) + public Float myOtherField; + + public ModelA() { + } + + public ModelB getModelB() { + return modelB; + } + + public void setModelB(ModelB modelB) { + this.modelB = modelB; + } + } + + public static class ModelB { + private String id; + + public ModelB() { + } + + // the `ModelB` object needs to have a constructor that takes a String (or Int / Date / etc.), + // or have a setter method for the String (or Int / Date / etc.), + // or have a fromString (or fromInt / fromDate - depending on the Scalar type) static method. + public static ModelB fromString(String id) { + ModelB modelB = new ModelB(); + modelB.id = id; + return modelB; + } + + @Override + public String toString() { + return id; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + } + + @GraphQLApi + public static class AdaptApi { + @Query + public ModelA getSomeString(ModelA modelA) { + return modelA; + } + } + + public static class CustomAdapter implements Adapter { + + @Override + public String to(Long o) throws Exception { + return String.valueOf(o); + } + + @Override + public Long from(String a) throws Exception { + return Long.parseLong(a); + } + } + + public static class CustomJsonbTypeAdapter implements JsonbAdapter { + + @Override + public String adaptToJson(Float aFloat) throws Exception { + return String.valueOf(aFloat); + } + + @Override + public Float adaptFromJson(String s) throws Exception { + return Float.valueOf(s); + } + } + + @Test + public void adaptTest() { + GraphQLSchema graphQLSchema = createGraphQLSchema(ModelA.class, ModelB.class, AdaptApi.class, CustomAdapter.class, + CustomJsonbTypeAdapter.class); + + GraphQLFieldDefinition modelAQuery = graphQLSchema.getQueryType().getField("someString"); + assertNotNull(modelAQuery); + + GraphQLInputObjectType modelAInput = graphQLSchema.getTypeAs("ModelAInput"); + assertNotNull(modelAInput); + assertEquals(3, modelAInput.getFields().size()); + GraphQLInputObjectField modelBField = modelAInput.getField("modelB"); + assertNotNull(modelBField); + assertEquals(GraphQLString, modelBField.getType()); + + GraphQLInputObjectField someFieldField = modelAInput.getField("someField"); + assertNotNull(someFieldField); + assertEquals("[String]", someFieldField.getType().toString()); + + GraphQLInputObjectField myOtherFieldField = modelAInput.getField("myOtherField"); + assertNotNull(myOtherFieldField); + assertEquals(GraphQLString, myOtherFieldField.getType()); + } + private void assertRolesAllowedDirective(GraphQLFieldDefinition field, String roleValue) { assertNotNull(field); diff --git a/server/integration-tests/src/test/java/io/smallrye/graphql/tests/adapting/AdaptTest.java b/server/integration-tests/src/test/java/io/smallrye/graphql/tests/adapting/AdaptTest.java new file mode 100644 index 000000000..5a68dc2cd --- /dev/null +++ b/server/integration-tests/src/test/java/io/smallrye/graphql/tests/adapting/AdaptTest.java @@ -0,0 +1,135 @@ +package io.smallrye.graphql.tests.adapting; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.net.URL; +import java.util.Collection; + +import jakarta.json.bind.adapter.JsonbAdapter; +import jakarta.json.bind.annotation.JsonbTypeAdapter; + +import org.eclipse.microprofile.graphql.GraphQLApi; +import org.eclipse.microprofile.graphql.Query; +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.container.test.api.RunAsClient; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.arquillian.test.api.ArquillianResource; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.junit.Test; +import org.junit.runner.RunWith; + +import io.smallrye.graphql.api.AdaptToScalar; +import io.smallrye.graphql.api.AdaptWith; +import io.smallrye.graphql.api.Adapter; +import io.smallrye.graphql.api.Scalar; +import io.smallrye.graphql.tests.GraphQLAssured; + +@RunWith(Arquillian.class) +@RunAsClient +public class AdaptTest { + + @Deployment + public static WebArchive deployment() { + return ShrinkWrap.create(WebArchive.class, "adapt-test.war") + .addClasses(ModelA.class, ModelB.class, AdaptApi.class, CustomJsonbTypeAdapter.class, CustomAdapter.class); + } + + @ArquillianResource + URL testingURL; + + public static class ModelA { + @AdaptToScalar(Scalar.String.class) + private ModelB modelB; + + @AdaptWith(CustomAdapter.class) + public Collection someField; + + @JsonbTypeAdapter(CustomJsonbTypeAdapter.class) + public String myOtherField; + + public ModelA() { + } + + public ModelB getModelB() { + return modelB; + } + + public void setModelB(ModelB modelB) { + this.modelB = modelB; + } + } + + public static class ModelB { + private String id; + + public ModelB() { + } + + // the `ModelB` object needs to have a constructor that takes a String (or Int / Date / etc.), + // or have a setter method for the String (or Int / Date / etc.), + // or have a fromString (or fromInt / fromDate - depending on the Scalar type) static method. + public static ModelB fromString(String id) { + ModelB modelB = new ModelB(); + modelB.id = id; + return modelB; + } + + @Override + public String toString() { + return id; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + } + + @GraphQLApi + public static class AdaptApi { + @Query + public ModelA getSomeString(ModelA modelA) { + return modelA; + } + } + + public static class CustomAdapter implements Adapter { + + @Override + public String to(Long o) throws Exception { + return String.valueOf(o); + } + + @Override + public Long from(String a) throws Exception { + return Long.parseLong(a); + } + } + + public static class CustomJsonbTypeAdapter implements JsonbAdapter { + + @Override + public Long adaptToJson(String s) throws Exception { + return Long.parseLong(s); + } + + @Override + public String adaptFromJson(Long aLong) throws Exception { + return String.valueOf(aLong); + } + } + + @Test + public void adaptTest() { + GraphQLAssured graphQLAssured = new GraphQLAssured(testingURL); + assertThat(graphQLAssured + .post("query { someString(modelA: { modelB: \"3\", someField: [\"5\",\"8\"], myOtherField: 4}) { modelB someField myOtherField }}")) + .containsIgnoringWhitespaces( + "{\"data\":{\"someString\":{\"modelB\":\"3\",\"someField\":[\"5\",\"8\"],\"myOtherField\": 4}}}") + .doesNotContain("error"); + } +}