From 494754fccd457ed126b6fbbfd12de430c6c76509 Mon Sep 17 00:00:00 2001 From: Vishal Bihani Date: Sat, 17 Aug 2024 19:24:42 +0530 Subject: [PATCH 1/2] Added JsonProperty annotation on fields for java data type generator --- .../graphql/dgs/codegen/JavaCodeGenTest.kt | 137 ++++++++++++++++++ .../expected/DgsConstants.java | 19 +++ .../expected/client/TestGraphQLQuery.java | 41 ++++++ .../expected/client/TestProjectionRoot.java | 19 +++ .../datafetchers/TestDatafetcher.java | 17 +++ .../expected/types/SomeType.java | 65 +++++++++ .../schema.graphql | 7 + .../generators/java/DataTypeGenerator.kt | 1 + .../codegen/generators/java/JavaPoetUtils.kt | 17 +++ 9 files changed, 323 insertions(+) create mode 100644 graphql-dgs-codegen-core/src/integTest/java/com/netflix/graphql/dgs/codegen/JavaCodeGenTest.kt create mode 100644 graphql-dgs-codegen-core/src/integTest/java/com/netflix/graphql/dgs/codegen/cases/dataClassWithFieldHavingNameInCaps/expected/DgsConstants.java create mode 100644 graphql-dgs-codegen-core/src/integTest/java/com/netflix/graphql/dgs/codegen/cases/dataClassWithFieldHavingNameInCaps/expected/client/TestGraphQLQuery.java create mode 100644 graphql-dgs-codegen-core/src/integTest/java/com/netflix/graphql/dgs/codegen/cases/dataClassWithFieldHavingNameInCaps/expected/client/TestProjectionRoot.java create mode 100644 graphql-dgs-codegen-core/src/integTest/java/com/netflix/graphql/dgs/codegen/cases/dataClassWithFieldHavingNameInCaps/expected/datafetchers/TestDatafetcher.java create mode 100644 graphql-dgs-codegen-core/src/integTest/java/com/netflix/graphql/dgs/codegen/cases/dataClassWithFieldHavingNameInCaps/expected/types/SomeType.java create mode 100644 graphql-dgs-codegen-core/src/integTest/java/com/netflix/graphql/dgs/codegen/cases/dataClassWithFieldHavingNameInCaps/schema.graphql diff --git a/graphql-dgs-codegen-core/src/integTest/java/com/netflix/graphql/dgs/codegen/JavaCodeGenTest.kt b/graphql-dgs-codegen-core/src/integTest/java/com/netflix/graphql/dgs/codegen/JavaCodeGenTest.kt new file mode 100644 index 00000000..1a5b05ee --- /dev/null +++ b/graphql-dgs-codegen-core/src/integTest/java/com/netflix/graphql/dgs/codegen/JavaCodeGenTest.kt @@ -0,0 +1,137 @@ +/* + * + * Copyright 2020 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.netflix.graphql.dgs.codegen + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Assertions.fail +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.MethodSource +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths +import java.util.stream.Collectors +import kotlin.io.path.createDirectories +import kotlin.io.path.exists +import kotlin.io.path.listDirectoryEntries +import kotlin.io.path.readText + +class JavaCodeGenTest { + + // set this to true to update all expected outputs instead of running tests + private val updateExpected = false + + @ParameterizedTest + @MethodSource("listTestsToRun") + fun testCodeGen(testName: String) { + val schema = readResource("/$testName/schema.graphql") + + val codeGenResult = CodeGen( + CodeGenConfig( + schemas = setOf(schema), + packageName = "com.netflix.graphql.dgs.codegen.cases.$testName.expected", + language = Language.JAVA, + generateClientApi = true, + typeMapping = when (testName) { + "dataClassWithMappedTypes" -> mapOf( + "Long" to "java.Long", + "DateTime" to "java.time.OffsetDateTime", + "PageInfo" to "graphql.relay.PageInfo", + "EntityConnection" to "graphql.relay.SimpleListConnection" + ) + "inputWithDefaultBigDecimal" -> mapOf( + "Decimal" to "java.math.BigDecimal" + ) + else -> emptyMap() + } + ) + ).generate() + + val fileNames = codeGenResult.javaSources() + .groupingBy { it.packageName.substringAfterLast('.') to it.typeSpec.name } + .eachCount() + + // fail if any file was defined twice + fileNames + .filterValues { it > 1 } + .keys + .forEach { fail("Duplicate file: ${it.first}.${it.second}") } + + // fail if any file was expected that's not generated + listAllFiles("/$testName/expected") + .map { + it.getName(it.nameCount - 2).toString() to it.getName(it.nameCount - 1).toString().removeSuffix(".kt") + } + .toSet().subtract(fileNames.keys) + .forEach { fail("Missing expected file: ${it.first}.${it.second}") } + + codeGenResult.javaSources().forEach { spec -> + + val type = spec.packageName.substringAfterLast("expected").trimStart('.') + val fileName = "/$testName/expected/$type/${spec.typeSpec.name}.java" + val actual = spec.toString() + + if (updateExpected) { + writeExpected(fileName, actual) + } else { + assertThat(actual).isEqualTo(readResource(fileName)) + } + } + + assertCompilesJava(codeGenResult) + } + + companion object { + + @Suppress("unused") + @JvmStatic + fun listTestsToRun(): List { + return getAbsolutePath("") + .listDirectoryEntries() + .map { it.getName(it.nameCount.dec()).toString() } + .sorted() + } + + private fun getAbsolutePath(suffix: String): Path { + val projectDirAbsolutePath = Paths.get("").toAbsolutePath().toString() + return Paths.get(projectDirAbsolutePath, "/src/integTest/java/com/netflix/graphql/dgs/codegen/cases/$suffix") + } + + private fun listAllFiles(suffix: String): List { + val path = getAbsolutePath(suffix) + if (!path.exists()) return emptyList() + return Files.walk(path) + .filter { Files.isRegularFile(it) } + .collect(Collectors.toList()) + } + + private fun readResource(fileName: String): String { + return getAbsolutePath(fileName).readText() + } + + private fun writeExpected(fileName: String, content: String) { + val path = getAbsolutePath(fileName) + + if (!path.exists()) { + path.parent.createDirectories() + } + + path.toFile().writeText(content) + } + } +} diff --git a/graphql-dgs-codegen-core/src/integTest/java/com/netflix/graphql/dgs/codegen/cases/dataClassWithFieldHavingNameInCaps/expected/DgsConstants.java b/graphql-dgs-codegen-core/src/integTest/java/com/netflix/graphql/dgs/codegen/cases/dataClassWithFieldHavingNameInCaps/expected/DgsConstants.java new file mode 100644 index 00000000..3b6d7c43 --- /dev/null +++ b/graphql-dgs-codegen-core/src/integTest/java/com/netflix/graphql/dgs/codegen/cases/dataClassWithFieldHavingNameInCaps/expected/DgsConstants.java @@ -0,0 +1,19 @@ +package com.netflix.graphql.dgs.codegen.cases.dataClassWithFieldHavingNameInCaps.expected; + +import java.lang.String; + +public class DgsConstants { + public static final String QUERY_TYPE = "Query"; + + public static class QUERY { + public static final String TYPE_NAME = "Query"; + + public static final String Test = "test"; + } + + public static class SOMETYPE { + public static final String TYPE_NAME = "SomeType"; + + public static final String CAPSField = "CAPSField"; + } +} diff --git a/graphql-dgs-codegen-core/src/integTest/java/com/netflix/graphql/dgs/codegen/cases/dataClassWithFieldHavingNameInCaps/expected/client/TestGraphQLQuery.java b/graphql-dgs-codegen-core/src/integTest/java/com/netflix/graphql/dgs/codegen/cases/dataClassWithFieldHavingNameInCaps/expected/client/TestGraphQLQuery.java new file mode 100644 index 00000000..e67384a5 --- /dev/null +++ b/graphql-dgs-codegen-core/src/integTest/java/com/netflix/graphql/dgs/codegen/cases/dataClassWithFieldHavingNameInCaps/expected/client/TestGraphQLQuery.java @@ -0,0 +1,41 @@ +package com.netflix.graphql.dgs.codegen.cases.dataClassWithFieldHavingNameInCaps.expected.client; + +import com.netflix.graphql.dgs.client.codegen.GraphQLQuery; +import java.lang.Override; +import java.lang.String; +import java.util.HashSet; +import java.util.Set; + +public class TestGraphQLQuery extends GraphQLQuery { + public TestGraphQLQuery(String queryName) { + super("query", queryName); + } + + public TestGraphQLQuery() { + super("query"); + } + + @Override + public String getOperationName() { + return "test"; + } + + public static Builder newRequest() { + return new Builder(); + } + + public static class Builder { + private Set fieldsSet = new HashSet<>(); + + private String queryName; + + public TestGraphQLQuery build() { + return new TestGraphQLQuery(queryName); + } + + public Builder queryName(String queryName) { + this.queryName = queryName; + return this; + } + } +} diff --git a/graphql-dgs-codegen-core/src/integTest/java/com/netflix/graphql/dgs/codegen/cases/dataClassWithFieldHavingNameInCaps/expected/client/TestProjectionRoot.java b/graphql-dgs-codegen-core/src/integTest/java/com/netflix/graphql/dgs/codegen/cases/dataClassWithFieldHavingNameInCaps/expected/client/TestProjectionRoot.java new file mode 100644 index 00000000..9c9688b4 --- /dev/null +++ b/graphql-dgs-codegen-core/src/integTest/java/com/netflix/graphql/dgs/codegen/cases/dataClassWithFieldHavingNameInCaps/expected/client/TestProjectionRoot.java @@ -0,0 +1,19 @@ +package com.netflix.graphql.dgs.codegen.cases.dataClassWithFieldHavingNameInCaps.expected.client; + +import com.netflix.graphql.dgs.client.codegen.BaseSubProjectionNode; + +public class TestProjectionRoot, ROOT extends BaseSubProjectionNode> extends BaseSubProjectionNode { + public TestProjectionRoot() { + super(null, null, java.util.Optional.of("SomeType")); + } + + public TestProjectionRoot __typename() { + getFields().put("__typename", null); + return this; + } + + public TestProjectionRoot CAPSField() { + getFields().put("CAPSField", null); + return this; + } +} diff --git a/graphql-dgs-codegen-core/src/integTest/java/com/netflix/graphql/dgs/codegen/cases/dataClassWithFieldHavingNameInCaps/expected/datafetchers/TestDatafetcher.java b/graphql-dgs-codegen-core/src/integTest/java/com/netflix/graphql/dgs/codegen/cases/dataClassWithFieldHavingNameInCaps/expected/datafetchers/TestDatafetcher.java new file mode 100644 index 00000000..1f967e79 --- /dev/null +++ b/graphql-dgs-codegen-core/src/integTest/java/com/netflix/graphql/dgs/codegen/cases/dataClassWithFieldHavingNameInCaps/expected/datafetchers/TestDatafetcher.java @@ -0,0 +1,17 @@ +package com.netflix.graphql.dgs.codegen.cases.dataClassWithFieldHavingNameInCaps.expected.datafetchers; + +import com.netflix.graphql.dgs.DgsComponent; +import com.netflix.graphql.dgs.DgsData; +import com.netflix.graphql.dgs.codegen.cases.dataClassWithFieldHavingNameInCaps.expected.types.SomeType; +import graphql.schema.DataFetchingEnvironment; + +@DgsComponent +public class TestDatafetcher { + @DgsData( + parentType = "Query", + field = "test" + ) + public SomeType getTest(DataFetchingEnvironment dataFetchingEnvironment) { + return null; + } +} diff --git a/graphql-dgs-codegen-core/src/integTest/java/com/netflix/graphql/dgs/codegen/cases/dataClassWithFieldHavingNameInCaps/expected/types/SomeType.java b/graphql-dgs-codegen-core/src/integTest/java/com/netflix/graphql/dgs/codegen/cases/dataClassWithFieldHavingNameInCaps/expected/types/SomeType.java new file mode 100644 index 00000000..a0c3b1b1 --- /dev/null +++ b/graphql-dgs-codegen-core/src/integTest/java/com/netflix/graphql/dgs/codegen/cases/dataClassWithFieldHavingNameInCaps/expected/types/SomeType.java @@ -0,0 +1,65 @@ +package com.netflix.graphql.dgs.codegen.cases.dataClassWithFieldHavingNameInCaps.expected.types; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.lang.Object; +import java.lang.Override; +import java.lang.String; +import java.util.Objects; + +public class SomeType { + @JsonProperty("CAPSField") + private String CAPSField; + + public SomeType() { + } + + public SomeType(String CAPSField) { + this.CAPSField = CAPSField; + } + + public String getCAPSField() { + return CAPSField; + } + + public void setCAPSField(String CAPSField) { + this.CAPSField = CAPSField; + } + + @Override + public String toString() { + return "SomeType{CAPSField='" + CAPSField + "'}"; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SomeType that = (SomeType) o; + return Objects.equals(CAPSField, that.CAPSField); + } + + @Override + public int hashCode() { + return Objects.hash(CAPSField); + } + + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder { + @JsonProperty("CAPSField") + private String CAPSField; + + public SomeType build() { + SomeType result = new SomeType(); + result.CAPSField = this.CAPSField; + return result; + } + + public Builder CAPSField(String CAPSField) { + this.CAPSField = CAPSField; + return this; + } + } +} diff --git a/graphql-dgs-codegen-core/src/integTest/java/com/netflix/graphql/dgs/codegen/cases/dataClassWithFieldHavingNameInCaps/schema.graphql b/graphql-dgs-codegen-core/src/integTest/java/com/netflix/graphql/dgs/codegen/cases/dataClassWithFieldHavingNameInCaps/schema.graphql new file mode 100644 index 00000000..4623ef6c --- /dev/null +++ b/graphql-dgs-codegen-core/src/integTest/java/com/netflix/graphql/dgs/codegen/cases/dataClassWithFieldHavingNameInCaps/schema.graphql @@ -0,0 +1,7 @@ +type Query { + test: SomeType +} + +type SomeType { + CAPSField: String +} \ No newline at end of file diff --git a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/java/DataTypeGenerator.kt b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/java/DataTypeGenerator.kt index cea66048..4e958d1c 100644 --- a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/java/DataTypeGenerator.kt +++ b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/java/DataTypeGenerator.kt @@ -468,6 +468,7 @@ abstract class BaseDataTypeGenerator( FieldSpec.builder(returnType, ReservedKeywordSanitizer.sanitize(fieldDefinition.name)).addModifiers(Modifier.PRIVATE) } + fieldBuilder.addAnnotation(jsonPropertyAnnotation(fieldDefinition.name)) if (fieldDefinition.description != null) { fieldBuilder.addJavadoc("\$L", fieldDefinition.description.content) } diff --git a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/java/JavaPoetUtils.kt b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/java/JavaPoetUtils.kt index 5bcc2319..4863cd2c 100644 --- a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/java/JavaPoetUtils.kt +++ b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/java/JavaPoetUtils.kt @@ -18,6 +18,7 @@ package com.netflix.graphql.dgs.codegen.generators.java +import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonSubTypes import com.fasterxml.jackson.annotation.JsonTypeInfo import com.netflix.graphql.dgs.codegen.CodeGen @@ -64,6 +65,22 @@ fun jsonTypeInfoAnnotation(): AnnotationSpec { .build() } + +/** + * Generate a [JsonProperty] annotation for the supplied + * field name. + * + * Example generated annotation: + * ``` + * @JsonProperty("fieldName") + * ``` + */ +fun jsonPropertyAnnotation(name: String): AnnotationSpec { + return AnnotationSpec.builder(JsonProperty::class.java) + .addMember("value", "\$S", name) + .build(); +} + /** * Generate a [JsonTypeInfo] annotation, to explicitly disable * polymorphic type handling. This is mostly useful as a workaround From a2317acb2911348d477070788f5fad033be1f658 Mon Sep 17 00:00:00 2001 From: Vishal Bihani Date: Sat, 17 Aug 2024 19:31:32 +0530 Subject: [PATCH 2/2] linting changes --- .../graphql/dgs/codegen/generators/java/JavaPoetUtils.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/java/JavaPoetUtils.kt b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/java/JavaPoetUtils.kt index 4863cd2c..9b9edd5c 100644 --- a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/java/JavaPoetUtils.kt +++ b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/java/JavaPoetUtils.kt @@ -65,7 +65,6 @@ fun jsonTypeInfoAnnotation(): AnnotationSpec { .build() } - /** * Generate a [JsonProperty] annotation for the supplied * field name. @@ -78,7 +77,7 @@ fun jsonTypeInfoAnnotation(): AnnotationSpec { fun jsonPropertyAnnotation(name: String): AnnotationSpec { return AnnotationSpec.builder(JsonProperty::class.java) .addMember("value", "\$S", name) - .build(); + .build() } /**