org.http4k.contract.openapi.v3.OpenApi3ApiRenderer.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of http4k-contract Show documentation
Show all versions of http4k-contract Show documentation
http4k typesafe HTTP contracts and OpenApi support
package org.http4k.contract.openapi.v3
import org.http4k.contract.Tag
import org.http4k.contract.openapi.ApiInfo
import org.http4k.contract.openapi.ApiRenderer
import org.http4k.contract.openapi.v3.BodyContent.FormContent
import org.http4k.contract.openapi.v3.BodyContent.NoSchema
import org.http4k.contract.openapi.v3.BodyContent.OneOfSchemaContent
import org.http4k.contract.openapi.v3.BodyContent.SchemaContent
import org.http4k.contract.openapi.v3.RequestParameter.PrimitiveParameter
import org.http4k.contract.openapi.v3.RequestParameter.SchemaParameter
import org.http4k.format.Json
import org.http4k.util.JsonSchema
/**
* Converts a API to OpenApi3 format JSON, using non-reflective JSON marshalling - this is the limited version
*
* If you are using Jackson, you probably want to use ApiRenderer.Auto()!
*/
class OpenApi3ApiRenderer(private val json: Json) : ApiRenderer, NODE> {
private val jsonToJsonSchema = JsonToJsonSchema(json, "components/schemas")
override fun api(api: Api): NODE =
with(api) {
json {
obj(
"openapi" to string(openapi),
"info" to info.asJson(),
"tags" to array(tags.map { it.asJson() }),
"paths" to paths.asJson(),
"components" to components.asJson(),
"servers" to array(servers.map { it.asJson() })
)
}
}
private fun ApiServer.asJson() = json {
obj(
"url" to string(url.toString()),
"description" to string(description ?: "")
)
}
private fun Tag.asJson(): NODE =
json {
obj(
listOf(
"name" to string(name),
"description" to description.asJson()
)
)
}
private fun Components.asJson() = json {
obj(
"schemas" to schemas,
"securitySchemes" to securitySchemes
)
}
private fun Map>>.asJson(): NODE =
json {
obj(
map {
it.key to obj(
it.value
.map { it.key to it.value.toJson() }.sortedBy { it.first }
)
}.sortedBy { it.first }
)
}
private fun ApiPath.toJson(): NODE = json {
obj(
listOfNotNull(
"summary" to summary.asJson(),
"description" to description.asJson(),
"tags" to array(tags.map { string(it) }),
"parameters" to parameters.asJson(),
if (this@toJson is ApiPath.WithBody) [email protected]() else null,
"responses" to responses.asJson(),
"security" to security,
"operationId" to operationId.asJson(),
"deprecated" to boolean(deprecated)
)
)
}
private fun RequestContents.asJson() = json {
content?.let {
"requestBody" to obj(
listOfNotNull(
"content" to it.asJson(),
"required" to boolean(content.isNotEmpty())
)
)
}
}
@JvmName("contentAsJson")
private fun Map.asJson(): NODE = json {
obj(
map {
it.key to (
listOf(it.value).filterIsInstance>().map { it.toJson() } +
listOf(it.value).filterIsInstance>().map { it.toJson() } +
listOf(it.value).filterIsInstance>().map { it.toJson() } +
listOf(it.value).filterIsInstance().map { it.toJson() }
).firstOrNull().orNullNode()
}
)
}
private fun NoSchema.toJson(): NODE = json {
obj("schema" to schema)
}
private fun OneOfSchemaContent.toJson(): NODE = json {
obj("schema" to obj("oneOf" to array(schema.oneOf)))
}
private fun SchemaContent.toJson(): NODE = json {
obj(
listOfNotNull(
example?.let { "example" to it },
schema?.let { "schema" to it }
)
)
}
private fun FormContent.toJson(): NODE = json {
obj("schema" to
obj(
"type" to string("object"),
"properties" to obj(
schema.properties.map {
it.key to obj(it.value.map { (key, value) ->
key to
when (value) {
is String -> value.asJson()
is Map<*, *> -> value.mapAsJson()
else -> error("")
}
})
}
),
"required" to array(schema.required.map { it.asJson() })
)
)
}
@JvmName("responseAsJson")
private fun Map>.asJson(): NODE = json {
obj(map {
it.key to
obj(
"description" to it.value.description.asJson(),
"content" to it.value.content.asJson()
)
})
}
private fun List>.asJson(): NODE = json {
array(
filterIsInstance>().map { it.asJson() }
+ filterIsInstance>().map { it.asJson() }
)
}
private fun SchemaParameter.asJson(): NODE = json {
obj(
listOfNotNull(
schema?.let { "schema" to it },
"in" to string(`in`),
"name" to string(name),
"required" to boolean(required),
"description" to description.asJson()
)
)
}
private fun PrimitiveParameter.asJson(): NODE = json {
obj(
"schema" to schema,
"in" to string(`in`),
"name" to string(name),
"required" to boolean(required),
"description" to description.asJson()
)
}
private fun ApiInfo.asJson() = json {
obj("title" to string(title), "version" to string(version), "description" to string(description ?: ""))
}
private fun String?.asJson() = this?.let { json.string(it) } ?: json.nullNode()
private fun Map<*, *>.mapAsJson() = json {
obj(map { it.key.toString() to string(it.value.toString()) }.toList())
}
private fun NODE?.orNullNode() = this ?: json.nullNode()
@Suppress("UNCHECKED_CAST")
override fun toSchema(obj: Any, overrideDefinitionId: String?): JsonSchema =
try {
jsonToJsonSchema.toSchema(obj as NODE, overrideDefinitionId)
} catch (e: ClassCastException) {
jsonToJsonSchema.toSchema(json.obj(), overrideDefinitionId)
}
}