Skip to content

Commit be9f47b

Browse files
authored
Add generateInputBuilders (#5146)
* add generateInputBuilders * update doc
1 parent 3235183 commit be9f47b

File tree

25 files changed

+621
-25
lines changed

25 files changed

+621
-25
lines changed

docs/source/advanced/plugin-configuration.mdx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,9 @@ apollo {
295295
generateModelBuilders.set(true)
296296
// Whether to generate fields as primitive types (`int`, `double`, `boolean`) instead of their boxed types (`Integer`, `Double`, etc..)
297297
generatePrimitiveTypes.set(true)
298+
// Opt-in Builders for Operations, Fragments and Input types. Builders are more ergonomic than default arguments when there are a lot of
299+
// optional arguments.
300+
generateInputBuilders.set(true)
298301
// The style to use for fields that are nullable in the Java generated code
299302
nullableFieldStyle.set("apolloOptional")
300303
// Whether to decapitalize field names in the generated models (for instance `FooBar` -> `fooBar`)

libraries/apollo-compiler/api/apollo-compiler.api

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,8 @@ public final class com/apollographql/apollo3/compiler/JavaNullable$Companion {
127127
}
128128

129129
public final class com/apollographql/apollo3/compiler/KotlinCodegenOptions {
130-
public fun <init> (Lcom/apollographql/apollo3/compiler/TargetLanguage;Ljava/util/List;ZZLcom/apollographql/apollo3/compiler/hooks/ApolloCompilerKotlinHooks;ZLjava/lang/String;Z)V
131-
public synthetic fun <init> (Lcom/apollographql/apollo3/compiler/TargetLanguage;Ljava/util/List;ZZLcom/apollographql/apollo3/compiler/hooks/ApolloCompilerKotlinHooks;ZLjava/lang/String;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V
130+
public fun <init> (Lcom/apollographql/apollo3/compiler/TargetLanguage;Ljava/util/List;ZZLcom/apollographql/apollo3/compiler/hooks/ApolloCompilerKotlinHooks;ZLjava/lang/String;ZZ)V
131+
public synthetic fun <init> (Lcom/apollographql/apollo3/compiler/TargetLanguage;Ljava/util/List;ZZLcom/apollographql/apollo3/compiler/hooks/ApolloCompilerKotlinHooks;ZLjava/lang/String;ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V
132132
public final fun getAddJvmOverloads ()Z
133133
public final fun getGenerateAsInternal ()Z
134134
public final fun getGenerateFilterNotNull ()Z
@@ -185,6 +185,7 @@ public final class com/apollographql/apollo3/compiler/OptionsKt {
185185
public static final field defaultGenerateDataBuilders Z
186186
public static final field defaultGenerateFilterNotNull Z
187187
public static final field defaultGenerateFragmentImplementations Z
188+
public static final field defaultGenerateInputBuilders Z
188189
public static final field defaultGenerateModelBuilders Z
189190
public static final field defaultGenerateOptionalOperationVariables Z
190191
public static final field defaultGeneratePrimitiveTypes Z

libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo3/compiler/ApolloCompiler.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,7 @@ object ApolloCompiler {
394394
addJvmOverloads: Boolean = defaultAddJvmOverloads,
395395
requiresOptInAnnotation: String = defaultRequiresOptInAnnotation,
396396
compilerKotlinHooks: ApolloCompilerKotlinHooks = defaultCompilerKotlinHooks,
397+
generateInputBuilders: Boolean = false
397398
): CodegenMetadata {
398399
/**
399400
* Inject all built-in scalars
@@ -484,7 +485,8 @@ object ApolloCompiler {
484485
addJvmOverloads = addJvmOverloads,
485486
requiresOptInAnnotation = requiresOptInAnnotation,
486487
compilerKotlinHooks = compilerKotlinHooks,
487-
languageVersion = targetLanguage
488+
languageVersion = targetLanguage,
489+
generateInputBuilders = generateInputBuilders
488490
)
489491
writeKotlin(
490492
commonCodegenOptions = commonCodegenOptions,

libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo3/compiler/Options.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ class KotlinCodegenOptions(
247247
*/
248248
val addJvmOverloads: Boolean = false,
249249
val requiresOptInAnnotation: String?,
250+
250251
/**
251252
* Whether to add the [JsExport] annotation to generated models. This is useful to be able to cast JSON parsed
252253
* responses into Kotlin classes using [unsafeCast].
@@ -255,6 +256,16 @@ class KotlinCodegenOptions(
255256
*/
256257
@ApolloExperimental
257258
val jsExport: Boolean = false,
259+
260+
/**
261+
* Whether to generate builders in addition to constructors for operations and input types.
262+
* Constructors are more concise but require passing an instance of `Optional` always, making them more verbose
263+
* for the cases where there are a lot of optional input parameters.
264+
*
265+
* Default: false
266+
*/
267+
@ApolloExperimental
268+
val generateInputBuilders: Boolean = false
258269
)
259270

260271
class JavaCodegenOptions(
@@ -369,6 +380,7 @@ const val defaultAddJvmOverloads = false
369380
const val defaultFieldsOnDisjointTypesMustMerge = true
370381
const val defaultGeneratePrimitiveTypes = false
371382
const val defaultJsExport = false
383+
const val defaultGenerateInputBuilders = false
372384
val defaultNullableFieldStyle = JavaNullable.NONE
373385
const val defaultDecapitalizeFields = false
374386
val defaultCompilerKotlinHooks = ApolloCompilerKotlinHooks.Identity

libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo3/compiler/codegen/kotlin/KotlinCodeGen.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ internal object KotlinCodeGen {
7272
val hooks = kotlinCodegenOptions.compilerKotlinHooks
7373
val generateSchema = commonCodegenOptions.generateSchema || generateDataBuilders
7474
val outputDir = commonCodegenOptions.outputDir
75+
val generateInputBuilders = kotlinCodegenOptions.generateInputBuilders
7576

7677
val upstreamResolver = resolverInfos.fold(null as KotlinResolver?) { acc, resolverInfo ->
7778
KotlinResolver(resolverInfo.entries, acc, scalarMapping, requiresOptInAnnotation, hooks)
@@ -108,7 +109,7 @@ internal object KotlinCodeGen {
108109
builders.add(EnumResponseAdapterBuilder(context, irEnum))
109110
}
110111
irSchema.irInputObjects.forEach { irInputObject ->
111-
builders.add(InputObjectBuilder(context, irInputObject))
112+
builders.add(InputObjectBuilder(context, irInputObject, generateInputBuilders))
112113
builders.add(InputObjectAdapterBuilder(context, irInputObject))
113114
}
114115
irSchema.irUnions.forEach {irUnion ->
@@ -156,7 +157,8 @@ internal object KotlinCodeGen {
156157
fragment,
157158
flatten,
158159
addJvmOverloads,
159-
generateDataBuilders
160+
generateDataBuilders,
161+
generateInputBuilders,
160162
)
161163
)
162164
if (fragment.variables.isNotEmpty()) {
@@ -184,6 +186,7 @@ internal object KotlinCodeGen {
184186
flatten,
185187
addJvmOverloads,
186188
generateDataBuilders,
189+
generateInputBuilders
187190
)
188191
)
189192
}

libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo3/compiler/codegen/kotlin/KotlinSymbols.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ internal object KotlinSymbols {
6161
val MapBuilder = ClassNames.MapBuilder.toKotlinPoetClassName()
6262
val StubbedProperty = ClassNames.StubbedProperty.toKotlinPoetClassName()
6363
val MandatoryTypenameProperty = ClassNames.MandatoryTypenameProperty.toKotlinPoetClassName()
64+
val Builder = ClassName("", "Builder")
6465

6566
/**
6667
* Kotlin class names

libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo3/compiler/codegen/kotlin/file/FragmentBuilder.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ internal class FragmentBuilder(
2626
flatten: Boolean,
2727
private val addJvmOverloads: Boolean,
2828
private val generateDataBuilders: Boolean,
29+
private val generateInputBuilders: Boolean,
2930
) : CgFileBuilder {
3031
private val layout = context.layout
3132
private val packageName = layout.fragmentPackageName(fragment.filePath)
@@ -67,16 +68,23 @@ internal class FragmentBuilder(
6768
}
6869

6970
private fun IrFragmentDefinition.typeSpec(): TypeSpec {
71+
val namedTypes = variables.map { it.toNamedType() }
72+
7073
return TypeSpec.classBuilder(simpleName)
7174
.addSuperinterface(superInterfaceType())
7275
.maybeAddDescription(description)
73-
.makeDataClass(variables.map { it.toNamedType().toParameterSpec(context) }, addJvmOverloads)
76+
.makeDataClass(namedTypes.map { it.toParameterSpec(context) }, addJvmOverloads)
7477
.addFunction(serializeVariablesFunSpec())
7578
.addFunction(adapterFunSpec(context, dataProperty))
7679
.addFunction(rootFieldFunSpec())
7780
// Fragments can have multiple data shapes
7881
.addTypes(dataTypeSpecs())
7982
.maybeAddJsExport(context)
83+
.apply {
84+
if (namedTypes.isNotEmpty() && generateInputBuilders) {
85+
addType(namedTypes.builderTypeSpec(context, ClassName(packageName, simpleName)))
86+
}
87+
}
8088
.applyIf(generateDataBuilders) {
8189
addType(
8290
TypeSpec.companionObjectBuilder()
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,33 @@
11
package com.apollographql.apollo3.compiler.codegen.kotlin.file
22

3+
import com.apollographql.apollo3.compiler.codegen.Identifier
4+
import com.apollographql.apollo3.compiler.codegen.Identifier.Builder
35
import com.apollographql.apollo3.compiler.codegen.kotlin.CgFile
46
import com.apollographql.apollo3.compiler.codegen.kotlin.CgFileBuilder
57
import com.apollographql.apollo3.compiler.codegen.kotlin.KotlinContext
8+
import com.apollographql.apollo3.compiler.codegen.kotlin.helpers.NamedType
69
import com.apollographql.apollo3.compiler.codegen.kotlin.helpers.makeDataClass
710
import com.apollographql.apollo3.compiler.codegen.kotlin.helpers.maybeAddDescription
811
import com.apollographql.apollo3.compiler.codegen.kotlin.helpers.toNamedType
912
import com.apollographql.apollo3.compiler.codegen.kotlin.helpers.toParameterSpec
13+
import com.apollographql.apollo3.compiler.codegen.kotlin.helpers.toPropertySpec
14+
import com.apollographql.apollo3.compiler.codegen.kotlin.helpers.toSetterFunSpec
1015
import com.apollographql.apollo3.compiler.ir.IrInputObject
16+
import com.apollographql.apollo3.compiler.ir.IrNonNullType
17+
import com.apollographql.apollo3.compiler.ir.IrOptionalType
1118
import com.squareup.kotlinpoet.ClassName
19+
import com.squareup.kotlinpoet.CodeBlock
20+
import com.squareup.kotlinpoet.FunSpec
1221
import com.squareup.kotlinpoet.TypeSpec
1322

1423
internal class InputObjectBuilder(
1524
val context: KotlinContext,
1625
val inputObject: IrInputObject,
26+
val generateInputBuilders: Boolean,
1727
) : CgFileBuilder {
1828
private val packageName = context.layout.typePackageName()
1929
private val simpleName = context.layout.inputObjectName(inputObject.name)
30+
private val className = ClassName(packageName, simpleName)
2031

2132
override fun build(): CgFile {
2233
return CgFile(
@@ -29,16 +40,60 @@ internal class InputObjectBuilder(
2940
override fun prepare() {
3041
context.resolver.registerSchemaType(
3142
inputObject.name,
32-
ClassName(packageName, simpleName)
43+
className
3344
)
3445
}
3546

36-
private fun IrInputObject.typeSpec() =
37-
TypeSpec
38-
.classBuilder(simpleName)
39-
.maybeAddDescription(description)
40-
.makeDataClass(fields.map {
41-
it.toNamedType().toParameterSpec(context)
42-
})
43-
.build()
47+
private fun IrInputObject.typeSpec(): TypeSpec {
48+
val namedTypes = fields.map {
49+
it.toNamedType()
50+
}
51+
return TypeSpec
52+
.classBuilder(simpleName)
53+
.maybeAddDescription(description)
54+
.makeDataClass(namedTypes.map { it.toParameterSpec(context) })
55+
.apply {
56+
if (namedTypes.isNotEmpty() && generateInputBuilders) {
57+
addType(namedTypes.builderTypeSpec(context, className))
58+
}
59+
}
60+
.build()
61+
}
4462
}
63+
64+
65+
internal fun List<NamedType>.builderTypeSpec(context: KotlinContext, returnedClassName: ClassName): TypeSpec {
66+
return TypeSpec.classBuilder(Builder)
67+
.apply {
68+
forEach {
69+
addProperty(it.toPropertySpec(context))
70+
addFunction(it.toSetterFunSpec(context))
71+
}
72+
}
73+
.addFunction(toBuildFunSpec(context, returnedClassName))
74+
.build()
75+
}
76+
77+
private fun List<NamedType>.toBuildFunSpec(context: KotlinContext, returnedClassName: ClassName): FunSpec {
78+
return FunSpec.builder(Identifier.build)
79+
.returns(returnedClassName)
80+
.addCode(
81+
CodeBlock.builder()
82+
.add("return·%T(\n", returnedClassName)
83+
.indent()
84+
.apply {
85+
forEach {
86+
val propertyName = context.layout.propertyName(it.graphQlName)
87+
add("%L·=·%L", propertyName, propertyName)
88+
if (it.type is IrNonNullType && it.type.ofType !is IrOptionalType) {
89+
add("·?:·error(\"missing·value·for·$propertyName\")")
90+
}
91+
add(",\n")
92+
}
93+
}
94+
.unindent()
95+
.add(")")
96+
.build()
97+
)
98+
.build()
99+
}

libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo3/compiler/codegen/kotlin/file/OperationBuilder.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ internal class OperationBuilder(
4040
flatten: Boolean,
4141
private val addJvmOverloads: Boolean,
4242
val generateDataBuilders: Boolean,
43+
val generateInputBuilders: Boolean
4344
) : CgFileBuilder {
4445
private val layout = context.layout
4546
private val packageName = layout.operationPackageName(operation.filePath)
@@ -90,11 +91,17 @@ internal class OperationBuilder(
9091
}
9192

9293
fun typeSpec(): TypeSpec {
94+
val namedTypes = operation.variables.map { it.toNamedType() }
9395
return TypeSpec.classBuilder(layout.operationName(operation))
9496
.addSuperinterface(superInterfaceType())
9597
.maybeAddDescription(operation.description)
96-
.makeDataClass(operation.variables.map { it.toNamedType().toParameterSpec(context) }, addJvmOverloads)
98+
.makeDataClass(namedTypes.map { it.toParameterSpec(context) }, addJvmOverloads)
9799
.maybeAddJsExport(context)
100+
.apply {
101+
if (namedTypes.isNotEmpty() && generateInputBuilders) {
102+
addType(namedTypes.builderTypeSpec(context, ClassName(packageName, simpleName)))
103+
}
104+
}
98105
.addFunction(operationIdFunSpec())
99106
.addFunction(queryDocumentFunSpec(generateQueryDocument))
100107
.addFunction(nameFunSpec())

libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo3/compiler/codegen/kotlin/helpers/KDoc.kt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@ internal fun ParameterSpec.Builder.maybeAddDescription(description: String?): Pa
3636
return addKdoc("%L", description)
3737
}
3838

39+
internal fun FunSpec.Builder.maybeAddDescription(description: String?): FunSpec.Builder {
40+
if (description.isNullOrBlank()) {
41+
return this
42+
}
43+
44+
return addKdoc("%L", description)
45+
}
46+
3947

4048
internal fun TypeSpec.Builder.maybeAddDeprecation(deprecationReason: String?): TypeSpec.Builder {
4149
if (deprecationReason.isNullOrBlank()) {
@@ -61,6 +69,14 @@ internal fun ParameterSpec.Builder.maybeAddDeprecation(deprecationReason: String
6169
return addAnnotation(deprecatedAnnotation(deprecationReason))
6270
}
6371

72+
internal fun FunSpec.Builder.maybeAddDeprecation(deprecationReason: String?): FunSpec.Builder {
73+
if (deprecationReason.isNullOrBlank()) {
74+
return this
75+
}
76+
77+
return addAnnotation(deprecatedAnnotation(deprecationReason))
78+
}
79+
6480
internal fun TypeSpec.Builder.maybeAddRequiresOptIn(resolver: KotlinResolver, optInFeature: String?): TypeSpec.Builder {
6581
if (optInFeature.isNullOrBlank()) {
6682
return this
@@ -88,6 +104,15 @@ internal fun ParameterSpec.Builder.maybeAddRequiresOptIn(resolver: KotlinResolve
88104
return addAnnotation(AnnotationSpec.builder(annotation).build())
89105
}
90106

107+
internal fun FunSpec.Builder.maybeAddRequiresOptIn(resolver: KotlinResolver, optInFeature: String?): FunSpec.Builder {
108+
if (optInFeature.isNullOrBlank()) {
109+
return this
110+
}
111+
112+
val annotation = resolver.resolveRequiresOptInAnnotation() ?: return this
113+
return addAnnotation(AnnotationSpec.builder(annotation).build())
114+
}
115+
91116
internal val suppressDeprecationAnnotationSpec = AnnotationSpec.builder(KotlinSymbols.Suppress)
92117
.addMember("%S", "DEPRECATION")
93118
.build()

0 commit comments

Comments
 (0)