diff --git a/openapi/src/OpenAPI.kt b/openapi/src/OpenAPI.kt index 1991d75..9be3d84 100644 --- a/openapi/src/OpenAPI.kt +++ b/openapi/src/OpenAPI.kt @@ -6,6 +6,8 @@ import io.swagger.v3.oas.annotations.Parameter import io.swagger.v3.oas.annotations.enums.ParameterIn import io.swagger.v3.oas.annotations.media.Schema.AccessMode import io.swagger.v3.oas.annotations.parameters.RequestBody +import io.swagger.v3.oas.annotations.responses.ApiResponse +import io.swagger.v3.oas.annotations.responses.ApiResponses import io.swagger.v3.oas.annotations.tags.Tag import klite.* import klite.RequestMethod.GET @@ -58,7 +60,6 @@ internal fun toTags(routes: List) = routes.asSequence() internal fun toOperation(route: Route): Pair { val op = route.annotation() val funHandler = route.handler as? FunHandler - val returnType = funHandler?.f?.returnType return (op?.method?.trimToNull() ?: route.method.name).lowercase() to mapOf( "operationId" to route.handler.let { (if (it is FunHandler) it.instance::class.simpleName + "." + it.f.name else it::class.simpleName) }, "tags" to listOfNotNull(funHandler?.let { it.instance::class.annotation()?.name ?: it.instance::class.simpleName }), @@ -66,11 +67,8 @@ internal fun toOperation(route: Route): Pair { it.params.filter { it.source != null }.map { p -> toParameter(p, op) } }, "requestBody" to toRequestBody(route, route.annotation() ?: op?.requestBody), - "responses" to if (returnType?.classifier == Unit::class) mapOf(NoContent.value to mapOf("description" to "No content")) - else mapOf(OK.value to mapOfNotNull("description" to "OK", "content" to returnType?.toJsonContent())) - ) + (op?.let { it.toNonEmptyValues { it.name != "method" } + mapOf( - "responses" to op.responses.associate { it.responseCode to it.toNonEmptyValues { it.name != "responseCode" } }.takeIf { it.isNotEmpty() } - ) } ?: emptyMap()) + "responses" to toResponsesByCode(route, op, funHandler?.f?.returnType) + ) + (op?.let { it.toNonEmptyValues { it.name !in setOf("method", "requestBody", "responses") } } ?: emptyMap()) } fun toParameter(p: Param, op: Operation? = null) = mapOf( @@ -124,9 +122,20 @@ private fun toRequestBody(route: Route, annotation: RequestBody?): Map { + val responses = LinkedHashMap() + if (returnType?.classifier == Unit::class) responses[NoContent] = mapOf("description" to "No content") + else if (op?.responses?.isEmpty() != false) responses[OK] = mapOfNotNull("description" to "OK", "content" to returnType?.toJsonContent()) + (route.annotations.filterIsInstance() + (route.annotation()?.value ?: emptyArray()) + (op?.responses ?: emptyArray())).forEach { + responses[StatusCode(it.responseCode.toInt())] = it.toNonEmptyValues { it.name != "responseCode" } + } + return responses } private fun KType.toJsonContent() = mapOf(MimeTypes.json to mapOf("schema" to toJsonSchema())) diff --git a/openapi/test/OpenAPITest.kt b/openapi/test/OpenAPITest.kt index 18cd9f7..857e2a7 100644 --- a/openapi/test/OpenAPITest.kt +++ b/openapi/test/OpenAPITest.kt @@ -16,8 +16,11 @@ import klite.MimeTypes import klite.RequestMethod.GET import klite.RequestMethod.POST import klite.Route +import klite.StatusCode.Companion.BadRequest +import klite.StatusCode.Companion.Found import klite.StatusCode.Companion.NoContent import klite.StatusCode.Companion.OK +import klite.StatusCode.Companion.Unauthorized import klite.annotations.* import org.junit.jupiter.api.Test import java.time.DayOfWeek @@ -65,7 +68,7 @@ class OpenAPITest { mapOf("name" to "force", "required" to false, "in" to QUERY, "schema" to mapOf("type" to "boolean")) ), "requestBody" to null, - "responses" to mapOf(OK.value to mapOf("description" to "OK", "content" to mapOf(MimeTypes.json to mapOf("schema" to mapOf("type" to "null"))))) + "responses" to mapOf(OK to mapOf("description" to "OK", "content" to mapOf(MimeTypes.json to mapOf("schema" to mapOf("type" to "null"))))) )) } @@ -84,29 +87,34 @@ class OpenAPITest { class MyRoutes { fun saveUser(e: HttpExchange, @PathParam userId: UUID, body: User) {} } - expect(toOperation(Route(POST, "/x".toRegex(), handler = FunHandler(MyRoutes(), MyRoutes::saveUser)))).toEqual("post" to mapOf( "operationId" to "MyRoutes.saveUser", "tags" to listOf("MyRoutes"), "parameters" to listOf( mapOf("name" to "userId", "required" to true, "in" to PATH, "schema" to mapOf("type" to "string", "format" to "uuid")) ), - "requestBody" to mapOf("content" to userSchema), - "responses" to mapOf(NoContent.value to mapOf("description" to "No content")) + "requestBody" to mapOf("content" to userSchema, "required" to true), + "responses" to mapOf(NoContent to mapOf("description" to "No content")) )) } @Test fun `request body from annotation's implementation field`() { class MyRoutes { @RequestBody(description = "Application and applicant", content = [Content(mediaType = MimeTypes.json, schema = Schema(implementation = User::class))]) + @ApiResponse(responseCode = "400", description = "Very bad request") + @ApiResponse(responseCode = "401", description = "Unauthorized") fun saveUser(e: HttpExchange): User = User("x", UUID.randomUUID()) } expect(toOperation(Route(POST, "/x".toRegex(), handler = FunHandler(MyRoutes(), MyRoutes::saveUser), annotations = MyRoutes::saveUser.annotations))).toEqual("post" to mapOf( "operationId" to "MyRoutes.saveUser", "tags" to listOf("MyRoutes"), "parameters" to emptyList(), - "requestBody" to mapOf("description" to "Application and applicant", "required" to true, "content" to userSchema), - "responses" to mapOf(OK.value to mapOf("description" to "OK", "content" to userSchema)) + "requestBody" to mapOf("description" to "Application and applicant", "required" to true, "content" to userSchema, "required" to true), + "responses" to mapOf( + OK to mapOf("description" to "OK", "content" to userSchema), + BadRequest to mapOf("description" to "Very bad request"), + Unauthorized to mapOf("description" to "Unauthorized"), + ) )) } @@ -116,7 +124,7 @@ class OpenAPITest { "tags" to emptyList(), "parameters" to null, "requestBody" to null, - "responses" to mapOf(OK.value to mapOf("description" to "OK")) + "responses" to mapOf(OK to mapOf("description" to "OK")) )) } @@ -133,7 +141,7 @@ class OpenAPITest { "tags" to listOf("my-tag"), "parameters" to listOf(mapOf("name" to "param", "in" to QUERY, "description" to "description")), "requestBody" to null, - "responses" to mapOf("302" to mapOf("description" to "desc")), + "responses" to mapOf(Found to mapOf("description" to "desc")), "summary" to "summary" )) }