Skip to content

Commit

Permalink
Client code: Generate deprecations (#608)
Browse files Browse the repository at this point in the history
* Client code generation now generates annotation and JavaDoc for deprecations, if enabled by addDeprecatedAnnotation
- sanitizeJavaDoc() is now also supported directly on a plain String

* (SQUASH) Review feedback: don't string-match the whole method
  • Loading branch information
theHacker authored Nov 29, 2023
1 parent e048106 commit 1d235a1
Show file tree
Hide file tree
Showing 3 changed files with 188 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,20 @@ class ClientApiGenerator(private val config: CodeGenConfig, private val document

it.inputValueDefinitions.forEach { inputValue ->
val findReturnType = TypeUtils(getDatatypesPackageName(), config, document).findReturnType(inputValue.type)

val deprecatedDirective = if (config.addDeprecatedAnnotation) {
inputValue
.getDirectives("deprecated")
?.firstOrNull() // Should we throw here, if there are multiple "@deprecated"?
} else {
null
}

val deprecationReason = deprecatedDirective
?.getArgument("reason")
?.let { it.value as? StringValue }
?.value

val methodBuilder = MethodSpec.methodBuilder(ReservedKeywordSanitizer.sanitize(inputValue.name))
.addParameter(findReturnType, ReservedKeywordSanitizer.sanitize(inputValue.name))
.returns(ClassName.get("", "Builder"))
Expand All @@ -127,9 +141,25 @@ class ClientApiGenerator(private val config: CodeGenConfig, private val document
""".trimMargin()
)

if (deprecatedDirective != null) {
methodBuilder.addAnnotation(java.lang.Deprecated::class.java)
}

// Build Javadoc, separate multiple blocks by empty line
val javaDocCodeBlocks = mutableListOf<String>()

if (inputValue.description != null) {
methodBuilder.addJavadoc(inputValue.description.sanitizeJavaDoc())
javaDocCodeBlocks.add(inputValue.description.sanitizeJavaDoc())
}
if (deprecationReason != null) {
javaDocCodeBlocks.add("@deprecated " + deprecationReason.sanitizeJavaDoc())
}

javaDocCodeBlocks
.takeIf { it.isNotEmpty() }
?.joinToString("\n\n")
?.also { methodBuilder.addJavadoc(it) }

builderClass.addMethod(methodBuilder.build())
.addField(findReturnType, ReservedKeywordSanitizer.sanitize(inputValue.name), Modifier.PRIVATE)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,15 @@ fun jsonSubTypeAnnotation(subTypes: Collection<ClassName>): AnnotationSpec {
* https://github.com/square/javapoet/issues/670
*/
fun Description.sanitizeJavaDoc(): String {
return this.content.lines().joinToString("\n").replace("$", "$$")
return this.content.lines().joinToString("\n").sanitizeJavaDoc()
}

/**
* Javapoet treats $ as a reference
* https://github.com/square/javapoet/issues/670
*/
fun String.sanitizeJavaDoc(): String {
return replace("$", "$$")
}

fun String.toTypeName(isGenericParam: Boolean = false): TypeName {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ package com.netflix.graphql.dgs.codegen.clientapi
import com.netflix.graphql.dgs.client.codegen.GraphQLQuery
import com.netflix.graphql.dgs.codegen.*
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test

class ClientApiGenBuilderTest {
Expand Down Expand Up @@ -138,4 +139,151 @@ class ClientApiGenBuilderTest {
assertThat(result2QueryObject.name).isNotNull
assertThat(result2QueryObject.name).isEqualTo("test")
}

@Nested
inner class Deprecation {

@Test
fun `adds @Deprecated annotation and reason from schema directives when setting enabled`() {
val schema = """
type Query {
filter(
nameFilter: String @deprecated(reason: "use idFilter instead"),
idFilter: ID
): [String]
}
""".trimIndent()

val codeGenResult = CodeGen(
CodeGenConfig(
schemas = setOf(schema),
packageName = basePackageName,
generateClientApiv2 = true,
maxProjectionDepth = 2,
addDeprecatedAnnotation = true
)
).generate()

assertThat(codeGenResult.javaQueryTypes.size).isEqualTo(1)
assertThat(codeGenResult.javaQueryTypes[0].typeSpec.name).isEqualTo("FilterGraphQLQuery")
assertThat(codeGenResult.javaQueryTypes[0].typeSpec.typeSpecs).hasSize(1)
assertThat(codeGenResult.javaQueryTypes[0].typeSpec.typeSpecs[0].methodSpecs).hasSize(4)
assertThat(codeGenResult.javaQueryTypes[0].typeSpec.typeSpecs[0].methodSpecs[1].name).isEqualTo("nameFilter")
assertThat(codeGenResult.javaQueryTypes[0].typeSpec.typeSpecs[0].methodSpecs[1].toString()).startsWith(
"""
|/**
| * @deprecated use idFilter instead
| */
|@java.lang.Deprecated
""".trimMargin()
)
}

@Test
fun `adds @Deprecated annotation without a Javadoc when there is no reason`() {
val schema = """
type Query {
filter(
nameFilter: String @deprecated,
idFilter: ID
): [String]
}
""".trimIndent()

val codeGenResult = CodeGen(
CodeGenConfig(
schemas = setOf(schema),
packageName = basePackageName,
generateClientApiv2 = true,
maxProjectionDepth = 2,
addDeprecatedAnnotation = true
)
).generate()

assertThat(codeGenResult.javaQueryTypes.size).isEqualTo(1)
assertThat(codeGenResult.javaQueryTypes[0].typeSpec.name).isEqualTo("FilterGraphQLQuery")
assertThat(codeGenResult.javaQueryTypes[0].typeSpec.typeSpecs).hasSize(1)
assertThat(codeGenResult.javaQueryTypes[0].typeSpec.typeSpecs[0].methodSpecs).hasSize(4)
assertThat(codeGenResult.javaQueryTypes[0].typeSpec.typeSpecs[0].methodSpecs[1].name).isEqualTo("nameFilter")
assertThat(codeGenResult.javaQueryTypes[0].typeSpec.typeSpecs[0].methodSpecs[1].toString()).startsWith(
"""
|@java.lang.Deprecated
""".trimMargin()
)
}

@Test
fun `Deprecation reason and field's description go both into JavaDoc separated by an empty line`() {
val schema = """
type Query {
filter(
${"\"\"\""}
Filters by name.
If not provided, no filter in regards to name is applied.
${"\"\"\""}
nameFilter: String @deprecated(reason: "use idFilter instead"),
idFilter: ID
): [String]
}
""".trimIndent()

val codeGenResult = CodeGen(
CodeGenConfig(
schemas = setOf(schema),
packageName = basePackageName,
generateClientApiv2 = true,
maxProjectionDepth = 2,
addDeprecatedAnnotation = true
)
).generate()

assertThat(codeGenResult.javaQueryTypes.size).isEqualTo(1)
assertThat(codeGenResult.javaQueryTypes[0].typeSpec.name).isEqualTo("FilterGraphQLQuery")
assertThat(codeGenResult.javaQueryTypes[0].typeSpec.typeSpecs).hasSize(1)
assertThat(codeGenResult.javaQueryTypes[0].typeSpec.typeSpecs[0].methodSpecs).hasSize(4)
assertThat(codeGenResult.javaQueryTypes[0].typeSpec.typeSpecs[0].methodSpecs[1].name).isEqualTo("nameFilter")
assertThat(codeGenResult.javaQueryTypes[0].typeSpec.typeSpecs[0].methodSpecs[1].toString()).startsWith(
"""
|/**
| * Filters by name.
| *
| * If not provided, no filter in regards to name is applied.
| *
| * @deprecated use idFilter instead
| */
|@java.lang.Deprecated
""".trimMargin()
)
}

@Test
fun `adds nothing extra when addDeprecatedAnnotation is not enabled`() {
val schema = """
type Query {
filter(
nameFilter: String @deprecated(reason: "use idFilter instead"),
idFilter: ID
): [String]
}
""".trimIndent()

val codeGenResult = CodeGen(
CodeGenConfig(
schemas = setOf(schema),
packageName = basePackageName,
generateClientApiv2 = true,
maxProjectionDepth = 2
)
).generate()

assertThat(codeGenResult.javaQueryTypes.size).isEqualTo(1)
assertThat(codeGenResult.javaQueryTypes[0].typeSpec.name).isEqualTo("FilterGraphQLQuery")
assertThat(codeGenResult.javaQueryTypes[0].typeSpec.typeSpecs).hasSize(1)
assertThat(codeGenResult.javaQueryTypes[0].typeSpec.typeSpecs[0].methodSpecs).hasSize(4)
assertThat(codeGenResult.javaQueryTypes[0].typeSpec.typeSpecs[0].methodSpecs[1].name).isEqualTo("nameFilter")
assertThat(codeGenResult.javaQueryTypes[0].typeSpec.typeSpecs[0].methodSpecs[1].annotations).isEmpty()
assertThat(codeGenResult.javaQueryTypes[0].typeSpec.typeSpecs[0].methodSpecs[1].javadoc.isEmpty).isTrue()
}
}
}

0 comments on commit 1d235a1

Please sign in to comment.