Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for records as input on the server side #1045

Merged
merged 1 commit into from
Sep 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.util.Optional;

import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.FieldInfo;
import org.jboss.jandex.MethodInfo;
import org.jboss.logging.Logger;
Expand Down Expand Up @@ -39,6 +40,8 @@ public class InputTypeCreator implements Creator<InputType> {
private final FieldCreator fieldCreator;
private final TypeAutoNameStrategy autoNameStrategy;

private final DotName RECORD = DotName.createSimple("java.lang.Record");

public InputTypeCreator(FieldCreator fieldCreator, TypeAutoNameStrategy autoNameStrategy) {
this.fieldCreator = fieldCreator;
this.autoNameStrategy = autoNameStrategy;
Expand Down Expand Up @@ -84,6 +87,18 @@ public boolean hasUseableConstructor(ClassInfo classInfo) {
* @return the creator, null, if no public constructor or factory method is found
*/
public MethodInfo findCreator(ClassInfo classInfo) {
if (classInfo.superName().equals(RECORD)) {
// records should always have a canonical constructor
// the creator will be picked by the JSONB impl at runtime anyway, so
// just make sure we can find a public constructor and move on
for (MethodInfo constructor : classInfo.constructors()) {
if (!Modifier.isPublic(constructor.flags()))
continue;
return constructor;
}
return null;
}

for (final MethodInfo constructor : classInfo.constructors()) {
if (!Modifier.isPublic(constructor.flags()))
continue;
Expand Down
3 changes: 3 additions & 0 deletions server/integration-tests-jdk16/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,9 @@
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<release>16</release>
<!-- parameters needed for RecordTest#testSimpleRecordWithFactory because otherwise Yasson
doesn't properly use the static factory method -->
<parameters>true</parameters>
</configuration>
</plugin>
</plugins>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package io.smallrye.graphql.tests.records;

import io.smallrye.graphql.client.Response;
import io.smallrye.graphql.client.core.Document;
import io.smallrye.graphql.client.core.InputObject;
import io.smallrye.graphql.client.core.InputObjectField;
import io.smallrye.graphql.client.dynamic.api.DynamicGraphQLClient;
import io.smallrye.graphql.client.dynamic.vertx.VertxDynamicGraphQLClientBuilder;
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.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Test;
import org.junit.runner.RunWith;

import javax.json.bind.annotation.JsonbCreator;
import java.net.URL;

import static io.smallrye.graphql.client.core.Argument.arg;
import static io.smallrye.graphql.client.core.Argument.args;
import static io.smallrye.graphql.client.core.Document.document;
import static io.smallrye.graphql.client.core.Field.field;
import static io.smallrye.graphql.client.core.InputObject.inputObject;
import static io.smallrye.graphql.client.core.InputObjectField.prop;
import static io.smallrye.graphql.client.core.Operation.operation;
import static org.junit.Assert.assertEquals;

/**
* This test verifies that the server side can handle Java records in GraphQL apis, both as input and as output types.
*/
@RunWith(Arquillian.class)
@RunAsClient
public class RecordTest {

@Deployment
public static WebArchive deployment() {
return ShrinkWrap.create(WebArchive.class)
.addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml")
.addClasses(SimpleRecord.class);
}

@ArquillianResource
URL testingURL;

@Test
public void testSimpleRecord() throws Exception {
try (DynamicGraphQLClient client = new VertxDynamicGraphQLClientBuilder()
.url(testingURL.toString() + "graphql").build()) {
Document query = document(operation(
field("simple",
args(arg("input",
inputObject(prop("a", "a"), prop("b", "b")))),
field("a"),
field("b"))));
Response response = client.executeSync(query);
assertEquals("a", response.getData().getJsonObject("simple").getString("a"));
assertEquals("b", response.getData().getJsonObject("simple").getString("b"));
}
}

@Test
public void testSimpleRecordWithFactory() throws Exception {
try (DynamicGraphQLClient client = new VertxDynamicGraphQLClientBuilder()
.url(testingURL.toString() + "graphql").build()) {
Document query = document(operation(
field("simpleWithFactory",
args(arg("input",
inputObject(prop("a", "a"), prop("b", "b")))),
field("a"),
field("b"))));
Response response = client.executeSync(query);
System.out.println(response);
System.out.println("query.build() = " + query.build());
assertEquals("a", response.getData().getJsonObject("simpleWithFactory").getString("a"));
assertEquals("b", response.getData().getJsonObject("simpleWithFactory").getString("b"));
}
}

@GraphQLApi
public static class Api {

@Query
public SimpleRecord simple(SimpleRecord input) {
return input;
}

@Query
public SimpleRecordWithFactory simpleWithFactory(SimpleRecordWithFactory input) {
return input;
}

}

public record SimpleRecord(String a, String b) {

// FIXME: until Yasson receives proper support for records, we have
// to use this hack to tell Yasson to use the canonical constructor when
// deserializing from JSON. Otherwise it will try to find and use a no-arg constructor, and
// that does not exist.
@JsonbCreator
public SimpleRecord {
}

}

public record SimpleRecordWithFactory(String a, String b) {

@JsonbCreator
public static SimpleRecordWithFactory build(String a, String b) {
return new SimpleRecordWithFactory(a, b);
}

}

}