docs: update docs for 4.0 release (#567)

This commit is contained in:
Ryan Brink
2024-01-21 16:12:02 -05:00
committed by GitHub
parent ac464ea9ca
commit 7a8bb43675
10 changed files with 273 additions and 133 deletions

View File

@ -12,6 +12,16 @@
## Released ## Released
## [4.0.0] - January 21st, 2024
### Added
- All changes from alpha release
### Changed
- Enrichments now mirror schema types
## [4.0.0-alpha] - September 3rd, 2023 ## [4.0.0-alpha] - September 3rd, 2023
### Added ### Added

View File

@ -1,8 +1,9 @@
package io.bkbn.kompendium.core package io.bkbn.kompendium.core
import io.bkbn.kompendium.core.fixtures.TestHelpers import io.bkbn.kompendium.core.fixtures.TestHelpers.openApiTestAllSerializers
import io.bkbn.kompendium.core.util.enrichedComplexGenericType import io.bkbn.kompendium.core.util.enrichedComplexGenericType
import io.bkbn.kompendium.core.util.enrichedGenericResponse import io.bkbn.kompendium.core.util.enrichedGenericResponse
import io.bkbn.kompendium.core.util.enrichedMap
import io.bkbn.kompendium.core.util.enrichedNestedCollection import io.bkbn.kompendium.core.util.enrichedNestedCollection
import io.bkbn.kompendium.core.util.enrichedSimpleRequest import io.bkbn.kompendium.core.util.enrichedSimpleRequest
import io.bkbn.kompendium.core.util.enrichedSimpleResponse import io.bkbn.kompendium.core.util.enrichedSimpleResponse
@ -12,24 +13,27 @@ import io.kotest.core.spec.style.DescribeSpec
class KompendiumEnrichmentTest : DescribeSpec({ class KompendiumEnrichmentTest : DescribeSpec({
describe("Enrichment") { describe("Enrichment") {
it("Can enrich a simple request") { it("Can enrich a simple request") {
TestHelpers.openApiTestAllSerializers("T0055__enriched_simple_request.json") { enrichedSimpleRequest() } openApiTestAllSerializers("T0055__enriched_simple_request.json") { enrichedSimpleRequest() }
} }
it("Can enrich a simple response") { it("Can enrich a simple response") {
TestHelpers.openApiTestAllSerializers("T0058__enriched_simple_response.json") { enrichedSimpleResponse() } openApiTestAllSerializers("T0058__enriched_simple_response.json") { enrichedSimpleResponse() }
} }
it("Can enrich a nested collection") { it("Can enrich a nested collection") {
TestHelpers.openApiTestAllSerializers("T0056__enriched_nested_collection.json") { enrichedNestedCollection() } openApiTestAllSerializers("T0056__enriched_nested_collection.json") { enrichedNestedCollection() }
} }
it("Can enrich a complex generic type") { it("Can enrich a complex generic type") {
TestHelpers.openApiTestAllSerializers( openApiTestAllSerializers(
"T0057__enriched_complex_generic_type.json" "T0057__enriched_complex_generic_type.json"
) { enrichedComplexGenericType() } ) { enrichedComplexGenericType() }
} }
it("Can enrich a generic object") { it("Can enrich a generic object") {
TestHelpers.openApiTestAllSerializers("T0067__enriched_generic_object.json") { enrichedGenericResponse() } openApiTestAllSerializers("T0067__enriched_generic_object.json") { enrichedGenericResponse() }
} }
it("Can enrich a top level list type") { it("Can enrich a top level list type") {
TestHelpers.openApiTestAllSerializers("T0077__enriched_top_level_list.json") { enrichedTopLevelCollection() } openApiTestAllSerializers("T0077__enriched_top_level_list.json") { enrichedTopLevelCollection() }
}
it("can enrich a map type") {
openApiTestAllSerializers("T0078__enriched_top_level_map.json") { enrichedMap() }
} }
} }
}) })

View File

@ -227,3 +227,35 @@ fun Routing.enrichedGenericResponse() {
} }
} }
} }
fun Routing.enrichedMap() {
route("/example") {
install(NotarizedRoute()) {
get = GetInfo.builder {
summary(TestModules.defaultPathSummary)
description(TestModules.defaultPathDescription)
response {
responseType<Map<String, TestSimpleRequest>>(
enrichment = MapEnrichment("blah") {
description = "A nested description"
valueEnrichment = ObjectEnrichment("nested") {
TestSimpleRequest::a {
StringEnrichment("blah-blah-blah") {
description = "A simple description"
}
}
TestSimpleRequest::b {
NumberEnrichment("blah-blah-blah") {
deprecated = true
}
}
}
}
)
description("A good response")
responseCode(HttpStatusCode.Created)
}
}
}
}
}

View File

@ -0,0 +1,103 @@
{
"openapi": "3.1.0",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "Test API",
"version": "1.33.7",
"description": "An amazing, fully-ish 😉 generated API spec",
"termsOfService": "https://example.com",
"contact": {
"name": "Homer Simpson",
"url": "https://gph.is/1NPUDiM",
"email": "chunkylover53@aol.com"
},
"license": {
"name": "MIT",
"url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE"
}
},
"servers": [
{
"url": "https://myawesomeapi.com",
"description": "Production instance of my API"
},
{
"url": "https://staging.myawesomeapi.com",
"description": "Where the fun stuff happens"
}
],
"paths": {
"/example": {
"get": {
"tags": [],
"summary": "Great Summary!",
"description": "testing more",
"parameters": [],
"responses": {
"201": {
"description": "A good response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Map-String-TestSimpleRequest-blah"
}
}
}
}
},
"deprecated": false
},
"parameters": []
}
},
"webhooks": {},
"components": {
"schemas": {
"TestSimpleRequest-nested": {
"type": "object",
"properties": {
"a": {
"type": "string",
"description": "A simple description"
},
"b": {
"type": "number",
"format": "int32",
"deprecated": true
}
},
"required": [
"a",
"b"
]
},
"TestSimpleRequest-blah": {
"type": "object",
"properties": {
"a": {
"type": "string",
"description": "A simple description"
},
"b": {
"type": "number",
"format": "int32",
"deprecated": true
}
},
"required": [
"a",
"b"
]
},
"Map-String-TestSimpleRequest-blah": {
"additionalProperties": {
"$ref": "#/components/schemas/TestSimpleRequest-blah"
},
"type": "object"
}
},
"securitySchemes": {}
},
"security": [],
"tags": []
}

View File

@ -1,29 +1,33 @@
Kompendium allows users to enrich their data types with additional information. This can be done by defining a Kompendium enables users to enrich their payloads with additional metadata
`ObjectEnrichment` object and passing it to the `enrichment` parameter of the relevant `requestType` or `responseType`. such as field description, deprecation, and more.
Enrichments, unlike annotations, are fully decoupled from the implementation of the class
itself. As such, we can not only enable different metadata on the same class in different
areas of the application, we can also reuse the same metadata in different areas, and even
support enrichment of types that you do not own, or types that are not easily annotated,
such as collections and maps.
A simple enrichment example looks like the following:
```kotlin ```kotlin
data class SimpleData(val a: String, val b: Int? = null) post = PostInfo.builder {
val myEnrichment = ObjectEnrichment<SimpleData>(id = "simple-enrichment") {
SimpleData::a {
description = "This will update the field description"
}
SimpleData::b {
// Will indicate in the UI that the field will be removed soon
deprecated = true
}
}
// In your route documentation
fun Routing.enrichedSimpleRequest() {
route("/example") {
install(NotarizedRoute()) {
parameters = TestModules.defaultParams
post = PostInfo.builder {
summary(TestModules.defaultPathSummary) summary(TestModules.defaultPathSummary)
description(TestModules.defaultPathDescription) description(TestModules.defaultPathDescription)
request { request {
requestType<SimpleData>(enrichment = myEnrichment) // Simply attach the enrichment to the request requestType(
enrichment = ObjectEnrichment("simple") {
TestSimpleRequest::a {
StringEnrichment(id = "simple-enrichment") {
description = "A simple description"
}
}
TestSimpleRequest::b {
NumberEnrichment(id = "blah-blah-blah") {
deprecated = true
}
}
}
)
description("A test request") description("A test request")
} }
response { response {
@ -31,76 +35,91 @@ fun Routing.enrichedSimpleRequest() {
responseType<TestCreatedResponse>() responseType<TestCreatedResponse>()
description(TestModules.defaultResponseDescription) description(TestModules.defaultResponseDescription)
} }
}
}
}
} }
``` ```
{% hint style="warning" %} For more information on the various enrichment types, please see the following sections.
An enrichment must provide an `id` field that is unique to the data class that is being enriched. This is because
under the hood, Kompendium appends this id to the data class identifier in order to support multiple different
enrichments
on the same data class.
If you provide duplicate ids, all but the first enrichment will be ignored, as Kompendium will view that as a cache hit, ## Scalar Enrichment
and skip analyzing the new enrichment.
{% endhint %}
### Nested Enrichments Currently, Kompendium supports enrichment of the following scalar types:
Enrichments are portable and composable, meaning that we can take an enrichment for a child data class - Boolean
and apply it inside a parent data class using the `typeEnrichment` property. - String
- Number
At the moment, all of these types extend a sealed interface `Enrichment`... as such you cannot provide
enrichments for custom scalars like dates and times. This is a known limitation, and will be addressed
in a future release.
## Object Enrichment
Object enrichment is the most common form of enrichment, and is used to enrich a complex type, and
the fields of a class.
## Collection Enrichment
Collection enrichment is used to enrich a collection type, and the elements of that collection.
```kotlin ```kotlin
data class ParentData(val a: String, val b: ChildData) post = PostInfo.builder {
data class ChildData(val c: String, val d: Int? = null) summary(TestModules.defaultPathSummary)
description(TestModules.defaultPathDescription)
val childEnrichment = ObjectEnrichment<ChildData>(id = "child-enrichment") { request {
ChildData::c { requestType(
description = "This will update the field description of field c on child data" enrichment = ObjectEnrichment("simple") {
ComplexRequest::tables {
CollectionEnrichment<NestedComplexItem>("blah-blah") {
description = "A nested description"
itemEnrichment = ObjectEnrichment("nested") {
NestedComplexItem::name {
StringEnrichment("beleheh") {
description = "A nested description"
} }
ChildData::d {
description = "This will update the field description of field d on child data"
} }
}
val parentEnrichment = ObjectEnrichment<ParentData>(id = "parent-enrichment") {
ParentData::a {
description = "This will update the field description"
} }
ParentData::b { }
description = "This will update the field description of field b on parent data" }
typeEnrichment = childEnrichment // Will apply the child enrichment to the internals of field b }
)
description("A test request")
}
response {
responseCode(HttpStatusCode.Created)
responseType<TestCreatedResponse>()
description(TestModules.defaultResponseDescription)
} }
} }
``` ```
## Available Enrichments ## Map Enrichment
All enrichments support the following properties: Map enrichment is used to enrich a map type, and the keys and values of that map.
- description -> Provides a reader friendly description of the field in the object ```kotlin
- deprecated -> Indicates that the field is deprecated and should not be used get = GetInfo.builder {
summary(TestModules.defaultPathSummary)
### String description(TestModules.defaultPathDescription)
response {
- minLength -> The minimum length of the string responseType<Map<String, TestSimpleRequest>>(
- maxLength -> The maximum length of the string enrichment = MapEnrichment("blah") {
- pattern -> A regex pattern that the string must match description = "A nested description"
- contentEncoding -> The encoding of the string valueEnrichment = ObjectEnrichment("nested") {
- contentMediaType -> The media type of the string TestSimpleRequest::a {
StringEnrichment("blah-blah-blah") {
### Numbers description = "A simple description"
}
- minimum -> The minimum value of the number }
- maximum -> The maximum value of the number TestSimpleRequest::b {
- exclusiveMinimum -> Indicates that the minimum value is exclusive NumberEnrichment("blah-blah-blah") {
- exclusiveMaximum -> Indicates that the maximum value is exclusive deprecated = true
- multipleOf -> Indicates that the number must be a multiple of the provided value }
}
### Arrays }
}
- minItems -> The minimum number of items in the array )
- maxItems -> The maximum number of items in the array description("A good response")
- uniqueItems -> Indicates that the array must contain unique items responseCode(HttpStatusCode.Created)
}
}
```

View File

@ -9,8 +9,9 @@ you to rip out and replace the amazing code you have already written.
| 1.X | 1 | 3.0 | | 1.X | 1 | 3.0 |
| 2.X | 1 | 3.0 | | 2.X | 1 | 3.0 |
| 3.X | 2 | 3.1 | | 3.X | 2 | 3.1 |
| 4.X | 2 | 3.1 |
> These docs are focused solely on Kompendium 3, previous versions should be considered deprecated and no longer > These docs are focused solely on Kompendium 4, previous versions should be considered deprecated and no longer
> maintained > maintained
# Getting Started # Getting Started

View File

@ -1,20 +1,6 @@
The Playground is a module inside the Kompendium repository that provides out of the box examples for a variety of The Playground is a module inside the Kompendium repository that provides out of the box examples for a variety of
Kompendium features. Kompendium features.
At the moment, the following playground applications are You can find all the playground
| 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 |
| Resources | Using the Ktor Resources 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) examples [here](https://github.com/bkbnio/kompendium/tree/main/playground/src/main/kotlin/io/bkbn/kompendium/playground)
in the Kompendium repository in the Kompendium repository

View File

@ -20,7 +20,8 @@ reference [OpenAPI spec](https://spec.openapis.org/oas/v3.1.0) itself.
For public facing APIs, having the default endpoint exposed at `/openapi.json` is totally fine. However, if you need 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. 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 with a custom json encoder, we could do the following For example, if we want to hide our schema behind a basic auth check with a custom json encoder, we could do the
following
```kotlin ```kotlin
private fun Application.mainModule() { private fun Application.mainModule() {
@ -39,18 +40,6 @@ private fun Application.mainModule() {
} }
} }
} }
openApiJson = {
authenticate("basic") {
route("/openapi.json") {
get {
call.respond(
HttpStatusCode.OK,
this@route.application.attributes[KompendiumAttributes.openApiSpec]
)
}
}
}
}
} }
} }
``` ```
@ -86,10 +75,8 @@ This means that we only need to define our custom type once, and then Kompendium
application. application.
> While intended for custom scalars, there is nothing stopping you from leveraging custom types to circumvent type > While intended for custom scalars, there is nothing stopping you from leveraging custom types to circumvent type
> analysis > analysis on any class you choose. If you have an alternative method of generating JsonSchema definitions, you could
> on any class you choose. If you have an alternative method of generating JsonSchema definitions, you could put them > put them all in this map and effectively prevent Kompendium from having to do any reflection
> all
> in this map and effectively prevent Kompendium from having to do any reflection
## Schema Configurator ## Schema Configurator

View File

@ -1,5 +1,5 @@
# Kompendium # Kompendium
project.version=4.0.0-alpha project.version=4.0.0
# Kotlin # Kotlin
kotlin.code.style=official kotlin.code.style=official

View File

@ -1,6 +1,5 @@
package io.bkbn.kompendium.playground package io.bkbn.kompendium.playground
import io.bkbn.kompendium.core.attribute.KompendiumAttributes
import io.bkbn.kompendium.core.metadata.GetInfo import io.bkbn.kompendium.core.metadata.GetInfo
import io.bkbn.kompendium.core.plugin.NotarizedApplication import io.bkbn.kompendium.core.plugin.NotarizedApplication
import io.bkbn.kompendium.core.plugin.NotarizedRoute import io.bkbn.kompendium.core.plugin.NotarizedRoute
@ -27,7 +26,6 @@ import io.ktor.server.engine.embeddedServer
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
import io.ktor.server.response.respond import io.ktor.server.response.respond
import io.ktor.server.routing.Route import io.ktor.server.routing.Route
import io.ktor.server.routing.application
import io.ktor.server.routing.get import io.ktor.server.routing.get
import io.ktor.server.routing.route import io.ktor.server.routing.route
import io.ktor.server.routing.routing import io.ktor.server.routing.routing