-
Notifications
You must be signed in to change notification settings - Fork 43
A few examples
Frédéric Nieto edited this page May 14, 2020
·
5 revisions
install(OpenAPIGen) {
// basic info
info {
version = "0.0.1"
title = "Test API"
description = "The Test API"
contact {
name = "Support"
email = "[email protected]"
}
}
// describe the server, add as many as you want
server("http://localhost:8080/") {
description = "Test server"
}
//optional custom schema object namer
replaceModule(DefaultSchemaNamer, object: SchemaNamer {
val regex = Regex("[A-Za-z0-9_.]+")
override fun get(type: KType): String {
return type.toString().replace(regex) { it.value.split(".").last() }.replace(Regex(">|<|, "), "_")
}
})
}
apiRouting {
// routing goes here
}
application.routing {
get("/openapi.json") {
call.respond(application.openAPIGen.api.serialize())
}
get("/") {
call.respondRedirect("/swagger-ui/index.html?url=/openapi.json", true)
}
}
get<ParameterType, ResponseType> { params -> // get request on '/' because no path is specified
respond(ResponseType(...))
}
route("someroute").get<ParameterType, ResponseType>( // get request on '/someroute'
info("String Param Endpoint", "This is a String Param Endpoint"), // A Route module that adds a name and description to the OpenAPI data for the endpoint, it is optional
example = ResponseType(...) // example for the OpenAPI data
) { params ->
respond(ResponseType(...))
}
Routes can be written in a chained manner:
route("a").route("b")...
or in blocks if you want a hierarchy
route("a") {
route("b") {
...
}
}
The options you have:
throws(HttpStatusCode.BadRequest, CustomException::class) { ... // no example, just the exception handling
throws(HttpStatusCode.BadRequest, "example", CustomException::class) { ... // exception handling with example, will respond example
throws(HttpStatusCode.BadRequest, "example", {ex: CustomException -> ex.toString()}) { ... // exception handling, will respond generated content
Now we want to respond a custom generic Error
object when an exception is thrown:
data class Error<P>(val id: String, val payload: P)
throws(HttpStatusCode.BadRequest, Error("bad.request", mapOf<String, String>()), {ex: CustomException -> Error(ex.id, ex.payload)}) {
get<ParameterType, ResponseType> { params ->
respond(ResponseType(...))
}
}
You can also define multiple ones:
data class Error<P>(val id: String, val payload: P)
throws(HttpStatusCode.BadRequest, Error("bad.request", mapOf<String, String>()), {ex: CustomException -> Error(ex.id, ex.payload)}) {
throws(HttpStatusCode.InternalServerError, Error("internal.error", mapOf<String, String>()), {ex: OtherCustomException -> Error(ex.id, ex.payload)}) {
get<ParameterType, ResponseType> { params ->
respond(ResponseType(...))
}
}
}
And different response types:
data class Error<P>(val id: String, val payload: P)
throws(HttpStatusCode.BadRequest, Error("bad.request", mapOf<String, String>()), {ex: CustomException -> Error(ex.id, ex.payload)}) {
throws(HttpStatusCode.InternalServerError, "err", {ex: OtherCustomException -> ex.id}) {
get<ParameterType, ResponseType> { params ->
respond(ResponseType(...))
}
}
}
If you want to respond normally you can also do:
// null example means no example, you can of course also add one if you want
throws(HttpStatusCode.OK, null, {ex: CustomException -> OtherResponseType(ex.response)}) {
get<ParameterType, ResponseType> { params ->
if (shouldRespondOther) throw CustomException(response)
respond(ResponseType(...))
}
}
If you use polymorphic json, and you: You have Type A
{
"@type" : "a",
"str": "Some String"
}
You have Type B
{
"@type" : "b",
"i": 1
}
You have Type C
{
"@type" : "c",
"l": 0
}
You would define it like this
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
@JsonSubTypes(
JsonSubTypes.Type(Base.A::class, name = "a"),
JsonSubTypes.Type(Base.B::class, name = "b"),
JsonSubTypes.Type(Base.C::class, name = "c")
)
sealed class Base {
class A(val str: String) : Base()
class B(val i: @Min(0) @Max(2) Int) : Base() // Min and Max constrain the value, it will be shown in the OpenAPI spec, you can also implement custom ones as the feature is not hard-coded, look at how they are defined
@WithExample // provide an example in a subtype, the companion object and the annotation are required
class C(val l: @Clamp(0, 10) Long) : Base() {
companion object: ExampleProvider<C> {
override val example: C? = C(5)
}
}
}
// and then use as always as request or response, here it just responds what it receives
post<Params, Base, Base>(
info("Sealed class Endpoint", "This is a Sealed class Endpoint"),
exampleRequest = Base.A("Hi"),
exampleResponse = Base.A("Hi")
) { params, base ->
respond(base)
}
const val contentType = "image/png"
@BinaryRequest([contentType]) // can be omitted if you don' t want to use it as request
@BinaryResponse([contentType]) // can be omitted if you don' t want to use it as response
data class RawPng(val stream: InputStream)
// then in your route like usual
post<Params, Response, RawPng> { params, body->
...
}
post<Params, RawPng, Body> { params, body ->
...
}
@FormDataRequest
data class PDFFileCreateDTO(@PartEncoding("application/pdf") val file: NamedFileInputStream, val name: String, val public: Boolean)
// then in your route like usual
post<Params, Response, PDFFileCreateDTO> { params, fileCreate ->
...
}