Skip to content

Requests and Responses

An example focusing on documenting requests and responses.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
package io.github.smiley4.ktoropenapi.examples

import com.fasterxml.jackson.core.util.DefaultIndenter
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter
import com.fasterxml.jackson.databind.SerializationFeature
import io.github.smiley4.ktoropenapi.OpenApi
import io.github.smiley4.ktoropenapi.post
import io.github.smiley4.ktoropenapi.openApi
import io.github.smiley4.ktorredoc.redoc
import io.github.smiley4.ktorswaggerui.swaggerUI
import io.ktor.http.HttpStatusCode
import io.ktor.serialization.jackson.jackson
import io.ktor.server.application.Application
import io.ktor.server.application.install
import io.ktor.server.engine.embeddedServer
import io.ktor.server.netty.Netty
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
import io.ktor.server.request.receive
import io.ktor.server.response.respond
import io.ktor.server.routing.route
import io.ktor.server.routing.routing

fun main() {
    embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true)
}

private fun Application.myModule() {

    // Install the OpenApi plugin and use the default configuration
    install(OpenApi)

    install(ContentNegotiation) {
        jackson {
            configure(SerializationFeature.INDENT_OUTPUT, true)
            setDefaultPrettyPrinter(DefaultPrettyPrinter().apply {
                indentArraysWith(DefaultPrettyPrinter.FixedSpaceIndenter.instance)
                indentObjectsWith(DefaultIndenter("  ", "\n"))
            })
        }
    }

    routing {

        // add the routes for OpenAPI spec, Swagger UI and ReDoc
        route("swagger") {
            swaggerUI("/api.json")
        }
        route("api.json") {
            openApi()
        }
        route("redoc") {
            redoc("/api.json")
        }

        // a documented route
        post("calculate", {
            // information about the request
            request {
                // specify the schema of the request body and some additional information
                body<Calculation> {
                    description = "the requested operation and values to perform the operation on"
                    required = true
                }
            }
            // information the possible responses
            response {
                // document the "200 OK" response
                code(HttpStatusCode.OK) {
                    description = "Calculation was performed successfully."
                    // specify the schema of the response body and some additional information
                    body<CalculationResult> {
                        description = "the result of an operation together with the original request"
                    }
                }
                // document the "422 Unprocessable Entity" response
                code(HttpStatusCode.UnprocessableEntity) {
                    description = "The requested calculation could not be performed, e.g. due to division by zero."
                }
            }
        }) {
            call.receive<Calculation>().let { calculation ->
                when (calculation.operation) {
                    OperationType.ADD -> {
                        call.respond(
                            HttpStatusCode.OK, CalculationResult(
                                calculation = calculation,
                                result = calculation.a + calculation.b
                            )
                        )
                    }
                    OperationType.SUB -> {
                        call.respond(
                            HttpStatusCode.OK, CalculationResult(
                                calculation = calculation,
                                result = calculation.a - calculation.b
                            )
                        )
                    }
                    OperationType.MUL -> {
                        call.respond(
                            HttpStatusCode.OK, CalculationResult(
                                calculation = calculation,
                                result = calculation.a * calculation.b
                            )
                        )
                    }
                    OperationType.DIV -> {
                        if (calculation.b == 0f) {
                            call.respond(HttpStatusCode.UnprocessableEntity)
                        } else {
                            call.respond(
                                HttpStatusCode.OK, CalculationResult(
                                    calculation = calculation,
                                    result = calculation.a / calculation.b
                                )
                            )
                        }
                    }
                }
            }
        }

    }

}

private enum class OperationType {
    ADD, SUB, MUL, DIV
}

private data class Calculation(
    val operation: OperationType,
    val a: Float,
    val b: Float
)

private data class CalculationResult(
    val calculation: Calculation,
    val result: Float
)