feat: allow media type overrides (#369)
This commit is contained in:
@ -12,6 +12,12 @@
|
|||||||
|
|
||||||
## Released
|
## Released
|
||||||
|
|
||||||
|
## [3.7.0] - November 5th, 2022
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Allow users to override media type in request and response
|
||||||
|
|
||||||
## [3.6.0] - November 5th, 2022
|
## [3.6.0] - November 5th, 2022
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
@ -7,7 +7,8 @@ import kotlin.reflect.typeOf
|
|||||||
class RequestInfo private constructor(
|
class RequestInfo private constructor(
|
||||||
val requestType: KType,
|
val requestType: KType,
|
||||||
val description: String,
|
val description: String,
|
||||||
val examples: Map<String, MediaType.Example>?
|
val examples: Map<String, MediaType.Example>?,
|
||||||
|
val mediaTypes: Set<String>
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -22,6 +23,7 @@ class RequestInfo private constructor(
|
|||||||
private var requestType: KType? = null
|
private var requestType: KType? = null
|
||||||
private var description: String? = null
|
private var description: String? = null
|
||||||
private var examples: Map<String, MediaType.Example>? = null
|
private var examples: Map<String, MediaType.Example>? = null
|
||||||
|
private var mediaTypes: Set<String>? = null
|
||||||
|
|
||||||
fun requestType(t: KType) = apply {
|
fun requestType(t: KType) = apply {
|
||||||
this.requestType = t
|
this.requestType = t
|
||||||
@ -35,10 +37,15 @@ class RequestInfo private constructor(
|
|||||||
this.examples = e.toMap().mapValues { (_, v) -> MediaType.Example(v) }
|
this.examples = e.toMap().mapValues { (_, v) -> MediaType.Example(v) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun mediaTypes(vararg m: String) = apply {
|
||||||
|
this.mediaTypes = m.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
fun build() = RequestInfo(
|
fun build() = RequestInfo(
|
||||||
requestType = requestType ?: error("Request type must be present"),
|
requestType = requestType ?: error("Request type must be present"),
|
||||||
description = description ?: error("Description must be present"),
|
description = description ?: error("Description must be present"),
|
||||||
examples = examples
|
examples = examples,
|
||||||
|
mediaTypes = mediaTypes ?: setOf("application/json")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,8 @@ class ResponseInfo private constructor(
|
|||||||
val responseCode: HttpStatusCode,
|
val responseCode: HttpStatusCode,
|
||||||
val responseType: KType,
|
val responseType: KType,
|
||||||
val description: String,
|
val description: String,
|
||||||
val examples: Map<String, MediaType.Example>?
|
val examples: Map<String, MediaType.Example>?,
|
||||||
|
val mediaTypes: Set<String>
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -25,6 +26,7 @@ class ResponseInfo private constructor(
|
|||||||
private var responseType: KType? = null
|
private var responseType: KType? = null
|
||||||
private var description: String? = null
|
private var description: String? = null
|
||||||
private var examples: Map<String, MediaType.Example>? = null
|
private var examples: Map<String, MediaType.Example>? = null
|
||||||
|
private var mediaTypes: Set<String>? = null
|
||||||
|
|
||||||
fun responseCode(code: HttpStatusCode) = apply {
|
fun responseCode(code: HttpStatusCode) = apply {
|
||||||
this.responseCode = code
|
this.responseCode = code
|
||||||
@ -42,11 +44,16 @@ class ResponseInfo private constructor(
|
|||||||
this.examples = e.toMap().mapValues { (_, v) -> MediaType.Example(v) }
|
this.examples = e.toMap().mapValues { (_, v) -> MediaType.Example(v) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun mediaTypes(vararg m: String) = apply {
|
||||||
|
this.mediaTypes = m.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
fun build() = ResponseInfo(
|
fun build() = ResponseInfo(
|
||||||
responseCode = responseCode ?: error("You must provide a response code in order to build a Response!"),
|
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!"),
|
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!"),
|
description = description ?: error("You must provide a description in order to build a Response!"),
|
||||||
examples = examples
|
examples = examples,
|
||||||
|
mediaTypes = mediaTypes ?: setOf("application/json")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,7 +84,7 @@ object Helpers {
|
|||||||
requestBody = when (this) {
|
requestBody = when (this) {
|
||||||
is MethodInfoWithRequest -> Request(
|
is MethodInfoWithRequest -> Request(
|
||||||
description = this.request.description,
|
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
|
required = true
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -93,7 +93,7 @@ object Helpers {
|
|||||||
responses = mapOf(
|
responses = mapOf(
|
||||||
this.response.responseCode.value to Response(
|
this.response.responseCode.value to Response(
|
||||||
description = this.response.description,
|
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())
|
).plus(this.errors.toResponseMap())
|
||||||
)
|
)
|
||||||
@ -101,21 +101,24 @@ object Helpers {
|
|||||||
private fun List<ResponseInfo>.toResponseMap(): Map<Int, Response> = associate { error ->
|
private fun List<ResponseInfo>.toResponseMap(): Map<Int, Response> = associate { error ->
|
||||||
error.responseCode.value to Response(
|
error.responseCode.value to Response(
|
||||||
description = error.description,
|
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<*>) {
|
when (this.classifier as KClass<*>) {
|
||||||
Unit::class -> null
|
Unit::class -> null
|
||||||
else -> mapOf(
|
else -> mediaTypes.associateWith {
|
||||||
"application/json" to MediaType(
|
MediaType(
|
||||||
schema = if (this.isMarkedNullable) OneOfDefinition(
|
schema = if (this.isMarkedNullable) OneOfDefinition(
|
||||||
NullableDefinition(),
|
NullableDefinition(),
|
||||||
ReferenceDefinition(this.getReferenceSlug())
|
ReferenceDefinition(this.getReferenceSlug())
|
||||||
) else ReferenceDefinition(this.getReferenceSlug()),
|
) else ReferenceDefinition(this.getReferenceSlug()),
|
||||||
examples = examples
|
examples = examples
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.nullableField
|
||||||
import io.bkbn.kompendium.core.util.TestModules.nullableNestedObject
|
import io.bkbn.kompendium.core.util.TestModules.nullableNestedObject
|
||||||
import io.bkbn.kompendium.core.util.TestModules.nullableReference
|
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.polymorphicCollectionResponse
|
||||||
import io.bkbn.kompendium.core.util.TestModules.polymorphicException
|
import io.bkbn.kompendium.core.util.TestModules.polymorphicException
|
||||||
import io.bkbn.kompendium.core.util.TestModules.polymorphicMapResponse
|
import io.bkbn.kompendium.core.util.TestModules.polymorphicMapResponse
|
||||||
@ -112,6 +113,9 @@ class KompendiumTest : DescribeSpec({
|
|||||||
it("Can notarize a route with non-required params") {
|
it("Can notarize a route with non-required params") {
|
||||||
openApiTestAllSerializers("T0011__non_required_params.json") { nonRequiredParams() }
|
openApiTestAllSerializers("T0011__non_required_params.json") { nonRequiredParams() }
|
||||||
}
|
}
|
||||||
|
it("Can override media types") {
|
||||||
|
openApiTestAllSerializers("T0052__override_media_types.json") { overrideMediaTypes() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
describe("Route Parsing") {
|
describe("Route Parsing") {
|
||||||
it("Can parse a simple path and store it under the expected route") {
|
it("Can parse a simple path and store it under the expected route") {
|
||||||
|
@ -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() {
|
fun Routing.simplePathParsing() {
|
||||||
route("/this") {
|
route("/this") {
|
||||||
route("/is") {
|
route("/is") {
|
||||||
|
123
core/src/test/resources/T0052__override_media_types.json
Normal file
123
core/src/test/resources/T0052__override_media_types.json
Normal 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": []
|
||||||
|
}
|
@ -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 😱")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Reference in New Issue
Block a user