Skip to content

Commit df659af

Browse files
Merge pull request #2972 from DataDog/aleksandr-gringauz/boxed-primitive-generation
Add support of oneOf(primitive|object) in the json parser generator
2 parents ee38a9a + 1585433 commit df659af

31 files changed

+1454
-106
lines changed

buildSrc/src/main/kotlin/com/datadog/gradle/plugin/jsonschema/JsonPrimitiveType.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,14 @@
77
package com.datadog.gradle.plugin.jsonschema
88

99
enum class JsonPrimitiveType {
10-
STRING, BOOLEAN, INTEGER, DOUBLE, NUMBER
10+
STRING, BOOLEAN, INTEGER, NUMBER
11+
}
12+
13+
fun JsonPrimitiveType.nameString(): String {
14+
return when (this) {
15+
JsonPrimitiveType.STRING -> "String"
16+
JsonPrimitiveType.BOOLEAN -> "Boolean"
17+
JsonPrimitiveType.INTEGER -> "Long"
18+
JsonPrimitiveType.NUMBER -> "Number"
19+
}
1120
}

buildSrc/src/main/kotlin/com/datadog/gradle/plugin/jsonschema/JsonSchemaReader.kt

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -331,23 +331,24 @@ class JsonSchemaReader(
331331
// because we can't make a type matching both, we simplify it to be always an array
332332
logger.warn("Simplifying a 'oneOf' constraint to $asArray")
333333
asArray
334-
} else if (options.all { it is TypeDefinition.Class || it is TypeDefinition.OneOfClass }) {
334+
} else {
335335
TypeDefinition.OneOfClass(
336336
typeName,
337337
options.flatMap {
338338
when (it) {
339339
is TypeDefinition.OneOfClass -> it.options
340-
is TypeDefinition.Class -> listOf(it)
341-
else -> emptyList()
340+
is TypeDefinition.Class -> listOf(TypeDefinition.OneOfClass.Option.Class(it))
341+
is TypeDefinition.Primitive -> listOf(TypeDefinition.OneOfClass.Option.Primitive(it))
342+
else -> {
343+
throw UnsupportedOperationException(
344+
"Unable to implement `oneOf` constraint with types:\n " +
345+
options.joinToString("\n ")
346+
)
347+
}
342348
}
343349
},
344350
description.orEmpty()
345351
)
346-
} else {
347-
throw UnsupportedOperationException(
348-
"Unable to implement `oneOf` constraint with types:\n " +
349-
options.joinToString("\n ")
350-
)
351352
}
352353
}
353354

buildSrc/src/main/kotlin/com/datadog/gradle/plugin/jsonschema/PokoExtensions.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ package com.datadog.gradle.plugin.jsonschema
88

99
import com.datadog.gradle.utils.joinToCamelCaseAsVar
1010
import com.squareup.kotlinpoet.BOOLEAN
11-
import com.squareup.kotlinpoet.DOUBLE
1211
import com.squareup.kotlinpoet.LONG
1312
import com.squareup.kotlinpoet.NOTHING
1413
import com.squareup.kotlinpoet.NUMBER
@@ -43,7 +42,6 @@ internal fun JsonType?.asKotlinTypeName(): TypeName {
4342
internal fun JsonPrimitiveType?.asKotlinTypeName(): TypeName {
4443
return when (this) {
4544
JsonPrimitiveType.BOOLEAN -> BOOLEAN
46-
JsonPrimitiveType.DOUBLE -> DOUBLE
4745
JsonPrimitiveType.STRING -> STRING
4846
JsonPrimitiveType.INTEGER -> LONG
4947
JsonPrimitiveType.NUMBER -> NUMBER

buildSrc/src/main/kotlin/com/datadog/gradle/plugin/jsonschema/TypeDefinition.kt

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ sealed class TypeDefinition {
5858

5959
data class Primitive(
6060
val type: JsonPrimitiveType,
61-
override val description: String = ""
61+
override val description: String = "",
62+
val parentType: OneOfClass? = null
6263
) : TypeDefinition() {
6364

6465
override fun mergedWith(other: TypeDefinition): TypeDefinition {
@@ -76,7 +77,6 @@ sealed class TypeDefinition {
7677
fun asPrimitiveTypeFun(): String {
7778
return when (type) {
7879
JsonPrimitiveType.BOOLEAN -> "asBoolean"
79-
JsonPrimitiveType.DOUBLE -> "asDouble"
8080
JsonPrimitiveType.STRING -> "asString"
8181
JsonPrimitiveType.INTEGER -> "asLong"
8282
JsonPrimitiveType.NUMBER -> "asNumber"
@@ -251,10 +251,15 @@ sealed class TypeDefinition {
251251

252252
data class OneOfClass(
253253
val name: String,
254-
val options: List<Class>,
254+
val options: List<Option>,
255255
override val description: String = ""
256256
) : TypeDefinition() {
257257

258+
sealed interface Option {
259+
data class Class(val cls: TypeDefinition.Class) : Option
260+
data class Primitive(val primitive: TypeDefinition.Primitive) : Option
261+
}
262+
258263
override fun mergedWith(other: TypeDefinition): TypeDefinition {
259264
error("Can't merge Multiclass with type $other")
260265
}

buildSrc/src/main/kotlin/com/datadog/gradle/plugin/jsonschema/generator/ClassJsonElementDeserializerGenerator.kt

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,12 @@ class ClassJsonElementDeserializerGenerator(
216216
Identifier.PARAM_COLLECTION
217217
)
218218

219-
is TypeDefinition.OneOfClass,
219+
is TypeDefinition.OneOfClass -> addStatement(
220+
"%L.add(%T.%L(it))",
221+
Identifier.PARAM_COLLECTION,
222+
arrayType.items.asKotlinTypeName(rootTypeName),
223+
Identifier.FUN_FROM_JSON_ELEMENT
224+
)
220225
is TypeDefinition.Class -> addStatement(
221226
"%L.add(%T.%L(it.asJsonObject))",
222227
Identifier.PARAM_COLLECTION,
@@ -262,12 +267,12 @@ class ClassJsonElementDeserializerGenerator(
262267
rootTypeName: String
263268
) {
264269
val opt = if (nullable) "?" else ""
265-
beginControlFlow("$assignee = $getter$opt.asJsonObject$opt.let")
270+
beginControlFlow("$assignee = $getter$opt.let")
266271

267272
addStatement(
268273
"%T.%L(it)",
269274
propertyType.asKotlinTypeName(rootTypeName),
270-
Identifier.FUN_FROM_JSON_OBJ
275+
Identifier.FUN_FROM_JSON_ELEMENT
271276
)
272277
endControlFlow()
273278
}

buildSrc/src/main/kotlin/com/datadog/gradle/plugin/jsonschema/generator/ClassNameRef.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@ object ClassNameRef {
2121
val NullPointerException = ClassName.bestGuess("java.lang.NullPointerException")
2222
val MutableList = ClassName.bestGuess("kotlin.collections.ArrayList")
2323
val MutableSet = ClassName.bestGuess("kotlin.collections.HashSet")
24+
val UnsupportedOperationException = ClassName.bestGuess("java.lang.UnsupportedOperationException")
2425
}

buildSrc/src/main/kotlin/com/datadog/gradle/plugin/jsonschema/generator/FileGenerator.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,14 @@ class FileGenerator(
2323

2424
private val classGenerator = ClassGenerator(packageName, knownTypes)
2525
private val enumGenerator = EnumClassGenerator(packageName, knownTypes)
26-
private val multiClassGenerator = MultiClassGenerator(classGenerator, packageName, knownTypes)
26+
private val oneOfPrimitiveOptionGenerator = OneOfPrimitiveOptionGenerator(packageName)
27+
28+
private val multiClassGenerator = MultiClassGenerator(
29+
classGenerator = classGenerator,
30+
oneOfPrimitiveOptionGenerator = oneOfPrimitiveOptionGenerator,
31+
packageName = packageName,
32+
knownTypes = knownTypes
33+
)
2734

2835
// region FileGenerator
2936

buildSrc/src/main/kotlin/com/datadog/gradle/plugin/jsonschema/generator/Identifier.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,14 @@ object Identifier {
1313
const val FUN_TO_JSON_ELT = "toJsonElement"
1414
const val FUN_FROM_JSON = "fromJson"
1515
const val FUN_FROM_JSON_OBJ = "fromJsonObject"
16+
const val FUN_FROM_JSON_PRIMITIVE = "fromJsonPrimitive"
17+
const val FUN_FROM_JSON_ELEMENT = "fromJsonElement"
1618

1719
const val PARAM_JSON_STR = "jsonString"
1820
const val PARAM_JSON_ARRAY = "jsonArray"
1921
const val PARAM_JSON_OBJ = "jsonObject"
22+
const val PARAM_JSON_ELEMENT = "jsonElement"
23+
const val PARAM_JSON_PRIMITIVE = "jsonPrimitive"
2024
const val PARAM_JSON_VALUE = "jsonValue"
2125
const val PARAM_ADDITIONAL_PROPS = "additionalProperties"
2226
const val PARAM_COLLECTION = "collection"

buildSrc/src/main/kotlin/com/datadog/gradle/plugin/jsonschema/generator/KotlinSpecGenerator.kt

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package com.datadog.gradle.plugin.jsonschema.generator
88

99
import com.datadog.gradle.plugin.jsonschema.TypeDefinition
1010
import com.datadog.gradle.plugin.jsonschema.asKotlinTypeName
11+
import com.datadog.gradle.plugin.jsonschema.nameString
1112
import com.squareup.kotlinpoet.ANY
1213
import com.squareup.kotlinpoet.ClassName
1314
import com.squareup.kotlinpoet.LIST
@@ -33,12 +34,19 @@ abstract class KotlinSpecGenerator<I : Any, O : Any>(
3334
protected fun TypeDefinition.name(): String? {
3435
return when (this) {
3536
is TypeDefinition.Array,
36-
is TypeDefinition.Primitive,
3737
is TypeDefinition.Constant,
3838
is TypeDefinition.Null -> null
3939
is TypeDefinition.Enum -> name
4040
is TypeDefinition.Class -> name
4141
is TypeDefinition.OneOfClass -> name
42+
is TypeDefinition.Primitive -> type.nameString()
43+
}
44+
}
45+
46+
protected fun TypeDefinition.OneOfClass.Option.name(): String? {
47+
return when (this) {
48+
is TypeDefinition.OneOfClass.Option.Class -> cls.name()
49+
is TypeDefinition.OneOfClass.Option.Primitive -> primitive.name()
4250
}
4351
}
4452

@@ -62,6 +70,18 @@ abstract class KotlinSpecGenerator<I : Any, O : Any>(
6270
}
6371
}
6472

73+
protected fun TypeDefinition.OneOfClass.Option.asKotlinTypeName(
74+
rootTypeName: String,
75+
parent: TypeDefinition.OneOfClass
76+
): TypeName {
77+
return when (this) {
78+
is TypeDefinition.OneOfClass.Option.Class -> cls.asKotlinTypeName(rootTypeName)
79+
is TypeDefinition.OneOfClass.Option.Primitive -> {
80+
ClassName(packageName, rootTypeName, parent.name, primitive.type.nameString())
81+
}
82+
}
83+
}
84+
6585
fun TypeDefinition.additionalPropertyTypeName(rootTypeName: String): TypeName {
6686
return if (this is TypeDefinition.Primitive) {
6787
this.asKotlinTypeName(rootTypeName)

buildSrc/src/main/kotlin/com/datadog/gradle/plugin/jsonschema/generator/MultiClassGenerator.kt

Lines changed: 62 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import com.squareup.kotlinpoet.jvm.throws
1818

1919
class MultiClassGenerator(
2020
val classGenerator: KotlinSpecGenerator<TypeDefinition.Class, TypeSpec.Builder>,
21+
val oneOfPrimitiveOptionGenerator: KotlinSpecGenerator<TypeDefinition.Primitive, TypeSpec.Builder>,
2122
packageName: String,
2223
knownTypes: MutableSet<KotlinTypeWrapper>
2324
) : TypeSpecGenerator<TypeDefinition.OneOfClass>(
@@ -43,17 +44,20 @@ class MultiClassGenerator(
4344

4445
definition.options.forEach {
4546
when (it) {
46-
is TypeDefinition.Class -> {
47-
val childType = it.copy(parentType = definition)
47+
is TypeDefinition.OneOfClass.Option.Class -> {
48+
val childType = it.cls.copy(parentType = definition)
4849
val wrapper = childType.withUniqueTypeName(rootTypeName)
4950
typeBuilder.addType(
5051
classGenerator.generate(childType, rootTypeName).build()
5152
)
5253
wrapper.written = true
5354
}
54-
else -> error(
55-
"Can't have type $it as child of a `one_of` block"
56-
)
55+
is TypeDefinition.OneOfClass.Option.Primitive -> {
56+
val childType = it.primitive.copy(parentType = definition)
57+
typeBuilder.addType(
58+
oneOfPrimitiveOptionGenerator.generate(childType, rootTypeName).build()
59+
)
60+
}
5761
}
5862
}
5963

@@ -99,15 +103,15 @@ class MultiClassGenerator(
99103
funBuilder.beginControlFlow("try")
100104

101105
funBuilder.addStatement(
102-
"val %L = %T.parseString(%L).asJsonObject",
103-
Identifier.PARAM_JSON_OBJ,
106+
"val %L = %T.parseString(%L)",
107+
Identifier.PARAM_JSON_ELEMENT,
104108
ClassNameRef.JsonParser,
105109
Identifier.PARAM_JSON_STR
106110
)
107111
funBuilder.addStatement(
108112
"return %L(%L)",
109-
Identifier.FUN_FROM_JSON_OBJ,
110-
Identifier.PARAM_JSON_OBJ
113+
Identifier.FUN_FROM_JSON_ELEMENT,
114+
Identifier.PARAM_JSON_ELEMENT
111115
)
112116

113117
funBuilder.nextControlFlow(
@@ -116,7 +120,7 @@ class MultiClassGenerator(
116120
ClassNameRef.IllegalStateException
117121
)
118122
funBuilder.addStatement("throw %T(", ClassNameRef.JsonParseException)
119-
funBuilder.addStatement(" \"$PARSE_ERROR_MSG %T\",", returnType)
123+
funBuilder.addStatement(" \"$PARSE_ERROR_MSG_ONE_OF_TYPE %T\",", returnType)
120124
funBuilder.addStatement(" %L", Identifier.CAUGHT_EXCEPTION)
121125
funBuilder.addStatement(")")
122126
funBuilder.endControlFlow()
@@ -129,10 +133,10 @@ class MultiClassGenerator(
129133
rootTypeName: String
130134
): FunSpec {
131135
val returnType = definition.asKotlinTypeName(rootTypeName)
132-
val funBuilder = FunSpec.builder(Identifier.FUN_FROM_JSON_OBJ)
136+
val funBuilder = FunSpec.builder(Identifier.FUN_FROM_JSON_ELEMENT)
133137
.addAnnotation(AnnotationSpec.builder(JvmStatic::class).build())
134138
.throws(ClassNameRef.JsonParseException)
135-
.addParameter(Identifier.PARAM_JSON_OBJ, ClassNameRef.JsonObject)
139+
.addParameter(Identifier.PARAM_JSON_ELEMENT, ClassNameRef.JsonElement)
136140
.returns(returnType)
137141

138142
// create error variable
@@ -141,15 +145,51 @@ class MultiClassGenerator(
141145
// try to parse against all possible types
142146
val options = mutableListOf<String>()
143147
definition.options.forEach {
144-
val typeName = it.asKotlinTypeName(rootTypeName)
148+
val typeName = it.asKotlinTypeName(rootTypeName, definition)
145149
val variableName = "as${it.name()}"
146150
funBuilder.beginControlFlow("val %L = try", variableName)
147-
funBuilder.addStatement(
148-
"%T.%L(%L)",
149-
typeName,
150-
Identifier.FUN_FROM_JSON_OBJ,
151-
Identifier.PARAM_JSON_OBJ
152-
)
151+
when (it) {
152+
is TypeDefinition.OneOfClass.Option.Class -> {
153+
funBuilder.beginControlFlow(
154+
"if (%L is %T)",
155+
Identifier.PARAM_JSON_ELEMENT,
156+
ClassNameRef.JsonObject
157+
)
158+
funBuilder.addStatement(
159+
"%T.%L(%L)",
160+
typeName,
161+
Identifier.FUN_FROM_JSON_OBJ,
162+
Identifier.PARAM_JSON_ELEMENT
163+
)
164+
funBuilder.nextControlFlow("else")
165+
funBuilder.addStatement(
166+
"throw %T(\"$PARSE_ERROR_MSG_TYPE \"\n + \"%T\")",
167+
ClassNameRef.JsonParseException,
168+
it.cls.asKotlinTypeName(rootTypeName)
169+
)
170+
}
171+
is TypeDefinition.OneOfClass.Option.Primitive -> {
172+
funBuilder.beginControlFlow(
173+
"if (%L is %T)",
174+
Identifier.PARAM_JSON_ELEMENT,
175+
ClassNameRef.JsonPrimitive
176+
)
177+
funBuilder.addStatement(
178+
"%T.%L(%L)",
179+
typeName,
180+
Identifier.FUN_FROM_JSON_PRIMITIVE,
181+
Identifier.PARAM_JSON_ELEMENT
182+
)
183+
funBuilder.nextControlFlow("else")
184+
funBuilder.addStatement(
185+
"throw %T(\"$PARSE_ERROR_MSG_TYPE \"\n + \"%T\")",
186+
ClassNameRef.JsonParseException,
187+
it.primitive.asKotlinTypeName(rootTypeName)
188+
)
189+
}
190+
}
191+
funBuilder.endControlFlow()
192+
153193
funBuilder.nextControlFlow(
154194
"catch (%L: %T)",
155195
Identifier.CAUGHT_EXCEPTION,
@@ -169,7 +209,7 @@ class MultiClassGenerator(
169209
funBuilder.addStatement(").firstOrNull { it != null }")
170210

171211
funBuilder.beginControlFlow("if (result == null)")
172-
funBuilder.addStatement("val message = \"$PARSE_ERROR_MSG \\n\" + \"%T\\n\" +", returnType)
212+
funBuilder.addStatement("val message = \"$PARSE_ERROR_MSG_ONE_OF_TYPE \\n\" + \"%T\\n\" +", returnType)
173213
funBuilder.addStatement(" errors.joinToString(\"\\n\") { it.message.toString() }")
174214
funBuilder.addStatement("throw %T(message)", ClassNameRef.JsonParseException)
175215
funBuilder.endControlFlow()
@@ -181,6 +221,7 @@ class MultiClassGenerator(
181221
// endregion
182222

183223
companion object {
184-
private const val PARSE_ERROR_MSG = "Unable to parse json into one of type"
224+
private const val PARSE_ERROR_MSG_ONE_OF_TYPE = "Unable to parse json into one of type"
225+
private const val PARSE_ERROR_MSG_TYPE = "Unable to parse json into type"
185226
}
186227
}

0 commit comments

Comments
 (0)