diff --git a/.gitignore b/.gitignore index 941bcfa2..4955aebc 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ graphql-dgs-codegen-core/compiled-sources/ generated generated-examples scripts/__pycache__ +.kotlin/ 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 b68e10b4..9a57e268 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 @@ -459,16 +459,25 @@ abstract class BaseDataTypeGenerator( addGetterAndSetter(it, javaType) } + val requiredFields = fields.filter { !it.nullable } + if (config.generateJSpecifyAnnotations) { - // Since JSpecify annotations are enabled, add a private no-arg constructor - // only for the Builder to access - addDefaultConstructor(javaType, false) + val allFieldsNullable = requiredFields.isEmpty() + addDefaultConstructor(javaType, allFieldsNullable) + + if (config.javaGenerateAllConstructor && fields.isNotEmpty() && fields.size < 256) { + addParameterizedConstructor(fields, javaType) + + if (requiredFields.isNotEmpty() && requiredFields.size < fields.size) { + addParameterizedConstructor(requiredFields, javaType) + } + } } else { addDefaultConstructor(javaType, true) - } - if (config.javaGenerateAllConstructor && fields.isNotEmpty() && fields.size < 256) { - addParameterizedConstructor(fields, javaType) + if (config.javaGenerateAllConstructor && fields.isNotEmpty() && fields.size < 256) { + addParameterizedConstructor(fields, javaType) + } } addToString(fields, javaType) diff --git a/graphql-dgs-codegen-core/src/test/kotlin/com/netflix/graphql/dgs/codegen/CodeGenTest.kt b/graphql-dgs-codegen-core/src/test/kotlin/com/netflix/graphql/dgs/codegen/CodeGenTest.kt index 7434ba61..e08ff3be 100644 --- a/graphql-dgs-codegen-core/src/test/kotlin/com/netflix/graphql/dgs/codegen/CodeGenTest.kt +++ b/graphql-dgs-codegen-core/src/test/kotlin/com/netflix/graphql/dgs/codegen/CodeGenTest.kt @@ -7061,4 +7061,114 @@ It takes a title and such. dataTypes[0].writeTo(System.out) assertCompilesJava(dataTypes) } + + @Test + fun `public no-arg constructor is generated when jspecify enabled and all fields are nullable`() { + val schema = + """ + type Foo { + a: String + b: Int + } + """.trimIndent() + + val result = + CodeGen( + CodeGenConfig( + schemas = setOf(schema), + packageName = BASE_PACKAGE_NAME, + language = Language.JAVA, + generateJSpecifyAnnotations = true, + javaGenerateAllConstructor = true, + ), + ).generate() + + val fooType = result.javaDataTypes.single { it.typeSpec().name() == "Foo" } + val constructors = fooType.typeSpec().methodSpecs().filter { it.isConstructor } + + assertThat(constructors).anySatisfy { ctor -> + assertThat(ctor.modifiers()).contains(javax.lang.model.element.Modifier.PUBLIC) + assertThat(ctor.parameters()).isEmpty() + } + + assertCompilesJava(result) + } + + @Test + fun `required-fields constructor is generated when jspecify enabled and non-null fields exist`() { + val schema = + """ + type Foo { + a: String! + b: Int + } + """.trimIndent() + + val result = + CodeGen( + CodeGenConfig( + schemas = setOf(schema), + packageName = BASE_PACKAGE_NAME, + language = Language.JAVA, + generateJSpecifyAnnotations = true, + javaGenerateAllConstructor = true, + ), + ).generate() + + val fooType = result.javaDataTypes.single { it.typeSpec().name() == "Foo" } + val constructors = fooType.typeSpec().methodSpecs().filter { it.isConstructor } + + assertThat(constructors).anySatisfy { ctor -> + assertThat(ctor.modifiers()).contains(javax.lang.model.element.Modifier.PUBLIC) + assertThat(ctor.parameters()).hasSize(1) + assertThat(ctor.parameters()[0].name()).isEqualTo("a") + } + + assertThat(constructors).anySatisfy { ctor -> + assertThat(ctor.modifiers()).contains(javax.lang.model.element.Modifier.PRIVATE) + assertThat(ctor.parameters()).isEmpty() + } + + assertCompilesJava(result) + } + + @Test + fun `required-fields constructor is not generated when all fields are required`() { + val schema = + """ + type Foo { + a: String! + b: Int! + } + """.trimIndent() + + val result = + CodeGen( + CodeGenConfig( + schemas = setOf(schema), + packageName = BASE_PACKAGE_NAME, + language = Language.JAVA, + generateJSpecifyAnnotations = true, + javaGenerateAllConstructor = true, + ), + ).generate() + + val fooType = result.javaDataTypes.single { it.typeSpec().name() == "Foo" } + val constructors = fooType.typeSpec().methodSpecs().filter { it.isConstructor } + + assertThat(constructors).anySatisfy { ctor -> + assertThat(ctor.modifiers()).contains(javax.lang.model.element.Modifier.PRIVATE) + assertThat(ctor.parameters()).isEmpty() + } + + val publicMultiArgCtors = + constructors.filter { ctor -> + ctor.modifiers().contains(javax.lang.model.element.Modifier.PUBLIC) && ctor.parameters().isNotEmpty() + } + + assertThat(publicMultiArgCtors).hasSize(1) + assertThat(publicMultiArgCtors.single().parameters()).hasSize(2) + + assertCompilesJava(result) + } }