This plugin implements a plug and play solution for generating OpenAPI (Swagger) specification for your Ktor server on any platform with minimal effort - no need to modify your existing code, no special DSL wrappers etc.
Just annotate your route(s) definitions with @GenerateOpenApi
and openapi.yaml
will be generated at build time.
Take a look at the Sample Project to get started.
plugins {
id("io.github.tabilzad.inspektor") version "0.8.2-alpha"
}
swagger {
documentation {
generateRequestSchemas = true
hideTransientFields = true
hidePrivateAndInternalFields = true
deriveFieldRequirementFromTypeNullability = true
info {
title = "Ktor Server Title"
description = "Ktor Server Description"
version = "1.0"
contact {
name = "Inspektor"
url = "https://github.com/tabilzad/inspektor"
}
}
}
pluginOptions {
format = "yaml" // or json
}
}
Feature | isSupported | type |
---|---|---|
Path/Endpoint definitions | ✅ | Automatic |
Ktor Resources | ✅ | Automatic |
Request Schemas | ✅ | Automatic |
Response Schemas | ✅ | Explicit |
Endpoint/Scheme Descriptions | ✅ | Explicit |
Endpoint Tagging | ✅ | Explicit |
Option | Default Value | Explanation |
---|---|---|
info.title |
"Open API Specification" |
Title for the API specification that is generated |
info.description |
"Generated using Ktor Docs Plugin" |
A brief description for the generated API specification |
info.version |
"1.0.0" |
Specifies the version for the generated API specification |
generateRequestSchemas |
true |
Determines if request body schemas should be automatically resolved and included |
hideTransientFields |
true |
Controls whether fields marked with @Transient are omitted in schema outputs |
hidePrivateAndInternalFields |
true |
Opts to exclude fields with private or internal modifiers from schema outputs |
deriveFieldRequirementFromTypeNullability |
true |
Automatically derive object fields' requirement from its type nullability |
servers |
[] | List of server URLs to be included in the spec (ex: listOf("http://localhost:8080") |
Option | Default Value | Explanation |
---|---|---|
enabled |
true |
Enable/Disables the plugin |
saveInBuild |
true |
Decides if the generated specification file should be saved in the build/ directory |
format |
yaml |
The chosen format for the OpenAPI specification (options: json/yaml) |
filePath |
$modulePath/build/resources/main/openapi/ |
The designated absolute path for saving the generated specification file |
Annotate the specific route definitions you want the OpenAPI specification to be generated for.
@GenerateOpenApi
fun Route.ordersRouting() {
route("/v1") {
post("/order1") {
/*...*/
}
}
}
You could also annotate the entire Application
module with multiple/nested route definitions. The plugin will
recursively visit each Route
. extension and generate its documentation.
@GenerateOpenApi
fun Application.ordersModule() {
routing {
routeOne()
routeTwo()
}
}
fun Route.routeOne() {
route("/v1") { /*...*/ }
}
fun Route.routeTwo() {
route("/v2") { /*...*/ }
routeThree()
}
Describe endpoints or schema fields.
@KtorSchema("this is my request")
data class RequestSample(
@KtorField("this is a string")
val string: String,
val int: Int,
val double: Double,
@KtorField(description = "this is instant", typr = "string", format = "date-time")
val date: Instant
)
@GenerateOpenApi
fun Route.ordersRouting() {
route("/v1") {
@KtorDescription(
summary = "Create Order",
description = "This endpoint will create an order",
)
post("/create") {
call.receive<RequestSample>()
}
route("/orders") {
@KtorDescription(
summary = "All Orders",
description = "This endpoint will return a list of all orders"
)
get { /*...*/ }
}
}
}
Defining response schemas and their corresponding HTTP status codes are done via @KtorResponds
annotation on an endpoint or responds<T>(HttpStatusCode)
inline extension on a RouteContext
. The latter is the preferred way since it is capable of defining types with generics. kotlin.Nothing
is treated specially and will result in empty response body for statutes like 204 NO_CONTENT
.
@GenerateOpenApi
fun Route.ordersRouting() {
route("/v1") {
@KtorResponds(
[
ResponseEntry("200", Order::class, description = "Created order"),
ResponseEntry("204", Nothing::class),
ResponseEntry("400", ErrorResponseSample::class, description = "Invalid order payload")
]
)
post("/create") { /*...*/ }
@KtorResponds([ResponseEntry("200", Order::class, isCollection=true, description = "Get all orders")])
get("/orders") { /*...*/ }
}
}
@GenerateOpenApi
fun Route.ordersRouting() {
route("/v1") {
post("/create") {
// Creates order
responds<Order>(HttpStatusCode.Ok)
responds<Nothing>(HttpStatusCode.NoContent)
// Invalid order payload
responds<ErrorResponseSample>(HttpStatusCode.BadRequest)
/*...*/
}
get("/orders"){
// Get all orders
responds<List<Order>>(HttpStatusCode.NoContent)
/*...*/
}
}
}
Using tags enables the categorization of individual endpoints into designated groups. Tags specified at the parent route will propogate down to all endpoints contained within it.
@Tag(["Orders"])
fun Route.ordersRouting() {
route("/v1") {
post("/create") { /*...*/ }
get("/orders") { /*...*/ }
}
route("/v2") {
post("/create") { /*...*/ }
get("/orders") { /*...*/ }
}
}
On the other hand, if the tags are specified with @KtorDescription
or @Tag
annotation on an endpoint, they are associated exclusively with that particular endpoint.
@GenerateOpenApi
fun Route.ordersRouting() {
route("/v1") {
@KtorDescription(tags = ["Order Operations"])
post("/order") { /*...*/ }
@Tag(["Cart Operations"])
get("/cart") { /*...*/ }
}
}
- Automatic Response resolution
- Support for polymorphic types with discriminators
- Option for an automatic tag resolution from module/route function declaration
- Tag descriptions