diff --git a/docs/concepts/enrichment.md b/docs/concepts/enrichment.md index ba3777af3..932b40f79 100644 --- a/docs/concepts/enrichment.md +++ b/docs/concepts/enrichment.md @@ -1,125 +1,106 @@ -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: +Kompendium allows users to enrich their data types with additional information. This can be done by defining a +`TypeEnrichment` object and passing it to the `enrichment` parameter of the relevant `requestType` or `responseType`. ```kotlin -post = PostInfo.builder { - summary(TestModules.defaultPathSummary) - description(TestModules.defaultPathDescription) - request { - requestType( - enrichment = ObjectEnrichment("simple") { - TestSimpleRequest::a { - StringEnrichment(id = "simple-enrichment") { - description = "A simple description" - } +data class SimpleData(val a: String, val b: Int? = null) + +val myEnrichment = TypeEnrichment(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(enrichment = myEnrichment) // Simply attach the enrichment to the request + description("A test request") } - TestSimpleRequest::b { - NumberEnrichment(id = "blah-blah-blah") { - deprecated = true - } + response { + responseCode(HttpStatusCode.Created) + responseType() + description(TestModules.defaultResponseDescription) } } - ) - description("A test request") - } - response { - responseCode(HttpStatusCode.Created) - responseType() - description(TestModules.defaultResponseDescription) + } } } ``` -For more information on the various enrichment types, please see the following sections. +{% 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. -## Scalar Enrichment +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 %} -Currently, Kompendium supports enrichment of the following scalar types: +### Nested Enrichments -- 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. +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. ```kotlin -post = PostInfo.builder { - summary(TestModules.defaultPathSummary) - description(TestModules.defaultPathDescription) - request { - requestType( - enrichment = ObjectEnrichment("simple") { - ComplexRequest::tables { - CollectionEnrichment("blah-blah") { - description = "A nested description" - itemEnrichment = ObjectEnrichment("nested") { - NestedComplexItem::name { - StringEnrichment("beleheh") { - description = "A nested description" - } - } - } - } - } - } - ) - description("A test request") +data class ParentData(val a: String, val b: ChildData) +data class ChildData(val c: String, val d: Int? = null) + +val childEnrichment = TypeEnrichment(id = "child-enrichment") { + ChildData::c { + description = "This will update the field description of field c on child data" } - response { - responseCode(HttpStatusCode.Created) - responseType() - description(TestModules.defaultResponseDescription) + ChildData::d { + description = "This will update the field description of field d on child data" + } +} + +val parentEnrichment = TypeEnrichment(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 } } ``` -## Map Enrichment +## Available Enrichments -Map enrichment is used to enrich a map type, and the keys and values of that map. +All enrichments support the following properties: -```kotlin -get = GetInfo.builder { - summary(TestModules.defaultPathSummary) - description(TestModules.defaultPathDescription) - response { - responseType>( - 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) - } -} -``` +- 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 diff --git a/docs/index.md b/docs/index.md index d888caf71..f87df8cba 100644 --- a/docs/index.md +++ b/docs/index.md @@ -9,9 +9,8 @@ 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 4, previous versions should be considered deprecated and no longer +> These docs are focused solely on Kompendium 3, previous versions should be considered deprecated and no longer > maintained # Getting Started @@ -37,11 +36,9 @@ custom type overrides (typically useful for custom scalars such as dates and tim ```kotlin private fun Application.mainModule() { install(NotarizedApplication()) { - spec = { - OpenApiSpec( - // ... - ) - } + spec = OpenApiSpec( + // ... + ) } } ``` diff --git a/docs/playground.md b/docs/playground.md index 140e85188..5c75f295c 100644 --- a/docs/playground.md +++ b/docs/playground.md @@ -1,6 +1,20 @@ The Playground is a module inside the Kompendium repository that provides out of the box examples for a variety of Kompendium features. -You can find all the playground +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 examples [here](https://github.com/bkbnio/kompendium/tree/main/playground/src/main/kotlin/io/bkbn/kompendium/playground) in the Kompendium repository diff --git a/docs/plugins/notarized_application.md b/docs/plugins/notarized_application.md index 26b9984ed..e752a279d 100644 --- a/docs/plugins/notarized_application.md +++ b/docs/plugins/notarized_application.md @@ -20,22 +20,21 @@ 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, we could do the following ```kotlin private fun Application.mainModule() { // Install content negotiation, auth, etc... install(NotarizedApplication()) { // ... - specRoute = { spec, routing -> - routing { - authenticate("basic") { - 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] + ) } } } @@ -75,14 +74,32 @@ 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 -Out of the box, Kompendium supports KotlinX serialization... however, in order to allow for users of other -serialization libraries to use Kompendium, we have provided a `SchemaConfigurator` interface that allows you to -configure how Kompendium will generate schema definitions. +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. -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. +```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() + } +} +``` diff --git a/docs/plugins/notarized_locations.md b/docs/plugins/notarized_locations.md index d2b9a1984..bc354cd86 100644 --- a/docs/plugins/notarized_locations.md +++ b/docs/plugins/notarized_locations.md @@ -23,7 +23,7 @@ level plugin, and **must** be install after both the `NotarizedApplication` plug private fun Application.mainModule() { install(Locations) install(NotarizedApplication()) { - spec = { baseSpec } + spec = baseSpec } install(NotarizedLocations()) { locations = mapOf( diff --git a/docs/plugins/notarized_resources.md b/docs/plugins/notarized_resources.md index dbfa7a44b..cff17e4a9 100644 --- a/docs/plugins/notarized_resources.md +++ b/docs/plugins/notarized_resources.md @@ -30,7 +30,7 @@ application in a single block. private fun Application.mainModule() { install(Resources) install(NotarizedApplication()) { - spec = { baseSpec } + spec = baseSpec } install(NotarizedResources()) { resources = mapOf(