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
## [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
### Added

View File

@ -1,8 +1,9 @@
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.enrichedGenericResponse
import io.bkbn.kompendium.core.util.enrichedMap
import io.bkbn.kompendium.core.util.enrichedNestedCollection
import io.bkbn.kompendium.core.util.enrichedSimpleRequest
import io.bkbn.kompendium.core.util.enrichedSimpleResponse
@ -12,24 +13,27 @@ import io.kotest.core.spec.style.DescribeSpec
class KompendiumEnrichmentTest : DescribeSpec({
describe("Enrichment") {
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") {
TestHelpers.openApiTestAllSerializers("T0058__enriched_simple_response.json") { enrichedSimpleResponse() }
openApiTestAllSerializers("T0058__enriched_simple_response.json") { enrichedSimpleResponse() }
}
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") {
TestHelpers.openApiTestAllSerializers(
openApiTestAllSerializers(
"T0057__enriched_complex_generic_type.json"
) { enrichedComplexGenericType() }
}
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") {
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,106 +1,125 @@
Kompendium allows users to enrich their data types with additional information. This can be done by defining a
`ObjectEnrichment` object and passing it to the `enrichment` parameter of the relevant `requestType` or `responseType`.
Kompendium enables users to enrich their payloads with additional metadata
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
data class SimpleData(val a: String, val b: Int? = null)
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)
description(TestModules.defaultPathDescription)
request {
requestType<SimpleData>(enrichment = myEnrichment) // Simply attach the enrichment to the request
description("A test request")
post = PostInfo.builder {
summary(TestModules.defaultPathSummary)
description(TestModules.defaultPathDescription)
request {
requestType(
enrichment = ObjectEnrichment("simple") {
TestSimpleRequest::a {
StringEnrichment(id = "simple-enrichment") {
description = "A simple description"
}
}
response {
responseCode(HttpStatusCode.Created)
responseType<TestCreatedResponse>()
description(TestModules.defaultResponseDescription)
TestSimpleRequest::b {
NumberEnrichment(id = "blah-blah-blah") {
deprecated = true
}
}
}
}
)
description("A test request")
}
response {
responseCode(HttpStatusCode.Created)
responseType<TestCreatedResponse>()
description(TestModules.defaultResponseDescription)
}
}
```
{% hint style="warning" %}
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.
For more information on the various enrichment types, please see the following sections.
If you provide duplicate ids, all but the first enrichment will be ignored, as Kompendium will view that as a cache hit,
and skip analyzing the new enrichment.
{% endhint %}
## Scalar Enrichment
### 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
and apply it inside a parent data class using the `typeEnrichment` property.
- Boolean
- 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
data class ParentData(val a: String, val b: ChildData)
data class ChildData(val c: String, val d: Int? = null)
val childEnrichment = ObjectEnrichment<ChildData>(id = "child-enrichment") {
ChildData::c {
description = "This will update the field description of field c on child data"
post = PostInfo.builder {
summary(TestModules.defaultPathSummary)
description(TestModules.defaultPathDescription)
request {
requestType(
enrichment = ObjectEnrichment("simple") {
ComplexRequest::tables {
CollectionEnrichment<NestedComplexItem>("blah-blah") {
description = "A nested description"
itemEnrichment = ObjectEnrichment("nested") {
NestedComplexItem::name {
StringEnrichment("beleheh") {
description = "A nested description"
}
}
}
}
}
}
)
description("A test request")
}
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
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
- deprecated -> Indicates that the field is deprecated and should not be used
### String
- minLength -> The minimum length of the string
- maxLength -> The maximum length of the string
- pattern -> A regex pattern that the string must match
- contentEncoding -> The encoding of the string
- contentMediaType -> The media type of the string
### Numbers
- minimum -> The minimum value of the number
- maximum -> The maximum value of the number
- exclusiveMinimum -> Indicates that the minimum value is exclusive
- exclusiveMaximum -> Indicates that the maximum value is exclusive
- 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
- uniqueItems -> Indicates that the array must contain unique items
```kotlin
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

@ -9,8 +9,9 @@ you to rip out and replace the amazing code you have already written.
| 1.X | 1 | 3.0 |
| 2.X | 1 | 3.0 |
| 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
# 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
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 |
| Resources | Using the Ktor Resources API to define routes |
You can find all of the playground
You can find all the playground
examples [here](https://github.com/bkbnio/kompendium/tree/main/playground/src/main/kotlin/io/bkbn/kompendium/playground)
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
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
private fun Application.mainModule() {
@ -30,23 +31,11 @@ private fun Application.mainModule() {
specRoute = { spec, routing ->
routing {
authenticate("basic") {
route("/openapi.json") {
get {
call.response.headers.append("Content-Type", "application/json")
call.respondText { CustomJsonEncoder.encodeToString(spec) }
}
route("/openapi.json") {
get {
call.response.headers.append("Content-Type", "application/json")
call.respondText { CustomJsonEncoder.encodeToString(spec) }
}
}
}
}
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.
> 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
> 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
@ -97,5 +84,5 @@ Out of the box, Kompendium supports KotlinX serialization... however, in order t
serialization libraries to use Kompendium, we have provided a `SchemaConfigurator` interface that allows you to
configure how Kompendium will generate schema definitions.
For an example of the `SchemaConfigurator` in action, please see the `KotlinxSchemaConfigurator`. This will give you
For an example of the `SchemaConfigurator` in action, please see the `KotlinxSchemaConfigurator`. This will give you
a good idea of the additional functionality it can add based on your own serialization needs.

View File

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

View File

@ -1,6 +1,5 @@
package io.bkbn.kompendium.playground
import io.bkbn.kompendium.core.attribute.KompendiumAttributes
import io.bkbn.kompendium.core.metadata.GetInfo
import io.bkbn.kompendium.core.plugin.NotarizedApplication
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.response.respond
import io.ktor.server.routing.Route
import io.ktor.server.routing.application
import io.ktor.server.routing.get
import io.ktor.server.routing.route
import io.ktor.server.routing.routing