Skip to content

Commit

Permalink
Merge pull request #104 from Netflix/feature/codegen-constants-102
Browse files Browse the repository at this point in the history
  • Loading branch information
berngp authored Apr 17, 2021
2 parents 764bd46 + d28740f commit 9fced2c
Show file tree
Hide file tree
Showing 8 changed files with 198 additions and 20 deletions.
1 change: 1 addition & 0 deletions graphql-dgs-codegen-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ dependencies {
implementation "com.graphql-java:graphql-java:16.+"
implementation "com.github.ajalt.clikt:clikt:3.1.+"
implementation "com.fasterxml.jackson.core:jackson-annotations:2.12.+"
implementation "commons-lang:commons-lang:2.6"

testImplementation "com.google.jimfs:jimfs:1.2"
testImplementation "com.google.testing.compile:compile-testing:0.+"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,8 @@ data class CodeGenConfig(
val omitNullInputFields: Boolean = false,
val maxProjectionDepth: Int = 10,
val kotlinAllFieldsOptional: Boolean = false,
/** If enabled, the names of the classes available via the DgsConstant class will be snake cased.*/
val snakeCaseConstantNames: Boolean = false,
) {
val packageNameClient: String
get() = "$packageName.$subPackageNameClient"
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
import com.squareup.javapoet.FieldSpec
import com.squareup.javapoet.JavaFile
import com.squareup.javapoet.TypeName
Expand All @@ -33,7 +34,7 @@ class ConstantsGenerator(private val config: CodeGenConfig, private val document
.addModifiers(Modifier.PUBLIC)

document.definitions.filterIsInstance<ObjectTypeDefinition>().filter { it !is ObjectTypeExtensionDefinition }.map {
val constantsType = createConstantTypeBuilder(it.name)
val constantsType = createConstantTypeBuilder(config, it.name)

val extensions = findExtensions(it.name, document.definitions)
val fields = it.fieldDefinitions.plus(extensions.flatMap { it.fieldDefinitions })
Expand All @@ -48,7 +49,7 @@ class ConstantsGenerator(private val config: CodeGenConfig, private val document
}

document.definitions.filterIsInstance<InputObjectTypeDefinition>().filter { it !is InputObjectTypeExtensionDefinition }.map {
val constantsType = createConstantTypeBuilder(it.name)
val constantsType = createConstantTypeBuilder(config, it.name)
constantsType.addField(FieldSpec.builder(TypeName.get(String::class.java), "TYPE_NAME").addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL).initializer(""""${it.name}"""").build())

val extensions = findInputExtensions(it.name, document.definitions)
Expand All @@ -62,7 +63,7 @@ class ConstantsGenerator(private val config: CodeGenConfig, private val document
}

document.definitions.filterIsInstance<InterfaceTypeDefinition>().map {
val constantsType = createConstantTypeBuilder(it.name)
val constantsType = createConstantTypeBuilder(config, it.name)

constantsType.addField(FieldSpec.builder(TypeName.get(String::class.java), "TYPE_NAME").addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL).initializer(""""${it.name}"""").build())
it.fieldDefinitions.forEach { field ->
Expand All @@ -73,7 +74,7 @@ class ConstantsGenerator(private val config: CodeGenConfig, private val document
}

document.definitions.filterIsInstance<UnionTypeDefinition>().map {
val constantsType = createConstantTypeBuilder(it.name)
val constantsType = createConstantTypeBuilder(config, it.name)
constantsType.addField(FieldSpec.builder(TypeName.get(String::class.java), "TYPE_NAME").addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL).initializer(""""${it.name}"""").build())
}

Expand All @@ -91,8 +92,15 @@ class ConstantsGenerator(private val config: CodeGenConfig, private val document
return CodeGenResult(constants = listOf(javaFile))
}

private fun createConstantTypeBuilder(name: String): TypeSpec.Builder {
return TypeSpec.classBuilder(name.toUpperCase())
private fun createConstantTypeBuilder(conf: CodeGenConfig, name: String): TypeSpec.Builder {
val className =
if (conf.snakeCaseConstantNames)
CodeGeneratorUtils.camelCasetoSnakeCase(name, CodeGeneratorUtils.Case.UPPERCASE)
else
name.toUpperCase()

return TypeSpec
.classBuilder(className)
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
}

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.KotlinCodeGenResult
import com.netflix.graphql.dgs.codegen.generators.shared.CodeGeneratorUtils
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.PropertySpec
Expand All @@ -31,7 +32,7 @@ class KotlinConstantsGenerator(private val config: CodeGenConfig, private val do
val baseConstantsType = TypeSpec.objectBuilder("DgsConstants")

document.definitions.filterIsInstance<ObjectTypeDefinition>().filter { it !is ObjectTypeExtensionDefinition }.map {
val constantsType = TypeSpec.objectBuilder((it.name.toUpperCase()))
val constantsType = createConstantTypeBuilder(config, it.name)

val extensions = findExtensions(it.name, document.definitions)
val fields = it.fieldDefinitions.plus(extensions.flatMap { it.fieldDefinitions }).distinctBy { it.name }
Expand All @@ -46,7 +47,7 @@ class KotlinConstantsGenerator(private val config: CodeGenConfig, private val do
}

document.definitions.filterIsInstance<InputObjectTypeDefinition>().filter { it !is InputObjectTypeExtensionDefinition }.map {
val constantsType = TypeSpec.objectBuilder((it.name.toUpperCase()))
val constantsType = createConstantTypeBuilder(config, it.name)

val extensions = findInputExtensions(it.name, document.definitions)
val fields = it.inputValueDefinitions.plus(extensions.flatMap { it.inputValueDefinitions })
Expand All @@ -59,7 +60,8 @@ class KotlinConstantsGenerator(private val config: CodeGenConfig, private val do
}

document.definitions.filterIsInstance<InterfaceTypeDefinition>().map {
val constantsType = TypeSpec.objectBuilder((it.name.toUpperCase()))
val constantsType = createConstantTypeBuilder(config, it.name)

constantsType.addProperty(PropertySpec.builder("TYPE_NAME", String::class).addModifiers(KModifier.CONST).initializer(""""${it.name}"""").build())

it.fieldDefinitions.filter(ReservedKeywordFilter.filterInvalidNames).forEach { field ->
Expand All @@ -70,7 +72,8 @@ class KotlinConstantsGenerator(private val config: CodeGenConfig, private val do
}

document.definitions.filterIsInstance<UnionTypeDefinition>().map {
val constantsType = TypeSpec.objectBuilder((it.name.toUpperCase()))
val constantsType = createConstantTypeBuilder(config, it.name)

constantsType.addProperty(PropertySpec.builder("TYPE_NAME", String::class).addModifiers(KModifier.CONST).initializer(""""${it.name}"""").build())
baseConstantsType.addType(constantsType.build())
}
Expand All @@ -90,6 +93,16 @@ class KotlinConstantsGenerator(private val config: CodeGenConfig, private val do
return KotlinCodeGenResult(constants = listOf(fileSpec))
}

private fun createConstantTypeBuilder(conf: CodeGenConfig, name: String): TypeSpec.Builder {
val className =
if (conf.snakeCaseConstantNames)
CodeGeneratorUtils.camelCasetoSnakeCase(name, CodeGeneratorUtils.Case.UPPERCASE)
else
name.toUpperCase()

return TypeSpec.classBuilder(className)
}

private fun addFieldName(constantsType: TypeSpec.Builder, name: String) {
constantsType.addProperty(PropertySpec.builder(name.capitalize(), String::class).addModifiers(KModifier.CONST).initializer(""""$name"""").build())
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
*
* 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.
*
*/

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

import org.apache.commons.lang.StringUtils

object CodeGeneratorUtils {

enum class Case {
LOWERCASE,
UPPERCASE
}

/**
* Transforms the input string, that should express a camel case notation, into one that expresses a snake case notation.
*
* @see StringUtils.splitByCharacterTypeCamelCase
* */
fun camelCasetoSnakeCase(input: String, case: Case = Case.LOWERCASE): String {
val parts = StringUtils.splitByCharacterTypeCamelCase(input)
return parts.joinToString(separator = "_") {
when (case) {
Case.LOWERCASE -> it.toLowerCase()
Case.UPPERCASE -> it.toUpperCase()
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,14 @@ import com.netflix.graphql.dgs.codegen.generators.java.disableJsonTypeInfoAnnota
import com.squareup.javapoet.*
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.MethodSource
import java.net.URL
import java.net.URLClassLoader
import java.nio.file.Files
import java.time.*
import java.util.stream.Stream
import javax.tools.JavaFileObject

class CodeGenTest {
Expand Down Expand Up @@ -1215,8 +1219,12 @@ class CodeGenTest {
assertCompilesJava(dataTypes)
}

@Test
fun generateConstants() {
@ParameterizedTest(name = "{index} => Snake Case? {0}; expected names {1}")
@MethodSource("generateConstantsArguments")
fun `Generates constants from Type names available via the DgsConstants class`(
snakeCaseEnabled: Boolean,
constantNames: List<String>,
) {
val schema = """
type Query {
people: [Person]
Expand All @@ -1225,19 +1233,42 @@ class CodeGenTest {
type Person {
firstname: String
lastname: String
metadata: PersonMetaData
}
type PersonMetaData { data: [String] }
type VPersonMetaData { data: [String] }
type V1PersonMetaData { data: [String] }
type URLMetaData { data: [String] }
""".trimIndent()

val result = CodeGen(
CodeGenConfig(
schemas = setOf(schema),
packageName = basePackageName,
snakeCaseConstantNames = snakeCaseEnabled,
)
).generate() as CodeGenResult
val type = result.constants[0].typeSpec
assertThat(type.typeSpecs).extracting("name").containsExactly("QUERY", "PERSON")
assertThat(type.name).isEqualTo("DgsConstants")
assertThat(type.typeSpecs).extracting("name").containsExactlyElementsOf(constantNames)
assertThat(type.typeSpecs[0].fieldSpecs).extracting("name").containsExactly("TYPE_NAME", "People")
assertThat(type.typeSpecs[1].fieldSpecs).extracting("name").containsExactly("TYPE_NAME", "Firstname", "Lastname")
}

companion object {
@JvmStatic
fun generateConstantsArguments(): Stream<Arguments> {
return Stream.of(
Arguments.of(
true,
listOf("QUERY", "PERSON", "PERSON_META_DATA", "V_PERSON_META_DATA", "V_1_PERSON_META_DATA", "URL_META_DATA")
),
Arguments.of(
false,
listOf("QUERY", "PERSON", "PERSONMETADATA", "VPERSONMETADATA", "V1PERSONMETADATA", "URLMETADATA")
),
)
}
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ import com.squareup.kotlinpoet.*
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.MethodSource
import java.net.URLClassLoader
import java.util.stream.Stream

class KotlinCodeGenTest {

Expand Down Expand Up @@ -1306,30 +1310,57 @@ class KotlinCodeGenTest {
assertThat(type.propertySpecs).extracting("name").contains("genre", "releaseYear")
}

@Test
fun generateConstants() {
@ParameterizedTest(name = "{index} => Snake Case? {0}; expected names {1}")
@MethodSource("generateConstantsArguments")
fun `Generates constants from Type names available via the DgsConstants class`(
snakeCaseEnabled: Boolean,
constantNames: List<String>,
) {
val schema = """
type Query {
people: [Person]
}
type Person {
firstname: String
lastname: String
metadata: PersonMetaData
}
type PersonMetaData { data: [String] }
type VPersonMetaData { data: [String] }
type V1PersonMetaData { data: [String] }
type URLMetaData { data: [String] }
""".trimIndent()

val result = CodeGen(
CodeGenConfig(
schemas = setOf(schema),
packageName = basePackageName,
language = Language.KOTLIN
language = Language.KOTLIN,
snakeCaseConstantNames = snakeCaseEnabled
)
).generate() as KotlinCodeGenResult
val type = result.constants[0].members[0] as TypeSpec
assertThat(type.typeSpecs).extracting("name").containsExactly("QUERY", "PERSON")
assertThat(type.typeSpecs).extracting("name").containsExactlyElementsOf(constantNames)
assertThat(type.typeSpecs[0].propertySpecs).extracting("name").containsExactly("TYPE_NAME", "People")
assertThat(type.typeSpecs[1].propertySpecs).extracting("name").containsExactly("TYPE_NAME", "Firstname", "Lastname")
assertThat(type.typeSpecs[1].propertySpecs).extracting("name").containsExactly("TYPE_NAME", "Firstname", "Lastname", "Metadata")
assertThat(type.typeSpecs[2].propertySpecs).extracting("name").containsExactly("TYPE_NAME", "Data")
}

companion object {
@JvmStatic
fun generateConstantsArguments(): Stream<Arguments> {
return Stream.of(
Arguments.of(
true,
listOf("QUERY", "PERSON", "PERSON_META_DATA", "V_PERSON_META_DATA", "V_1_PERSON_META_DATA", "URL_META_DATA")
),
Arguments.of(
false,
listOf("QUERY", "PERSON", "PERSONMETADATA", "VPERSONMETADATA", "V1PERSONMETADATA", "URLMETADATA")
),
)
}
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
*
* 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.
*
*/

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

import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.MethodSource
import java.util.stream.Stream

internal class CodeGeneratorUtilsTest {

@ParameterizedTest(name = "{index} => {0}, expected {1}")
@MethodSource("toSnakeCaseData")
fun toSnakeCase(input: String, output: String) {
assertThat(CodeGeneratorUtils.camelCasetoSnakeCase(input)).isEqualTo(output)
}

companion object {
@JvmStatic
fun toSnakeCaseData(): Stream<Arguments> = Stream.of(
"abc" to "abc",
"ABC" to "abc",
"Abc" to "abc",
"1AB" to "1_ab",
"1Abc" to "1_abc",
"ABCefg" to "ab_cefg",
"A1BCefg" to "a_1_b_cefg",
"AbCeFg" to "ab_ce_fg",
).map { Arguments.of(it.first, it.second) }
}
}

0 comments on commit 9fced2c

Please sign in to comment.