Skip to content

Commit

Permalink
feat(#389): allow templating for generated Java / Kotlin class names
Browse files Browse the repository at this point in the history
Adds an optional `nameTemplate` input parameter to the plugin, allowing
customization of the generated Java / Kotlin class names.

The following template variables are supported:
- name - the original name of the class
- schemaType - the GraphQL schema type (Type, Input, Interface, Enum)

The default value for this new property is null. In this case the output
will be identical to the current one.

Examples:

Given an original class name `Person` and schema type `Type`:
 - null -> Person
 - "{name}GraphQL{schemaType}" -> PersonGraphQLType
 - "{name}GraphQL" -> PersonGraphQL
 - "{name}{schemaType}" -> PersonType
  • Loading branch information
BlasiusSecundus committed Jan 21, 2024
1 parent 506ad51 commit 8d2dc23
Show file tree
Hide file tree
Showing 24 changed files with 448 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,8 @@ class CodeGenConfig(
var javaGenerateAllConstructor: Boolean = true,
var implementSerializable: Boolean = false,
var addGeneratedAnnotation: Boolean = false,
var addDeprecatedAnnotation: Boolean = false
var addDeprecatedAnnotation: Boolean = false,
var nameTemplate: String? = null
) {
val packageNameClient: String = "$packageName.$subPackageNameClient"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,39 @@

package com.netflix.graphql.dgs.codegen.generators.java

import com.netflix.graphql.dgs.codegen.*
import com.netflix.graphql.dgs.codegen.CodeGenConfig
import com.netflix.graphql.dgs.codegen.CodeGenResult
import com.netflix.graphql.dgs.codegen.filterSkipped
import com.netflix.graphql.dgs.codegen.generators.shared.CodeGeneratorUtils.templatedClassName
import com.netflix.graphql.dgs.codegen.generators.shared.SiteTarget
import com.netflix.graphql.dgs.codegen.generators.shared.applyDirectivesJava
import com.squareup.javapoet.*
import graphql.language.*
import com.netflix.graphql.dgs.codegen.shouldSkip
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.CodeBlock
import com.squareup.javapoet.FieldSpec
import com.squareup.javapoet.JavaFile
import com.squareup.javapoet.MethodSpec
import com.squareup.javapoet.ParameterSpec
import com.squareup.javapoet.TypeSpec
import graphql.language.ArrayValue
import graphql.language.BooleanValue
import graphql.language.Description
import graphql.language.Directive
import graphql.language.Document
import graphql.language.EnumValue
import graphql.language.FloatValue
import graphql.language.InputObjectTypeDefinition
import graphql.language.InputObjectTypeExtensionDefinition
import graphql.language.IntValue
import graphql.language.InterfaceTypeDefinition
import graphql.language.ListType
import graphql.language.ObjectTypeDefinition
import graphql.language.ObjectTypeExtensionDefinition
import graphql.language.ObjectValue
import graphql.language.StringValue
import graphql.language.Type
import graphql.language.TypeName
import graphql.language.UnionTypeDefinition
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.io.Serializable
Expand All @@ -39,7 +66,7 @@ class DataTypeGenerator(config: CodeGenConfig, document: Document) : BaseDataTyp

logger.info("Generating data type ${definition.name}")

val name = definition.name
val name = definition.templatedClassName(config.nameTemplate)
val unionTypes = document.getDefinitionsOfType(UnionTypeDefinition::class.java).filter { union ->
union.memberTypes.asSequence().map { it as TypeName }.any { it.name == name }
}.map { it.name }
Expand Down Expand Up @@ -120,7 +147,7 @@ class InputTypeGenerator(config: CodeGenConfig, document: Document) : BaseDataTy

logger.info("Generating input type ${definition.name}")

val name = definition.name
val name = definition.templatedClassName(config.nameTemplate)
val fieldDefinitions = definition.inputValueDefinitions.map {
val defaultValue = it.defaultValue?.let { defVal ->
when (defVal) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package com.netflix.graphql.dgs.codegen.generators.java

import com.netflix.graphql.dgs.codegen.CodeGenConfig
import com.netflix.graphql.dgs.codegen.CodeGenResult
import com.netflix.graphql.dgs.codegen.generators.shared.CodeGeneratorUtils.templatedClassName
import com.netflix.graphql.dgs.codegen.generators.shared.SiteTarget
import com.netflix.graphql.dgs.codegen.generators.shared.applyDirectivesJava
import com.netflix.graphql.dgs.codegen.shouldSkip
Expand All @@ -42,7 +43,7 @@ class EnumTypeGenerator(private val config: CodeGenConfig) {

val javaType =
TypeSpec
.enumBuilder(definition.name)
.enumBuilder(definition.templatedClassName(config.nameTemplate))
.addModifiers(Modifier.PUBLIC)
.addOptionalGeneratedAnnotation(config)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,18 @@ import com.netflix.graphql.dgs.codegen.CodeGenConfig
import com.netflix.graphql.dgs.codegen.CodeGenResult
import com.netflix.graphql.dgs.codegen.filterSkipped
import com.netflix.graphql.dgs.codegen.generators.shared.CodeGeneratorUtils.capitalized
import com.netflix.graphql.dgs.codegen.generators.shared.CodeGeneratorUtils.templatedClassName
import com.netflix.graphql.dgs.codegen.shouldSkip
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.JavaFile
import com.squareup.javapoet.MethodSpec
import com.squareup.javapoet.TypeSpec
import graphql.language.*
import graphql.language.Document
import graphql.language.FieldDefinition
import graphql.language.InterfaceTypeDefinition
import graphql.language.InterfaceTypeExtensionDefinition
import graphql.language.ObjectTypeDefinition
import graphql.language.TypeName
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import javax.lang.model.element.Modifier
Expand All @@ -48,7 +54,7 @@ class InterfaceGenerator(private val config: CodeGenConfig, private val document
}

logger.info("Generating type ${definition.name}")
val javaType = TypeSpec.interfaceBuilder(definition.name)
val javaType = TypeSpec.interfaceBuilder(definition.templatedClassName(config.nameTemplate))
.addOptionalGeneratedAnnotation(config)
.addModifiers(Modifier.PUBLIC)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import com.netflix.graphql.dgs.codegen.CodeGenConfig
import com.netflix.graphql.dgs.codegen.CodeGenResult
import com.netflix.graphql.dgs.codegen.filterSkipped
import com.netflix.graphql.dgs.codegen.generators.java.InputTypeGenerator
import com.netflix.graphql.dgs.codegen.generators.shared.CodeGeneratorUtils.templatedClassName
import com.netflix.graphql.dgs.codegen.generators.shared.applyDirectivesKotlin
import com.netflix.graphql.dgs.codegen.shouldSkip
import com.squareup.kotlinpoet.BOOLEAN
Expand Down Expand Up @@ -67,7 +68,7 @@ class KotlinDataTypeGenerator(config: CodeGenConfig, document: Document) :
.filterSkipped()
.map { Field(it.name, typeUtils.findReturnType(it.type), typeUtils.isNullable(it.type), null, it.description, it.directives) }
val interfaces = definition.implements
return generate(definition.name, fields, interfaces, document, definition.description, definition.directives)
return generate(definition.templatedClassName(config.nameTemplate), fields, interfaces, document, definition.description, definition.directives)
}
}

Expand All @@ -94,7 +95,7 @@ class KotlinInputTypeGenerator(config: CodeGenConfig, document: Document) :
}
)
val interfaces = emptyList<Type<*>>()
return generate(definition.name, fields, interfaces, document, definition.description, definition.directives)
return generate(definition.templatedClassName(config.nameTemplate), fields, interfaces, document, definition.description, definition.directives)
}

private fun generateCode(value: Value<Value<*>>, type: KtTypeName): CodeBlock =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import com.netflix.graphql.dgs.codegen.CodeGenConfig
import com.netflix.graphql.dgs.codegen.CodeGenResult
import com.netflix.graphql.dgs.codegen.generators.java.EnumTypeGenerator
import com.netflix.graphql.dgs.codegen.generators.java.ReservedKeywordSanitizer
import com.netflix.graphql.dgs.codegen.generators.shared.CodeGeneratorUtils.templatedClassName
import com.netflix.graphql.dgs.codegen.generators.shared.applyDirectivesKotlin
import com.netflix.graphql.dgs.codegen.shouldSkip
import com.squareup.kotlinpoet.FileSpec
Expand All @@ -41,7 +42,7 @@ class KotlinEnumTypeGenerator(private val config: CodeGenConfig) {

logger.info("Generating enum type ${definition.name}")

val kotlinType = TypeSpec.classBuilder(definition.name)
val kotlinType = TypeSpec.classBuilder(definition.templatedClassName(config.nameTemplate))
.addOptionalGeneratedAnnotation(config)
.addModifiers(KModifier.ENUM)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package com.netflix.graphql.dgs.codegen.generators.kotlin

import com.netflix.graphql.dgs.codegen.CodeGenConfig
import com.netflix.graphql.dgs.codegen.CodeGenResult
import com.netflix.graphql.dgs.codegen.generators.shared.CodeGeneratorUtils.templatedClassName
import com.netflix.graphql.dgs.codegen.shouldSkip
import com.squareup.kotlinpoet.*
import graphql.language.*
Expand All @@ -46,7 +47,7 @@ class KotlinInterfaceTypeGenerator(private val config: CodeGenConfig, private va

logger.info("Generating type {}", definition.name)

val interfaceBuilder = TypeSpec.interfaceBuilder(definition.name)
val interfaceBuilder = TypeSpec.interfaceBuilder(definition.templatedClassName(config.nameTemplate))
.addOptionalGeneratedAnnotation(config)
if (definition.description != null) {
interfaceBuilder.addKdoc("%L", definition.description.sanitizeKdoc())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import com.netflix.graphql.dgs.codegen.generators.kotlin.sanitizeKdoc
import com.netflix.graphql.dgs.codegen.generators.kotlin.suppressInapplicableJvmNameAnnotation
import com.netflix.graphql.dgs.codegen.generators.kotlin.toKtTypeName
import com.netflix.graphql.dgs.codegen.generators.shared.CodeGeneratorUtils.capitalized
import com.netflix.graphql.dgs.codegen.generators.shared.CodeGeneratorUtils.templatedClassName
import com.netflix.graphql.dgs.codegen.generators.shared.SchemaExtensionsUtils.findTypeExtensions
import com.netflix.graphql.dgs.codegen.generators.shared.excludeSchemaTypeExtension
import com.netflix.graphql.dgs.codegen.shouldSkip
Expand Down Expand Up @@ -166,7 +167,7 @@ fun generateKotlin2DataTypes(
.build()

// create the data class
val typeSpec = TypeSpec.classBuilder(typeDefinition.name)
val typeSpec = TypeSpec.classBuilder(typeDefinition.templatedClassName(config.nameTemplate))
.addOptionalGeneratedAnnotation(config)
// add docs if available
.apply {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import com.netflix.graphql.dgs.codegen.CodeGenConfig
import com.netflix.graphql.dgs.codegen.generators.kotlin.addEnumConstants
import com.netflix.graphql.dgs.codegen.generators.kotlin.addOptionalGeneratedAnnotation
import com.netflix.graphql.dgs.codegen.generators.kotlin.sanitizeKdoc
import com.netflix.graphql.dgs.codegen.generators.shared.CodeGeneratorUtils.templatedClassName
import com.netflix.graphql.dgs.codegen.generators.shared.SchemaExtensionsUtils.findEnumExtensions
import com.netflix.graphql.dgs.codegen.generators.shared.applyDirectivesKotlin
import com.netflix.graphql.dgs.codegen.generators.shared.excludeSchemaTypeExtension
Expand Down Expand Up @@ -55,7 +56,7 @@ fun generateKotlin2EnumTypes(
.flatMap { it.enumValueDefinitions }

// create the enum class
val enumSpec = TypeSpec.classBuilder(enumDefinition.name)
val enumSpec = TypeSpec.classBuilder(enumDefinition.templatedClassName(config.nameTemplate))
.addOptionalGeneratedAnnotation(config)
.addModifiers(KModifier.ENUM)
// add docs if available
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import com.netflix.graphql.dgs.codegen.GraphQLInput
import com.netflix.graphql.dgs.codegen.generators.kotlin.ReservedKeywordFilter
import com.netflix.graphql.dgs.codegen.generators.kotlin.addOptionalGeneratedAnnotation
import com.netflix.graphql.dgs.codegen.generators.kotlin.sanitizeKdoc
import com.netflix.graphql.dgs.codegen.generators.shared.CodeGeneratorUtils.templatedClassName
import com.netflix.graphql.dgs.codegen.generators.shared.SchemaExtensionsUtils.findInputExtensions
import com.netflix.graphql.dgs.codegen.generators.shared.excludeSchemaTypeExtension
import com.netflix.graphql.dgs.codegen.shouldSkip
Expand Down Expand Up @@ -70,7 +71,7 @@ fun generateKotlin2InputTypes(
val typeName = ClassName(config.packageNameTypes, inputDefinition.name)

// create the input class
val typeSpec = TypeSpec.classBuilder(typeName)
val typeSpec = TypeSpec.classBuilder(inputDefinition.templatedClassName(config.nameTemplate))
.addOptionalGeneratedAnnotation(config)
// add docs if available
.apply {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import com.netflix.graphql.dgs.codegen.generators.kotlin.jsonTypeInfoAnnotation
import com.netflix.graphql.dgs.codegen.generators.kotlin.jvmNameAnnotation
import com.netflix.graphql.dgs.codegen.generators.kotlin.sanitizeKdoc
import com.netflix.graphql.dgs.codegen.generators.kotlin.suppressInapplicableJvmNameAnnotation
import com.netflix.graphql.dgs.codegen.generators.shared.CodeGeneratorUtils.templatedClassName
import com.netflix.graphql.dgs.codegen.generators.shared.SchemaExtensionsUtils.findInterfaceExtensions
import com.netflix.graphql.dgs.codegen.generators.shared.SchemaExtensionsUtils.findUnionExtensions
import com.netflix.graphql.dgs.codegen.generators.shared.excludeSchemaTypeExtension
Expand Down Expand Up @@ -82,7 +83,7 @@ fun generateKotlin2Interfaces(
val overrideFields = typeLookup.overrideFields(implementedInterfaces)

// create the interface
val interfaceSpec = TypeSpec.interfaceBuilder(interfaceDefinition.name)
val interfaceSpec = TypeSpec.interfaceBuilder(interfaceDefinition.templatedClassName(config.nameTemplate))
.addOptionalGeneratedAnnotation(config)
.addModifiers(KModifier.SEALED)
// add docs if available
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@

package com.netflix.graphql.dgs.codegen.generators.shared

import graphql.language.EnumTypeDefinition
import graphql.language.InputObjectTypeDefinition
import graphql.language.InterfaceTypeDefinition
import graphql.language.NamedNode
import graphql.language.ObjectTypeDefinition

object CodeGeneratorUtils {

enum class Case {
Expand All @@ -40,6 +46,37 @@ object CodeGeneratorUtils {

fun String.capitalized(): String = replaceFirstChar(Character::toTitleCase)

/**
* Returns the name generated using [nameTemplate]. If [nameTemplate] is null, returns the name of the node.
*
* The following variables are available in the template:
* - name: the original name of the node
* - schemaType: the GraphQL Schema type of the node (Type, Input, Interface, Enum)
*
* Examples:
* Given a node named "Person" and schema type "Type", the following templates will generate the following names:
* - null -> "Person"
* - "{name}GraphQL{schemaType}" -> "PersonGraphQLType"
* - "{name}GraphQL" -> "PersonGraphQL"
* - "{name}{schemaType}" -> "PersonType"
*/
fun NamedNode<*>.templatedClassName(nameTemplate: String?): String =
nameTemplate
?.replace(
"{name}",
this.name
)
?.replace(
"{schemaType}",
when (this) {
is ObjectTypeDefinition -> "Type"
is InputObjectTypeDefinition -> "Input"
is InterfaceTypeDefinition -> "Interface"
is EnumTypeDefinition -> "Enum"
else -> ""
}
) ?: this.name

/**
* Mostly copied from Apache Commons StringUtils.splitByCharacterType
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4554,4 +4554,23 @@ It takes a title and such.
assertThat(dataTypes[0].typeSpec.fieldSpecs[0].initializer.toString()).isEqualTo("Locale.forLanguageTag(\"en-US\")")
assertCompilesJava(dataTypes)
}

@TemplateClassNameTest
fun generateSerializedDataClassWithCustomName(
schema: String,
nameTemplate: String?,
expectedName: String
) {
val dataTypes = CodeGen(
CodeGenConfig(
schemas = setOf(schema),
packageName = basePackageName,
nameTemplate = nameTemplate
)
)
.generate()
.javaSources()

assertThat(dataTypes.firstOrNull()?.typeSpec?.name).isEqualTo(expectedName)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3763,4 +3763,21 @@ It takes a title and such.
assertThat(typeSpec.primaryConstructor!!.parameters[0].defaultValue.toString()).isEqualTo("Locale.forLanguageTag(\"en-US\")")
assertCompilesKotlin(dataTypes)
}

@TemplateClassNameTest
fun `Should generate class names based on template`(
schema: String,
nameTemplate: String?,
expectedName: String
) {
val dataTypes = CodeGen(
CodeGenConfig(
schemas = setOf(schema),
language = Language.KOTLIN,
nameTemplate = nameTemplate
)
).generate().kotlinSources()

assertThat(dataTypes.firstOrNull()?.name).isEqualTo(expectedName)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,23 +28,7 @@ import java.io.Serializable
class Kotline2CodeGenTest {
@Test
fun generateSerializableDataClass() {
val schema = """
type Person {
firstname: String
lastname: String
}
""".trimIndent()

val codeGenResult = CodeGen(
CodeGenConfig(
schemas = setOf(schema),
packageName = basePackageName,
language = Language.KOTLIN,
generateKotlinNullableClasses = true,
generateKotlinClosureProjections = true,
implementSerializable = true
)
).generate()
val codeGenResult = CodeGen(getTestCodeGenConfig()).generate()

val dataTypes = codeGenResult.kotlinDataTypes

Expand Down Expand Up @@ -92,4 +76,32 @@ class Kotline2CodeGenTest {
)
assertCompilesKotlin(result.kotlinEnumTypes)
}

@TemplateClassNameTest
fun generateSerializedDataClassWithCustomName(
schema: String,
nameTemplate: String?,
expectedName: String
) {
val dataTypes = CodeGen(getTestCodeGenConfig(nameTemplate = nameTemplate, schema = schema))
.generate()
.kotlinSources()

assertThat(dataTypes.firstOrNull()?.name).isEqualTo(expectedName)
}

companion object {
private fun getTestCodeGenConfig(
nameTemplate: String? = null,
schema: String = PERSON_TYPE_SCHEMA
): CodeGenConfig = CodeGenConfig(
schemas = setOf(schema),
packageName = basePackageName,
language = Language.KOTLIN,
generateKotlinNullableClasses = true,
generateKotlinClosureProjections = true,
implementSerializable = true,
nameTemplate = nameTemplate
)
}
}
Loading

0 comments on commit 8d2dc23

Please sign in to comment.