diff --git a/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/Kotlin2CodeGenTest.kt b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/Kotlin2CodeGenTest.kt index 56353ea29..c057c5954 100644 --- a/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/Kotlin2CodeGenTest.kt +++ b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/Kotlin2CodeGenTest.kt @@ -63,7 +63,17 @@ class Kotlin2CodeGenTest { "Decimal" to "java.math.BigDecimal" ) else -> emptyMap() - } + }, + typePrefix = when (testName) { + "dataClassWithPrefix" -> "Dgs" + "inputWithPrefix" -> "Dgs" + else -> "" + }, + typeSuffix = when (testName) { + "dataClassWithSuffix" -> "Type" + "inputWithSuffix" -> "Type" + else -> "" + }, ) ).generate() diff --git a/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithPrefix/expected/DgsClient.kt b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithPrefix/expected/DgsClient.kt new file mode 100644 index 000000000..ad25ca0ce --- /dev/null +++ b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithPrefix/expected/DgsClient.kt @@ -0,0 +1,15 @@ +package com.netflix.graphql.dgs.codegen.cases.dataClassWithPrefix.expected + +import com.netflix.graphql.dgs.client.codegen.InputValueSerializerInterface +import com.netflix.graphql.dgs.codegen.GraphQLProjection +import com.netflix.graphql.dgs.codegen.cases.dataClassWithPrefix.expected.client.QueryProjection +import graphql.language.OperationDefinition +import kotlin.String + +public object DgsClient { + public fun buildQuery(inputValueSerializer: InputValueSerializerInterface? = null, + _projection: QueryProjection.() -> QueryProjection + ): String = + GraphQLProjection.asQuery(OperationDefinition.Operation.QUERY, + QueryProjection(inputValueSerializer), _projection) +} diff --git a/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithPrefix/expected/DgsConstants.kt b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithPrefix/expected/DgsConstants.kt new file mode 100644 index 000000000..a1019e011 --- /dev/null +++ b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithPrefix/expected/DgsConstants.kt @@ -0,0 +1,29 @@ +package com.netflix.graphql.dgs.codegen.cases.dataClassWithPrefix.expected + +import kotlin.String + +public object DgsConstants { + public const val QUERY_TYPE: String = "Query" + + public object QUERY { + public const val TYPE_NAME: String = "Query" + + public const val Search: String = "search" + + public object SEARCH_INPUT_ARGUMENT { + public const val MovieFilter: String = "movieFilter" + } + } + + public object MOVIE { + public const val TYPE_NAME: String = "Movie" + + public const val Title: String = "title" + } + + public object MOVIEFILTER { + public const val TYPE_NAME: String = "MovieFilter" + + public const val TitleFilter: String = "titleFilter" + } +} diff --git a/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithPrefix/expected/client/MovieProjection.kt b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithPrefix/expected/client/MovieProjection.kt new file mode 100644 index 000000000..5bf95a6b8 --- /dev/null +++ b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithPrefix/expected/client/MovieProjection.kt @@ -0,0 +1,14 @@ +package com.netflix.graphql.dgs.codegen.cases.dataClassWithPrefix.expected.client + +import com.netflix.graphql.dgs.client.codegen.InputValueSerializerInterface +import com.netflix.graphql.dgs.codegen.GraphQLProjection + +public class MovieProjection( + inputValueSerializer: InputValueSerializerInterface? = null, +) : GraphQLProjection(inputValueSerializer) { + public val title: MovieProjection + get() { + field("title") + return this + } +} diff --git a/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithPrefix/expected/client/QueryProjection.kt b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithPrefix/expected/client/QueryProjection.kt new file mode 100644 index 000000000..66b9fc0e8 --- /dev/null +++ b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithPrefix/expected/client/QueryProjection.kt @@ -0,0 +1,20 @@ +package com.netflix.graphql.dgs.codegen.cases.dataClassWithPrefix.expected.client + +import com.netflix.graphql.dgs.client.codegen.InputValueSerializerInterface +import com.netflix.graphql.dgs.codegen.GraphQLProjection +import com.netflix.graphql.dgs.codegen.cases.dataClassWithPrefix.expected.types.DgsMovieFilter +import kotlin.String + +public class QueryProjection( + inputValueSerializer: InputValueSerializerInterface? = null, +) : GraphQLProjection(inputValueSerializer) { + public fun search( + movieFilter: DgsMovieFilter, + _alias: String? = null, + _projection: MovieProjection.() -> MovieProjection, + ): QueryProjection { + field(_alias, "search", MovieProjection(inputValueSerializer), _projection, "movieFilter" to + movieFilter) + return this + } +} diff --git a/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithPrefix/expected/types/DgsMovie.kt b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithPrefix/expected/types/DgsMovie.kt new file mode 100644 index 000000000..757477c8f --- /dev/null +++ b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithPrefix/expected/types/DgsMovie.kt @@ -0,0 +1,47 @@ +package com.netflix.graphql.dgs.codegen.cases.dataClassWithPrefix.expected.types + +import com.fasterxml.jackson.`annotation`.JsonIgnoreProperties +import com.fasterxml.jackson.`annotation`.JsonProperty +import com.fasterxml.jackson.`annotation`.JsonTypeInfo +import com.fasterxml.jackson.databind.`annotation`.JsonDeserialize +import com.fasterxml.jackson.databind.`annotation`.JsonPOJOBuilder +import com.netflix.graphql.dgs.codegen.cases.dataClassDocs.expected.types.Movie +import java.lang.IllegalStateException +import kotlin.String +import kotlin.jvm.JvmName + +/** + * Movies are fun to watch. + * They also work well as examples in GraphQL. + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.NONE) +@JsonDeserialize(builder = DgsMovie.Builder::class) +public class DgsMovie( + title: () -> String? = titleDefault, +) { + private val __title: () -> String? = title + + @get:JvmName("getTitle") + public val title: String? + get() = __title.invoke() + + public companion object { + private val titleDefault: () -> String? = + { throw IllegalStateException("Field `title` was not requested") } + } + + @JsonPOJOBuilder + @JsonIgnoreProperties("__typename") + public class Builder { + private var title: () -> String? = titleDefault + + @JsonProperty("title") + public fun withTitle(title: String?): Builder = this.apply { + this.title = { title } + } + + public fun build(): Movie = Movie( + title = title, + ) + } +} diff --git a/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithPrefix/expected/types/DgsMovieFilter.kt b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithPrefix/expected/types/DgsMovieFilter.kt new file mode 100644 index 000000000..dbcd921c0 --- /dev/null +++ b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithPrefix/expected/types/DgsMovieFilter.kt @@ -0,0 +1,21 @@ +package com.netflix.graphql.dgs.codegen.cases.dataClassWithPrefix.expected.types + +import com.fasterxml.jackson.`annotation`.JsonCreator +import com.fasterxml.jackson.`annotation`.JsonProperty +import com.netflix.graphql.dgs.codegen.GraphQLInput +import kotlin.Any +import kotlin.Pair +import kotlin.String +import kotlin.collections.List + +/** + * Example filter for Movies. + * + * It takes a title and such. + */ +public class DgsMovieFilter @JsonCreator constructor( + @JsonProperty("titleFilter") + public val titleFilter: String? = default("titleFilter", null), +) : GraphQLInput() { + override fun fields(): List> = listOf("titleFilter" to titleFilter) +} diff --git a/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithPrefix/expected/types/DgsQuery.kt b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithPrefix/expected/types/DgsQuery.kt new file mode 100644 index 000000000..3b71af2ed --- /dev/null +++ b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithPrefix/expected/types/DgsQuery.kt @@ -0,0 +1,41 @@ +package com.netflix.graphql.dgs.codegen.cases.dataClassWithPrefix.expected.types + + import com.fasterxml.jackson.`annotation`.JsonIgnoreProperties + import com.fasterxml.jackson.`annotation`.JsonProperty + import com.fasterxml.jackson.`annotation`.JsonTypeInfo + import com.fasterxml.jackson.databind.`annotation`.JsonDeserialize + import com.fasterxml.jackson.databind.`annotation`.JsonPOJOBuilder + import java.lang.IllegalStateException + import kotlin.jvm.JvmName + + @JsonTypeInfo(use = JsonTypeInfo.Id.NONE) + @JsonDeserialize(builder = DgsQuery.Builder::class) + public class DgsQuery( + search: () -> DgsMovie? = searchDefault, + ) { + private val __search: () -> DgsMovie? = search + + @get:JvmName("getSearch") + public val search: DgsMovie? + get() = __search.invoke() + + public companion object { + private val searchDefault: () -> DgsMovie? = + { throw IllegalStateException("Field `search` was not requested") } + } + + @JsonPOJOBuilder + @JsonIgnoreProperties("__typename") + public class Builder { + private var search: () -> DgsMovie? = searchDefault + + @JsonProperty("search") + public fun withSearch(search: DgsMovie?): Builder = this.apply { + this.search = { search } + } + + public fun build(): DgsQuery = DgsQuery( + search = search, + ) + } + } \ No newline at end of file diff --git a/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithPrefix/schema.graphql b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithPrefix/schema.graphql new file mode 100644 index 000000000..09e4f5f93 --- /dev/null +++ b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithPrefix/schema.graphql @@ -0,0 +1,20 @@ +type Query { + search(movieFilter: MovieFilter!): Movie +} + +""" +Movies are fun to watch. +They also work well as examples in GraphQL. +""" +type Movie { + title: String +} + +""" +Example filter for Movies. + +It takes a title and such. +""" +input MovieFilter { + titleFilter: String +} diff --git a/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/inputWithPrefix/expected/DgsClient.kt b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/inputWithPrefix/expected/DgsClient.kt new file mode 100644 index 000000000..1d9f69de7 --- /dev/null +++ b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/inputWithPrefix/expected/DgsClient.kt @@ -0,0 +1,14 @@ +package com.netflix.graphql.dgs.codegen.cases.inputWithPrefix.expected + +import com.netflix.graphql.dgs.client.codegen.InputValueSerializerInterface +import com.netflix.graphql.dgs.codegen.GraphQLProjection +import com.netflix.graphql.dgs.codegen.cases.inputWithPrefix.expected.client.QueryProjection +import graphql.language.OperationDefinition +import kotlin.String + +public object DgsClient { + public fun buildQuery(inputValueSerializer: InputValueSerializerInterface? = null, + _projection: QueryProjection.() -> QueryProjection): String = + GraphQLProjection.asQuery(OperationDefinition.Operation.QUERY, + QueryProjection(inputValueSerializer), _projection) +} diff --git a/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/inputWithPrefix/expected/DgsConstants.kt b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/inputWithPrefix/expected/DgsConstants.kt new file mode 100644 index 000000000..87ac309c0 --- /dev/null +++ b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/inputWithPrefix/expected/DgsConstants.kt @@ -0,0 +1,23 @@ +package com.netflix.graphql.dgs.codegen.cases.inputWithPrefix.expected + +import kotlin.String + +public object DgsConstants { + public const val QUERY_TYPE: String = "Query" + + public object QUERY { + public const val TYPE_NAME: String = "Query" + + public const val Movies: String = "movies" + + public object MOVIES_INPUT_ARGUMENT { + public const val Filter: String = "filter" + } + } + + public object MOVIEFILTER { + public const val TYPE_NAME: String = "MovieFilter" + + public const val Genre: String = "genre" + } +} diff --git a/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/inputWithPrefix/expected/client/QueryProjection.kt b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/inputWithPrefix/expected/client/QueryProjection.kt new file mode 100644 index 000000000..e67995eb6 --- /dev/null +++ b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/inputWithPrefix/expected/client/QueryProjection.kt @@ -0,0 +1,15 @@ +package com.netflix.graphql.dgs.codegen.cases.inputWithPrefix.expected.client + +import com.netflix.graphql.dgs.client.codegen.InputValueSerializerInterface +import com.netflix.graphql.dgs.codegen.GraphQLProjection +import com.netflix.graphql.dgs.codegen.cases.inputWithPrefix.expected.types.DgsMovieFilter + +public class QueryProjection( + inputValueSerializer: InputValueSerializerInterface? = null, +) : GraphQLProjection(inputValueSerializer) { + public fun movies(filter: DgsMovieFilter? = default("filter")): + QueryProjection { + field("movies", "filter" to filter) + return this + } +} diff --git a/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/inputWithPrefix/expected/types/DgsMovieFilter.kt b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/inputWithPrefix/expected/types/DgsMovieFilter.kt new file mode 100644 index 000000000..93d7ab6f0 --- /dev/null +++ b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/inputWithPrefix/expected/types/DgsMovieFilter.kt @@ -0,0 +1,16 @@ +package com.netflix.graphql.dgs.codegen.cases.inputWithPrefix.expected.types + +import com.fasterxml.jackson.`annotation`.JsonCreator +import com.fasterxml.jackson.`annotation`.JsonProperty +import com.netflix.graphql.dgs.codegen.GraphQLInput +import kotlin.Any +import kotlin.Pair +import kotlin.String +import kotlin.collections.List + +public class DgsMovieFilter @JsonCreator constructor( + @JsonProperty("genre") + public val genre: String? = default("genre", null), +) : GraphQLInput() { + override fun fields(): List> = listOf("genre" to genre) +} diff --git a/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/inputWithPrefix/expected/types/DgsQuery.kt b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/inputWithPrefix/expected/types/DgsQuery.kt new file mode 100644 index 000000000..a9c67afde --- /dev/null +++ b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/inputWithPrefix/expected/types/DgsQuery.kt @@ -0,0 +1,43 @@ +package com.netflix.graphql.dgs.codegen.cases.inputWithPrefix.expected.types + +import com.fasterxml.jackson.`annotation`.JsonIgnoreProperties +import com.fasterxml.jackson.`annotation`.JsonProperty +import com.fasterxml.jackson.`annotation`.JsonTypeInfo +import com.fasterxml.jackson.databind.`annotation`.JsonDeserialize +import com.fasterxml.jackson.databind.`annotation`.JsonPOJOBuilder +import java.lang.IllegalStateException +import kotlin.String +import kotlin.collections.List +import kotlin.jvm.JvmName + +@JsonTypeInfo(use = JsonTypeInfo.Id.NONE) +@JsonDeserialize(builder = DgsQuery.Builder::class) +public class DgsQuery( + movies: () -> List? = moviesDefault, +) { + private val __movies: () -> List? = movies + + @get:JvmName("getMovies") + public val movies: List? + get() = __movies.invoke() + + public companion object { + private val moviesDefault: () -> List? = + { throw IllegalStateException("Field `movies` was not requested") } + } + + @JsonPOJOBuilder + @JsonIgnoreProperties("__typename") + public class Builder { + private var movies: () -> List? = moviesDefault + + @JsonProperty("movies") + public fun withMovies(movies: List?): Builder = this.apply { + this.movies = { movies } + } + + public fun build(): DgsQuery = DgsQuery( + movies = movies, + ) + } +} diff --git a/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/inputWithPrefix/schema.graphql b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/inputWithPrefix/schema.graphql new file mode 100644 index 000000000..9c42d7dc2 --- /dev/null +++ b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/inputWithPrefix/schema.graphql @@ -0,0 +1,7 @@ +type Query { + movies(filter: MovieFilter): [String] +} + +input MovieFilter { + genre: String +} diff --git a/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/inputWithPrefix/test/QueryTest.kt b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/inputWithPrefix/test/QueryTest.kt new file mode 100644 index 000000000..ce2546428 --- /dev/null +++ b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/inputWithPrefix/test/QueryTest.kt @@ -0,0 +1,112 @@ +/* + * + * 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.cases.inputWithPrefix.test + +import com.netflix.graphql.dgs.codegen.cases.inputWithPrefix.expected.DgsClient +import com.netflix.graphql.dgs.codegen.cases.inputWithPrefix.expected.types.DgsMovieFilter +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test + +class QueryTest { + + @Test + fun testQueryWithNoFilter() { + val query = DgsClient.buildQuery { + movies() + } + + Assertions.assertEquals( + """{ + | __typename + | movies + |} + | + """.trimMargin(), + query + ) + } + + @Test + fun testQueryWithEmptyFilter() { + val query = DgsClient.buildQuery { + movies(filter = DgsMovieFilter()) + } + + Assertions.assertEquals( + """{ + | __typename + | movies(filter: {}) + |} + | + """.trimMargin(), + query + ) + } + + @Test + fun testQueryWithNullFilter() { + val query = DgsClient.buildQuery { + movies(filter = DgsMovieFilter(genre = null)) + } + + Assertions.assertEquals( + """{ + | __typename + | movies(filter: {genre : null}) + |} + | + """.trimMargin(), + query + ) + } + + @Test + fun testQueryWithFilter() { + val query = DgsClient.buildQuery { + movies(filter = DgsMovieFilter(genre = "horror")) + } + + Assertions.assertEquals( + """{ + | __typename + | movies(filter: {genre : "horror"}) + |} + | + """.trimMargin(), + query + ) + } + + @Test + fun testQueryWithNewline() { + val query = DgsClient.buildQuery { + movies(filter = DgsMovieFilter(genre = "horror\ncomedy")) + } + + Assertions.assertEquals( + """{ + | __typename + | movies(filter: {genre : "horror\ncomedy"}) + |} + | + """.trimMargin(), + query + ) + } +} 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 694a7fcc8..8bef35b36 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 @@ -529,7 +529,9 @@ class CodeGenConfig( var implementSerializable: Boolean = false, var addGeneratedAnnotation: Boolean = false, var disableDatesInGeneratedAnnotation: Boolean = false, - var addDeprecatedAnnotation: Boolean = false + var addDeprecatedAnnotation: Boolean = false, + var typePrefix: String = "", + var typeSuffix: String = "" ) { val packageNameClient: String = "$packageName.$subPackageNameClient" diff --git a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/CodeGenCli.kt b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/CodeGenCli.kt index 672637b30..c50030c05 100644 --- a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/CodeGenCli.kt +++ b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/CodeGenCli.kt @@ -59,6 +59,8 @@ class CodeGenCli : CliktCommand("Generate Java sources for SCHEMA file(s)") { private val shortProjectionNames by option("--short-projection-names").flag() private val generateInterfaceSetters by option("--generate-interface-setters").flag() private val generateDocs by option("--generate-docs").flag() + private val typePrefix by option("--type-prefix").default("") + private val typeSuffix by option("--type-suffix").default("") override fun run() { val inputSchemas = if (schemas.isEmpty()) { @@ -94,7 +96,9 @@ class CodeGenCli : CliktCommand("Generate Java sources for SCHEMA file(s)") { generateDataTypes = generateDataTypes, generateInterfaces = generateInterfaces, generateInterfaceSetters = generateInterfaceSetters, - generateDocs = generateDocs + generateDocs = generateDocs, + typePrefix = typePrefix, + typeSuffix = typeSuffix ) } else { CodeGenConfig( @@ -115,7 +119,9 @@ class CodeGenCli : CliktCommand("Generate Java sources for SCHEMA file(s)") { generateDataTypes = generateDataTypes, generateInterfaces = generateInterfaces, generateInterfaceSetters = generateInterfaceSetters, - generateDocs = generateDocs + generateDocs = generateDocs, + typePrefix = typePrefix, + typeSuffix = typeSuffix ) } ).generate() 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 cea660480..2a8dc12e1 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,44 +18,20 @@ 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.SiteTarget import com.netflix.graphql.dgs.codegen.generators.shared.applyDirectivesJava -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.ParameterizedTypeName -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.ObjectTypeDefinition -import graphql.language.ObjectTypeExtensionDefinition -import graphql.language.ObjectValue -import graphql.language.StringValue -import graphql.language.Type +import com.netflix.graphql.dgs.codegen.shouldSkip +import com.squareup.javapoet.* +import graphql.language.* import graphql.language.TypeName -import graphql.language.UnionTypeDefinition -import graphql.language.Value import org.slf4j.Logger import org.slf4j.LoggerFactory import java.io.Serializable import java.math.BigDecimal -import java.util.Arrays -import java.util.Collections -import java.util.Locale -import java.util.Objects +import java.util.* import javax.lang.model.element.Modifier import com.squareup.javapoet.TypeName as JavaTypeName @@ -71,7 +47,7 @@ class DataTypeGenerator(config: CodeGenConfig, document: Document) : BaseDataTyp logger.info("Generating data type {}", definition.name) - val name = definition.name + val name = config.typePrefix + definition.name + config.typeSuffix val unionTypes = document.getDefinitionsOfType(UnionTypeDefinition::class.java).asSequence().filter { union -> union.memberTypes.asSequence().map { it as TypeName }.any { it.name == name } }.map { it.name }.toList() @@ -211,6 +187,7 @@ class InputTypeGenerator(config: CodeGenConfig, document: Document) : BaseDataTy CodeBlock.join(value.values.map { generateCode(it, type.className, inputTypeDefinitions) }, ", ") ) } + is ObjectValue -> { val inputObjectDefinition = inputTypeDefinitions.first { val expectedCanonicalClassName = config.typeMapping[it.name] ?: "${config.packageNameTypes}.${it.name}" @@ -238,6 +215,7 @@ class InputTypeGenerator(config: CodeGenConfig, document: Document) : BaseDataTy ) } } + else -> CodeBlock.of("\$L", value) } } @@ -264,7 +242,15 @@ class InputTypeGenerator(config: CodeGenConfig, document: Document) : BaseDataTy } } -internal data class Field(val name: String, val type: JavaTypeName, val initialValue: CodeBlock? = null, val overrideGetter: Boolean = false, val interfaceType: com.squareup.javapoet.TypeName? = null, val description: Description? = null, val directives: List = listOf()) +internal data class Field( + val name: String, + val type: JavaTypeName, + val initialValue: CodeBlock? = null, + val overrideGetter: Boolean = false, + val interfaceType: com.squareup.javapoet.TypeName? = null, + val description: Description? = null, + val directives: List = listOf() +) abstract class BaseDataTypeGenerator( internal val packageName: String, @@ -449,7 +435,8 @@ abstract class BaseDataTypeGenerator( private fun addInterface(type: String, javaType: TypeSpec.Builder) { val interfaceTypeMappedName: String? = config.typeMapping[type] - val interfaceName: ClassName = if (interfaceTypeMappedName == null) ClassName.get(packageName, type) else ClassName.bestGuess(interfaceTypeMappedName) + val interfaceName: ClassName = + if (interfaceTypeMappedName == null) ClassName.get(packageName, type) else ClassName.bestGuess(interfaceTypeMappedName) javaType.addSuperinterface(interfaceName) } @@ -472,10 +459,15 @@ abstract class BaseDataTypeGenerator( fieldBuilder.addJavadoc("\$L", fieldDefinition.description.content) } - val getterPrefix = if (returnType == com.squareup.javapoet.TypeName.BOOLEAN && config.generateIsGetterForPrimitiveBooleanFields) "is" else "get" - val getterName = typeUtils.transformIfDefaultClassMethodExists("${getterPrefix}${fieldDefinition.name[0].uppercase()}${fieldDefinition.name.substring(1)}", TypeUtils.getClass) + val getterPrefix = + if (returnType == com.squareup.javapoet.TypeName.BOOLEAN && config.generateIsGetterForPrimitiveBooleanFields) "is" else "get" + val getterName = typeUtils.transformIfDefaultClassMethodExists( + "${getterPrefix}${fieldDefinition.name[0].uppercase()}${fieldDefinition.name.substring(1)}", + TypeUtils.getClass + ) - val getterMethodBuilder = MethodSpec.methodBuilder(getterName).addModifiers(Modifier.PUBLIC).returns(returnType).addStatement("return \$N", ReservedKeywordSanitizer.sanitize(fieldDefinition.name)) + val getterMethodBuilder = MethodSpec.methodBuilder(getterName).addModifiers(Modifier.PUBLIC).returns(returnType) + .addStatement("return \$N", ReservedKeywordSanitizer.sanitize(fieldDefinition.name)) if (fieldDefinition.overrideGetter) { getterMethodBuilder.addAnnotation(Override::class.java) } @@ -484,7 +476,10 @@ abstract class BaseDataTypeGenerator( getterMethodBuilder.addJavadoc("\$L", fieldDefinition.description.content) } - val setterName = typeUtils.transformIfDefaultClassMethodExists("set${fieldDefinition.name[0].uppercase()}${fieldDefinition.name.substring(1)}", TypeUtils.setClass) + val setterName = typeUtils.transformIfDefaultClassMethodExists( + "set${fieldDefinition.name[0].uppercase()}${fieldDefinition.name.substring(1)}", + TypeUtils.setClass + ) val parameterBuilder = ParameterSpec.builder(returnType, ReservedKeywordSanitizer.sanitize(fieldDefinition.name)) val setterMethodBuilder = MethodSpec.methodBuilder(setterName) .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 968a6cec5..2b46762c4 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 @@ -171,7 +171,9 @@ abstract class AbstractKotlinDataTypeGenerator( description: Description? = null, directives: List = emptyList() ): CodeGenResult { - val kotlinType = TypeSpec.classBuilder(name) + val typeName = config.typePrefix + name + config.typeSuffix + + val kotlinType = TypeSpec.classBuilder(typeName) .addOptionalGeneratedAnnotation(config) if (config.implementSerializable) { diff --git a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin/KotlinTypeUtils.kt b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin/KotlinTypeUtils.kt index c836011ea..139acf029 100644 --- a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin/KotlinTypeUtils.kt +++ b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin/KotlinTypeUtils.kt @@ -139,6 +139,8 @@ class KotlinTypeUtils(private val packageName: String, private val config: CodeG return commonScalars.getValue(name) } + val typeName = config.typePrefix + name + config.typeSuffix + return when (name) { STRING.simpleName -> STRING "StringValue" -> STRING @@ -150,7 +152,7 @@ class KotlinTypeUtils(private val packageName: String, private val config: CodeG "BooleanValue" -> BOOLEAN "ID" -> STRING "IDValue" -> STRING - else -> "${config.packageNameTypes}.$name".toKtTypeName() + else -> "${config.packageNameTypes}.$typeName".toKtTypeName() } } 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 ff88de1d8..bc96bfb75 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 @@ -88,6 +88,7 @@ fun generateKotlin2DataTypes( .toList() fun type(field: FieldDefinition) = typeLookup.findReturnType(config.packageNameTypes, field.type) + val typeName = config.typePrefix + typeDefinition.name + config.typeSuffix // get a list of fields to override val overrideFields = typeLookup.overrideFields(implementedInterfaces) @@ -118,7 +119,7 @@ fun generateKotlin2DataTypes( .build() // create a builder for this class; default to lambda that throws if accessed - val builderClassName = ClassName(config.packageNameTypes, typeDefinition.name, "Builder") + val builderClassName = ClassName(config.packageNameTypes, typeName, "Builder") val builder = TypeSpec.classBuilder("Builder") .addOptionalGeneratedAnnotation(config) .addAnnotation(jsonBuilderAnnotation()) @@ -152,10 +153,10 @@ fun generateKotlin2DataTypes( // add a build method to return the constructed class .addFunction( FunSpec.builder("build") - .returns(typeDefinition.name.toKtTypeName()) + .returns(typeName.toKtTypeName()) .addCode( fields.let { fs -> - val builder = CodeBlock.builder().add("return %T(\n", ClassName(config.packageNameTypes, typeDefinition.name)) + val builder = CodeBlock.builder().add("return %T(\n", ClassName(config.packageNameTypes, typeName)) fs.forEach { f -> builder.add(" %N = %N,\n", f.name, f.name) } builder.add(")").build() } @@ -165,7 +166,7 @@ fun generateKotlin2DataTypes( .build() // create the data class - val typeSpec = TypeSpec.classBuilder(typeDefinition.name) + val typeSpec = TypeSpec.classBuilder(typeName) .addOptionalGeneratedAnnotation(config) // add docs if available .apply { 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 02a5bf379..1e07d9bcb 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 @@ -68,7 +68,8 @@ fun generateKotlin2InputTypes( fun type(field: InputValueDefinition) = typeLookup.findReturnType(config.packageNameTypes, field.type) - val typeName = ClassName(config.packageNameTypes, inputDefinition.name) + val name = config.typePrefix + inputDefinition.name + config.typeSuffix + val typeName = ClassName(config.packageNameTypes, name) // create the input class val typeSpec = TypeSpec.classBuilder(typeName) diff --git a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin2/Kotlin2TypeLookup.kt b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin2/Kotlin2TypeLookup.kt index 6ea3b4827..97ceb11d9 100644 --- a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin2/Kotlin2TypeLookup.kt +++ b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin2/Kotlin2TypeLookup.kt @@ -23,31 +23,11 @@ import com.netflix.graphql.dgs.codegen.generators.kotlin.toKtTypeName import com.netflix.graphql.dgs.codegen.generators.shared.JAVA_TYPE_DIRECTIVE_NAME import com.netflix.graphql.dgs.codegen.generators.shared.SchemaExtensionsUtils.findTypeExtensions import com.netflix.graphql.dgs.codegen.generators.shared.parseMappedType -import com.squareup.kotlinpoet.BOOLEAN -import com.squareup.kotlinpoet.ClassName -import com.squareup.kotlinpoet.DOUBLE -import com.squareup.kotlinpoet.INT -import com.squareup.kotlinpoet.LIST +import com.squareup.kotlinpoet.* import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy -import com.squareup.kotlinpoet.STRING import graphql.Scalars -import graphql.language.Document -import graphql.language.EnumTypeDefinition -import graphql.language.ImplementingTypeDefinition -import graphql.language.InterfaceTypeDefinition -import graphql.language.ListType -import graphql.language.NamedNode -import graphql.language.Node -import graphql.language.NodeTraverser -import graphql.language.NodeVisitorStub -import graphql.language.NonNullType -import graphql.language.ObjectTypeDefinition -import graphql.language.OperationDefinition -import graphql.language.ScalarTypeDefinition -import graphql.language.StringValue -import graphql.language.Type +import graphql.language.* import graphql.language.TypeName -import graphql.language.UnionTypeDefinition import graphql.util.TraversalControl import graphql.util.TraverserContext import com.squareup.kotlinpoet.TypeName as KtTypeName @@ -69,6 +49,9 @@ class Kotlin2TypeLookup( "Subscription" to OperationDefinition.Operation.SUBSCRIPTION ) + private val typePrefix = config.typePrefix + private val typeSuffix = config.typeSuffix + /** * A set of object type names defined in the document */ @@ -282,6 +265,8 @@ class Kotlin2TypeLookup( return builtinType } - return "$packageName.${typeName.name}".toKtTypeName() + val name = typePrefix + typeName.name + typeSuffix + + return "$packageName.$name".toKtTypeName() } } 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 44a96fceb..1e102fcd1 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 @@ -5065,4 +5065,36 @@ It takes a title and such. ).generate() assertThat(result.javaDataTypes[0].typeSpec.fieldSpecs[1].type.toString() == "java.lang.String") } + + @Test + fun `Generate Java data types with prefix and suffix`() { + val schema = """ + type Query { + person: Person + } + + type Person { + name: String + age: Int + } + """.trimIndent() + + val config = CodeGenConfig( + schemas = setOf(schema), + packageName = "com.netflix.test", + language = Language.JAVA, + typePrefix = "My", + typeSuffix = "Type" + ) + + val codeGen = CodeGen(config) + val result = codeGen.generate() + + val dataTypes = result.javaDataTypes + assertThat(dataTypes).hasSize(1) + + val personFile = dataTypes.find { it.typeSpec.name == "MyPersonType" } + assertThat(personFile).isNotNull + assertThat(personFile!!.toString()).contains("public class MyPersonType") + } } 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 0a16f70df..edbbe06b3 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 @@ -4176,4 +4176,41 @@ It takes a title and such. assertThat(superinterfaces).contains("com.netflix.graphql.dgs.codegen.tests.generated.types.A") assertThat(superinterfaces).contains("com.netflix.graphql.dgs.codegen.tests.generated.types.B") } + + @Test + fun `Generate Kotlin data types with prefix and suffix`() { + val schema = """ + type Query { + person: Person + } + + type Person { + firstname: String + lastname: String + } + """.trimIndent() + + val config = CodeGenConfig( + schemas = setOf(schema), + packageName = basePackageName, + language = Language.KOTLIN, + typePrefix = "My", + typeSuffix = "Type" + ) + + val codeGen = CodeGen(config) + val result = codeGen.generate() + + val dataTypes = result.kotlinDataTypes + assertThat(dataTypes.size).isEqualTo(1) + assertThat(dataTypes[0].name).isEqualTo("MyPersonType") + assertThat(dataTypes[0].packageName).isEqualTo(typesPackageName) + val type = dataTypes[0].members[0] as TypeSpec + + assertThat(type.modifiers).contains(KModifier.DATA) + assertThat(type.propertySpecs.size).isEqualTo(2) + assertThat(type.propertySpecs).extracting("name").contains("firstname", "lastname") + + assertCompilesKotlin(dataTypes) + } } 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 c17ab3874..cc51592c3 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 @@ -134,4 +134,44 @@ class Kotline2CodeGenTest { assertCompilesKotlin(result.kotlinEnumTypes) } + + @Test + fun `Generate data types with prefix and suffix`() { + val schema = """ + type Query { + person: Person + } + + type Person { + firstname: String + lastname: String + } + """.trimIndent() + + val config = CodeGenConfig( + schemas = setOf(schema), + packageName = basePackageName, + language = Language.KOTLIN, + typePrefix = "My", + typeSuffix = "Type", + generateKotlinNullableClasses = true + ) + + val codeGen = CodeGen(config) + val result = codeGen.generate() + + val dataTypes = result.kotlinDataTypes + assertThat(dataTypes.size).isEqualTo(2) + + val personType = dataTypes.find { it.name == "MyPersonType" } + assertThat(personType).isNotNull + assertThat(personType!!.name).isEqualTo("MyPersonType") + assertThat(personType.packageName).isEqualTo(typesPackageName) + val type = personType.members[0] as TypeSpec + + assertThat(type.propertySpecs.size).isEqualTo(4) + assertThat(type.propertySpecs).extracting("name").contains("firstname", "lastname") + + assertCompilesKotlin(dataTypes) + } }