Skip to content

Commit a5fca5e

Browse files
committed
feat(codegen): implement input/output functions to generated enums to simplify usage with existing enums
1 parent 7a01528 commit a5fca5e

File tree

2 files changed

+97
-3
lines changed
  • graphql-kotlin-toolkit-codegen/src/main/kotlin/com/auritylab/graphql/kotlin/toolkit/codegen/generator
  • graphql-kotlin-toolkit-common/src/main/kotlin/com/auritylab/graphql/kotlin/toolkit/common/directive/implementation

2 files changed

+97
-3
lines changed

graphql-kotlin-toolkit-codegen/src/main/kotlin/com/auritylab/graphql/kotlin/toolkit/codegen/generator/EnumGenerator.kt

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@ package com.auritylab.graphql.kotlin.toolkit.codegen.generator
33
import com.auritylab.graphql.kotlin.toolkit.codegen.CodegenOptions
44
import com.auritylab.graphql.kotlin.toolkit.codegen.mapper.GeneratedMapper
55
import com.auritylab.graphql.kotlin.toolkit.codegen.mapper.KotlinTypeMapper
6+
import com.auritylab.graphql.kotlin.toolkit.common.directive.DirectiveFacade
67
import com.squareup.kotlinpoet.ClassName
8+
import com.squareup.kotlinpoet.CodeBlock
79
import com.squareup.kotlinpoet.FileSpec
810
import com.squareup.kotlinpoet.FunSpec
11+
import com.squareup.kotlinpoet.KModifier
12+
import com.squareup.kotlinpoet.MemberName
913
import com.squareup.kotlinpoet.PropertySpec
1014
import com.squareup.kotlinpoet.TypeSpec
1115
import graphql.schema.GraphQLEnumType
@@ -40,16 +44,105 @@ internal class EnumGenerator(
4044
.initializer("stringValue")
4145
.build()
4246
)
43-
.also {
47+
.also { builder ->
4448
// Go through all enum values and create enum constants within this enum.
4549
enum.values.forEach { enum ->
46-
it.addEnumConstant(
50+
builder.addEnumConstant(
4751
enum.name, TypeSpec.anonymousClassBuilder()
4852
.addSuperclassConstructorParameter("%S", enum.name)
4953
.build()
5054
)
5155
}
56+
57+
// If the "kRepresentation" directive is set on the enum, we have to add a parser/converter function
58+
DirectiveFacade.representation.getArguments(enum)?.className
59+
?.let {
60+
// Add the output property/function.
61+
builder.addProperty(buildParserProperty(it))
62+
builder.addFunction(buildPropertyOperatorFunction())
63+
64+
// Add the input functions within a companion, as they are static.
65+
builder.addType(
66+
TypeSpec.companionObjectBuilder()
67+
.addFunction(buildInputParser(it))
68+
.addFunction(buildInputParserOperatorFunction(it))
69+
.build()
70+
)
71+
}
5272
}
5373
.build()
5474
}
75+
76+
/**
77+
* Will build a parser property which is capable of converting the enum constant into the given
78+
* [representationClass]. The function utilizes the `.valueOf(...)` method on the enum constant.
79+
*/
80+
private fun buildParserProperty(representationClass: String): PropertySpec {
81+
// Resolve the class name and the valueOf function of the class.
82+
val parsedClass = ClassName.bestGuess(representationClass)
83+
84+
// Build the converter code.
85+
val code = CodeBlock.builder()
86+
.beginControlFlow("return try")
87+
.addStatement("%L(name)", MemberName(parsedClass, "valueOf").canonicalName)
88+
.endControlFlow()
89+
.beginControlFlow("catch(ex: IllegalArgumentException)")
90+
.addStatement(
91+
"throw NoSuchElementException(%P)",
92+
"Enum value '\$name' could not be found on enum '${parsedClass.canonicalName}'"
93+
)
94+
.endControlFlow()
95+
.build()
96+
97+
return PropertySpec.builder("representation", parsedClass)
98+
.getter(FunSpec.getterBuilder().addCode(code).build())
99+
.build()
100+
}
101+
102+
/**
103+
* Will build a function which overrides the invoke operator and delegates to the "representation" property
104+
* to resolve the representation.
105+
*/
106+
private fun buildPropertyOperatorFunction(): FunSpec {
107+
return FunSpec.builder("invoke")
108+
.addModifiers(KModifier.OPERATOR)
109+
.addCode("return representation")
110+
.build()
111+
}
112+
113+
/**
114+
* Will build a function which accepts the representation enum as input and returns the matching generated
115+
* enum constant. This also utilizes the #valueOf(...) method of the enum.
116+
*/
117+
private fun buildInputParser(representationClass: String): FunSpec {
118+
val code = CodeBlock.builder()
119+
.beginControlFlow("return try")
120+
.addStatement("valueOf(input.name)")
121+
.endControlFlow()
122+
.beginControlFlow("catch(ex: IllegalArgumentException)")
123+
.addStatement(
124+
"throw NoSuchElementException(%P)",
125+
"Enum value '\${input.name}' could not be found on enum '\${this::class.java.canonicalName}'"
126+
)
127+
.endControlFlow()
128+
.build()
129+
130+
// Build the function with the input and the generated code.
131+
return FunSpec.builder("of")
132+
.addParameter("input", ClassName.bestGuess(representationClass))
133+
.addCode(code)
134+
.build()
135+
}
136+
137+
/**
138+
* Will build a invoke function override, which delegates to the #of(...) function to convert the
139+
* representation enum to the generated enum constant.
140+
*/
141+
private fun buildInputParserOperatorFunction(representationClass: String): FunSpec {
142+
return FunSpec.builder("invoke")
143+
.addModifiers(KModifier.OPERATOR)
144+
.addParameter("input", ClassName.bestGuess(representationClass))
145+
.addCode("return of(input)")
146+
.build()
147+
}
55148
}

graphql-kotlin-toolkit-common/src/main/kotlin/com/auritylab/graphql/kotlin/toolkit/common/directive/implementation/RepresentationDirective.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ class RepresentationDirective : AbstractDirective("kRepresentation", false),
1717
.validLocations(
1818
Introspection.DirectiveLocation.OBJECT,
1919
Introspection.DirectiveLocation.SCALAR,
20-
Introspection.DirectiveLocation.INTERFACE
20+
Introspection.DirectiveLocation.INTERFACE,
21+
Introspection.DirectiveLocation.ENUM
2122
)
2223
.argument {
2324
it.name("class")

0 commit comments

Comments
 (0)