Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,14 @@
package com.datadog.gradle.plugin.jsonschema

enum class JsonPrimitiveType {
STRING, BOOLEAN, INTEGER, DOUBLE, NUMBER
STRING, BOOLEAN, INTEGER, NUMBER
}

fun JsonPrimitiveType.nameString(): String {
return when (this) {
JsonPrimitiveType.STRING -> "String"
JsonPrimitiveType.BOOLEAN -> "Boolean"
JsonPrimitiveType.INTEGER -> "Long"
JsonPrimitiveType.NUMBER -> "Number"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -331,23 +331,24 @@ class JsonSchemaReader(
// because we can't make a type matching both, we simplify it to be always an array
logger.warn("Simplifying a 'oneOf' constraint to $asArray")
asArray
} else if (options.all { it is TypeDefinition.Class || it is TypeDefinition.OneOfClass }) {
} else {
TypeDefinition.OneOfClass(
typeName,
options.flatMap {
when (it) {
is TypeDefinition.OneOfClass -> it.options
is TypeDefinition.Class -> listOf(it)
else -> emptyList()
is TypeDefinition.Class -> listOf(TypeDefinition.OneOfClass.Option.Class(it))
is TypeDefinition.Primitive -> listOf(TypeDefinition.OneOfClass.Option.Primitive(it))
else -> {
throw UnsupportedOperationException(
"Unable to implement `oneOf` constraint with types:\n " +
options.joinToString("\n ")
)
}
}
},
description.orEmpty()
)
} else {
throw UnsupportedOperationException(
"Unable to implement `oneOf` constraint with types:\n " +
options.joinToString("\n ")
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ package com.datadog.gradle.plugin.jsonschema

import com.datadog.gradle.utils.joinToCamelCaseAsVar
import com.squareup.kotlinpoet.BOOLEAN
import com.squareup.kotlinpoet.DOUBLE
import com.squareup.kotlinpoet.LONG
import com.squareup.kotlinpoet.NOTHING
import com.squareup.kotlinpoet.NUMBER
Expand Down Expand Up @@ -43,7 +42,6 @@ internal fun JsonType?.asKotlinTypeName(): TypeName {
internal fun JsonPrimitiveType?.asKotlinTypeName(): TypeName {
return when (this) {
JsonPrimitiveType.BOOLEAN -> BOOLEAN
JsonPrimitiveType.DOUBLE -> DOUBLE
JsonPrimitiveType.STRING -> STRING
JsonPrimitiveType.INTEGER -> LONG
JsonPrimitiveType.NUMBER -> NUMBER
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ sealed class TypeDefinition {

data class Primitive(
val type: JsonPrimitiveType,
override val description: String = ""
override val description: String = "",
val parentType: OneOfClass? = null
) : TypeDefinition() {

override fun mergedWith(other: TypeDefinition): TypeDefinition {
Expand All @@ -76,7 +77,6 @@ sealed class TypeDefinition {
fun asPrimitiveTypeFun(): String {
return when (type) {
JsonPrimitiveType.BOOLEAN -> "asBoolean"
JsonPrimitiveType.DOUBLE -> "asDouble"
JsonPrimitiveType.STRING -> "asString"
JsonPrimitiveType.INTEGER -> "asLong"
JsonPrimitiveType.NUMBER -> "asNumber"
Expand Down Expand Up @@ -251,10 +251,15 @@ sealed class TypeDefinition {

data class OneOfClass(
val name: String,
val options: List<Class>,
val options: List<Option>,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Now options of oneOf can be either a class or a primitive.

override val description: String = ""
) : TypeDefinition() {

sealed interface Option {
data class Class(val cls: TypeDefinition.Class) : Option
data class Primitive(val primitive: TypeDefinition.Primitive) : Option
}

override fun mergedWith(other: TypeDefinition): TypeDefinition {
error("Can't merge Multiclass with type $other")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,12 @@ class ClassJsonElementDeserializerGenerator(
Identifier.PARAM_COLLECTION
)

is TypeDefinition.OneOfClass,
is TypeDefinition.OneOfClass -> addStatement(
"%L.add(%T.%L(it))",
Identifier.PARAM_COLLECTION,
arrayType.items.asKotlinTypeName(rootTypeName),
Identifier.FUN_FROM_JSON_ELEMENT
)
is TypeDefinition.Class -> addStatement(
"%L.add(%T.%L(it.asJsonObject))",
Identifier.PARAM_COLLECTION,
Expand Down Expand Up @@ -262,12 +267,12 @@ class ClassJsonElementDeserializerGenerator(
rootTypeName: String
) {
val opt = if (nullable) "?" else ""
beginControlFlow("$assignee = $getter$opt.asJsonObject$opt.let")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is an important place that I missed. It corresponds to the situation when a oneOf is a field of some object.

When we are deserializing a oneOf, we need to treat it as JsonElement now. But here it was cast to JsonObject and it failed if the option of oneOf was of primitive type.

My new tests only tested the case when oneOf(primitive) was an element of an array.

I modified an old test here to test this scenario.

beginControlFlow("$assignee = $getter$opt.let")

addStatement(
"%T.%L(it)",
propertyType.asKotlinTypeName(rootTypeName),
Identifier.FUN_FROM_JSON_OBJ
Identifier.FUN_FROM_JSON_ELEMENT
)
endControlFlow()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ object ClassNameRef {
val NullPointerException = ClassName.bestGuess("java.lang.NullPointerException")
val MutableList = ClassName.bestGuess("kotlin.collections.ArrayList")
val MutableSet = ClassName.bestGuess("kotlin.collections.HashSet")
val UnsupportedOperationException = ClassName.bestGuess("java.lang.UnsupportedOperationException")
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,14 @@ class FileGenerator(

private val classGenerator = ClassGenerator(packageName, knownTypes)
private val enumGenerator = EnumClassGenerator(packageName, knownTypes)
private val multiClassGenerator = MultiClassGenerator(classGenerator, packageName, knownTypes)
private val oneOfPrimitiveOptionGenerator = OneOfPrimitiveOptionGenerator(packageName)

private val multiClassGenerator = MultiClassGenerator(
classGenerator = classGenerator,
oneOfPrimitiveOptionGenerator = oneOfPrimitiveOptionGenerator,
packageName = packageName,
knownTypes = knownTypes
)

// region FileGenerator

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,14 @@ object Identifier {
const val FUN_TO_JSON_ELT = "toJsonElement"
const val FUN_FROM_JSON = "fromJson"
const val FUN_FROM_JSON_OBJ = "fromJsonObject"
const val FUN_FROM_JSON_PRIMITIVE = "fromJsonPrimitive"
const val FUN_FROM_JSON_ELEMENT = "fromJsonElement"

const val PARAM_JSON_STR = "jsonString"
const val PARAM_JSON_ARRAY = "jsonArray"
const val PARAM_JSON_OBJ = "jsonObject"
const val PARAM_JSON_ELEMENT = "jsonElement"
const val PARAM_JSON_PRIMITIVE = "jsonPrimitive"
const val PARAM_JSON_VALUE = "jsonValue"
const val PARAM_ADDITIONAL_PROPS = "additionalProperties"
const val PARAM_COLLECTION = "collection"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package com.datadog.gradle.plugin.jsonschema.generator

import com.datadog.gradle.plugin.jsonschema.TypeDefinition
import com.datadog.gradle.plugin.jsonschema.asKotlinTypeName
import com.datadog.gradle.plugin.jsonschema.nameString
import com.squareup.kotlinpoet.ANY
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.LIST
Expand All @@ -33,12 +34,19 @@ abstract class KotlinSpecGenerator<I : Any, O : Any>(
protected fun TypeDefinition.name(): String? {
return when (this) {
is TypeDefinition.Array,
is TypeDefinition.Primitive,
is TypeDefinition.Constant,
is TypeDefinition.Null -> null
is TypeDefinition.Enum -> name
is TypeDefinition.Class -> name
is TypeDefinition.OneOfClass -> name
is TypeDefinition.Primitive -> type.nameString()
}
}

protected fun TypeDefinition.OneOfClass.Option.name(): String? {
return when (this) {
is TypeDefinition.OneOfClass.Option.Class -> cls.name()
is TypeDefinition.OneOfClass.Option.Primitive -> primitive.name()
}
}

Expand All @@ -62,6 +70,18 @@ abstract class KotlinSpecGenerator<I : Any, O : Any>(
}
}

protected fun TypeDefinition.OneOfClass.Option.asKotlinTypeName(
rootTypeName: String,
parent: TypeDefinition.OneOfClass
): TypeName {
return when (this) {
is TypeDefinition.OneOfClass.Option.Class -> cls.asKotlinTypeName(rootTypeName)
is TypeDefinition.OneOfClass.Option.Primitive -> {
ClassName(packageName, rootTypeName, parent.name, primitive.type.nameString())
}
}
}

fun TypeDefinition.additionalPropertyTypeName(rootTypeName: String): TypeName {
return if (this is TypeDefinition.Primitive) {
this.asKotlinTypeName(rootTypeName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import com.squareup.kotlinpoet.jvm.throws

class MultiClassGenerator(
val classGenerator: KotlinSpecGenerator<TypeDefinition.Class, TypeSpec.Builder>,
val oneOfPrimitiveOptionGenerator: KotlinSpecGenerator<TypeDefinition.Primitive, TypeSpec.Builder>,
packageName: String,
knownTypes: MutableSet<KotlinTypeWrapper>
) : TypeSpecGenerator<TypeDefinition.OneOfClass>(
Expand All @@ -43,17 +44,20 @@ class MultiClassGenerator(

definition.options.forEach {
when (it) {
is TypeDefinition.Class -> {
val childType = it.copy(parentType = definition)
is TypeDefinition.OneOfClass.Option.Class -> {
val childType = it.cls.copy(parentType = definition)
val wrapper = childType.withUniqueTypeName(rootTypeName)
typeBuilder.addType(
classGenerator.generate(childType, rootTypeName).build()
)
wrapper.written = true
}
else -> error(
"Can't have type $it as child of a `one_of` block"
)
is TypeDefinition.OneOfClass.Option.Primitive -> {
val childType = it.primitive.copy(parentType = definition)
typeBuilder.addType(
oneOfPrimitiveOptionGenerator.generate(childType, rootTypeName).build()
)
}
}
}

Expand Down Expand Up @@ -99,15 +103,15 @@ class MultiClassGenerator(
funBuilder.beginControlFlow("try")

funBuilder.addStatement(
"val %L = %T.parseString(%L).asJsonObject",
Identifier.PARAM_JSON_OBJ,
"val %L = %T.parseString(%L)",
Identifier.PARAM_JSON_ELEMENT,
ClassNameRef.JsonParser,
Identifier.PARAM_JSON_STR
)
funBuilder.addStatement(
"return %L(%L)",
Identifier.FUN_FROM_JSON_OBJ,
Identifier.PARAM_JSON_OBJ
Identifier.FUN_FROM_JSON_ELEMENT,
Identifier.PARAM_JSON_ELEMENT
)

funBuilder.nextControlFlow(
Expand All @@ -116,7 +120,7 @@ class MultiClassGenerator(
ClassNameRef.IllegalStateException
)
funBuilder.addStatement("throw %T(", ClassNameRef.JsonParseException)
funBuilder.addStatement(" \"$PARSE_ERROR_MSG %T\",", returnType)
funBuilder.addStatement(" \"$PARSE_ERROR_MSG_ONE_OF_TYPE %T\",", returnType)
funBuilder.addStatement(" %L", Identifier.CAUGHT_EXCEPTION)
funBuilder.addStatement(")")
funBuilder.endControlFlow()
Expand All @@ -129,10 +133,10 @@ class MultiClassGenerator(
rootTypeName: String
): FunSpec {
val returnType = definition.asKotlinTypeName(rootTypeName)
val funBuilder = FunSpec.builder(Identifier.FUN_FROM_JSON_OBJ)
val funBuilder = FunSpec.builder(Identifier.FUN_FROM_JSON_ELEMENT)
.addAnnotation(AnnotationSpec.builder(JvmStatic::class).build())
.throws(ClassNameRef.JsonParseException)
.addParameter(Identifier.PARAM_JSON_OBJ, ClassNameRef.JsonObject)
.addParameter(Identifier.PARAM_JSON_ELEMENT, ClassNameRef.JsonElement)
.returns(returnType)

// create error variable
Expand All @@ -141,15 +145,51 @@ class MultiClassGenerator(
// try to parse against all possible types
val options = mutableListOf<String>()
definition.options.forEach {
val typeName = it.asKotlinTypeName(rootTypeName)
val typeName = it.asKotlinTypeName(rootTypeName, definition)
val variableName = "as${it.name()}"
funBuilder.beginControlFlow("val %L = try", variableName)
funBuilder.addStatement(
"%T.%L(%L)",
typeName,
Identifier.FUN_FROM_JSON_OBJ,
Identifier.PARAM_JSON_OBJ
)
when (it) {
is TypeDefinition.OneOfClass.Option.Class -> {
funBuilder.beginControlFlow(
"if (%L is %T)",
Identifier.PARAM_JSON_ELEMENT,
ClassNameRef.JsonObject
)
funBuilder.addStatement(
"%T.%L(%L)",
typeName,
Identifier.FUN_FROM_JSON_OBJ,
Identifier.PARAM_JSON_ELEMENT
)
funBuilder.nextControlFlow("else")
funBuilder.addStatement(
"throw %T(\"$PARSE_ERROR_MSG_TYPE \"\n + \"%T\")",
ClassNameRef.JsonParseException,
it.cls.asKotlinTypeName(rootTypeName)
)
}
is TypeDefinition.OneOfClass.Option.Primitive -> {
funBuilder.beginControlFlow(
"if (%L is %T)",
Identifier.PARAM_JSON_ELEMENT,
ClassNameRef.JsonPrimitive
)
funBuilder.addStatement(
"%T.%L(%L)",
typeName,
Identifier.FUN_FROM_JSON_PRIMITIVE,
Identifier.PARAM_JSON_ELEMENT
)
funBuilder.nextControlFlow("else")
funBuilder.addStatement(
"throw %T(\"$PARSE_ERROR_MSG_TYPE \"\n + \"%T\")",
ClassNameRef.JsonParseException,
it.primitive.asKotlinTypeName(rootTypeName)
)
}
}
funBuilder.endControlFlow()

funBuilder.nextControlFlow(
"catch (%L: %T)",
Identifier.CAUGHT_EXCEPTION,
Expand All @@ -169,7 +209,7 @@ class MultiClassGenerator(
funBuilder.addStatement(").firstOrNull { it != null }")

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

companion object {
private const val PARSE_ERROR_MSG = "Unable to parse json into one of type"
private const val PARSE_ERROR_MSG_ONE_OF_TYPE = "Unable to parse json into one of type"
private const val PARSE_ERROR_MSG_TYPE = "Unable to parse json into type"
}
}
Loading
Loading