refactored to leverage KType (#21)
This commit is contained in:
@ -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
|
||||||
|
10
README.md
10
README.md
@ -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!
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -1,49 +1,60 @@
|
|||||||
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
|
||||||
|
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)
|
else -> handleComplexType(clazz, cache)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleComplexType(clazz: KClass<*>, cache: SchemaMap): SchemaMap =
|
private fun handleComplexType(clazz: KClass<*>, cache: SchemaMap): SchemaMap =
|
||||||
when (cache.containsKey(clazz.simpleName)) {
|
when (cache.containsKey(clazz.simpleName)) {
|
||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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}-")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user