Skip to content

Added support for default array values#1

Merged
zappolowski merged 3 commits into
zappolowski:bug/19-enum-default-value-handlingfrom
Netflix:default-values-updated
Feb 17, 2021
Merged

Added support for default array values#1
zappolowski merged 3 commits into
zappolowski:bug/19-enum-default-value-handlingfrom
Netflix:default-values-updated

Conversation

@paulbakker
Copy link
Copy Markdown

I found a corner case of default array values.
Since we are still Java 8 compatible, there isn't really a nice way to initialize a List of default values, so it's always just generating Collections.emptyList(), which is probably by far the most use case anyway. We could consider adding a flag later to generate Java > 8 style code, but that's a separate issue.

In Kotlin it's checking the contained type, and creates either an emptyList() or a listOf(). For the latter case it re-uses the default value logic to create the right contents in the list.

@zappolowski
Copy link
Copy Markdown
Owner

Since we are still Java 8 compatible, there isn't really a nice way to initialize a List of default values

Isn't Arrays.asList() already available in Java 8?

is StringValue -> CodeBlock.of("%S", defVal.value)
is FloatValue -> CodeBlock.of("%L", defVal.value)
is EnumValue -> CodeBlock.of("%M", MemberName(type as ClassName, defVal.name))
is ArrayValue -> if(defVal.values.isEmpty()) CodeBlock.of("emptyList()") else CodeBlock.of("listOf(%L)", defVal.values.map { v ->
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC collection.map { body }.joinToString() could be rewritten as collection.joinToString { body }.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would need to do a toString() on each codeblock in that case.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm using (in my patch for another comment)

CodeBlock.of("listOf(%L)", value.values.joinToString { v -> generateCode(v, type).toString() })

which saves an intermediary list. The call to toString() is explicit in this approach, but would happen anyhow (Iterable<T>.joinToString uses Iterable<T>.joinTo which eventually calls Appendable.appendElement which calls toString() by default :) )

is StringValue -> CodeBlock.of("%S", v.value)
is FloatValue -> CodeBlock.of("%Lf", v.value)
is EnumValue -> CodeBlock.of("%M", MemberName((type as ParameterizedTypeName).typeArguments[0] as ClassName, v.name))
else -> ""
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't handle ArrayValue here, which would prevent having nested lists (I'm not sure if this is allowed in GraphQL at all).

That when is the same as the enclosing on (minus the nesting). Is it easily feasible to extract this? This would handling more types (like the missing ObjectValue easier).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Arrays in arrays are not possible AFAIK. I thought about extracting it, but I think the code just gets more complicated that way, also because the blocks aren't exactly the same. Feel free to give it a try though if you disagree.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From some quick testing, nested lists seem to be allowed as long as they are homogeneous.

What do you think about this?

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 616d292..cb1bf66 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
@@ -23,9 +23,9 @@ import com.netflix.graphql.dgs.codegen.KotlinCodeGenResult
 import com.netflix.graphql.dgs.codegen.filterSkipped
 import com.netflix.graphql.dgs.codegen.shouldSkip
 import com.squareup.kotlinpoet.*
+import com.squareup.kotlinpoet.TypeName
 import graphql.language.*
 
-
 class KotlinDataTypeGenerator(private val config: CodeGenConfig, private val document: Document): AbstractKotlinDataTypeGenerator(config.packageName + ".types", config) {
     fun generate(definition: ObjectTypeDefinition, extensions: List<ObjectTypeExtensionDefinition>): KotlinCodeGenResult {
         if(definition.shouldSkip()) {
@@ -56,32 +56,33 @@ class KotlinInputTypeGenerator(private val config: CodeGenConfig, private val do
                 .filter(ReservedKeywordFilter.filterInvalidNames)
                 .map {
                     val type = typeUtils.findReturnType(it.type)
-                    val defaultValue = it.defaultValue?.let { defVal ->
-                        when (defVal) {
-                            is BooleanValue -> CodeBlock.of("%L", defVal.isValue)
-                            is IntValue -> CodeBlock.of("%L", defVal.value)
-                            is StringValue -> CodeBlock.of("%S", defVal.value)
-                            is FloatValue -> CodeBlock.of("%L", defVal.value)
-                            is EnumValue -> CodeBlock.of("%M", MemberName(type as ClassName, defVal.name))
-                            is ArrayValue -> if(defVal.values.isEmpty()) CodeBlock.of("emptyList()") else CodeBlock.of("listOf(%L)", defVal.values.map { v ->
-                                when(v) {
-                                    is BooleanValue -> CodeBlock.of("%L", v.isValue)
-                                    is IntValue -> CodeBlock.of("%L", v.value)
-                                    is StringValue -> CodeBlock.of("%S", v.value)
-                                    is FloatValue -> CodeBlock.of("%Lf", v.value)
-                                    is EnumValue -> CodeBlock.of("%M", MemberName((type as ParameterizedTypeName).typeArguments[0] as ClassName, v.name))
-                                    else -> ""
-                                }
-                            }.joinToString())
-                            else -> CodeBlock.of("%L", defVal)
-                        }
-                    }
+                    val defaultValue = it.defaultValue?.let { value -> generateCode(value, type) }
                     Field(it.name, type, typeUtils.isNullable(it.type), defaultValue)
                 }.plus(extensions.flatMap { it.inputValueDefinitions }.map { Field(it.name, typeUtils.findReturnType(it.type), typeUtils.isNullable(it.type)) })
         val interfaces = emptyList<Type<*>>()
         return generate(definition.name, fields, interfaces, true, document)
     }
 
+    private fun generateCode(value: Value<Value<*>>, type: TypeName): CodeBlock =
+        when (value) {
+            is BooleanValue -> CodeBlock.of("%L", value.isValue)
+            is IntValue -> CodeBlock.of("%L", value.value)
+            is StringValue -> CodeBlock.of("%S", value.value)
+            is FloatValue -> CodeBlock.of("%L", value.value)
+            is EnumValue -> CodeBlock.of("%M", MemberName(type.className, value.name))
+            is ArrayValue ->
+                if (value.values.isEmpty()) CodeBlock.of("emptyList()")
+                else CodeBlock.of("listOf(%L)", value.values.joinToString { v -> generateCode(v, type).toString() })
+            else -> CodeBlock.of("%L", value)
+        }
+
+    private val TypeName.className: ClassName
+        get() = when (this) {
+            is ClassName -> this
+            is ParameterizedTypeName -> typeArguments[0].className
+            else -> TODO()
+        }
+
     override fun getPackageName(): String {
         return config.packageName + ".types"
     }

which could (haven't tested it yet) handle nested arrays as well.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we're safe to ignore lists of lists, but would be fine with the refactoring you propose

@paulbakker
Copy link
Copy Markdown
Author

Since we are still Java 8 compatible, there isn't really a nice way to initialize a List of default values

Isn't Arrays.asList() already available in Java 8?

Clearly I'm doing to much Kotlin these days. Added default array values for Java based on this now.

@paulbakker
Copy link
Copy Markdown
Author

I suggest we merge this into the original PR and do any remaining finishing touches there?

@zappolowski zappolowski merged commit 9ca3838 into zappolowski:bug/19-enum-default-value-handling Feb 17, 2021
@paulbakker paulbakker deleted the default-values-updated branch February 17, 2021 20:20
zappolowski added a commit that referenced this pull request Feb 17, 2021
Added support for default array values
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants