docs: initial gitbook docs (#336)

This commit is contained in:
Ryan Brink
2022-09-29 21:36:48 -05:00
committed by GitHub
parent 622dd6a5e7
commit 1759be5ba3
10 changed files with 474 additions and 57 deletions

1
.gitbook.yaml Normal file
View File

@ -0,0 +1 @@
root: ./docs/

View File

@ -2,55 +2,4 @@
[![version](https://img.shields.io/maven-central/v/io.bkbn/kompendium-core?style=flat-square)](https://search.maven.org/search?q=io.bkbn%20kompendium)
## Table of Contents
- [What Is Kompendium](#what-is-kompendium)
- [How to Install](#how-to-install)
- [Local Development](#local-development)
- [The Playground](#the-playground)
## What is Kompendium
Kompendium is intended to be a minimally invasive OpenApi Specification generator for Ktor. Minimally invasive meaning
that users will use only Ktor native functions when implementing their API, and will supplement with Kompendium code in
order to generate the appropriate spec.
### Compatability
| Kompendium | Ktor | OpenAPI |
|------------|------|---------|
| 1.X | 1 | 3.0 |
| 2.X | 1 | 3.0 |
| 3.X | 2 | 3.1 |
## How to install
Kompendium publishes all releases to Maven Central. As such, using the release versions of `Kompendium` is as simple as
declaring it as an implementation dependency in your `build.gradle.kts`
```kotlin
repositories {
mavenCentral()
}
dependencies {
implementation("io.bkbn:kompendium-core:latest.release")
}
```
In addition to publishing releases to Maven Central, a snapshot version gets published to GitHub Packages on every merge
to `main`. These can be consumed by adding the repository to your gradle build file. Instructions can be
found [here](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-gradle-registry#using-a-published-package)
## Local Development
Kompendium should run locally right out of the box, no configuration necessary (assuming you have JDK 11+ installed).
New features can be built locally and published to your local maven repository with the `./gradlew publishToMavenLocal`
command!
## The Playground
This repo contains a `playground` module that contains a number of working examples showcasing the capabilities of
Kompendium.
Feel free to check it out, or even create your own example!
Documentation is stored in the `docs` folder and is hosted [here](https://bkbn.gitbook.io/kompendium)

View File

@ -1,3 +0,0 @@
{
"root": "./docs"
}

8
docs/SUMMARY.md Normal file
View File

@ -0,0 +1,8 @@
# Summary
* [Introduction](index.md)
* [Plugins](plugins/index.md)
* [Notarized Application](plugins/notarized_application.md)
* [Notarized Route](plugins/notarized_route.md)
* [Notarized Locations](plugins/notarized_locations.md)
* [The Playground](playground.md)

View File

@ -1,3 +1,101 @@
# Kompendium
Kompendium is intended to be a non-invasive OpenAPI spec generator for [Ktor](https://ktor.io) APIs. By operating
entirely through Ktor's plugin architecture, Kompendium allows you to incrementally document your API without requiring
you to rip out and replace the amazing code you have already written.
Hi :)
# Compatibility
| Kompendium | Ktor | OpenAPI |
|------------|------|---------|
| 1.X | 1 | 3.0 |
| 2.X | 1 | 3.0 |
| 3.X | 2 | 3.1 |
> These docs are focused solely on Kompendium 3, previous versions should be considered deprecated and no longer
> maintained
# Getting Started
## Adding the Artifact
All Kompendium artifacts are published to Maven Central. Most Kompendium users will only need to import the core
dependency
```kotlin
dependencies {
// other dependencies...
implementation("io.bkbn:kompendium-core:latest.release")
}
```
## Notarizing a Ktor Application
Once we have added the dependencies, installed the `NotarizedApplication` plugin. This is an application-level
Ktor plugin that is used to instantiate and configure Kompendium. Your OpenAPI spec metadata will go here, along with
custom type overrides (typically useful for custom scalars such as dates and times), along with other configurations.
```kotlin
private fun Application.mainModule() {
install(NotarizedApplication()) {
spec = OpenApiSpec(
// ...
)
}
}
```
At this point, you will have a valid OpenAPI specification generated at runtime, which can be accessed by default
at the `/openapi.json` path of your api.
For more detail on the `NotarizedApplication` plugin, please see the [docs](./plugins/notarized_application.md)
## Notarizing a Ktor Route
Once you have notarized your application, you can begin to notarize individual routes using the `NotarizedRoute` plugin.
This is a route-level Ktor plugin that is used to configure the documentation for a specific endpoint of your API. The
route documentation will be piped back to the application-level plugin, and will be automatically injected into the
OpenApi specification.
Setting up documentation on a route is easiest achieved by creating an extension function on the Ktor `Route` class
```kotlin
private fun Route.documentation() {
install(NotarizedRoute()) {
parameters = listOf(
Parameter(
name = "id",
`in` = Parameter.Location.path,
schema = TypeDefinition.STRING
)
)
get = GetInfo.builder {
summary("Get user by id")
description("A very neat endpoint!")
response {
responseCode(HttpStatusCode.OK)
responseType<ExampleResponse>()
description("Will return whether or not the user is real 😱")
}
}
}
}
```
Once you have created your documentation function, it can be attached to the route simply by calling it at the desired
path.
```kotlin
private fun Application.mainModule() {
// ...
routing {
// ...
route("/{id}") {
documentation()
get {
// ...
}
}
}
}
```
For more details on the `NotarizedRoute` plugin, please see the [docs](./plugins/notarized_route.md)

19
docs/playground.md Normal file
View File

@ -0,0 +1,19 @@
The Playground is a module inside the Kompendium repository that provides out of the box examples for a variety of
Kompendium features.
At the moment, the following playground applications are
| Example | Description |
|--------------|------------------------------------------------------------|
| Basic | A minimally viable Kompendium application |
| Auth | Documenting authenticated routes |
| Custom Types | Documenting custom scalars to be used by Kompendium |
| Exceptions | Documenting exception responses |
| Gson | Serialization using Gson instead of the default Kotlinx |
| Hidden Docs | Place your generated documentation behind authorization |
| Jackson | Serialization using Jackson instead of the default KotlinX |
| Locations | Using the Ktor Locations API to define routes |
You can find all of the playground
examples [here](https://github.com/bkbnio/kompendium/tree/main/playground/src/main/kotlin/io/bkbn/kompendium/playground)
in the Kompendium repository

11
docs/plugins/index.md Normal file
View File

@ -0,0 +1,11 @@
Plugins are the lifeblood of Kompendium.
It starts with the `NotarizedApplication`, where Kompendium is instantiated and attached to the API. This is where spec
metadata is defined, custom types are defined, and more.
From there, a `NotarizedRoute` plugin is attached to each route you wish to document. This allows API documentation to
be an iterative process. Each route you notarize will be picked up and injected into the OpenAPI spec that Kompendium
generates for you.
Finally, there is the `NotarizedLocations` plugin that allows you to leverage and document your usage of the
Ktor [Locations](https://ktor.io/docs/locations.html) API.

View File

@ -0,0 +1,105 @@
The `NotarizedApplication` plugin sits at the center of the entire Kompendium setup. It is a pre-requisite to
installing any other Kompendium plugins.
# Configuration
Very little configuration is needed for a basic documentation setup, but
several configuration options are available that allow you to modify Kompendium to fit your needs.
## Spec
This is where you will define the server metadata that lives outside the scope of any specific route. For
full information, you can inspect the `OpenApiSpec` data class, and of course
reference [OpenAPI spec](https://spec.openapis.org/oas/v3.1.0) itself.
> ⚠️ Please note, the `path` field of the `OpenApiSpec` is intended to be filled in by `NotarizedRoute` plugin
> definitions. Writing custom paths manually could lead to unexpected behavior
## Custom Routing
For public facing APIs, having the default endpoint exposed at `/openapi.json` is totally fine. However, if you need
more granular control over the route that exposes the generated schema, you can modify the `openApiJson` config value.
For example, if we want to hide our schema behind a basic auth check, we could do the following
```kotlin
private fun Application.mainModule() {
// Install content negotiation, auth, etc...
install(NotarizedApplication()) {
// ...
openApiJson = {
authenticate("basic") {
route("/openapi.json") {
get {
call.respond(
HttpStatusCode.OK,
this@route.application.attributes[KompendiumAttributes.openApiSpec]
)
}
}
}
}
}
}
```
## Custom Types
Kompendium is _really_ good at converting simple scalar and complex objects into JsonSchema compliant specs. However,
there is a subset of values that cause it trouble. These are most commonly classes that produce "complex scalars",
such as dates and times, along with object representations of scalars such as `BigInteger`.
In situations like this, you will need to define a map of custom types to JsonSchema definitions that Kompendium can use
to short-circuit its type analysis.
For example, say we would like to serialize `kotlinx.datetime.Instant` entities as a field in our response objects. We
would need to add it as a custom type.
```kotlin
private fun Application.mainModule() {
// ...
install(NotarizedApplication()) {
spec = baseSpec
customTypes = mapOf(
typeOf<Instant>() to TypeDefinition(type = "string", format = "date-time")
)
}
}
```
Doing this will save it in a cache that our `NotarizedRoute` plugin definitions will check from prior to attempting to
perform type inspection.
This means that we only need to define our custom type once, and then Kompendium will reuse it across the entire
application.
> While intended for custom scalars, there is nothing stopping you from leveraging custom types to circumvent type
> analysis
> on any class you choose. If you have an alternative method of generating JsonSchema definitions, you could put them
> all
> in this map and effectively prevent Kompendium from having to do any reflection
## Schema Configurator
The `SchemaConfigurator` is an interface that allows users to bridge the gap between Kompendium serialization and custom
serialization strategies that the serializer they are using for their API. For example, if you are using KotlinX
serialization in order to convert kotlin fields from camel case to snake case, you could leverage
the `KotlinXSchemaConfigurator` in order to instruct Kompendium on how to serialize values properly.
```kotlin
private fun Application.mainModule() {
install(ContentNegotiation) {
json(Json {
serializersModule = KompendiumSerializersModule.module
encodeDefaults = true
explicitNulls = false
})
}
install(NotarizedApplication()) {
spec = baseSpec
// Adds support for @Transient and @SerialName
// If you are not using them this is not required.
schemaConfigurator = KotlinXSchemaConfigurator()
}
}
```

View File

@ -0,0 +1,62 @@
The Ktor Locations API is an experimental API that allows users to add increased type safety to their defined routes.
You can read more about it [here](https://ktor.io/docs/locations.html).
Kompendium supports Locations through an ancillary module `kompendium-locations`
## Adding the Artifact
Prior to documenting your locations, you will need to add the artifact to your gradle build file.
```kotlin
dependencies {
implementation("io.bkbn:kompendium-locations:latest.release")
}
```
## Installing Plugin
Once you have installed the dependency, you can install the plugin. The `NotarizedLocations` plugin is an _application_
level plugin, and **must** be install after both the `NotarizedApplication` plugin and the Ktor `Locations` plugin.
```kotlin
private fun Application.mainModule() {
install(Locations)
install(NotarizedApplication()) {
spec = baseSpec
}
install(NotarizedLocations()) {
locations = mapOf(
Listing::class to NotarizedLocations.LocationMetadata(
parameters = listOf(
Parameter(
name = "name",
`in` = Parameter.Location.path,
schema = TypeDefinition.STRING
),
Parameter(
name = "page",
`in` = Parameter.Location.path,
schema = TypeDefinition.INT
)
),
get = GetInfo.builder {
summary("Get user by id")
description("A very neat endpoint!")
response {
responseCode(HttpStatusCode.OK)
responseType<ExampleResponse>()
description("Will return whether or not the user is real 😱")
}
}
),
)
}
}
```
Here, the `locations` property is a map of `KClass<*>` to metadata describing that locations metadata. This
metadata is functionally identical to how a standard `NotarizedRoute` is defined.
> ⚠️ If you try to map a class that is not annotated with the ktor `@Location` annotation, you will get a runtime
> exception!

View File

@ -0,0 +1,167 @@
The `NotarizedRoute` plugin is the method by which you define the functionality and metadata for an individual route of
your API.
# Route Level Metadata
## Route Tags
OpenAPI uses the concept of tags in order to logically group operations. Tags defined at the `NotarizedRoute` level will
be applied to _all_ operations defined in this route. In order to define tags for a specific operation,
see [below](#operation-tags).
```kotlin
private fun Route.documentation() {
install(NotarizedRoute()) {
tags = setOf("User")
// will apply the User tag to all operations defined below
}
}
```
## Route Parameters
Parameters can be defined at the route level. Doing so will assign the parameters to _all_ operations defined in this
route. In order to define parameters for a specific operation, see [below](#operation-parameters).
```kotlin
private fun Route.documentation() {
install(NotarizedRoute()) {
parameters = listOf(
Parameter(
name = "id",
`in` = Parameter.Location.path,
schema = TypeDefinition.STRING
)
)
// Will apply the id parameter to all operations defined below
}
}
```
# Defining Operations
Obviously, our route documentation would not be very useful without a way to detail the operations that are available at
the specified route. These operations will take the metadata defined, along with any existing info present at the route
level detailed [above](#route-level-metadata). Together, this defines an OpenAPI path operation.
## Operation Builders
Each HTTP Operation (Get, Put Post, Patch, Delete, Head, Options) has its own `Builder` that Kompendium uses to define
the necessary information to associate with the detailed operation. For example, a simple `GET` request could be
defined as follows.
```kotlin
install(NotarizedRoute()) {
get = GetInfo.builder {
summary("Get ImportantDetail")
description("Retrieves an Important Detail from the database")
response {
responseCode(HttpStatusCode.OK)
responseType<TestResponse>()
description("The Detail in Question")
}
}
}
```
## Operation Tags
Operation tags work much like route tags, except they only apply to the operation they are defined in. They are defined
slightly differently, as a function on the builder, rather than an instance variable directly.
```kotlin
install(NotarizedRoute()) {
get = GetInfo.builder {
tags("User")
// ...
}
}
```
## Operation Parameters
Operation parameters work much like route parameters, except they only apply to the operation they are defined in. They
are defined slightly differently, as a function on the builder, rather than an instance variable directly.
```kotlin
install(NotarizedRoute()) {
get = GetInfo.builder {
parameters(
Parameter(
name = "a",
`in` = Parameter.Location.path,
schema = TypeDefinition.STRING,
),
Parameter(
name = "aa",
`in` = Parameter.Location.query,
schema = TypeDefinition.INT
)
)
// ...
}
}
```
## Response Info
All operations are required to define a response info block, detailing the standard response that users of the API
should expect when performing this operation. At its most simple, doing so looks like the following
```kotlin
get = GetInfo.builder {
summary("Get user by id")
description("A very neat endpoint!")
response {
responseCode(HttpStatusCode.OK)
responseType<ExampleResponse>()
description("Will return whether or not the user is real 😱")
}
}
```
As you can see, we attach an http status code, a description, and finally the type that represents the payload that
users should expect. In order to indicate that no payload is expected, use `responseType<Unit>()`. This should typically
be paired with a `204` status code.
## Request Info
On operations that allow a request body to be associated, you must also define a response info block so that Kompendium
can determine how to populate the required operation data.
```kotlin
post = PostInfo.builder {
summary("Create User")
description("Will create a new user entity")
request {
requestType<CreateUserRequest>()
description("Data required to create new user")
}
response {
responseCode(HttpStatusCode.Created)
responseType<UserCreatedResponse>()
description("User was created successfully")
}
}
```
## Error Info
In addition to the standard response, you can attach additional responses via the `canRespond` function.
```kotlin
get = GetInfo.builder {
summary("Get user by id")
description("A very neat endpoint!")
response {
responseCode(HttpStatusCode.OK)
responseType<ExampleResponse>()
description("Will return whether or not the user is real 😱")
}
canRespond {
description("Bad Things Happened")
responseCode(HttpStatusCode.InternalServerError)
responseType<ExceptionResponse>()
}
}
```