refactored to leverage KType (#21)

This commit is contained in:
Ryan Brink
2021-04-16 15:37:23 -04:00
committed by GitHub
parent fdd18674da
commit 810f290f0d
7 changed files with 161 additions and 150 deletions

View File

@ -1,5 +1,12 @@
# Changelog # Changelog
## [0.2.0] - April 16th, 2021
### Changed
- Another re-haul to the reflection analysis
- Top level generics, enums, collections, and maps now supported 🙌
## [0.1.1] - April 16th, 2021 ## [0.1.1] - April 16th, 2021
### Added ### Added

View File

@ -40,11 +40,11 @@ dependencies {
### Warning 🚨 ### Warning 🚨
Kompendium is still under active development ⚠️ There are a number of yet-to-be-implemented features, including Kompendium is still under active development ⚠️ There are a number of yet-to-be-implemented features, including
- Query and Path Parameters - Query and Path Parameters 🔍
- Tags - Tags 🏷
- Multiple Responses - Multiple Responses 📜
- Security Schemas - Security Schemas 🔏
- Top level enum classes (enums as fields in a response are a go ✅) - Sealed Class / Polymorphic Support 😬
- Validation / Enforcement (❓👀❓) - Validation / Enforcement (❓👀❓)
If you have a feature that is not listed here, please open an issue! If you have a feature that is not listed here, please open an issue!

View File

@ -1,7 +1,7 @@
# Kompendium # Kompendium
project.version=0.1.1 project.version=0.2.0
# Kotlin # Kotlin
kotlin.code.style=official kotlin.code.style=official
# Gradle # Gradle
org.gradle.vfs.watch=true #org.gradle.vfs.watch=true
org.gradle.vfs.verbose=true #org.gradle.vfs.verbose=true

View File

@ -81,8 +81,8 @@ object Kompendium {
inline fun <reified TReq : Any, reified TResp : Any> generateComponentSchemas( inline fun <reified TReq : Any, reified TResp : Any> generateComponentSchemas(
block: () -> Route block: () -> Route
): Route { ): Route {
val responseKontent = generateKontent(TResp::class) val responseKontent = generateKontent<TResp>()
val requestKontent = generateKontent(TReq::class) val requestKontent = generateKontent<TReq>()
openApiSpec.components.schemas.putAll(responseKontent) openApiSpec.components.schemas.putAll(responseKontent)
openApiSpec.components.schemas.putAll(requestKontent) openApiSpec.components.schemas.putAll(requestKontent)
return block.invoke() return block.invoke()

View File

@ -1,47 +1,58 @@
package org.leafygreens.kompendium package org.leafygreens.kompendium
import java.lang.reflect.ParameterizedType
import java.util.UUID import java.util.UUID
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.reflect.KProperty import kotlin.reflect.KType
import kotlin.reflect.full.isSubclassOf import kotlin.reflect.full.isSubclassOf
import kotlin.reflect.full.memberProperties import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.javaField import kotlin.reflect.jvm.javaField
import kotlin.reflect.typeOf
import org.leafygreens.kompendium.models.meta.SchemaMap import org.leafygreens.kompendium.models.meta.SchemaMap
import org.leafygreens.kompendium.models.oas.ArraySchema import org.leafygreens.kompendium.models.oas.ArraySchema
import org.leafygreens.kompendium.models.oas.DictionarySchema import org.leafygreens.kompendium.models.oas.DictionarySchema
import org.leafygreens.kompendium.models.oas.EnumSchema import org.leafygreens.kompendium.models.oas.EnumSchema
import org.leafygreens.kompendium.models.oas.FormatSchema import org.leafygreens.kompendium.models.oas.FormatSchema
import org.leafygreens.kompendium.models.oas.ObjectSchema import org.leafygreens.kompendium.models.oas.ObjectSchema
import org.leafygreens.kompendium.models.oas.OpenApiSpecComponentSchema
import org.leafygreens.kompendium.models.oas.ReferencedSchema import org.leafygreens.kompendium.models.oas.ReferencedSchema
import org.leafygreens.kompendium.models.oas.SimpleSchema import org.leafygreens.kompendium.models.oas.SimpleSchema
import org.leafygreens.kompendium.util.Helpers
import org.leafygreens.kompendium.util.Helpers.COMPONENT_SLUG import org.leafygreens.kompendium.util.Helpers.COMPONENT_SLUG
import org.leafygreens.kompendium.util.Helpers.genericNameAdapter import org.leafygreens.kompendium.util.Helpers.genericNameAdapter
import org.leafygreens.kompendium.util.Helpers.logged import org.leafygreens.kompendium.util.Helpers.getReferenceSlug
import org.leafygreens.kompendium.util.Helpers.toPair
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
object Kontent { object Kontent {
private val logger = LoggerFactory.getLogger(javaClass) private val logger = LoggerFactory.getLogger(javaClass)
fun generateKontent( @OptIn(ExperimentalStdlibApi::class)
clazz: KClass<*>, inline fun <reified T> generateKontent(
cache: SchemaMap = emptyMap() cache: SchemaMap = emptyMap()
): SchemaMap = logged(object {}.javaClass.enclosingMethod.name, mapOf("cache" to cache)) { ): SchemaMap {
when { val kontentType = typeOf<T>()
clazz == Unit::class -> cache return generateKTypeKontent(kontentType, cache)
clazz == Int::class -> cache.plus(clazz.simpleName!! to FormatSchema("int32", "integer")) }
clazz == Long::class -> cache.plus(clazz.simpleName!! to FormatSchema("int64", "integer"))
clazz == Double::class -> cache.plus(clazz.simpleName!! to FormatSchema("double", "number")) fun generateKTypeKontent(
clazz == Float::class -> cache.plus(clazz.simpleName!! to FormatSchema("float", "number")) type: KType,
clazz == String::class -> cache.plus(clazz.simpleName!! to SimpleSchema("string")) cache: SchemaMap = emptyMap()
clazz == Boolean::class -> cache.plus(clazz.simpleName!! to SimpleSchema("boolean")) ): SchemaMap = Helpers.logged(object {}.javaClass.enclosingMethod.name, mapOf("cache" to cache)) {
clazz == UUID::class -> cache.plus(clazz.simpleName!! to FormatSchema("uuid", "string")) logger.info("Parsing Kontent of $type")
clazz.isSubclassOf(Enum::class) -> error("Top level enums are currently not supported by Kompendium") when (val clazz = type.classifier as KClass<*>) {
clazz.typeParameters.isNotEmpty() -> error("Top level generics are not supported by Kompendium") Unit::class -> cache
else -> handleComplexType(clazz, cache) Int::class -> cache.plus(clazz.simpleName!! to FormatSchema("int32", "integer"))
Long::class -> cache.plus(clazz.simpleName!! to FormatSchema("int64", "integer"))
Double::class -> cache.plus(clazz.simpleName!! to FormatSchema("double", "number"))
Float::class -> cache.plus(clazz.simpleName!! to FormatSchema("float", "number"))
String::class -> cache.plus(clazz.simpleName!! to SimpleSchema("string"))
Boolean::class -> cache.plus(clazz.simpleName!! to SimpleSchema("boolean"))
UUID::class -> cache.plus(clazz.simpleName!! to FormatSchema("uuid", "string"))
else -> when {
clazz.isSubclassOf(Collection::class) -> handleCollectionType(type, clazz, cache)
clazz.isSubclassOf(Enum::class) -> handleEnumType(clazz, cache)
clazz.isSubclassOf(Map::class) -> handleMapType(type, clazz, cache)
else -> handleComplexType(clazz, cache)
}
} }
} }
@ -60,7 +71,7 @@ object Kontent {
logger.info("Detected field $field") logger.info("Detected field $field")
if (!newCache.containsKey(field.simpleName)) { if (!newCache.containsKey(field.simpleName)) {
logger.info("Cache was missing ${field.simpleName}, adding now") logger.info("Cache was missing ${field.simpleName}, adding now")
newCache = generateFieldKontent(prop, field, newCache) newCache = generateKTypeKontent(prop.returnType, newCache)
} }
val propSchema = ReferencedSchema(field.getReferenceSlug(prop)) val propSchema = ReferencedSchema(field.getReferenceSlug(prop))
Pair(prop.name, propSchema) Pair(prop.name, propSchema)
@ -72,56 +83,35 @@ object Kontent {
} }
} }
private fun KClass<*>.getReferenceSlug(prop: KProperty<*>): String = when { private fun handleEnumType(clazz: KClass<*>, cache: SchemaMap): SchemaMap {
this.typeParameters.isNotEmpty() -> "$COMPONENT_SLUG/${genericNameAdapter(this, prop)}" val options = clazz.java.enumConstants.map { it.toString() }.toSet()
else -> "$COMPONENT_SLUG/${simpleName}" return cache.plus(clazz.simpleName!! to EnumSchema(options))
} }
private fun generateFieldKontent( private fun handleMapType(type: KType, clazz: KClass<*>, cache: SchemaMap): SchemaMap {
prop: KProperty<*>, logger.info("Map detected for $type, generating schema and appending to cache")
field: KClass<*>, val (keyType, valType) = type.arguments.map { it.type }
cache: SchemaMap logger.info("Obtained map types -> key: $keyType and value: $valType")
): SchemaMap = logged(object {}.javaClass.enclosingMethod.name, mapOf("cache" to cache)) { if (keyType?.classifier != String::class) {
when { error("Invalid Map $type: OpenAPI dictionaries must have keys of type String")
field.isSubclassOf(Enum::class) -> enumFieldHandler(prop, field, cache)
field.isSubclassOf(Map::class) -> mapFieldHandler(prop, field, cache)
field.isSubclassOf(Collection::class) -> collectionFieldHandler(prop, field, cache)
else -> generateKontent(field, cache)
} }
} val valClassName = (valType?.classifier as KClass<*>).simpleName
val referenceName = genericNameAdapter(type, clazz)
private fun enumFieldHandler(prop: KProperty<*>, field: KClass<*>, cache: SchemaMap): SchemaMap { val valueReference = ReferencedSchema("$COMPONENT_SLUG/$valClassName")
logger.info("Enum detected for $prop, gathering values")
val options = prop.javaField?.type?.enumConstants?.map { it.toString() }?.toSet()
?: error("unable to parse enum $prop")
return cache.plus(field.simpleName!! to EnumSchema(options))
}
private fun mapFieldHandler(prop: KProperty<*>, field: KClass<*>, cache: SchemaMap): SchemaMap {
logger.info("Map detected for $prop, generating schema and appending to cache")
val (keyClass, valClass) = (prop.javaField?.genericType as ParameterizedType)
.actualTypeArguments.slice(IntRange(0, 1))
.map { it as Class<*> }
.map { it.kotlin }
.toPair()
if (keyClass != String::class) error("Invalid Map $prop: OpenAPI dictionaries must have keys of type String")
val referenceName = genericNameAdapter(field, prop)
val valueReference = ReferencedSchema("$COMPONENT_SLUG/${valClass.simpleName}")
val schema = DictionarySchema(additionalProperties = valueReference) val schema = DictionarySchema(additionalProperties = valueReference)
val updatedCache = generateKontent(valClass, cache) val updatedCache = generateKTypeKontent(valType!!, cache)
return updatedCache.plus(referenceName to schema) return updatedCache.plus(referenceName to schema)
} }
private fun collectionFieldHandler(prop: KProperty<*>, field: KClass<*>, cache: SchemaMap): SchemaMap { private fun handleCollectionType(type: KType, clazz: KClass<*>, cache: SchemaMap): SchemaMap {
logger.info("Collection detected for $prop, generating schema and appending to cache") logger.info("Collection detected for $type, generating schema and appending to cache")
val collectionClass = ((prop.javaField?.genericType as ParameterizedType) val collectionType = type.arguments.first().type!!
.actualTypeArguments.first() as Class<*>).kotlin val collectionClass = collectionType.classifier as KClass<*>
logger.info("Obtained collection class: $collectionClass") logger.info("Obtained collection class: $collectionClass")
val referenceName = genericNameAdapter(field, prop) val referenceName = genericNameAdapter(type, clazz)
val valueReference = ReferencedSchema("$COMPONENT_SLUG/${collectionClass.simpleName}") val valueReference = ReferencedSchema("${COMPONENT_SLUG}/${collectionClass.simpleName}")
val schema = ArraySchema(items = valueReference) val schema = ArraySchema(items = valueReference)
val updatedCache = generateKontent(collectionClass, cache) val updatedCache = generateKTypeKontent(collectionType, cache)
return updatedCache.plus(referenceName to schema) return updatedCache.plus(referenceName to schema)
} }
} }

View File

@ -8,19 +8,8 @@ import io.ktor.util.InternalAPI
import java.lang.reflect.ParameterizedType import java.lang.reflect.ParameterizedType
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
import kotlin.reflect.full.findAnnotation import kotlin.reflect.KType
import kotlin.reflect.full.isSubclassOf
import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.javaField import kotlin.reflect.jvm.javaField
import org.leafygreens.kompendium.Kontent
import org.leafygreens.kompendium.annotations.KompendiumField
import org.leafygreens.kompendium.models.oas.ArraySchema
import org.leafygreens.kompendium.models.oas.DictionarySchema
import org.leafygreens.kompendium.models.oas.EnumSchema
import org.leafygreens.kompendium.models.oas.FormatSchema
import org.leafygreens.kompendium.models.oas.ObjectSchema
import org.leafygreens.kompendium.models.oas.OpenApiSpecComponentSchema
import org.leafygreens.kompendium.models.oas.SimpleSchema
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
object Helpers { object Helpers {
@ -29,6 +18,9 @@ object Helpers {
const val COMPONENT_SLUG = "#/components/schemas" const val COMPONENT_SLUG = "#/components/schemas"
/**
* TODO Explain this
*/
@OptIn(InternalAPI::class) @OptIn(InternalAPI::class)
fun Route.calculatePath(tail: String = ""): String { fun Route.calculatePath(tail: String = ""): String {
logger.info("Building path for ${selector::class}") logger.info("Building path for ${selector::class}")
@ -68,8 +60,20 @@ object Helpers {
} }
} }
/**
* Simple extension function that will take a [Pair] and place it (if absent) into a [MutableMap].
*
* @receiver [MutableMap]
* @param pair to add to map
*/
fun <K, V> MutableMap<K, V>.putPairIfAbsent(pair: Pair<K, V>) = putIfAbsent(pair.first, pair.second) fun <K, V> MutableMap<K, V>.putPairIfAbsent(pair: Pair<K, V>) = putIfAbsent(pair.first, pair.second)
/**
* Simple extension function that will convert a list with two items into a [Pair]
* @receiver [List]
* @return [Pair]
* @throws [IllegalArgumentException] when the list size is not exactly two
*/
fun <T> List<T>.toPair(): Pair<T, T> { fun <T> List<T>.toPair(): Pair<T, T> {
if (this.size != 2) { if (this.size != 2) {
throw IllegalArgumentException("List is not of length 2!") throw IllegalArgumentException("List is not of length 2!")
@ -77,12 +81,6 @@ object Helpers {
return Pair(this[0], this[1]) return Pair(this[0], this[1])
} }
fun genericNameAdapter(field: KClass<*>, prop: KProperty<*>): String {
val typeArgs = (prop.javaField?.genericType as ParameterizedType).actualTypeArguments
val classNames = typeArgs.map { it as Class<*> }.map { it.kotlin }.map { it.simpleName }
return classNames.joinToString(separator = "-", prefix = "${field.simpleName}-")
}
/** /**
* Higher order function that takes a map of names to objects and will log their state ahead of function invocation * Higher order function that takes a map of names to objects and will log their state ahead of function invocation
* along with the result of the function invocation * along with the result of the function invocation
@ -93,4 +91,32 @@ object Helpers {
logger.info("Result of $functionName invocation: $result") logger.info("Result of $functionName invocation: $result")
return result return result
} }
/**
* Will build a reference slug that is useful for schema caching and references, particularly
* in the case of a class with type parameters
*/
fun KClass<*>.getReferenceSlug(prop: KProperty<*>): String = when {
this.typeParameters.isNotEmpty() -> "$COMPONENT_SLUG/${genericNameAdapter(this, prop)}"
else -> "$COMPONENT_SLUG/${simpleName}"
}
/**
* Adapts a class with type parameters into a reference friendly string
*/
fun genericNameAdapter(field: KClass<*>, prop: KProperty<*>): String {
val typeArgs = (prop.javaField?.genericType as ParameterizedType).actualTypeArguments
val classNames = typeArgs.map { it as Class<*> }.map { it.kotlin }.map { it.simpleName }
return classNames.joinToString(separator = "-", prefix = "${field.simpleName}-")
}
/**
* Adapts a class with type parameters into a reference friendly string
*/
fun genericNameAdapter(type: KType, clazz: KClass<*>): String {
val classNames = type.arguments
.map { it.type?.classifier as KClass<*> }
.map { it.simpleName }
return classNames.joinToString(separator = "-", prefix = "${clazz.simpleName}-")
}
} }

View File

@ -7,9 +7,11 @@ import kotlin.test.assertFailsWith
import kotlin.test.assertNotNull import kotlin.test.assertNotNull
import kotlin.test.assertTrue import kotlin.test.assertTrue
import org.leafygreens.kompendium.Kontent.generateKontent import org.leafygreens.kompendium.Kontent.generateKontent
import org.leafygreens.kompendium.models.oas.DictionarySchema
import org.leafygreens.kompendium.models.oas.FormatSchema import org.leafygreens.kompendium.models.oas.FormatSchema
import org.leafygreens.kompendium.models.oas.ObjectSchema import org.leafygreens.kompendium.models.oas.ObjectSchema
import org.leafygreens.kompendium.models.oas.ReferencedSchema import org.leafygreens.kompendium.models.oas.ReferencedSchema
import org.leafygreens.kompendium.util.ComplexRequest
import org.leafygreens.kompendium.util.TestInvalidMap import org.leafygreens.kompendium.util.TestInvalidMap
import org.leafygreens.kompendium.util.TestNestedModel import org.leafygreens.kompendium.util.TestNestedModel
import org.leafygreens.kompendium.util.TestSimpleModel import org.leafygreens.kompendium.util.TestSimpleModel
@ -19,15 +21,13 @@ import org.leafygreens.kompendium.util.TestSimpleWithList
import org.leafygreens.kompendium.util.TestSimpleWithMap import org.leafygreens.kompendium.util.TestSimpleWithMap
import org.leafygreens.kompendium.util.TestWithUUID import org.leafygreens.kompendium.util.TestWithUUID
@ExperimentalStdlibApi
internal class KontentTest { internal class KontentTest {
@Test @Test
fun `Unit returns empty map on generate`() { fun `Unit returns empty map on generate`() {
// when
val clazz = Unit::class
// do // do
val result = generateKontent(clazz) val result = generateKontent<Unit>()
// expect // expect
assertTrue { result.isEmpty() } assertTrue { result.isEmpty() }
@ -35,53 +35,34 @@ internal class KontentTest {
@Test @Test
fun `Primitive types return a single map result`() { fun `Primitive types return a single map result`() {
// when
val clazz = Long::class
// do // do
val result = generateKontent(clazz) val result = generateKontent<Long>()
// expect // expect
assertEquals(1, result.count(), "Should have a single result") assertEquals(1, result.count(), "Should have a single result")
assertEquals(FormatSchema("int64", "integer"), result["Long"]) assertEquals(FormatSchema("int64", "integer"), result["Long"])
} }
@Test
fun `Throws an error when top level generics are detected`() {
// when
val womp = mapOf("asdf" to "fdsa", "2cool" to "4school")
val clazz = womp::class
// expect
assertFailsWith<IllegalStateException> { generateKontent(clazz) }
}
@Test @Test
fun `Objects reference their base types in the cache`() { fun `Objects reference their base types in the cache`() {
// when
val clazz = TestSimpleModel::class
// do // do
val result = generateKontent(clazz) val result = generateKontent<TestSimpleModel>()
// expect // expect
assertNotNull(result) assertNotNull(result)
assertEquals(3, result.count()) assertEquals(3, result.count())
assertTrue { result.containsKey(clazz.simpleName) } assertTrue { result.containsKey(TestSimpleModel::class.simpleName) }
} }
@Test @Test
fun `generation works for nested object types`() { fun `generation works for nested object types`() {
// when
val clazz = TestNestedModel::class
// do // do
val result = generateKontent(clazz) val result = generateKontent<TestNestedModel>()
// expect // expect
assertNotNull(result) assertNotNull(result)
assertEquals(4, result.count()) assertEquals(4, result.count())
assertTrue { result.containsKey(clazz.simpleName) } assertTrue { result.containsKey(TestNestedModel::class.simpleName) }
assertTrue { result.containsKey(TestSimpleModel::class.simpleName) } assertTrue { result.containsKey(TestSimpleModel::class.simpleName) }
} }
@ -89,11 +70,10 @@ internal class KontentTest {
fun `generation does not repeat for cached items`() { fun `generation does not repeat for cached items`() {
// when // when
val clazz = TestNestedModel::class val clazz = TestNestedModel::class
val initialCache = generateKontent(clazz) val initialCache = generateKontent<TestNestedModel>()
val claxx = TestSimpleModel::class
// do // do
val result = generateKontent(claxx, initialCache) val result = generateKontent<TestSimpleModel>(initialCache)
// expect TODO Spy to check invocation count? // expect TODO Spy to check invocation count?
assertNotNull(result) assertNotNull(result)
@ -104,85 +84,93 @@ internal class KontentTest {
@Test @Test
fun `generation allows for enum fields`() { fun `generation allows for enum fields`() {
// when
val clazz = TestSimpleWithEnums::class
// do // do
val result = generateKontent(clazz) val result = generateKontent<TestSimpleWithEnums>()
// expect // expect
assertNotNull(result) assertNotNull(result)
assertEquals(3, result.count()) assertEquals(3, result.count())
assertTrue { result.containsKey(clazz.simpleName) } assertTrue { result.containsKey(TestSimpleWithEnums::class.simpleName) }
} }
@Test @Test
fun `generation allows for map fields`() { fun `generation allows for map fields`() {
// when
val clazz = TestSimpleWithMap::class
// do // do
val result = generateKontent(clazz) val result = generateKontent<TestSimpleWithMap>()
// expect // expect
assertNotNull(result) assertNotNull(result)
assertEquals(5, result.count()) assertEquals(5, result.count())
assertTrue { result.containsKey("Map-String-TestSimpleModel") } assertTrue { result.containsKey("Map-String-TestSimpleModel") }
assertTrue { result.containsKey(clazz.simpleName) } assertTrue { result.containsKey(TestSimpleWithMap::class.simpleName) }
val os = result[clazz.simpleName] as ObjectSchema val os = result[TestSimpleWithMap::class.simpleName] as ObjectSchema
val expectedRef = ReferencedSchema("#/components/schemas/Map-String-TestSimpleModel") val expectedRef = ReferencedSchema("#/components/schemas/Map-String-TestSimpleModel")
assertEquals(expectedRef, os.properties["b"]) assertEquals(expectedRef, os.properties["b"])
} }
@Test @Test
fun `map fields that are not string result in error`() { fun `map fields that are not string result in error`() {
// when
val clazz = TestInvalidMap::class
// expect // expect
assertFailsWith<IllegalStateException> { generateKontent(clazz) } assertFailsWith<IllegalStateException> { generateKontent<TestInvalidMap>() }
} }
@Test @Test
fun `generation allows for collection fields`() { fun `generation allows for collection fields`() {
// when
val clazz = TestSimpleWithList::class
// do // do
val result = generateKontent(clazz) val result = generateKontent<TestSimpleWithList>()
// expect // expect
assertNotNull(result) assertNotNull(result)
assertEquals(6, result.count()) assertEquals(6, result.count())
assertTrue { result.containsKey("List-TestSimpleModel") } assertTrue { result.containsKey("List-TestSimpleModel") }
assertTrue { result.containsKey(clazz.simpleName) } assertTrue { result.containsKey(TestSimpleWithList::class.simpleName) }
} }
@Test @Test
fun `generics as enums throws an exception`() { fun `Can parse enum list as a field`() {
// when // do
val clazz = TestSimpleWithEnumList::class val result = generateKontent<TestSimpleWithEnumList>()
// expect // expect
assertFailsWith<java.lang.IllegalStateException> { generateKontent(clazz) } assertNotNull(result)
} }
@Test @Test
fun `UUID schema support`() { fun `UUID schema support`() {
// when
val clazz = TestWithUUID::class
// do // do
val result = generateKontent(clazz) val result = generateKontent<TestWithUUID>()
// expect // expect
assertNotNull(result) assertNotNull(result)
assertEquals(2, result.count()) assertEquals(2, result.count())
assertTrue { result.containsKey(UUID::class.simpleName) } assertTrue { result.containsKey(UUID::class.simpleName) }
assertTrue { result.containsKey(clazz.simpleName) } assertTrue { result.containsKey(TestWithUUID::class.simpleName) }
val expectedSchema = result[UUID::class.simpleName] as FormatSchema val expectedSchema = result[UUID::class.simpleName] as FormatSchema
assertEquals(FormatSchema("uuid", "string"), expectedSchema) assertEquals(FormatSchema("uuid", "string"), expectedSchema)
} }
@Test
fun `Generate top level list response`() {
// do
val result = generateKontent<List<TestSimpleModel>>()
// expect
assertNotNull(result)
}
@Test
fun `Can handle a complex type`() {
// do
val result = generateKontent<ComplexRequest>()
// expect
assertNotNull(result)
assertEquals(7, result.count())
assertTrue { result.containsKey("Map-String-CrazyItem") }
val ds = result["Map-String-CrazyItem"] as DictionarySchema
val rs = ds.additionalProperties as ReferencedSchema
assertEquals(ReferencedSchema("#/components/schemas/CrazyItem"), rs)
}
} }