Skip to content

sheepdreamofandroids/Ktor-OpenAPI-Generator

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

44 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Ktor OpenAPI Generator

The Ktor OpenAPI Generator is a library to automatically generate the descriptor as you route your ktor application.

Ktor OpenAPI Generator is:

  • Modular
  • Strongly typed
  • Explicit

Currently Supported:

  • Authentication ineroperability with strongly typed Principal (OAuth only, see TestServer in tests)
  • Content Negociation interoperability (see TestServer in tests)
  • Custom response codes (as parameter in @Response)
  • Automatic and custom content Type routing and parsing (see com.papsign.ktor.openapigen.content.type, Binary Parser and default JSON parser (that uses the ktor implicit parsing/serializing))
  • Exception handling (use .throws(ex) {} in the routes with an APIException object) with Status pages interop (with .withAPI in the StatusPages configuration)
  • tags (.tag(tag) {} in route with a tag object, currently must be an enum, but may be subject to change)
  • Parameter Parsing (see basic example), /!\ only supports primitive types currently, needs to be put up to openapi specification.

It is inspired by ktor Locations, but makes no use of it.

Take a look at the wiki for advanced features and mechanics

Installation

Gradle

Step 1. Add the JitPack repository to your build file

allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}

Step 2. Add the dependency

dependencies {
        implementation 'com.github.papsign:Ktor-OpenAPI-Generator:-SNAPSHOT'
}

Git Submodule

Install the submodule:

git submodule add https://github.com/papsign/Ktor-OpenAPI-Generator.git openapigen

Declare the folder in settings.gradle:

...
include 'openapigen'

Declare the dependency in the main build.gradle

apply plugin: 'kotlin'

...

dependencies {
    compile project(":openapigen")
    ...
}

Examples

Basic Example:

apiRouting {

    //bare minimum, just like Ktor but strongly typed
    get<StringParam, StringResponse> { params ->
        respond(StringResponse(params.a))
    }

    route("inine").get<StringParam, StringResponse>(
        info("String Param Endpoint", "This is a String Param Endpoint"), // A Route module that describes an endpoint, it is optional
        example = StringResponse("Hi")
    ) { params ->
        respond(StringResponse(params.a))
    }

    route("block") {
        // use Unit if there are no parameters / body / response
        post<Unit, StringUsable,  StringUsable>(
            info("String Post Endpoint", "This is a String Post Endpoint"),
            exampleRequest = StringUsable("Ho"),
            exampleResponse = StringUsable("Ho")
        ) { params, body ->
            respond(body)
        }
    }
}
            
// Path works like the @Location from locations, but for transparency we recommend only using it to extract the parameters
@Path("string/{a}")
data class StringParam(
    @PathParam("A simple String Param") val a: String,
    @QueryParam("Optional String") val optional: String? // Nullable Types are optional
)

// A response can be any class, but a description will be generated from the annotation
@Response("A String Response")
data class StringResponse(val str: String)


// DTOs can be requests and responses, annotations are optional
@Response("A String Response")
@Request("A String Request")
data class StringUsable(val str: String)

Creates this openapi.json description:

{  
   "info":{  
      "title":"Test API",
      "version":"0.0.1",
      "description":"The Test API",
      "termsOfService":null,
      "contact":{  
         "name":"Support",
         "url":null,
         "email":"support@test.com"
      },
      "license":null
   },
   "openapi":"3.0.0",
   "servers":[  
      {  
         "url":"http://localhost:8080/",
         "description":"Test server",
         "variables":null
      }
   ],
   "paths":{  
      "/string/{a}":{  
         "get":{  
            "tags":null,
            "summary":null,
            "description":null,
            "externalDocs":null,
            "operationId":null,
            "parameters":null,
            "requestBody":null,
            "responses":{  
               "200":{  
                  "description":"OK",
                  "headers":{  

                  },
                  "content":{  
                     "application/json":{  
                        "schema":{  
                           "$ref":"#/components/schemas/StringParam"
                        },
                        "example":null,
                        "examples":null
                     }
                  }
               }
            },
            "deprecated":null,
            "security":null,
            "servers":null
         },
         "parameters":[  
            {  
               "name":"a",
               "in":"path",
               "required":true,
               "description":"A simple String Param",
               "deprecated":false,
               "allowEmptyValue":null,
               "schema":{  
                  "type":"string",
                  "format":null,
                  "nullable":false,
                  "minimum":null,
                  "maximum":null,
                  "example":null
               },
               "example":null,
               "examples":null
            },
            {  
               "name":"optional",
               "in":"query",
               "required":false,
               "description":"Optional String",
               "deprecated":false,
               "allowEmptyValue":false,
               "schema":{  
                  "type":"string",
                  "format":null,
                  "nullable":true,
                  "minimum":null,
                  "maximum":null,
                  "example":null
               },
               "example":null,
               "examples":null
            }
         ]
      },
      "/inine/string/{a}":{  
         "get":{  
            "tags":null,
            "summary":"String Param Endpoint",
            "description":"This is a String Param Endpoint",
            "externalDocs":null,
            "operationId":null,
            "parameters":null,
            "requestBody":null,
            "responses":{  
               "200":{  
                  "description":"A String Response",
                  "headers":{  

                  },
                  "content":{  
                     "application/json":{  
                        "schema":{  
                           "$ref":"#/components/schemas/StringResponse"
                        },
                        "example":{  
                           "str":"Hi"
                        },
                        "examples":null
                     }
                  }
               }
            },
            "deprecated":null,
            "security":null,
            "servers":null
         },
         "parameters":[  
            {  
               "name":"a",
               "in":"path",
               "required":true,
               "description":"A simple String Param",
               "deprecated":false,
               "allowEmptyValue":null,
               "schema":{  
                  "type":"string",
                  "format":null,
                  "nullable":false,
                  "minimum":null,
                  "maximum":null,
                  "example":null
               },
               "example":null,
               "examples":null
            },
            {  
               "name":"optional",
               "in":"query",
               "required":false,
               "description":"Optional String",
               "deprecated":false,
               "allowEmptyValue":false,
               "schema":{  
                  "type":"string",
                  "format":null,
                  "nullable":true,
                  "minimum":null,
                  "maximum":null,
                  "example":null
               },
               "example":null,
               "examples":null
            }
         ]
      },
      "/block":{  
         "post":{  
            "tags":null,
            "summary":"String Post Endpoint",
            "description":"This is a String Post Endpoint",
            "externalDocs":null,
            "operationId":null,
            "parameters":null,
            "requestBody":{  
               "content":{  
                  "application/json":{  
                     "schema":{  
                        "$ref":"#/components/schemas/StringUsable"
                     },
                     "example":{  
                        "str":"Ho"
                     },
                     "examples":null
                  }
               },
               "description":"A String Request",
               "required":null
            },
            "responses":{  
               "200":{  
                  "description":"A String Response",
                  "headers":{  

                  },
                  "content":{  
                     "application/json":{  
                        "schema":{  
                           "$ref":"#/components/schemas/StringUsable"
                        },
                        "example":{  
                           "str":"Ho"
                        },
                        "examples":null
                     }
                  }
               }
            },
            "deprecated":null,
            "security":null,
            "servers":null
         },
         "parameters":[  

         ]
      }
   },
   "components":{  
      "schemas":{  
         "StringParam":{  
            "properties":{  
               "a":{  
                  "type":"string",
                  "format":null,
                  "nullable":false,
                  "minimum":null,
                  "maximum":null,
                  "example":null
               },
               "optional":{  
                  "type":"string",
                  "format":null,
                  "nullable":true,
                  "minimum":null,
                  "maximum":null,
                  "example":null
               }
            },
            "required":[  
               "a"
            ],
            "nullable":false,
            "example":null,
            "type":"object"
         },
         "StringResponse":{  
            "properties":{  
               "str":{  
                  "type":"string",
                  "format":null,
                  "nullable":false,
                  "minimum":null,
                  "maximum":null,
                  "example":null
               }
            },
            "required":[  
               "str"
            ],
            "nullable":false,
            "example":null,
            "type":"object"
         },
         "StringUsable":{  
            "properties":{  
               "str":{  
                  "type":"string",
                  "format":null,
                  "nullable":false,
                  "minimum":null,
                  "maximum":null,
                  "example":null
               }
            },
            "required":[  
               "str"
            ],
            "nullable":false,
            "example":null,
            "type":"object"
         }
      },
      "responses":{  

      },
      "parameters":{  

      },
      "examples":{  

      },
      "requestBodies":{  

      },
      "headers":{  

      },
      "securitySchemes":{  

      }
   },
   "security":[  

   ],
   "tags":[  

   ],
   "externalDocs":null
}

Full Example:

object Basic {

    @JvmStatic
    fun main(args: Array<String>) {
        embeddedServer(Netty, 8080, "localhost") {
            //define basic OpenAPI info
            install(OpenAPIGen) {
                // basic info
                info {
                    version = "0.0.1"
                    title = "Test API"
                    description = "The Test API"
                    contact {
                        name = "Support"
                        email = "support@test.com"
                    }
                }
                // describe the server, add as many as you want
                server("http://localhost:8080/") {
                    description = "Test server"
                }
                //optional
                schemaNamer = {
                    //rename DTOs from java type name to generator compatible form
                    val regex = Regex("[A-Za-z0-9_.]+")
                    it.toString().replace(regex) { it.value.split(".").last() }.replace(Regex(">|<|, "), "_")
                }
            }

            install(ContentNegotiation) {
                jackson()
            }

            // normal Ktor routing
            routing {
                get("/openapi.json") {
                    call.respond(application.openAPIGen.api)
                }

                get("/") {
                    call.respondRedirect("/swagger-ui/index.html?url=/openapi.json", true)
                }
            }

            //Described routing
            apiRouting {

                //bare minimum, just like Ktor but strongly typed
                get<StringParam, StringResponse> { params ->
                    respond(StringResponse(params.a))
                }
                
                route("inine").get<StringParam, StringResponse>(
                    info("String Param Endpoint", "This is a String Param Endpoint"), // A Route module that describes an endpoint, it is optional
                    example = StringResponse("Hi")
                ) { params ->
                    respond(StringResponse(params.a))
                }

                route("block") {
                    // use Unit if there are no parameters / body / response
                    post<Unit, StringUsable,  StringUsable>(
                        info("String Post Endpoint", "This is a String Post Endpoint"),
                        exampleRequest = StringUsable("Ho"),
                        exampleResponse = StringUsable("Ho")
                    ) { params, body ->
                        respond(body)
                    }
                }
            }
        }.start(true)

    }

    // Path works like the @Location from locations, but for transparency we recommend only using it to extract the parameters
    @Path("string/{a}")
    data class StringParam(
        @PathParam("A simple String Param") val a: String,
        @QueryParam("Optional String") val optional: String? // Nullable Types are optional
    )

    // A response can be any class, but a description will be generated from the annotation
    @Response("A String Response")
    data class StringResponse(val str: String)


    // DTOs can be requests and responses, annotations are optional
    @Response("A String Response")
    @Request("A String Request")
    data class StringUsable(val str: String)
}

For an advanced example with most of the features, see the tests.

About

Ktor OpenAPI/Swagger 3 Generator

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Kotlin 100.0%