feat: Introduce Support for Response Headers (#446)
This commit is contained in:
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
- Add support for response headers
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package io.bkbn.kompendium.core.metadata
|
package io.bkbn.kompendium.core.metadata
|
||||||
|
|
||||||
import io.bkbn.kompendium.enrichment.TypeEnrichment
|
import io.bkbn.kompendium.enrichment.TypeEnrichment
|
||||||
|
import io.bkbn.kompendium.oas.payload.Header
|
||||||
import io.bkbn.kompendium.oas.payload.MediaType
|
import io.bkbn.kompendium.oas.payload.MediaType
|
||||||
import io.ktor.http.HttpStatusCode
|
import io.ktor.http.HttpStatusCode
|
||||||
import kotlin.reflect.KType
|
import kotlin.reflect.KType
|
||||||
@ -12,7 +13,8 @@ class ResponseInfo private constructor(
|
|||||||
val typeEnrichment: TypeEnrichment<*>?,
|
val typeEnrichment: TypeEnrichment<*>?,
|
||||||
val description: String,
|
val description: String,
|
||||||
val examples: Map<String, MediaType.Example>?,
|
val examples: Map<String, MediaType.Example>?,
|
||||||
val mediaTypes: Set<String>
|
val mediaTypes: Set<String>,
|
||||||
|
val responseHeaders: Map<String, Header>?
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -30,6 +32,11 @@ class ResponseInfo private constructor(
|
|||||||
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
|
private var mediaTypes: Set<String>? = null
|
||||||
|
private var responseHeaders: Map<String, Header>? = null
|
||||||
|
|
||||||
|
fun responseHeaders(headers: Map<String, Header>) = apply {
|
||||||
|
this.responseHeaders = headers
|
||||||
|
}
|
||||||
|
|
||||||
fun responseCode(code: HttpStatusCode) = apply {
|
fun responseCode(code: HttpStatusCode) = apply {
|
||||||
this.responseCode = code
|
this.responseCode = code
|
||||||
@ -64,7 +71,8 @@ class ResponseInfo private constructor(
|
|||||||
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!"),
|
||||||
typeEnrichment = typeEnrichment,
|
typeEnrichment = typeEnrichment,
|
||||||
examples = examples,
|
examples = examples,
|
||||||
mediaTypes = mediaTypes ?: setOf("application/json")
|
mediaTypes = mediaTypes ?: setOf("application/json"),
|
||||||
|
responseHeaders = responseHeaders
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -140,6 +140,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,
|
||||||
|
headers = this.response.responseHeaders,
|
||||||
content = this.response.responseType.toReferenceContent(
|
content = this.response.responseType.toReferenceContent(
|
||||||
examples = this.response.examples,
|
examples = this.response.examples,
|
||||||
mediaTypes = this.response.mediaTypes,
|
mediaTypes = this.response.mediaTypes,
|
||||||
@ -152,6 +153,7 @@ 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,
|
||||||
|
headers = error.responseHeaders,
|
||||||
content = error.responseType.toReferenceContent(
|
content = error.responseType.toReferenceContent(
|
||||||
examples = error.examples,
|
examples = error.examples,
|
||||||
mediaTypes = error.mediaTypes,
|
mediaTypes = error.mediaTypes,
|
||||||
|
@ -52,6 +52,7 @@ import io.bkbn.kompendium.core.util.postNoReqBody
|
|||||||
import io.bkbn.kompendium.core.util.primitives
|
import io.bkbn.kompendium.core.util.primitives
|
||||||
import io.bkbn.kompendium.core.util.reqRespExamples
|
import io.bkbn.kompendium.core.util.reqRespExamples
|
||||||
import io.bkbn.kompendium.core.util.requiredParams
|
import io.bkbn.kompendium.core.util.requiredParams
|
||||||
|
import io.bkbn.kompendium.core.util.responseHeaders
|
||||||
import io.bkbn.kompendium.core.util.returnsList
|
import io.bkbn.kompendium.core.util.returnsList
|
||||||
import io.bkbn.kompendium.core.util.rootRoute
|
import io.bkbn.kompendium.core.util.rootRoute
|
||||||
import io.bkbn.kompendium.core.util.samePathDifferentMethodsAndAuth
|
import io.bkbn.kompendium.core.util.samePathDifferentMethodsAndAuth
|
||||||
@ -133,6 +134,9 @@ class KompendiumTest : DescribeSpec({
|
|||||||
it("Can support a post request with no request body") {
|
it("Can support a post request with no request body") {
|
||||||
openApiTestAllSerializers("T0065__post_no_req_body.json") { postNoReqBody() }
|
openApiTestAllSerializers("T0065__post_no_req_body.json") { postNoReqBody() }
|
||||||
}
|
}
|
||||||
|
it("Can notarize a route with response headers") {
|
||||||
|
openApiTestAllSerializers("T0066__notarized_get_with_response_headers.json") { responseHeaders() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
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") {
|
||||||
|
@ -20,7 +20,9 @@ import io.bkbn.kompendium.core.util.TestModules.defaultPathSummary
|
|||||||
import io.bkbn.kompendium.core.util.TestModules.defaultResponseDescription
|
import io.bkbn.kompendium.core.util.TestModules.defaultResponseDescription
|
||||||
import io.bkbn.kompendium.core.util.TestModules.rootPath
|
import io.bkbn.kompendium.core.util.TestModules.rootPath
|
||||||
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
||||||
|
import io.bkbn.kompendium.oas.payload.Header
|
||||||
import io.bkbn.kompendium.oas.payload.Parameter
|
import io.bkbn.kompendium.oas.payload.Parameter
|
||||||
|
import io.ktor.http.HttpHeaders
|
||||||
import io.ktor.http.HttpStatusCode
|
import io.ktor.http.HttpStatusCode
|
||||||
import io.ktor.server.application.call
|
import io.ktor.server.application.call
|
||||||
import io.ktor.server.application.install
|
import io.ktor.server.application.install
|
||||||
@ -56,6 +58,38 @@ fun Routing.notarizedGet() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Routing.responseHeaders() {
|
||||||
|
route(defaultPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
parameters = defaultParams
|
||||||
|
get = GetInfo.builder {
|
||||||
|
response {
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<TestResponse>()
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
responseHeaders(
|
||||||
|
mapOf(
|
||||||
|
HttpHeaders.ETag to Header(
|
||||||
|
TypeDefinition.STRING,
|
||||||
|
"https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag"
|
||||||
|
),
|
||||||
|
HttpHeaders.LastModified to Header(
|
||||||
|
TypeDefinition.STRING,
|
||||||
|
"https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Last-Modified"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
get {
|
||||||
|
call.respondText { "hey dude ‼️ congrats on the get request" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun Routing.notarizedPost() {
|
fun Routing.notarizedPost() {
|
||||||
route(defaultPath) {
|
route(defaultPath) {
|
||||||
install(NotarizedRoute()) {
|
install(NotarizedRoute()) {
|
||||||
|
@ -0,0 +1,110 @@
|
|||||||
|
{
|
||||||
|
"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": {
|
||||||
|
"/test/{a}": {
|
||||||
|
"get": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Great Summary!",
|
||||||
|
"description": "testing more",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A Successful Endeavor",
|
||||||
|
"headers": {
|
||||||
|
"ETag": {
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag",
|
||||||
|
"required": true,
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
"Last-Modified": {
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Last-Modified",
|
||||||
|
"required": true,
|
||||||
|
"deprecated": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "a",
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aa",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"deprecated": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"TestResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"c": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"c"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
Reference in New Issue
Block a user