serializing for complex types (#10)
This commit is contained in:
@ -1,3 +1,12 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## [0.0.6] - April 15th, 2021
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Logging to get a more intuitive sense for operations performed
|
||||||
|
- Serialization for Maps, Collections and Enums
|
||||||
|
|
||||||
## [0.0.5] - April 15th, 2021
|
## [0.0.5] - April 15th, 2021
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Kompendium
|
# Kompendium
|
||||||
project.version=0.0.5
|
project.version=0.0.6
|
||||||
# Kotlin
|
# Kotlin
|
||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
# Gradle
|
# Gradle
|
||||||
|
@ -5,21 +5,11 @@ import io.ktor.http.HttpMethod
|
|||||||
import io.ktor.routing.Route
|
import io.ktor.routing.Route
|
||||||
import io.ktor.routing.method
|
import io.ktor.routing.method
|
||||||
import io.ktor.util.pipeline.PipelineInterceptor
|
import io.ktor.util.pipeline.PipelineInterceptor
|
||||||
import java.lang.reflect.ParameterizedType
|
|
||||||
import kotlin.reflect.KClass
|
|
||||||
import kotlin.reflect.KProperty
|
|
||||||
import kotlin.reflect.full.findAnnotation
|
import kotlin.reflect.full.findAnnotation
|
||||||
import kotlin.reflect.full.memberProperties
|
|
||||||
import kotlin.reflect.jvm.javaField
|
|
||||||
import org.leafygreens.kompendium.annotations.KompendiumField
|
|
||||||
import org.leafygreens.kompendium.annotations.KompendiumRequest
|
import org.leafygreens.kompendium.annotations.KompendiumRequest
|
||||||
import org.leafygreens.kompendium.annotations.KompendiumResponse
|
import org.leafygreens.kompendium.annotations.KompendiumResponse
|
||||||
import org.leafygreens.kompendium.models.meta.MethodInfo
|
import org.leafygreens.kompendium.models.meta.MethodInfo
|
||||||
import org.leafygreens.kompendium.models.oas.ArraySchema
|
|
||||||
import org.leafygreens.kompendium.models.oas.FormatSchema
|
|
||||||
import org.leafygreens.kompendium.models.oas.ObjectSchema
|
|
||||||
import org.leafygreens.kompendium.models.oas.OpenApiSpec
|
import org.leafygreens.kompendium.models.oas.OpenApiSpec
|
||||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecComponentSchema
|
|
||||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfo
|
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfo
|
||||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecMediaType
|
import org.leafygreens.kompendium.models.oas.OpenApiSpecMediaType
|
||||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecPathItem
|
import org.leafygreens.kompendium.models.oas.OpenApiSpecPathItem
|
||||||
@ -27,8 +17,8 @@ import org.leafygreens.kompendium.models.oas.OpenApiSpecPathItemOperation
|
|||||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecReferenceObject
|
import org.leafygreens.kompendium.models.oas.OpenApiSpecReferenceObject
|
||||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecRequest
|
import org.leafygreens.kompendium.models.oas.OpenApiSpecRequest
|
||||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecResponse
|
import org.leafygreens.kompendium.models.oas.OpenApiSpecResponse
|
||||||
import org.leafygreens.kompendium.models.oas.SimpleSchema
|
|
||||||
import org.leafygreens.kompendium.util.Helpers.calculatePath
|
import org.leafygreens.kompendium.util.Helpers.calculatePath
|
||||||
|
import org.leafygreens.kompendium.util.Helpers.objectSchemaPair
|
||||||
import org.leafygreens.kompendium.util.Helpers.putPairIfAbsent
|
import org.leafygreens.kompendium.util.Helpers.putPairIfAbsent
|
||||||
|
|
||||||
object Kompendium {
|
object Kompendium {
|
||||||
@ -130,45 +120,6 @@ object Kompendium {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Investigate a caching mechanism to reduce overhead... then just reference once created
|
|
||||||
fun objectSchemaPair(clazz: KClass<*>): Pair<String, ObjectSchema> {
|
|
||||||
val o = objectSchema(clazz)
|
|
||||||
return Pair(clazz.simpleName!!, o)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun objectSchema(clazz: KClass<*>): ObjectSchema =
|
|
||||||
ObjectSchema(properties = clazz.memberProperties.associate { prop ->
|
|
||||||
val field = prop.javaField?.type?.kotlin
|
|
||||||
val anny = prop.findAnnotation<KompendiumField>()
|
|
||||||
val schema = when (field) {
|
|
||||||
List::class -> listFieldSchema(prop)
|
|
||||||
else -> fieldToSchema(field as KClass<*>)
|
|
||||||
}
|
|
||||||
|
|
||||||
val name = anny?.let {
|
|
||||||
anny.name
|
|
||||||
} ?: prop.name
|
|
||||||
|
|
||||||
Pair(name, schema)
|
|
||||||
})
|
|
||||||
|
|
||||||
private fun listFieldSchema(prop: KProperty<*>): ArraySchema {
|
|
||||||
val listType = ((prop.javaField?.genericType
|
|
||||||
as ParameterizedType).actualTypeArguments.first()
|
|
||||||
as Class<*>).kotlin
|
|
||||||
return ArraySchema(fieldToSchema(listType))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun fieldToSchema(field: KClass<*>): OpenApiSpecComponentSchema = when (field) {
|
|
||||||
Int::class -> FormatSchema("int32", "integer")
|
|
||||||
Long::class -> FormatSchema("int64", "integer")
|
|
||||||
Double::class -> FormatSchema("double", "number")
|
|
||||||
Float::class -> FormatSchema("float", "number")
|
|
||||||
String::class -> SimpleSchema("string")
|
|
||||||
Boolean::class -> SimpleSchema("boolean")
|
|
||||||
else -> objectSchema(field)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun resetSchema() {
|
internal fun resetSchema() {
|
||||||
openApiSpec = OpenApiSpec(
|
openApiSpec = OpenApiSpec(
|
||||||
info = OpenApiSpecInfo(),
|
info = OpenApiSpecInfo(),
|
||||||
|
@ -7,6 +7,14 @@ data class ObjectSchema(
|
|||||||
val properties: Map<String, OpenApiSpecComponentSchema>
|
val properties: Map<String, OpenApiSpecComponentSchema>
|
||||||
) : OpenApiSpecComponentSchema("object")
|
) : OpenApiSpecComponentSchema("object")
|
||||||
|
|
||||||
|
data class DictionarySchema(
|
||||||
|
val additionalProperties: OpenApiSpecComponentSchema
|
||||||
|
) : OpenApiSpecComponentSchema("object")
|
||||||
|
|
||||||
|
data class EnumSchema(
|
||||||
|
val `enum`: Set<String>
|
||||||
|
) : OpenApiSpecComponentSchema("string")
|
||||||
|
|
||||||
data class SimpleSchema(override val type: String) : OpenApiSpecComponentSchema(type)
|
data class SimpleSchema(override val type: String) : OpenApiSpecComponentSchema(type)
|
||||||
|
|
||||||
data class FormatSchema(val format: String, override val type: String) : OpenApiSpecComponentSchema(type)
|
data class FormatSchema(val format: String, override val type: String) : OpenApiSpecComponentSchema(type)
|
||||||
|
@ -5,21 +5,145 @@ import io.ktor.routing.PathSegmentParameterRouteSelector
|
|||||||
import io.ktor.routing.RootRouteSelector
|
import io.ktor.routing.RootRouteSelector
|
||||||
import io.ktor.routing.Route
|
import io.ktor.routing.Route
|
||||||
import io.ktor.util.InternalAPI
|
import io.ktor.util.InternalAPI
|
||||||
|
import java.lang.reflect.ParameterizedType
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
import kotlin.reflect.full.findAnnotation
|
||||||
|
import kotlin.reflect.full.isSubclassOf
|
||||||
|
import kotlin.reflect.full.memberProperties
|
||||||
|
import kotlin.reflect.jvm.javaField
|
||||||
|
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
|
||||||
|
|
||||||
object Helpers {
|
object Helpers {
|
||||||
|
|
||||||
|
private val logger = LoggerFactory.getLogger(javaClass)
|
||||||
|
|
||||||
@OptIn(InternalAPI::class)
|
@OptIn(InternalAPI::class)
|
||||||
fun Route.calculatePath(tail: String = ""): String = when (selector) {
|
fun Route.calculatePath(tail: String = ""): String {
|
||||||
is RootRouteSelector -> tail
|
logger.info("Building path for ${selector::class}")
|
||||||
is PathSegmentParameterRouteSelector -> parent?.calculatePath("/$selector$tail") ?: "/{$selector}$tail"
|
return when (selector) {
|
||||||
is PathSegmentConstantRouteSelector -> parent?.calculatePath("/$selector$tail") ?: "/$selector$tail"
|
is RootRouteSelector -> {
|
||||||
|
logger.info("Root route detected, returning path: $tail")
|
||||||
|
tail
|
||||||
|
}
|
||||||
|
is PathSegmentParameterRouteSelector -> {
|
||||||
|
logger.info("Found segment parameter $selector, continuing to parent")
|
||||||
|
val newTail = "/$selector$tail"
|
||||||
|
parent?.calculatePath(newTail) ?: run {
|
||||||
|
logger.info("No parent found, returning current path")
|
||||||
|
newTail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is PathSegmentConstantRouteSelector -> {
|
||||||
|
logger.info("Found segment constant $selector, continuing to parent")
|
||||||
|
val newTail = "/$selector$tail"
|
||||||
|
parent?.calculatePath(newTail) ?: run {
|
||||||
|
logger.info("No parent found, returning current path")
|
||||||
|
newTail
|
||||||
|
}
|
||||||
|
}
|
||||||
else -> when (selector.javaClass.simpleName) {
|
else -> when (selector.javaClass.simpleName) {
|
||||||
// dumb ass workaround to this object being internal to ktor
|
// dumb ass workaround to this object being internal to ktor
|
||||||
"TrailingSlashRouteSelector" -> parent?.calculatePath("$tail/") ?: "$tail/"
|
"TrailingSlashRouteSelector" -> {
|
||||||
else -> error("unknown selector type $selector")
|
logger.info("Found trailing slash route selector")
|
||||||
|
val newTail = "$tail/"
|
||||||
|
parent?.calculatePath(newTail) ?: run {
|
||||||
|
logger.info("No parent found, returning current path")
|
||||||
|
newTail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> error("Unhandled selector type ${selector::class}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
|
// TODO Investigate a caching mechanism to reduce overhead... then just reference once created
|
||||||
|
fun objectSchemaPair(clazz: KClass<*>): Pair<String, ObjectSchema> {
|
||||||
|
logger.info("Generating object schema for ${clazz.simpleName}")
|
||||||
|
val o = objectSchema(clazz)
|
||||||
|
return Pair(clazz.simpleName!!, o)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun objectSchema(clazz: KClass<*>): ObjectSchema =
|
||||||
|
ObjectSchema(properties = clazz.memberProperties.associate { prop ->
|
||||||
|
logger.info("Analyzing $prop in class $clazz")
|
||||||
|
val field = prop.javaField?.type?.kotlin
|
||||||
|
val anny = prop.findAnnotation<KompendiumField>()
|
||||||
|
|
||||||
|
if (anny != null) logger.info("Found field annotation: $anny")
|
||||||
|
|
||||||
|
|
||||||
|
val schema = when {
|
||||||
|
field?.isSubclassOf(Enum::class) == true -> {
|
||||||
|
logger.info("Detected that $prop is an enum")
|
||||||
|
val options = prop.javaField?.type?.enumConstants?.map { it.toString() }?.toSet()
|
||||||
|
?: error("unable to parse enum $prop")
|
||||||
|
EnumSchema(options)
|
||||||
|
}
|
||||||
|
field?.isSubclassOf(Map::class) == true || field?.isSubclassOf(Map.Entry::class) == true -> {
|
||||||
|
logger.info("$prop is a Map, doing some crazy stuff")
|
||||||
|
mapFieldSchema(prop)
|
||||||
|
}
|
||||||
|
field?.isSubclassOf(Collection::class) == true -> {
|
||||||
|
logger.info("$prop is a List, building array schema")
|
||||||
|
listFieldSchema(prop)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
logger.info("$prop is not a list or map, going directly to schema detection")
|
||||||
|
fieldToSchema(field as KClass<*>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val name = anny?.let {
|
||||||
|
logger.info("Overriding property name with annotation $anny")
|
||||||
|
anny.name
|
||||||
|
} ?: prop.name
|
||||||
|
|
||||||
|
Pair(name, schema)
|
||||||
|
})
|
||||||
|
|
||||||
|
private fun mapFieldSchema(prop: KProperty<*>): DictionarySchema {
|
||||||
|
val (keyType, valType) = (prop.javaField?.genericType as ParameterizedType)
|
||||||
|
.actualTypeArguments.slice(IntRange(0, 1))
|
||||||
|
.map { it as Class<*> }
|
||||||
|
.map { it.kotlin }
|
||||||
|
.toPair()
|
||||||
|
if (keyType != String::class) error("Invalid Map $prop: OpenAPI dictionaries must have keys of type String")
|
||||||
|
return DictionarySchema(additionalProperties = fieldToSchema(valType))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun listFieldSchema(prop: KProperty<*>): ArraySchema {
|
||||||
|
val listType = ((prop.javaField?.genericType
|
||||||
|
as ParameterizedType).actualTypeArguments.first()
|
||||||
|
as Class<*>).kotlin
|
||||||
|
logger.info("Obtained List type, converting to schema $listType")
|
||||||
|
return ArraySchema(fieldToSchema(listType))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fieldToSchema(field: KClass<*>): OpenApiSpecComponentSchema = when (field) {
|
||||||
|
Int::class -> FormatSchema("int32", "integer")
|
||||||
|
Long::class -> FormatSchema("int64", "integer")
|
||||||
|
Double::class -> FormatSchema("double", "number")
|
||||||
|
Float::class -> FormatSchema("float", "number")
|
||||||
|
String::class -> SimpleSchema("string")
|
||||||
|
Boolean::class -> SimpleSchema("boolean")
|
||||||
|
else -> objectSchema(field)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <T> List<T>.toPair(): Pair<T, T> {
|
||||||
|
if (this.size != 2) {
|
||||||
|
throw IllegalArgumentException("List is not of length 2!")
|
||||||
|
}
|
||||||
|
return Pair(this[0], this[1])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ import org.leafygreens.kompendium.models.oas.OpenApiSpecInfo
|
|||||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfoContact
|
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfoContact
|
||||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfoLicense
|
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfoLicense
|
||||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecServer
|
import org.leafygreens.kompendium.models.oas.OpenApiSpecServer
|
||||||
|
import org.leafygreens.kompendium.util.ComplexRequest
|
||||||
import org.leafygreens.kompendium.util.TestCreatedResponse
|
import org.leafygreens.kompendium.util.TestCreatedResponse
|
||||||
import org.leafygreens.kompendium.util.TestData
|
import org.leafygreens.kompendium.util.TestData
|
||||||
import org.leafygreens.kompendium.util.TestDeleteResponse
|
import org.leafygreens.kompendium.util.TestDeleteResponse
|
||||||
@ -96,7 +97,6 @@ internal class KompendiumTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `Notarized post does not interrupt the pipeline`() {
|
fun `Notarized post does not interrupt the pipeline`() {
|
||||||
withTestApplication({
|
withTestApplication({
|
||||||
@ -162,7 +162,6 @@ internal class KompendiumTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `Notarized delete does not interrupt the pipeline`() {
|
fun `Notarized delete does not interrupt the pipeline`() {
|
||||||
withTestApplication({
|
withTestApplication({
|
||||||
@ -258,6 +257,22 @@ internal class KompendiumTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Can notarize a complex type`() {
|
||||||
|
withTestApplication({
|
||||||
|
configModule()
|
||||||
|
openApiModule()
|
||||||
|
complexType()
|
||||||
|
}) {
|
||||||
|
// do
|
||||||
|
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
|
||||||
|
|
||||||
|
// expect
|
||||||
|
val expected = TestData.getFileSnapshot("complex_type.json").trim()
|
||||||
|
assertEquals(expected, json, "The received json spec should match the expected content")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
val testGetInfo = MethodInfo("Another get test", "testing more")
|
val testGetInfo = MethodInfo("Another get test", "testing more")
|
||||||
val testPostInfo = MethodInfo("Test post endpoint", "Post your tests here!")
|
val testPostInfo = MethodInfo("Test post endpoint", "Post your tests here!")
|
||||||
@ -356,6 +371,16 @@ internal class KompendiumTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun Application.complexType() {
|
||||||
|
routing {
|
||||||
|
route("/test") {
|
||||||
|
notarizedPut<Unit, ComplexRequest, TestResponse>(testPutInfo) {
|
||||||
|
call.respondText { "heya" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun Application.openApiModule() {
|
private fun Application.openApiModule() {
|
||||||
routing {
|
routing {
|
||||||
route("/openapi.json") {
|
route("/openapi.json") {
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
package org.leafygreens.kompendium.util
|
||||||
|
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertNotNull
|
||||||
|
import org.leafygreens.kompendium.util.Helpers.objectSchemaPair
|
||||||
|
|
||||||
|
internal class HelpersTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `can build an object schema from a complex object`() {
|
||||||
|
// when
|
||||||
|
val clazz = ComplexRequest::class
|
||||||
|
|
||||||
|
// do
|
||||||
|
val result = objectSchemaPair(clazz)
|
||||||
|
|
||||||
|
// expect
|
||||||
|
assertNotNull(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -24,3 +24,29 @@ data class TestCreatedResponse(val id: Int, val c: String)
|
|||||||
|
|
||||||
@KompendiumResponse(KompendiumHttpCodes.NO_CONTENT, "Entity was deleted successfully")
|
@KompendiumResponse(KompendiumHttpCodes.NO_CONTENT, "Entity was deleted successfully")
|
||||||
object TestDeleteResponse
|
object TestDeleteResponse
|
||||||
|
|
||||||
|
@KompendiumRequest("Request object to create a backbone project")
|
||||||
|
data class ComplexRequest(
|
||||||
|
val org: String,
|
||||||
|
@KompendiumField("amazing_field")
|
||||||
|
val amazingField: String,
|
||||||
|
val tables: List<NestedComplexItem>
|
||||||
|
) {
|
||||||
|
fun testThing() {
|
||||||
|
println("hey mom 👋")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class NestedComplexItem(
|
||||||
|
val name: String,
|
||||||
|
val alias: CustomAlias
|
||||||
|
)
|
||||||
|
|
||||||
|
typealias CustomAlias = Map<String, CrazyItem>
|
||||||
|
|
||||||
|
data class CrazyItem(val enumeration: SimpleEnum)
|
||||||
|
|
||||||
|
enum class SimpleEnum {
|
||||||
|
ONE,
|
||||||
|
TWO
|
||||||
|
}
|
||||||
|
107
kompendium-core/src/test/resources/complex_type.json
Normal file
107
kompendium-core/src/test/resources/complex_type.json
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
{
|
||||||
|
"openapi" : "3.0.3",
|
||||||
|
"info" : {
|
||||||
|
"title" : "Test API",
|
||||||
|
"version" : "1.33.7",
|
||||||
|
"description" : "An amazing, fully-ish \uD83D\uDE09 generated API spec",
|
||||||
|
"termsOfService" : "https://example.com",
|
||||||
|
"contact" : {
|
||||||
|
"name" : "Homer Simpson",
|
||||||
|
"url" : "https://gph.is/1NPUDiM",
|
||||||
|
"email" : "chunkylover53@aol.com"
|
||||||
|
},
|
||||||
|
"license" : {
|
||||||
|
"name" : "MIT",
|
||||||
|
"url" : "https://github.com/lg-backbone/kompendium/blob/main/LICENSE"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"servers" : [ {
|
||||||
|
"url" : "https://myawesomeapi.com",
|
||||||
|
"description" : "Production instance of my API"
|
||||||
|
}, {
|
||||||
|
"url" : "https://staging.myawesomeapi.com",
|
||||||
|
"description" : "Where the fun stuff happens"
|
||||||
|
} ],
|
||||||
|
"paths" : {
|
||||||
|
"/test" : {
|
||||||
|
"put" : {
|
||||||
|
"tags" : [ ],
|
||||||
|
"summary" : "Test put endpoint",
|
||||||
|
"description" : "Put your tests here!",
|
||||||
|
"requestBody" : {
|
||||||
|
"description" : "Request object to create a backbone project",
|
||||||
|
"content" : {
|
||||||
|
"application/json" : {
|
||||||
|
"schema" : {
|
||||||
|
"$ref" : "#/components/schemas/ComplexRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required" : false
|
||||||
|
},
|
||||||
|
"responses" : {
|
||||||
|
"200" : {
|
||||||
|
"description" : "A Successful Endeavor",
|
||||||
|
"content" : {
|
||||||
|
"application/json" : {
|
||||||
|
"schema" : {
|
||||||
|
"$ref" : "#/components/schemas/TestResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated" : false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components" : {
|
||||||
|
"schemas" : {
|
||||||
|
"TestResponse" : {
|
||||||
|
"properties" : {
|
||||||
|
"c" : {
|
||||||
|
"type" : "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type" : "object"
|
||||||
|
},
|
||||||
|
"ComplexRequest" : {
|
||||||
|
"properties" : {
|
||||||
|
"amazing_field" : {
|
||||||
|
"type" : "string"
|
||||||
|
},
|
||||||
|
"org" : {
|
||||||
|
"type" : "string"
|
||||||
|
},
|
||||||
|
"tables" : {
|
||||||
|
"items" : {
|
||||||
|
"properties" : {
|
||||||
|
"alias" : {
|
||||||
|
"additionalProperties" : {
|
||||||
|
"properties" : {
|
||||||
|
"enumeration" : {
|
||||||
|
"enum" : [ "ONE", "TWO" ],
|
||||||
|
"type" : "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type" : "object"
|
||||||
|
},
|
||||||
|
"type" : "object"
|
||||||
|
},
|
||||||
|
"name" : {
|
||||||
|
"type" : "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type" : "object"
|
||||||
|
},
|
||||||
|
"type" : "array"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type" : "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes" : { }
|
||||||
|
},
|
||||||
|
"security" : [ ],
|
||||||
|
"tags" : [ ]
|
||||||
|
}
|
Reference in New Issue
Block a user