Skip to content
Open
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 @@ -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(),
Expand All @@ -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;
Expand All @@ -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)
Expand Down Expand Up @@ -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()
}
}
Expand Down Expand Up @@ -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()
}
}
Expand All @@ -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())
}
Expand Down Expand Up @@ -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;
Expand All @@ -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(),
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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\"")
}
}
Loading