From 8d2dc23db311f9d7cdf8681cb7e6ab18762a95a1 Mon Sep 17 00:00:00 2001 From: Balazs Vajner Date: Wed, 22 Mar 2023 21:57:21 +0100 Subject: [PATCH] feat(#389): allow templating for generated Java / Kotlin class names 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 --- .../netflix/graphql/dgs/codegen/CodeGen.kt | 3 +- .../generators/java/DataTypeGenerator.kt | 37 ++++++++++-- .../generators/java/EnumTypeGenerator.kt | 3 +- .../generators/java/InterfaceGenerator.kt | 10 +++- .../kotlin/KotlinDataTypeGenerator.kt | 5 +- .../kotlin/KotlinEnumTypeGenerator.kt | 3 +- .../kotlin/KotlinInterfaceTypeGenerator.kt | 3 +- .../kotlin2/GenerateKotlin2DataTypes.kt | 3 +- .../kotlin2/GenerateKotlin2EnumTypes.kt | 3 +- .../kotlin2/GenerateKotlin2InputTypes.kt | 3 +- .../kotlin2/GenerateKotlin2Interfaces.kt | 3 +- .../generators/shared/CodeGeneratorUtils.kt | 37 ++++++++++++ .../graphql/dgs/codegen/CodeGenTest.kt | 19 ++++++ .../graphql/dgs/codegen/KotlinCodeGenTest.kt | 17 ++++++ .../dgs/codegen/Kotline2CodeGenTest.kt | 46 +++++++++------ .../netflix/graphql/dgs/codegen/TestUtils.kt | 34 +++++++++++ .../dgs/codegen/gradle/GenerateJavaTask.kt | 8 ++- .../graphql/dgs/CodegenGradlePluginTest.kt | 58 +++++++++++++++++++ .../build_custom_name_template.gradle | 42 ++++++++++++++ .../build_custom_name_template_kotlin.gradle | 43 ++++++++++++++ .../build_custom_name_template_kotlin2.gradle | 46 +++++++++++++++ ..._test_settings_custom_name_template.gradle | 19 ++++++ ...ettings_custom_name_template_kotlin.gradle | 19 ++++++ ...ttings_custom_name_template_kotlin2.gradle | 19 ++++++ 24 files changed, 448 insertions(+), 35 deletions(-) create mode 100644 graphql-dgs-codegen-gradle/src/test/resources/test-project/build_custom_name_template.gradle create mode 100644 graphql-dgs-codegen-gradle/src/test/resources/test-project/build_custom_name_template_kotlin.gradle create mode 100644 graphql-dgs-codegen-gradle/src/test/resources/test-project/build_custom_name_template_kotlin2.gradle create mode 100644 graphql-dgs-codegen-gradle/src/test/resources/test-project/smoke_test_settings_custom_name_template.gradle create mode 100644 graphql-dgs-codegen-gradle/src/test/resources/test-project/smoke_test_settings_custom_name_template_kotlin.gradle create mode 100644 graphql-dgs-codegen-gradle/src/test/resources/test-project/smoke_test_settings_custom_name_template_kotlin2.gradle diff --git a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/CodeGen.kt b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/CodeGen.kt index bbc474e45..fc8f983be 100644 --- a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/CodeGen.kt +++ b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/CodeGen.kt @@ -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" 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 49dfbab58..9f18d2bd1 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 @@ -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 @@ -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 } @@ -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) { diff --git a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/java/EnumTypeGenerator.kt b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/java/EnumTypeGenerator.kt index 235bafbb7..9a45da856 100644 --- a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/java/EnumTypeGenerator.kt +++ b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/java/EnumTypeGenerator.kt @@ -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 @@ -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) diff --git a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/java/InterfaceGenerator.kt b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/java/InterfaceGenerator.kt index a02c3d3ee..bc3e581ab 100644 --- a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/java/InterfaceGenerator.kt +++ b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/java/InterfaceGenerator.kt @@ -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 @@ -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) diff --git a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin/KotlinDataTypeGenerator.kt b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin/KotlinDataTypeGenerator.kt index 9b580b926..3a232772f 100644 --- a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin/KotlinDataTypeGenerator.kt +++ b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin/KotlinDataTypeGenerator.kt @@ -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 @@ -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) } } @@ -94,7 +95,7 @@ class KotlinInputTypeGenerator(config: CodeGenConfig, document: Document) : } ) val interfaces = emptyList>() - 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>, type: KtTypeName): CodeBlock = diff --git a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin/KotlinEnumTypeGenerator.kt b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin/KotlinEnumTypeGenerator.kt index 4c48ec115..6aa6e4ff9 100644 --- a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin/KotlinEnumTypeGenerator.kt +++ b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin/KotlinEnumTypeGenerator.kt @@ -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 @@ -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) diff --git a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin/KotlinInterfaceTypeGenerator.kt b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin/KotlinInterfaceTypeGenerator.kt index 23f7261f6..8634c687b 100644 --- a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin/KotlinInterfaceTypeGenerator.kt +++ b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin/KotlinInterfaceTypeGenerator.kt @@ -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.* @@ -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()) diff --git a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin2/GenerateKotlin2DataTypes.kt b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin2/GenerateKotlin2DataTypes.kt index 9fc33e406..d834a29e9 100644 --- a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin2/GenerateKotlin2DataTypes.kt +++ b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin2/GenerateKotlin2DataTypes.kt @@ -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 @@ -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 { diff --git a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin2/GenerateKotlin2EnumTypes.kt b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin2/GenerateKotlin2EnumTypes.kt index 0cba5ad2f..f2626a392 100644 --- a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin2/GenerateKotlin2EnumTypes.kt +++ b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin2/GenerateKotlin2EnumTypes.kt @@ -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 @@ -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 diff --git a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin2/GenerateKotlin2InputTypes.kt b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin2/GenerateKotlin2InputTypes.kt index b7fbc7993..48d41298a 100644 --- a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin2/GenerateKotlin2InputTypes.kt +++ b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin2/GenerateKotlin2InputTypes.kt @@ -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 @@ -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 { diff --git a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin2/GenerateKotlin2Interfaces.kt b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin2/GenerateKotlin2Interfaces.kt index 097283e6e..8dab1e185 100644 --- a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin2/GenerateKotlin2Interfaces.kt +++ b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin2/GenerateKotlin2Interfaces.kt @@ -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 @@ -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 diff --git a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/shared/CodeGeneratorUtils.kt b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/shared/CodeGeneratorUtils.kt index d38ea18a0..c9a47f195 100644 --- a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/shared/CodeGeneratorUtils.kt +++ b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/shared/CodeGeneratorUtils.kt @@ -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 { @@ -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 */ 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 f469ee49e..89ee2874f 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 @@ -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) + } } diff --git a/graphql-dgs-codegen-core/src/test/kotlin/com/netflix/graphql/dgs/codegen/KotlinCodeGenTest.kt b/graphql-dgs-codegen-core/src/test/kotlin/com/netflix/graphql/dgs/codegen/KotlinCodeGenTest.kt index acb4f29cd..7dcfb9c5e 100644 --- a/graphql-dgs-codegen-core/src/test/kotlin/com/netflix/graphql/dgs/codegen/KotlinCodeGenTest.kt +++ b/graphql-dgs-codegen-core/src/test/kotlin/com/netflix/graphql/dgs/codegen/KotlinCodeGenTest.kt @@ -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) + } } diff --git a/graphql-dgs-codegen-core/src/test/kotlin/com/netflix/graphql/dgs/codegen/Kotline2CodeGenTest.kt b/graphql-dgs-codegen-core/src/test/kotlin/com/netflix/graphql/dgs/codegen/Kotline2CodeGenTest.kt index b794627f6..41f50996e 100644 --- a/graphql-dgs-codegen-core/src/test/kotlin/com/netflix/graphql/dgs/codegen/Kotline2CodeGenTest.kt +++ b/graphql-dgs-codegen-core/src/test/kotlin/com/netflix/graphql/dgs/codegen/Kotline2CodeGenTest.kt @@ -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 @@ -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 + ) + } } diff --git a/graphql-dgs-codegen-core/src/test/kotlin/com/netflix/graphql/dgs/codegen/TestUtils.kt b/graphql-dgs-codegen-core/src/test/kotlin/com/netflix/graphql/dgs/codegen/TestUtils.kt index 85e78f93d..caded8ef5 100644 --- a/graphql-dgs-codegen-core/src/test/kotlin/com/netflix/graphql/dgs/codegen/TestUtils.kt +++ b/graphql-dgs-codegen-core/src/test/kotlin/com/netflix/graphql/dgs/codegen/TestUtils.kt @@ -34,6 +34,8 @@ import org.jetbrains.kotlin.cli.common.messages.MessageRenderer import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler import org.jetbrains.kotlin.config.Services +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.CsvSource import org.junit.platform.commons.util.ReflectionUtils import java.io.File import java.lang.reflect.Method @@ -165,6 +167,38 @@ fun TypeSpec.assertJavaGeneratedAnnotation() { fun AnnotationSpec.canonicalName(): String = (type as ClassName).canonicalName() fun KAnnotationSpec.canonicalName() = (typeName as KClassName).canonicalName +@ParameterizedTest +@CsvSource( + value = [ + "'$PERSON_TYPE_SCHEMA',,Person", + "'$PERSON_TYPE_SCHEMA',{name}Dto,PersonDto", + "'$PERSON_TYPE_SCHEMA',{name}{schemaType}Dto,PersonTypeDto", + "'$PERSON_TYPE_SCHEMA',{name}GraphQL{schemaType},PersonGraphQLType", + "'$PERSON_TYPE_SCHEMA',{name}{schemaType},PersonType", + "'$PERSON_INPUT_SCHEMA',,Person", + "'$PERSON_INPUT_SCHEMA',{name}Dto,PersonDto", + "'$PERSON_INPUT_SCHEMA',{name}{schemaType}Dto,PersonInputDto", + "'$PERSON_INPUT_SCHEMA',{name}GraphQL{schemaType},PersonGraphQLInput", + "'$PERSON_INPUT_SCHEMA',{name}{schemaType},PersonInput", + "'$PERSON_INTERFACE_SCHEMA',,Person", + "'$PERSON_INTERFACE_SCHEMA',{name}Dto,PersonDto", + "'$PERSON_INTERFACE_SCHEMA',{name}{schemaType}Dto,PersonInterfaceDto", + "'$PERSON_INTERFACE_SCHEMA',{name}GraphQL{schemaType},PersonGraphQLInterface", + "'$PERSON_INTERFACE_SCHEMA',{name}{schemaType},PersonInterface", + "'$ENUM_SCHEMA',,Color", + "'$ENUM_SCHEMA',{name}Dto,ColorDto", + "'$ENUM_SCHEMA',{name}{schemaType}Dto,ColorEnumDto", + "'$ENUM_SCHEMA',{name}GraphQL{schemaType},ColorGraphQLEnum", + "'$ENUM_SCHEMA',{name}{schemaType},ColorEnum" + ] +) +@Target(AnnotationTarget.FUNCTION) +annotation class TemplateClassNameTest + +const val PERSON_TYPE_SCHEMA: String = "type Person { firstname: String, lastname: String }" +const val PERSON_INPUT_SCHEMA = "input Person { firstname: String, lastname: String }" +const val PERSON_INTERFACE_SCHEMA = "interface Person { firstname: String, lastname: String }" +const val ENUM_SCHEMA = "enum Color { RED, GREEN, BLUE }" const val basePackageName = "com.netflix.graphql.dgs.codegen.tests.generated" const val typesPackageName = "$basePackageName.types" const val dataFetcherPackageName = "$basePackageName.datafetchers" diff --git a/graphql-dgs-codegen-gradle/src/main/kotlin/com/netflix/graphql/dgs/codegen/gradle/GenerateJavaTask.kt b/graphql-dgs-codegen-gradle/src/main/kotlin/com/netflix/graphql/dgs/codegen/gradle/GenerateJavaTask.kt index 4b642af30..647fb695b 100644 --- a/graphql-dgs-codegen-gradle/src/main/kotlin/com/netflix/graphql/dgs/codegen/gradle/GenerateJavaTask.kt +++ b/graphql-dgs-codegen-gradle/src/main/kotlin/com/netflix/graphql/dgs/codegen/gradle/GenerateJavaTask.kt @@ -25,6 +25,7 @@ import org.gradle.api.DefaultTask import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.model.ObjectFactory import org.gradle.api.tasks.* +import org.gradle.api.tasks.Optional import org.jetbrains.kotlin.gradle.plugin.KotlinPluginWrapper import java.io.File import java.nio.file.Paths @@ -156,6 +157,10 @@ open class GenerateJavaTask @Inject constructor( @Input var includeClassImports = mutableMapOf>() + @Input + @Optional + var nameTemplate: String? = null + @Classpath val dgsCodegenClasspath: ConfigurableFileCollection = objectFactory.fileCollection().from( project.configurations.findByName("dgsCodegen") @@ -212,7 +217,8 @@ open class GenerateJavaTask @Inject constructor( includeImports = includeImports, includeEnumImports = includeEnumImports, includeClassImports = includeClassImports, - generateCustomAnnotations = generateCustomAnnotations + generateCustomAnnotations = generateCustomAnnotations, + nameTemplate = nameTemplate ) logger.info("Codegen config: {}", config) diff --git a/graphql-dgs-codegen-gradle/src/test/kotlin/com/netflix/graphql/dgs/CodegenGradlePluginTest.kt b/graphql-dgs-codegen-gradle/src/test/kotlin/com/netflix/graphql/dgs/CodegenGradlePluginTest.kt index 5533be912..feae7c681 100644 --- a/graphql-dgs-codegen-gradle/src/test/kotlin/com/netflix/graphql/dgs/CodegenGradlePluginTest.kt +++ b/graphql-dgs-codegen-gradle/src/test/kotlin/com/netflix/graphql/dgs/CodegenGradlePluginTest.kt @@ -24,6 +24,8 @@ import org.assertj.core.api.Assertions.assertThat import org.gradle.testkit.runner.GradleRunner import org.gradle.testkit.runner.TaskOutcome.SUCCESS import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.CsvSource import java.io.File /** @@ -114,6 +116,62 @@ class CodegenGradlePluginTest { assertThat(File(EXPECTED_DEFAULT_PATH + "Result.java").exists()).isTrue } + @Test + fun sourcesGenerated_WithNameTemplate() { + // build a project + val result = GradleRunner.create() + .withProjectDir(File("src/test/resources/test-project/")) + .withPluginClasspath() + .withArguments( + "--stacktrace", + "-c", + "smoke_test_settings_custom_name_template.gradle", + "-b", + "build_custom_name_template.gradle", + "clean", + "build" + ) + .forwardOutput() + .withDebug(true) + .build() + + // Verify the result + assertThat(result.task(":build")).extracting { it?.outcome }.isEqualTo(SUCCESS) + // Verify that POJOs are generated in the configured directory + assertThat(File(EXPECTED_DEFAULT_PATH + "ResultGraphQLType.java").exists()).isTrue + assertThat(File(EXPECTED_DEFAULT_PATH + "FilterGraphQLInput.java").exists()).isTrue + } + + @ParameterizedTest + @CsvSource( + "smoke_test_settings_custom_name_template_kotlin.gradle,build_custom_name_template_kotlin.gradle", + "smoke_test_settings_custom_name_template_kotlin2.gradle,build_custom_name_template_kotlin2.gradle" + ) + fun sourcesGenerated_WithNameTemplate_Kotlin(settingsFile: String, buildFile: String) { + // build a project + val result = GradleRunner.create() + .withProjectDir(File("src/test/resources/test-project/")) + .withPluginClasspath() + .withArguments( + "--stacktrace", + "-c", + settingsFile, + "-b", + buildFile, + "clean", + "build" + ) + .forwardOutput() + .withDebug(true) + .build() + + // Verify the result + assertThat(result.task(":build")).extracting { it?.outcome }.isEqualTo(SUCCESS) + // Verify that POJOs are generated in the configured directory + assertThat(File(EXPECTED_DEFAULT_PATH + "ResultGraphQLType.kt").exists()).isTrue + assertThat(File(EXPECTED_DEFAULT_PATH + "FilterGraphQLInput.kt").exists()).isTrue + } + @Test fun sourcesGenerated_OmitNullInputFields() { // build a project diff --git a/graphql-dgs-codegen-gradle/src/test/resources/test-project/build_custom_name_template.gradle b/graphql-dgs-codegen-gradle/src/test/resources/test-project/build_custom_name_template.gradle new file mode 100644 index 000000000..3358f18f5 --- /dev/null +++ b/graphql-dgs-codegen-gradle/src/test/resources/test-project/build_custom_name_template.gradle @@ -0,0 +1,42 @@ +/* + * + * Copyright 2020 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. + * + */ + +plugins { + id 'java' + id 'com.netflix.dgs.codegen' +} + +configurations { + // injected by Gradle Runner through test configuration, see CodegenGradlePluginTest + CodeGenConfiguration.exclude group: "com.netflix.graphql.dgs.codegen", module: "graphql-dgs-codegen-core" +} + +generateJava { + schemaPaths = ["${projectDir}/src/main/resources/schema"] + packageName = 'com.netflix.testproject.graphql' + typeMapping = [Date:"java.time.LocalDateTime"] + nameTemplate = "{name}GraphQL{schemaType}" +} + +codegen.clientCoreConventionsEnabled = false + +tasks.register("copyMainSources", Copy) { + //This should be enough to depend on the 'generateJava' task + from sourceSets.main.java + into "build/tmp/main" +} \ No newline at end of file diff --git a/graphql-dgs-codegen-gradle/src/test/resources/test-project/build_custom_name_template_kotlin.gradle b/graphql-dgs-codegen-gradle/src/test/resources/test-project/build_custom_name_template_kotlin.gradle new file mode 100644 index 000000000..5db5e6f18 --- /dev/null +++ b/graphql-dgs-codegen-gradle/src/test/resources/test-project/build_custom_name_template_kotlin.gradle @@ -0,0 +1,43 @@ +/* + * + * Copyright 2020 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. + * + */ + +plugins { + id 'java' + id 'com.netflix.dgs.codegen' +} + +configurations { + // injected by Gradle Runner through test configuration, see CodegenGradlePluginTest + CodeGenConfiguration.exclude group: "com.netflix.graphql.dgs.codegen", module: "graphql-dgs-codegen-core" +} + +generateJava { + schemaPaths = ["${projectDir}/src/main/resources/schema"] + packageName = 'com.netflix.testproject.graphql' + typeMapping = [Date:"java.time.LocalDateTime"] + language = "KOTLIN" + nameTemplate = "{name}GraphQL{schemaType}" +} + +codegen.clientCoreConventionsEnabled = false + +tasks.register("copyMainSources", Copy) { + //This should be enough to depend on the 'generateJava' task + from sourceSets.main.java + into "build/tmp/main" +} \ No newline at end of file diff --git a/graphql-dgs-codegen-gradle/src/test/resources/test-project/build_custom_name_template_kotlin2.gradle b/graphql-dgs-codegen-gradle/src/test/resources/test-project/build_custom_name_template_kotlin2.gradle new file mode 100644 index 000000000..4f8220027 --- /dev/null +++ b/graphql-dgs-codegen-gradle/src/test/resources/test-project/build_custom_name_template_kotlin2.gradle @@ -0,0 +1,46 @@ +/* + * + * Copyright 2020 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. + * + */ + +plugins { + id 'java' + id 'com.netflix.dgs.codegen' +} + +configurations { + // injected by Gradle Runner through test configuration, see CodegenGradlePluginTest + CodeGenConfiguration.exclude group: "com.netflix.graphql.dgs.codegen", module: "graphql-dgs-codegen-core" +} + +generateJava { + schemaPaths = ["${projectDir}/src/main/resources/schema"] + packageName = 'com.netflix.testproject.graphql' + typeMapping = [Date:"java.time.LocalDateTime"] + language = "KOTLIN" + nameTemplate = "{name}GraphQL{schemaType}" + generateKotlinNullableClasses = true + generateKotlinClosureProjections = true + implementSerializable = true +} + +codegen.clientCoreConventionsEnabled = false + +tasks.register("copyMainSources", Copy) { + //This should be enough to depend on the 'generateJava' task + from sourceSets.main.java + into "build/tmp/main" +} \ No newline at end of file diff --git a/graphql-dgs-codegen-gradle/src/test/resources/test-project/smoke_test_settings_custom_name_template.gradle b/graphql-dgs-codegen-gradle/src/test/resources/test-project/smoke_test_settings_custom_name_template.gradle new file mode 100644 index 000000000..e013aa733 --- /dev/null +++ b/graphql-dgs-codegen-gradle/src/test/resources/test-project/smoke_test_settings_custom_name_template.gradle @@ -0,0 +1,19 @@ +/* + * + * Copyright 2020 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. + * + */ + +rootProject.buildFileName = 'build_custom_name_template.gradle' diff --git a/graphql-dgs-codegen-gradle/src/test/resources/test-project/smoke_test_settings_custom_name_template_kotlin.gradle b/graphql-dgs-codegen-gradle/src/test/resources/test-project/smoke_test_settings_custom_name_template_kotlin.gradle new file mode 100644 index 000000000..c436f84e4 --- /dev/null +++ b/graphql-dgs-codegen-gradle/src/test/resources/test-project/smoke_test_settings_custom_name_template_kotlin.gradle @@ -0,0 +1,19 @@ +/* + * + * Copyright 2020 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. + * + */ + +rootProject.buildFileName = 'build_custom_name_template_kotlin.gradle' diff --git a/graphql-dgs-codegen-gradle/src/test/resources/test-project/smoke_test_settings_custom_name_template_kotlin2.gradle b/graphql-dgs-codegen-gradle/src/test/resources/test-project/smoke_test_settings_custom_name_template_kotlin2.gradle new file mode 100644 index 000000000..408a037b3 --- /dev/null +++ b/graphql-dgs-codegen-gradle/src/test/resources/test-project/smoke_test_settings_custom_name_template_kotlin2.gradle @@ -0,0 +1,19 @@ +/* + * + * Copyright 2020 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. + * + */ + +rootProject.buildFileName = 'build_custom_name_template_kotlin2.gradle'