in graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin2/GenerateKotlin2DataTypes.kt [59:260]
fun generateKotlin2DataTypes(
config: CodeGenConfig,
document: Document,
requiredTypes: Set<String>,
): List<FileSpec> {
val typeLookup = Kotlin2TypeLookup(config, document)
return document
.getDefinitionsOfType(ObjectTypeDefinition::class.java)
.excludeSchemaTypeExtension()
.filter { config.generateDataTypes || it.name in requiredTypes }
.filter { !it.shouldSkip(config) }
.map { typeDefinition ->
logger.info("Generating data type {}", typeDefinition.name)
// get all interfaces this type implements
val implementedInterfaces = typeLookup.implementedInterfaces(typeDefinition)
val implementedUnionTypes = typeLookup.implementedUnionTypes(typeDefinition.name)
val superInterfaces = implementedInterfaces + implementedUnionTypes
// get any fields defined via schema extensions
val extensionTypes = findTypeExtensions(typeDefinition.name, document.definitions)
// get all fields defined on the type itself or any extension types
val fields =
sequenceOf(typeDefinition)
.plus(extensionTypes)
.flatMap { it.fieldDefinitions }
.filterSkipped()
.filter(ReservedKeywordFilter.filterInvalidNames)
.toList()
fun type(field: FieldDefinition) = typeLookup.findReturnType(config.packageNameTypes, field.type)
// get a list of fields to override
val overrideFields = typeLookup.overrideFields(implementedInterfaces)
// create a companion object to store defaults for each field
val companionObject =
TypeSpec
.companionObjectBuilder()
.addOptionalGeneratedAnnotation(config)
// add a default lambda for each field that throws if accessed
.addProperties(
fields.map { field ->
PropertySpec
.builder(
name = "${field.name}Default",
type = LambdaTypeName.get(returnType = type(field)),
).addModifiers(KModifier.PRIVATE)
.initializer(
buildCodeBlock {
addStatement(
"\n{ throw %T(%S) }",
IllegalStateException::class,
"Field `${field.name}` was not requested",
)
},
).build()
},
).build()
// create a builder for this class; default to lambda that throws if accessed
val builderClassName = ClassName(config.packageNameTypes, typeDefinition.name, "Builder")
val builder =
TypeSpec
.classBuilder("Builder")
.addOptionalGeneratedAnnotation(config)
.addAnnotation(jsonBuilderAnnotation())
.addAnnotation(jsonIgnorePropertiesAnnotation("__typename"))
// add a backing property for each field
.addProperties(
fields.map { field ->
PropertySpec
.builder(
name = field.name,
type = LambdaTypeName.get(returnType = type(field)),
).addModifiers(KModifier.PRIVATE)
.mutable()
.initializer("${field.name}Default")
.build()
},
)
// add a method to set the field
.addFunctions(
fields.map { field ->
FunSpec
.builder("with${field.name.capitalized()}")
.addAnnotation(jsonPropertyAnnotation(field.name))
.addParameter(field.name, type(field))
.addControlFlow("return this.apply") {
addStatement("this.%N = { %N }", field.name, field.name)
}.returns(builderClassName)
.build()
},
)
// add a build method to return the constructed class
.addFunction(
FunSpec
.builder("build")
.returns(typeDefinition.name.toKtTypeName())
.addCode(
fields.let { fs ->
val builder =
CodeBlock.builder().add(
"return %T(\n",
ClassName(config.packageNameTypes, typeDefinition.name),
)
fs.forEach { f -> builder.add(" %N = %N,\n", f.name, f.name) }
builder.add(")").build()
},
).build(),
).build()
// create the data class
val typeSpec =
TypeSpec
.classBuilder(typeDefinition.name)
.addOptionalGeneratedAnnotation(config)
// add docs if available
.apply {
if (typeDefinition.description != null) {
addKdoc("%L", typeDefinition.description.sanitizeKdoc())
}
}
// add jackson annotations
.addAnnotation(disableJsonTypeInfoAnnotation())
.addAnnotation(
jsonDeserializeAnnotation(
builderClassName,
),
)
// add nested classes
.addType(companionObject)
.addType(builder)
// add interfaces to implement
.addSuperinterfaces(
superInterfaces.map { typeLookup.findKtInterfaceName(it, config.packageNameTypes) },
)
// add Serializable interface if requested
.apply {
if (config.implementSerializable) {
addSuperinterface(Serializable::class.asClassName())
}
}
// add a constructor with a supplier for every field
.primaryConstructor(
FunSpec
.constructorBuilder()
.addParameters(
fields.map { field ->
ParameterSpec
.builder(
name = field.name,
type = LambdaTypeName.get(returnType = type(field)),
).defaultValue("${field.name}Default")
.build()
},
).build(),
)
// add a backing property for each field
.addProperties(
fields.map { field ->
PropertySpec
.builder(
name = "__${field.name}",
type = LambdaTypeName.get(returnType = type(field)),
).addModifiers(KModifier.PRIVATE)
.initializer("%N", field.name)
.build()
},
)
// add a getter for each field
.addProperties(
fields.map { field ->
PropertySpec
.builder(
name = field.name,
type = type(field),
).apply {
if (field.description != null) {
addKdoc("%L", field.description.sanitizeKdoc())
}
}.apply {
if (field.name in overrideFields) {
addModifiers(KModifier.OVERRIDE)
addAnnotation(suppressInapplicableJvmNameAnnotation())
}
}.addAnnotation(jvmNameAnnotation(field.name))
.getter(
FunSpec
.getterBuilder()
.addStatement("return __${field.name}.invoke()")
.build(),
).build()
},
).build()
// return a file per type
FileSpec.get(config.packageNameTypes, typeSpec)
}
}