feat: allow media type overrides (#369)

This commit is contained in:
Ryan Brink
2022-11-05 16:45:06 -04:00
committed by GitHub
parent 2492661f1f
commit a5376cfa82
8 changed files with 201 additions and 11 deletions

View File

@ -12,6 +12,12 @@
## Released
## [3.7.0] - November 5th, 2022
### Added
- Allow users to override media type in request and response
## [3.6.0] - November 5th, 2022
### Changed

View File

@ -7,7 +7,8 @@ import kotlin.reflect.typeOf
class RequestInfo private constructor(
val requestType: KType,
val description: String,
val examples: Map<String, MediaType.Example>?
val examples: Map<String, MediaType.Example>?,
val mediaTypes: Set<String>
) {
companion object {
@ -22,6 +23,7 @@ class RequestInfo private constructor(
private var requestType: KType? = null
private var description: String? = null
private var examples: Map<String, MediaType.Example>? = null
private var mediaTypes: Set<String>? = null
fun requestType(t: KType) = apply {
this.requestType = t
@ -35,10 +37,15 @@ class RequestInfo private constructor(
this.examples = e.toMap().mapValues { (_, v) -> MediaType.Example(v) }
}
fun mediaTypes(vararg m: String) = apply {
this.mediaTypes = m.toSet()
}
fun build() = RequestInfo(
requestType = requestType ?: error("Request type must be present"),
description = description ?: error("Description must be present"),
examples = examples
examples = examples,
mediaTypes = mediaTypes ?: setOf("application/json")
)
}
}

View File

@ -9,7 +9,8 @@ class ResponseInfo private constructor(
val responseCode: HttpStatusCode,
val responseType: KType,
val description: String,
val examples: Map<String, MediaType.Example>?
val examples: Map<String, MediaType.Example>?,
val mediaTypes: Set<String>
) {
companion object {
@ -25,6 +26,7 @@ class ResponseInfo private constructor(
private var responseType: KType? = null
private var description: String? = null
private var examples: Map<String, MediaType.Example>? = null
private var mediaTypes: Set<String>? = null
fun responseCode(code: HttpStatusCode) = apply {
this.responseCode = code
@ -42,11 +44,16 @@ class ResponseInfo private constructor(
this.examples = e.toMap().mapValues { (_, v) -> MediaType.Example(v) }
}
fun mediaTypes(vararg m: String) = apply {
this.mediaTypes = m.toSet()
}
fun build() = ResponseInfo(
responseCode = responseCode ?: error("You must provide a response code in order to build a Response!"),
responseType = responseType ?: error("You must provide a response type in order to build a Response!"),
description = description ?: error("You must provide a description in order to build a Response!"),
examples = examples
examples = examples,
mediaTypes = mediaTypes ?: setOf("application/json")
)
}
}

View File

@ -84,7 +84,7 @@ object Helpers {
requestBody = when (this) {
is MethodInfoWithRequest -> Request(
description = this.request.description,
content = this.request.requestType.toReferenceContent(this.request.examples),
content = this.request.requestType.toReferenceContent(this.request.examples, this.request.mediaTypes),
required = true
)
@ -93,7 +93,7 @@ object Helpers {
responses = mapOf(
this.response.responseCode.value to Response(
description = this.response.description,
content = this.response.responseType.toReferenceContent(this.response.examples)
content = this.response.responseType.toReferenceContent(this.response.examples, this.response.mediaTypes)
)
).plus(this.errors.toResponseMap())
)
@ -101,21 +101,24 @@ object Helpers {
private fun List<ResponseInfo>.toResponseMap(): Map<Int, Response> = associate { error ->
error.responseCode.value to Response(
description = error.description,
content = error.responseType.toReferenceContent(error.examples)
content = error.responseType.toReferenceContent(error.examples, error.mediaTypes)
)
}
private fun KType.toReferenceContent(examples: Map<String, MediaType.Example>?): Map<String, MediaType>? =
private fun KType.toReferenceContent(
examples: Map<String, MediaType.Example>?,
mediaTypes: Set<String>
): Map<String, MediaType>? =
when (this.classifier as KClass<*>) {
Unit::class -> null
else -> mapOf(
"application/json" to MediaType(
else -> mediaTypes.associateWith {
MediaType(
schema = if (this.isMarkedNullable) OneOfDefinition(
NullableDefinition(),
ReferenceDefinition(this.getReferenceSlug())
) else ReferenceDefinition(this.getReferenceSlug()),
examples = examples
)
)
}
}
}

View File

@ -36,6 +36,7 @@ import io.bkbn.kompendium.core.util.TestModules.nullableEnumField
import io.bkbn.kompendium.core.util.TestModules.nullableField
import io.bkbn.kompendium.core.util.TestModules.nullableNestedObject
import io.bkbn.kompendium.core.util.TestModules.nullableReference
import io.bkbn.kompendium.core.util.TestModules.overrideMediaTypes
import io.bkbn.kompendium.core.util.TestModules.polymorphicCollectionResponse
import io.bkbn.kompendium.core.util.TestModules.polymorphicException
import io.bkbn.kompendium.core.util.TestModules.polymorphicMapResponse
@ -112,6 +113,9 @@ class KompendiumTest : DescribeSpec({
it("Can notarize a route with non-required params") {
openApiTestAllSerializers("T0011__non_required_params.json") { nonRequiredParams() }
}
it("Can override media types") {
openApiTestAllSerializers("T0052__override_media_types.json") { overrideMediaTypes() }
}
}
describe("Route Parsing") {
it("Can parse a simple path and store it under the expected route") {

View File

@ -315,6 +315,28 @@ object TestModules {
}
}
fun Routing.overrideMediaTypes() {
route("/media_types") {
install(NotarizedRoute()) {
put = PutInfo.builder {
summary(defaultPathSummary)
description(defaultPathDescription)
request {
mediaTypes("multipart/form-data", "application/json")
requestType<TestRequest>()
description("A cool request")
}
response {
mediaTypes("application/xml")
responseType<TestResponse>()
description("A good response")
responseCode(HttpStatusCode.Created)
}
}
}
}
}
fun Routing.simplePathParsing() {
route("/this") {
route("/is") {

View File

@ -0,0 +1,123 @@
{
"openapi": "3.1.0",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "Test API",
"version": "1.33.7",
"description": "An amazing, fully-ish 😉 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/bkbnio/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": {
"/media_types": {
"put": {
"tags": [],
"summary": "Great Summary!",
"description": "testing more",
"parameters": [],
"requestBody": {
"description": "A cool request",
"content": {
"multipart/form-data": {
"schema": {
"$ref": "#/components/schemas/TestRequest"
}
},
"application/json": {
"schema": {
"$ref": "#/components/schemas/TestRequest"
}
}
},
"required": true
},
"responses": {
"201": {
"description": "A good response",
"content": {
"application/xml": {
"schema": {
"$ref": "#/components/schemas/TestResponse"
}
}
}
}
},
"deprecated": false
},
"parameters": []
}
},
"webhooks": {},
"components": {
"schemas": {
"TestResponse": {
"type": "object",
"properties": {
"c": {
"type": "string"
}
},
"required": [
"c"
]
},
"TestRequest": {
"type": "object",
"properties": {
"aaa": {
"items": {
"type": "number",
"format": "int64"
},
"type": "array"
},
"b": {
"type": "number",
"format": "double"
},
"fieldName": {
"$ref": "#/components/schemas/TestNested"
}
},
"required": [
"aaa",
"b",
"fieldName"
]
},
"TestNested": {
"type": "object",
"properties": {
"nesty": {
"type": "string"
}
},
"required": [
"nesty"
]
}
},
"securitySchemes": {}
},
"security": [],
"tags": []
}

View File

@ -165,3 +165,21 @@ get = GetInfo.builder {
}
}
```
## Media Types
By default, Kompendium will set the only media type to "application/json". If you would like to override the media type
for a specific request or response (including errors), you can do so with the `mediaTypes` method
```kotlin
get = GetInfo.builder {
summary("Get user by id")
description("A very neat endpoint!")
response {
mediaTypes("application/xml")
responseCode(HttpStatusCode.OK)
responseType<ExampleResponse>()
description("Will return whether or not the user is real 😱")
}
}
```