Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ class Kotlin2CodeGenTest {
mapOf(
"Currency" to "java.util.Currency",
)
"inputWithDefaultMap" ->
mapOf(
"JSON" to "kotlin.collections.Map<kotlin.String, kotlin.Any?>",
)
else -> emptyMap()
},
),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.netflix.graphql.dgs.codegen.cases.inputWithDefaultMap.expected

import com.netflix.graphql.dgs.client.codegen.InputValueSerializerInterface
import com.netflix.graphql.dgs.codegen.GraphQLProjection
import com.netflix.graphql.dgs.codegen.cases.inputWithDefaultMap.expected.client.QueryProjection
import graphql.language.OperationDefinition
import kotlin.String

@Generated
public object DgsClient {
public fun buildQuery(inputValueSerializer: InputValueSerializerInterface? = null,
_projection: QueryProjection.() -> QueryProjection): String =
GraphQLProjection.asQuery(OperationDefinition.Operation.QUERY,
QueryProjection(inputValueSerializer), _projection)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.netflix.graphql.dgs.codegen.cases.inputWithDefaultMap.expected

import kotlin.String

@Generated
public object DgsConstants {
public const val QUERY_TYPE: String = "Query"

@Generated
public object QUERY {
public const val TYPE_NAME: String = "Query"

public const val Movies: String = "movies"

@Generated
public object MOVIES_INPUT_ARGUMENT {
public const val Filter: String = "filter"
}
}

@Generated
public object MOVIEFILTER {
public const val TYPE_NAME: String = "MovieFilter"

public const val Metadata: String = "metadata"

public const val Details: String = "details"

public const val Variants: String = "variants"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.netflix.graphql.dgs.codegen.cases.inputWithDefaultMap.expected

import kotlin.`annotation`.AnnotationRetention
import kotlin.`annotation`.Retention

@Retention(value = AnnotationRetention.BINARY)
public annotation class Generated
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.netflix.graphql.dgs.codegen.cases.inputWithDefaultMap.expected.client

import com.netflix.graphql.dgs.client.codegen.InputValueSerializerInterface
import com.netflix.graphql.dgs.codegen.GraphQLProjection
import com.netflix.graphql.dgs.codegen.cases.inputWithDefaultMap.expected.Generated
import com.netflix.graphql.dgs.codegen.cases.inputWithDefaultMap.expected.types.MovieFilter

@Generated
public class QueryProjection(
inputValueSerializer: InputValueSerializerInterface? = null,
) : GraphQLProjection(inputValueSerializer) {
public fun movies(filter: MovieFilter? = default<QueryProjection, MovieFilter?>("filter")):
QueryProjection {
field("movies", "filter" to filter)
return this
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.netflix.graphql.dgs.codegen.cases.inputWithDefaultMap.expected.types

import com.fasterxml.jackson.`annotation`.JsonProperty
import com.netflix.graphql.dgs.codegen.GraphQLInput
import com.netflix.graphql.dgs.codegen.cases.inputWithDefaultMap.expected.Generated
import kotlin.Any
import kotlin.Pair
import kotlin.String
import kotlin.collections.List
import kotlin.collections.Map

@Generated
public data class MovieFilter(
@JsonProperty("metadata")
public val metadata: Map<String, Any?> = default<MovieFilter, Map<String, Any?>>("metadata",
emptyMap()),
@JsonProperty("details")
public val details: Map<String, Any?>? = default<MovieFilter, Map<String, Any?>?>("details",
mapOf("title" to "Alien", "year" to 1_979, "tags" to listOf("sci-fi"), "director" to
mapOf("name" to "Ridley Scott"))),
@JsonProperty("variants")
public val variants: List<Map<String, Any?>?>? = default<MovieFilter,
List<Map<String, Any?>?>?>("variants", listOf(emptyMap(), mapOf("title" to "Aliens"))),
) : GraphQLInput() {
override fun fields(): List<Pair<String, Any?>> = listOf("metadata" to metadata, "details" to
details, "variants" to variants)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.netflix.graphql.dgs.codegen.cases.inputWithDefaultMap.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.inputWithDefaultMap.expected.Generated
import java.lang.IllegalStateException
import kotlin.String
import kotlin.jvm.JvmName

@Generated
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
@JsonDeserialize(builder = Query.Builder::class)
public class Query(
movies: () -> String? = moviesDefault,
) {
private val __movies: () -> String? = movies

@get:JvmName("getMovies")
public val movies: String?
get() = __movies.invoke()

@Generated
public companion object {
private val moviesDefault: () -> String? =
{ throw IllegalStateException("Field `movies` was not requested") }
}

@Generated
@JsonPOJOBuilder
@JsonIgnoreProperties("__typename")
public class Builder {
private var movies: () -> String? = moviesDefault

@JsonProperty("movies")
public fun withMovies(movies: String?): Builder = this.apply {
this.movies = { movies }
}

public fun build(): Query = Query(
movies = movies,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
scalar JSON

type Query {
movies(filter: MovieFilter): String
}

input MovieFilter {
metadata: JSON! = {}
details: JSON = {title: "Alien", year: 1979, tags: ["sci-fi"], director: {name: "Ridley Scott"}}
variants: [JSON] = [{}, {title: "Aliens"}]
}
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,12 @@ class InputTypeGenerator internal constructor(
LOCALE -> localeCodeBlock(value, type)
JAVA_URI -> uriCodeBlock(value, type)
ClassName.LONG.box() -> longCodeBlock(value, type)
else -> defaultCodeBlock(value, type, inputTypeDefinitions)
else ->
if (value is ObjectValue && type.isMapLike) {
mapCodeBlock(value, type, inputTypeDefinitions)
} else {
defaultCodeBlock(value, type, inputTypeDefinitions)
}
}

private fun defaultCodeBlock(
Expand All @@ -259,8 +264,8 @@ class InputTypeGenerator internal constructor(
is BooleanValue -> CodeBlock.of("\$L", value.isValue)
is IntValue -> CodeBlock.of("\$L", value.value)
is StringValue -> {
// Only generate string literal default values for string types to prevent invalid Java initialization
if (type is ClassName && type == ClassName.get(String::class.java)) {
// Only generate string literal default values for String and Object types to prevent invalid Java initialization
if (type is ClassName && (type == ClassName.get(String::class.java) || type == ClassName.OBJECT)) {
CodeBlock.of("\$S", value.value)
} else {
null
Expand All @@ -277,19 +282,19 @@ class InputTypeGenerator internal constructor(
"\$T.asList(\$L)",
Arrays::class.java,
CodeBlock.join(
value.values.map { generateCode(it, type.className, inputTypeDefinitions) },
value.values.map { generateCode(it, type.listElementType, inputTypeDefinitions) },
", ",
),
)
}

is ObjectValue -> {
val inputObjectDefinition =
inputTypeDefinitions.first {
inputTypeDefinitions.firstOrNull {
val expectedCanonicalClassName =
config.typeMapping[it.name] ?: "${config.packageNameTypes}.${it.name}"
expectedCanonicalClassName == type.className.canonicalName()
}
} ?: error("$type cannot be created from $value, expected an input type or a Map")
if (value.objectFields.isEmpty()) {
CodeBlock.of("new \$T()", type)
} else {
Expand Down Expand Up @@ -325,6 +330,31 @@ class InputTypeGenerator internal constructor(
else -> CodeBlock.of("\$L", value)
}

private fun mapCodeBlock(
value: ObjectValue,
type: JavaTypeName,
inputTypeDefinitions: List<InputObjectTypeDefinition>,
): CodeBlock {
if (value.objectFields.isEmpty()) {
return CodeBlock.of("\$T.emptyMap()", Collections::class.java)
}
val typeArguments = (type as? ParameterizedTypeName)?.typeArguments()
val keyType = typeArguments?.getOrNull(0) ?: ClassName.get(String::class.java)
val valueType = typeArguments?.getOrNull(1) ?: ClassName.OBJECT
return CodeBlock.of(
"new \$T<\$T, \$T>(){{\$L}}",
LinkedHashMap::class.java,
keyType,
valueType,
CodeBlock.join(
value.objectFields.map {
CodeBlock.of("put(\$S, \$L);", it.name, generateCode(it.value, valueType, inputTypeDefinitions))
},
"",
),
)
}

private fun bigDecimalCodeBlock(
value: Value<out Value<*>>,
type: JavaTypeName,
Expand Down Expand Up @@ -375,6 +405,17 @@ class InputTypeGenerator internal constructor(
return CodeBlock.of("\$LL", value.value)
}

private val JavaTypeName.isMapLike: Boolean
get() = this == ClassName.OBJECT || (if (this is ParameterizedTypeName) rawType() else this) == ClassName.get(Map::class.java)

private val JavaTypeName.listElementType: JavaTypeName
get() =
if (this is ParameterizedTypeName && rawType() == ClassName.get(List::class.java)) {
typeArguments().first()
} else {
className
}

private val JavaTypeName.className: ClassName
get() =
when (this) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ fun generateKotlinCode(
?: checkAndGetLongCodeBlock(value, type)
?: checkAndGetBigDecimalCodeBlock(value, type)
?: checkAndGetCurrencyCodeBlock(value, type)
?: checkAndGetMapCodeBlock(value, type, inputTypeDefinitions, config, typeUtils)
?: when (value) {
is BooleanValue -> CodeBlock.of("%L", value.isValue)
is IntValue -> CodeBlock.of("%L", value.value)
Expand All @@ -50,7 +51,7 @@ fun generateKotlinCode(
value.values.joinToString { v ->
generateKotlinCode(
v,
type,
type.listElementType,
inputTypeDefinitions,
config,
typeUtils,
Expand All @@ -61,11 +62,11 @@ fun generateKotlinCode(

is ObjectValue -> {
val inputObjectDefinition =
inputTypeDefinitions.first {
inputTypeDefinitions.firstOrNull {
val expectedCanonicalClassName =
config.typeMapping[it.name] ?: "${config.packageNameTypes}.${it.name}"
expectedCanonicalClassName == type.className.canonicalName
}
} ?: error("$type cannot be created from $value, expected an input type or a Map")

CodeBlock.of(
type.className.canonicalName + "(%L)",
Expand Down Expand Up @@ -143,6 +144,54 @@ private fun checkAndGetCurrencyCodeBlock(
null
}

private fun checkAndGetMapCodeBlock(
value: Value<Value<*>>,
type: TypeName,
inputTypeDefinitions: Collection<InputObjectTypeDefinition>,
config: CodeGenConfig,
typeUtils: KotlinTypeUtils,
): CodeBlock? {
if (value !is ObjectValue || !type.isMapLike) {
return null
}
val isMutable = type.rawClassName?.canonicalName == MUTABLE_MAP.canonicalName
if (value.objectFields.isEmpty()) {
return CodeBlock.of(if (isMutable) "mutableMapOf()" else "emptyMap()")
}
val valueType = (type as? ParameterizedTypeName)?.typeArguments?.getOrNull(1) ?: ANY.copy(nullable = true)
return CodeBlock.of(
if (isMutable) "mutableMapOf(%L)" else "mapOf(%L)",
value.objectFields
.map {
CodeBlock.of(
"%S to %L",
it.name,
generateKotlinCode(
it.value,
valueType,
inputTypeDefinitions,
config,
typeUtils,
),
)
}.joinToCode(),
)
}

private val TypeName.rawClassName: ClassName?
get() =
when (this) {
is ClassName -> this
is ParameterizedTypeName -> rawType
else -> null
}

private val TypeName.isMapLike: Boolean
get() = rawClassName?.canonicalName in setOf(MAP.canonicalName, MUTABLE_MAP.canonicalName, ANY.canonicalName)

private val TypeName.listElementType: TypeName
get() = if (this is ParameterizedTypeName && rawType.canonicalName == LIST.canonicalName) typeArguments[0] else this

private val TypeName.className: ClassName
get() =
when (this) {
Expand Down
Loading
Loading