-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Description
It would be really nice if Ktor could support hosting a Swagger UI that is generated from your routes configuration.
For example, the following could be used to generate a Swagger UI.
data class PetModel(val id: Int?, val name: String)
data class PetsModel(val pets: MutableList<PetModel>)
val data = PetsModel(mutableListOf(PetModel(1, "max"), PetModel(2, "moritz")))
fun newId() = ((data.pets.map { it.id ?: 0 }.max()) ?: 0) + 1
@Group("pet operations")
@Location("/pets/{id}")
class pet(val id: Int)
@Group("pet operations")
@Location("/pets")
class pets
@Group("debug")
@Location("/request/info")
class requestInfo
@Group("debug")
@Location("/request/withHeader")
class withHeader
class Header(val optionalHeader: String?, val mandatoryHeader: Int)
@Group("debug")
@Location("/request/withQueryParameter")
class withQueryParameter
class QueryParameter(val optionalParameter: String?, val mandatoryParameter: Int)
fun main(args: Array<String>) {
val server = embeddedServer(Netty, getInteger("server.port", 8080)) {
install(DefaultHeaders)
install(Compression)
install(CallLogging)
install(ContentNegotiation) {
gson {
setPrettyPrinting()
}
}
install(Locations)
install(SwaggerSupport) {
forwardRoot = true
swagger.info = Information(
version = "0.1",
title = "sample api implemented in ktor",
description = "This is a sample which combines [ktor](https://github.com/Kotlin/ktor) with [swaggerUi](https://swagger.io/). You find the sources on [github](https://github.com/nielsfalk/ktor-swagger)",
contact = Contact(
name = "Niels Falk",
url = "https://nielsfalk.de"
)
)
}
routing {
get<pets>("all".responds(ok<PetsModel>())) {
call.respond(data)
}
post<pets, PetModel>("create".responds(ok<PetModel>())) { _, entity ->
// http201 would be better but there is no way to do this see org.jetbrains.ktor.gson.GsonSupport.renderJsonContent
call.respond(entity.copy(id = newId()).apply {
data.pets.add(this)
})
}
get<pet>("find".responds(ok<PetModel>(), notFound())) { params ->
data.pets.find { it.id == params.id }
?.let {
call.respond(it)
}
}
put<pet, PetModel>("update".responds(ok<PetModel>(), notFound())) { params, entity ->
if (data.pets.removeIf { it.id == params.id && it.id == entity.id }) {
data.pets.add(entity)
call.respond(entity)
}
}
delete<pet>("delete".responds(ok<Unit>(), notFound())) { params ->
if (data.pets.removeIf { it.id == params.id }) {
call.respond(Unit)
}
}
get<requestInfo>(
responds(ok<Unit>()),
respondRequestDetails()
)
get<withQueryParameter>(
responds(ok<Unit>())
.parameter<QueryParameter>(),
respondRequestDetails()
)
get<withHeader>(
responds(ok<Unit>())
.header<Header>(),
respondRequestDetails()
)
}
}
server.start(wait = true)
}
fun respondRequestDetails(): suspend PipelineContext<Unit, ApplicationCall>.(Any) -> Unit {
return {
call.respond(
mapOf(
"parameter" to call.parameters,
"header" to call.request.headers
).format()
)
}
}
private fun Map<String, StringValues>.format() =
mapValues {
it.value.toMap()
.flatMap { (key, value) -> value.map { key to it } }
.map { (key, value) -> "$key: $value" }
.joinToString(separator = ",\n")
}
.map { (key, value) -> "$key:\n$value" }
.joinToString(separator = "\n\n")
This is the swagger UI generated from the above.
I spent today overhauling @nielsfalk's project ktor-swagger to use the newest version of Ktor and also use Gradle to build the application PR here.
I think this project has quite a bit of potential and could satisfy a need in the community by allowing for a fast way to create documentation for API's written using Ktor.
If the Ktor team would like to adopt this project as a feature, I'm happy to try to make the port from the external project it is today into this repository.
If the interest does not exist to adopt a new feature, I totally understand. The concern that I have with publishing this myself (or with @nielsfalk assistance) is the issue of incompatible breaking changes in Ktor (as Ktor is pre-1.0).
I open the floor to the developers of this project. I'd love to see this integrated as a fully supported feature, but I understand if this is outside the scope of this project.
