diff --git a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/java/ClientApiGenerator.kt b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/java/ClientApiGenerator.kt index 3da71267..92f6333e 100644 --- a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/java/ClientApiGenerator.kt +++ b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/java/ClientApiGenerator.kt @@ -214,21 +214,20 @@ class ClientApiGenerator( it.inputValueDefinitions.forEach { inputValue -> val findReturnType = TypeUtils(getDatatypesPackageName(), config, document).findReturnType(inputValue.type) + val sanitizedInputName = javaReservedKeywordSanitizer.sanitize(inputValue.name) val deprecatedDirective = getDeprecateDirective(inputValue) val deprecationReason = deprecatedDirective?.let { it1 -> getDeprecatedReason(it1) } val methodBuilder = MethodSpec - .methodBuilder(javaReservedKeywordSanitizer.sanitize(inputValue.name)) - .addParameter(findReturnType, javaReservedKeywordSanitizer.sanitize(inputValue.name)) + .methodBuilder(sanitizedInputName) + .addParameter(findReturnType, sanitizedInputName) .returns(ClassName.get("", "Builder")) .addModifiers(Modifier.PUBLIC) .addCode( """ - |this.${javaReservedKeywordSanitizer.sanitize( - inputValue.name, - )} = ${javaReservedKeywordSanitizer.sanitize(inputValue.name)}; + |this.$sanitizedInputName = $sanitizedInputName; |this.fieldsSet.add("${inputValue.name}"); |return this; """.trimMargin(), @@ -237,19 +236,19 @@ class ClientApiGenerator( addDeprecationWarnings(deprecatedDirective, methodBuilder, inputValue, deprecationReason) builderClass .addMethod(methodBuilder.build()) - .addField(findReturnType, javaReservedKeywordSanitizer.sanitize(inputValue.name), Modifier.PRIVATE) + .addField(findReturnType, sanitizedInputName, Modifier.PRIVATE) val inputValueType = inputValue.type val typeForVariableDefinition = getVariableDefinitionType(inputValueType) val referenceMethodBuilder = MethodSpec - .methodBuilder(javaReservedKeywordSanitizer.sanitize(inputValue.name) + "Reference") + .methodBuilder(sanitizedInputName + "Reference") .addParameter(stringType, "variableRef") .returns(ClassName.get("", "Builder")) .addModifiers(Modifier.PUBLIC) .addCode( """ - |this.variableReferences.put("${javaReservedKeywordSanitizer.sanitize(inputValue.name)}", variableRef); + |this.variableReferences.put("${inputValue.name}", variableRef); |this.variableDefinitions.add(graphql.language.VariableDefinition.newVariableDefinition(variableRef, $typeForVariableDefinition).build()); |this.fieldsSet.add("${inputValue.name}"); |return this; @@ -259,21 +258,21 @@ class ClientApiGenerator( addDeprecationWarnings(deprecatedDirective, referenceMethodBuilder, inputValue, deprecationReason) builderClass.addMethod(referenceMethodBuilder.build()) - constructorBuilder.addParameter(findReturnType, javaReservedKeywordSanitizer.sanitize(inputValue.name)) - legacyConstructorBuilder.addParameter(findReturnType, javaReservedKeywordSanitizer.sanitize(inputValue.name)) + constructorBuilder.addParameter(findReturnType, sanitizedInputName) + legacyConstructorBuilder.addParameter(findReturnType, sanitizedInputName) if (findReturnType.isPrimitive) { val code = """ - |getInput().put("${inputValue.name}", ${javaReservedKeywordSanitizer.sanitize(inputValue.name)}); + |getInput().put("${inputValue.name}", $sanitizedInputName); """.trimMargin() constructorBuilder.addCode(code) legacyConstructorBuilder.addCode(code) } else { val code = """ - |if (${javaReservedKeywordSanitizer.sanitize(inputValue.name)} != null || fieldsSet.contains("${inputValue.name}")) { - | getInput().put("${inputValue.name}", ${javaReservedKeywordSanitizer.sanitize(inputValue.name)}); + |if ($sanitizedInputName != null || fieldsSet.contains("${inputValue.name}")) { + | getInput().put("${inputValue.name}", $sanitizedInputName); |} """.trimMargin() constructorBuilder.addCode(code) @@ -551,9 +550,10 @@ class ClientApiGenerator( |getInputArguments().computeIfAbsent("${fieldDefinition.name}", k -> new ${'$'}T<>()); |${ fieldDefinition.inputValueDefinitions.joinToString("\n") { input -> + val sanitizedName = javaReservedKeywordSanitizer.sanitize(input.name) """ - |InputArgument ${input.name}Arg = new InputArgument("${input.name}", ${input.name}, false, null); - |getInputArguments().get("${fieldDefinition.name}").add(${input.name}Arg); + |InputArgument ${sanitizedName}Arg = new InputArgument("${input.name}", $sanitizedName, false, null); + |getInputArguments().get("${fieldDefinition.name}").add(${sanitizedName}Arg); """.trimMargin() } } @@ -590,11 +590,12 @@ class ClientApiGenerator( |getInputArguments().computeIfAbsent("${fieldDefinition.name}", k -> new ${'$'}T<>()); |${ fieldDefinition.inputValueDefinitions.joinToString("\n") { input -> + val sanitizedName = javaReservedKeywordSanitizer.sanitize(input.name) """ - |InputArgument ${input.name}Arg = new InputArgument("${input.name}", ${input.name}Reference, true, ${getVariableDefinitionType( + |InputArgument ${sanitizedName}Arg = new InputArgument("${input.name}", ${sanitizedName}Reference, true, ${getVariableDefinitionType( input.type, )}); - |getInputArguments().get("${fieldDefinition.name}").add(${input.name}Arg); + |getInputArguments().get("${fieldDefinition.name}").add(${sanitizedName}Arg); """.trimMargin() } } @@ -604,7 +605,8 @@ class ClientApiGenerator( ).addModifiers(Modifier.PUBLIC) fieldDefinition.inputValueDefinitions.forEach { input -> - methodBuilder.addParameter(ParameterSpec.builder(ClassName.get(String::class.java), "${input.name}Reference").build()) + val sanitizedName = javaReservedKeywordSanitizer.sanitize(input.name) + methodBuilder.addParameter(ParameterSpec.builder(ClassName.get(String::class.java), "${sanitizedName}Reference").build()) } return javaType.addMethod(methodBuilder.build()) } @@ -934,9 +936,10 @@ class ClientApiGenerator( |getInputArguments().computeIfAbsent("${it.name}", k -> new ${'$'}T<>()); |${ it.inputValueDefinitions.joinToString("\n") { input -> + val sanitizedName = javaReservedKeywordSanitizer.sanitize(input.name) """ - |InputArgument ${input.name}Arg = new InputArgument("${input.name}", ${input.name}, false, null); - |getInputArguments().get("${it.name}").add(${input.name}Arg); + |InputArgument ${sanitizedName}Arg = new InputArgument("${input.name}", $sanitizedName, false, null); + |getInputArguments().get("${it.name}").add(${sanitizedName}Arg); """.trimMargin() }} |return this; @@ -945,8 +948,9 @@ class ClientApiGenerator( ).addModifiers(Modifier.PUBLIC) it.inputValueDefinitions.forEach { input -> + val sanitizedName = javaReservedKeywordSanitizer.sanitize(input.name) methodWithInputArgumentsBuilder.addParameter( - ParameterSpec.builder(typeUtils.findReturnType(input.type), input.name).build(), + ParameterSpec.builder(typeUtils.findReturnType(input.type), sanitizedName).build(), ) } diff --git a/graphql-dgs-codegen-core/src/test/kotlin/com/netflix/graphql/dgs/codegen/clientapi/ClientApiGenReservedKeywordTest.kt b/graphql-dgs-codegen-core/src/test/kotlin/com/netflix/graphql/dgs/codegen/clientapi/ClientApiGenReservedKeywordTest.kt new file mode 100644 index 00000000..8c665e6b --- /dev/null +++ b/graphql-dgs-codegen-core/src/test/kotlin/com/netflix/graphql/dgs/codegen/clientapi/ClientApiGenReservedKeywordTest.kt @@ -0,0 +1,123 @@ +/* + * + * Copyright 2025 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.clientapi + +import com.netflix.graphql.dgs.codegen.* +import org.assertj.core.api.Assertions.assertThat +import org.intellij.lang.annotations.Language +import org.junit.jupiter.api.Test + +class ClientApiGenReservedKeywordTest { + @Test + fun `Field named package should be sanitized in generated client API`() { + @Language("GraphQL") + val schema = + """ + type Query { + something: Something + } + + type Something { + package: String + } + """.trimIndent() + + val codeGenResult = + CodeGen( + CodeGenConfig( + schemas = setOf(schema), + packageName = "com.netflix.graphql.dgs.codegen.tests.generated", + generateClientApi = true, + ), + ).generate() + + assertThat(codeGenResult.javaDataTypes).hasSize(1) + assertThat(codeGenResult.clientProjections).hasSize(1) + + // Check that the field name is properly sanitized in the generated data type + val dataType = codeGenResult.javaDataTypes[0].typeSpec() + val fieldNames = dataType.fieldSpecs().map { it.name() } + assertThat(fieldNames).contains("_package") + assertThat(fieldNames).doesNotContain("package") + } + + @Test + fun `Query field with argument named package should generate valid code`() { + @Language("GraphQL") + val schema = + """ + type Query { + something(package: String): String + } + """.trimIndent() + + val codeGenResult = + CodeGen( + CodeGenConfig( + schemas = setOf(schema), + packageName = "com.netflix.graphql.dgs.codegen.tests.generated", + generateClientApi = true, + ), + ).generate() + + // Verify the generated code compiles without errors + assertCompilesJava( + codeGenResult.clientProjections + + codeGenResult.javaQueryTypes + + codeGenResult.javaEnumTypes + + codeGenResult.javaDataTypes + + codeGenResult.javaInterfaces, + ) + } + + @Test + fun `Variable reference method should use original GraphQL field name as map key`() { + @Language("GraphQL") + val schema = + """ + type Query { + something(package: String): String + } + """.trimIndent() + + val codeGenResult = + CodeGen( + CodeGenConfig( + schemas = setOf(schema), + packageName = "com.netflix.graphql.dgs.codegen.tests.generated", + generateClientApi = true, + ), + ).generate() + + // Find the SomethingGraphQLQuery type and its Builder class + val queryType = codeGenResult.javaQueryTypes.first() + val builderClass = + queryType.typeSpec().typeSpecs().find { it.name() == "Builder" } + ?: throw AssertionError("Builder class not found") + + // Find the _packageReference method + val referenceMethod = + builderClass.methodSpecs().find { it.name() == "_packageReference" } + ?: throw AssertionError("_packageReference method not found") + + // Verify the method uses original GraphQL name "package" as the key, not "_package" + val methodCode = referenceMethod.code().toString() + assertThat(methodCode).contains("variableReferences.put(\"package\"") + assertThat(methodCode).doesNotContain("variableReferences.put(\"_package\"") + } +}