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 56353ea2..c038529f 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 @@ -19,7 +19,9 @@ package com.netflix.graphql.dgs.codegen import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.fail +import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.MethodSource import java.nio.file.Files @@ -62,6 +64,9 @@ class Kotlin2CodeGenTest { "inputWithDefaultBigDecimal" -> mapOf( "Decimal" to "java.math.BigDecimal" ) + "inputWithDefaultCurrency" -> mapOf( + "Currency" to "java.util.Currency" + ) else -> emptyMap() } ) @@ -101,6 +106,11 @@ class Kotlin2CodeGenTest { assertCompilesKotlin(codeGenResult) } + @Test + fun `assert updateExpected is false`() { + Assertions.assertFalse(updateExpected) + } + companion object { @Suppress("unused") diff --git a/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/inputWithDefaultCurrency/expected/DgsClient.kt b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/inputWithDefaultCurrency/expected/DgsClient.kt new file mode 100644 index 00000000..5ef20981 --- /dev/null +++ b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/inputWithDefaultCurrency/expected/DgsClient.kt @@ -0,0 +1,14 @@ +package com.netflix.graphql.dgs.codegen.cases.inputWithDefaultCurrency.expected + +import com.netflix.graphql.dgs.client.codegen.InputValueSerializerInterface +import com.netflix.graphql.dgs.codegen.GraphQLProjection +import com.netflix.graphql.dgs.codegen.cases.inputWithDefaultCurrency.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/inputWithDefaultCurrency/expected/DgsConstants.kt b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/inputWithDefaultCurrency/expected/DgsConstants.kt new file mode 100644 index 00000000..c6d8c6ea --- /dev/null +++ b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/inputWithDefaultCurrency/expected/DgsConstants.kt @@ -0,0 +1,23 @@ +package com.netflix.graphql.dgs.codegen.cases.inputWithDefaultCurrency.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 Orders: String = "orders" + + public object ORDERS_INPUT_ARGUMENT { + public const val Filter: String = "filter" + } + } + + public object ORDERFILTER { + public const val TYPE_NAME: String = "OrderFilter" + + public const val Value: String = "value" + } +} diff --git a/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/inputWithDefaultCurrency/expected/client/QueryProjection.kt b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/inputWithDefaultCurrency/expected/client/QueryProjection.kt new file mode 100644 index 00000000..7decd133 --- /dev/null +++ b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/inputWithDefaultCurrency/expected/client/QueryProjection.kt @@ -0,0 +1,15 @@ +package com.netflix.graphql.dgs.codegen.cases.inputWithDefaultCurrency.expected.client + +import com.netflix.graphql.dgs.client.codegen.InputValueSerializerInterface +import com.netflix.graphql.dgs.codegen.GraphQLProjection +import com.netflix.graphql.dgs.codegen.cases.inputWithDefaultCurrency.expected.types.OrderFilter + +public class QueryProjection( + inputValueSerializer: InputValueSerializerInterface? = null, +) : GraphQLProjection(inputValueSerializer) { + public fun orders(filter: OrderFilter? = default("filter")): + QueryProjection { + field("orders", "filter" to filter) + return this + } +} diff --git a/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/inputWithDefaultCurrency/expected/types/OrderFilter.kt b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/inputWithDefaultCurrency/expected/types/OrderFilter.kt new file mode 100644 index 00000000..aeb77896 --- /dev/null +++ b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/inputWithDefaultCurrency/expected/types/OrderFilter.kt @@ -0,0 +1,18 @@ +package com.netflix.graphql.dgs.codegen.cases.inputWithDefaultCurrency.expected.types + +import com.fasterxml.jackson.`annotation`.JsonCreator +import com.fasterxml.jackson.`annotation`.JsonProperty +import com.netflix.graphql.dgs.codegen.GraphQLInput +import java.util.Currency +import kotlin.Any +import kotlin.Pair +import kotlin.String +import kotlin.collections.List + +public class OrderFilter @JsonCreator constructor( + @JsonProperty("value") + public val `value`: Currency = default("value", + java.util.Currency.getInstance("USD")), +) : GraphQLInput() { + override fun fields(): List> = listOf("value" to `value`) +} diff --git a/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/inputWithDefaultCurrency/expected/types/Query.kt b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/inputWithDefaultCurrency/expected/types/Query.kt new file mode 100644 index 00000000..e8d0167f --- /dev/null +++ b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/inputWithDefaultCurrency/expected/types/Query.kt @@ -0,0 +1,42 @@ +package com.netflix.graphql.dgs.codegen.cases.inputWithDefaultCurrency.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.jvm.JvmName + +@JsonTypeInfo(use = JsonTypeInfo.Id.NONE) +@JsonDeserialize(builder = Query.Builder::class) +public class Query( + orders: () -> String? = ordersDefault, +) { + private val __orders: () -> String? = orders + + @get:JvmName("getOrders") + public val orders: String? + get() = __orders.invoke() + + public companion object { + private val ordersDefault: () -> String? = + { throw IllegalStateException("Field `orders` was not requested") } + } + + @JsonPOJOBuilder + @JsonIgnoreProperties("__typename") + public class Builder { + private var orders: () -> String? = ordersDefault + + @JsonProperty("orders") + public fun withOrders(orders: String?): Builder = this.apply { + this.orders = { orders } + } + + public fun build(): Query = Query( + orders = orders, + ) + } +} diff --git a/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/inputWithDefaultCurrency/schema.graphql b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/inputWithDefaultCurrency/schema.graphql new file mode 100644 index 00000000..1affa92d --- /dev/null +++ b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/inputWithDefaultCurrency/schema.graphql @@ -0,0 +1,9 @@ +scalar Currency + +type Query { + orders(filter: OrderFilter): String +} + +input OrderFilter { + value: Currency! = "USD" +} 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 cea66048..ab2480c8 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 @@ -54,6 +54,7 @@ import java.io.Serializable import java.math.BigDecimal import java.util.Arrays import java.util.Collections +import java.util.Currency import java.util.Locale import java.util.Objects import javax.lang.model.element.Modifier @@ -155,6 +156,7 @@ class InputTypeGenerator(config: CodeGenConfig, document: Document) : BaseDataTy private val logger: Logger = LoggerFactory.getLogger(InputTypeGenerator::class.java) private val LOCALE: ClassName = ClassName.get(Locale::class.java) private val BIG_DECIMAL: ClassName = ClassName.get(BigDecimal::class.java) + private val CURRENCY: ClassName = ClassName.get(Currency::class.java) } fun generate( @@ -191,11 +193,19 @@ class InputTypeGenerator(config: CodeGenConfig, document: Document) : BaseDataTy type: JavaTypeName, inputTypeDefinitions: List ): CodeBlock { - if (type == LOCALE) { - return localeCodeBlock(value, type) - } else if (type == BIG_DECIMAL) { - return bigDecimalCodeBlock(value, type) + return when (type) { + LOCALE -> localeCodeBlock(value, type) + BIG_DECIMAL -> bigDecimalCodeBlock(value, type) + CURRENCY -> currencyCodeBlock(value, type) + else -> defaultCodeGen(value, type, inputTypeDefinitions) } + } + + private fun defaultCodeGen( + value: Value>, + type: JavaTypeName, + inputTypeDefinitions: List + ): CodeBlock { return when (value) { is BooleanValue -> CodeBlock.of("\$L", value.isValue) is IntValue -> CodeBlock.of("\$L", value.value) @@ -256,6 +266,13 @@ class InputTypeGenerator(config: CodeGenConfig, document: Document) : BaseDataTy } } + private fun currencyCodeBlock(value: Value>, type: JavaTypeName): CodeBlock { + return when (value) { + is StringValue -> CodeBlock.of("\$T.getInstance(\$S)", CURRENCY, value.value) + else -> error("$type cannot be created from $value, expected String value") + } + } + private val JavaTypeName.className: ClassName get() = when (this) { is ClassName -> this diff --git a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/shared/GenerateKotlinCode.kt b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/shared/GenerateKotlinCode.kt index 683e6375..515b9a45 100644 --- a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/shared/GenerateKotlinCode.kt +++ b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/shared/GenerateKotlinCode.kt @@ -33,6 +33,7 @@ fun generateKotlinCode( ): CodeBlock { return checkAndGetLocaleCodeBlock(value, type) ?: checkAndGetBigDecimalCodeBlock(value, type) + ?: checkAndGetCurrencyCodeBlock(value, type) ?: when (value) { is BooleanValue -> CodeBlock.of("%L", value.isValue) is IntValue -> CodeBlock.of("%L", value.value) @@ -105,6 +106,15 @@ private fun checkAndGetBigDecimalCodeBlock(value: Value>, type: TypeNam } else null } +private fun checkAndGetCurrencyCodeBlock(value: Value>, type: TypeName): CodeBlock? { + return if (type.className.canonicalName == "java.util.Currency") { + when (value) { + is StringValue -> CodeBlock.of("%L", "java.util.Currency.getInstance(\"${value.value}\")") + else -> error("$type cannot be created from $value, expected String value") + } + } else null +} + private val TypeName.className: ClassName get() = when (this) { is ClassName -> this 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 9ea9fdf1..c9059a0a 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 @@ -5040,4 +5040,50 @@ It takes a title and such. ).generate() assertThat(result.javaDataTypes[0].typeSpec.fieldSpecs[1].type.toString() == "java.lang.String") } + + @Test + fun `The default value for Currency should be overridden and wrapped`() { + val schema = """ + scalar Currency + + input MyInput { + currency: Currency! = "USD" + } + """.trimIndent() + + val codeGenResult = CodeGen( + CodeGenConfig( + schemas = setOf(schema), + packageName = basePackageName, + generateClientApi = true, + typeMapping = mapOf("Decimal" to "java.math.BigDecimal") + ) + ).generate() + + val dataTypes = codeGenResult.javaDataTypes + assertThat(dataTypes[0].typeSpec.fieldSpecs[0].initializer.toString()).isEqualTo("java.util.Currency.getInstance(\"USD\")") + assertCompilesJava(dataTypes) + } + + @Test + fun `Codegen should fail with nice message given unsupported default value provided for Currency`() { + val schema = """ + scalar Currency + + input MyInput { + currency: Currency! = 1 + } + """.trimIndent() + + assertThatThrownBy { + CodeGen( + CodeGenConfig( + schemas = setOf(schema), + packageName = basePackageName, + generateClientApi = true, + typeMapping = mapOf("Currency" to "java.util.Currency") + ) + ).generate() + }.hasMessage("java.util.Currency cannot be created from IntValue{value=1}, expected String value") + } } 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 0a16f70d..6139176f 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,51 @@ 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 `The default value for Currency should be overridden and wrapped`() { + val schema = """ + scalar Currency + + input MyInput { + currency: Currency! = "USD" + } + """.trimIndent() + + val codeGenResult = CodeGen( + CodeGenConfig( + schemas = setOf(schema), + packageName = basePackageName, + language = Language.KOTLIN, + typeMapping = mapOf("Currency" to "java.util.Currency") + ) + ).generate() + + val dataTypes = codeGenResult.kotlinDataTypes + val typeSpec = dataTypes[0].members[0] as TypeSpec + assertThat(typeSpec.primaryConstructor!!.parameters[0].defaultValue.toString()).isEqualTo("java.util.Currency.getInstance(\"USD\")") + assertCompilesKotlin(dataTypes) + } + + @Test + fun `Codegen should fail with nice message given unsupported default value provided for Currency`() { + val schema = """ + scalar Currency + + input MyInput { + currency: Currency! = 1 + } + """.trimIndent() + + assertThatThrownBy { + CodeGen( + CodeGenConfig( + schemas = setOf(schema), + packageName = basePackageName, + language = Language.KOTLIN, + typeMapping = mapOf("Currency" to "java.util.Currency") + ) + ).generate() + }.hasMessage("java.util.Currency cannot be created from IntValue{value=1}, expected String value") + } }