Compare commits
11 Commits
v3.0.0-alp
...
v3.0.0
Author | SHA1 | Date | |
---|---|---|---|
c312b201b3 | |||
0d4b1ddd03 | |||
8ae74705ba | |||
99f5cb5b86 | |||
e89636fa58 | |||
7cd0e6154b | |||
62080f3248 | |||
2007b1bb8c | |||
5beeade430 | |||
2e29b46f0f | |||
90b1f47dbb |
14
CHANGELOG.md
14
CHANGELOG.md
@ -5,6 +5,9 @@
|
|||||||
### Added
|
### Added
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
- Can now put redoc route behind authentication
|
||||||
|
- Fixed issue where type erasure was breaking nested generics
|
||||||
|
- Fix bug with null references not displaying properly on properties
|
||||||
|
|
||||||
### Remove
|
### Remove
|
||||||
|
|
||||||
@ -12,6 +15,17 @@
|
|||||||
|
|
||||||
## Released
|
## Released
|
||||||
|
|
||||||
|
## [3.0.0] - August 16th, 2022
|
||||||
|
### Added
|
||||||
|
- Ktor 2 Support 🎉
|
||||||
|
- OpenAPI 3.1 Standard
|
||||||
|
- JsonSchema Generator
|
||||||
|
- `NotarizedRoute` plugin
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- SwaggerUI module removed (due to lack of OpenAPI 3.1 support)
|
||||||
|
- Kompendium Annotations removed (field renames, undeclared fields, etc. will be follow-up work)
|
||||||
|
|
||||||
## [2.3.5] - June 7th, 2022
|
## [2.3.5] - June 7th, 2022
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
73
Project.md
73
Project.md
@ -1,6 +1,6 @@
|
|||||||
# Kompendium
|
# Kompendium
|
||||||
|
|
||||||
Welcome to Kompendium, the straight-forward, minimally-invasive OpenAPI generator for Ktor.
|
Welcome to Kompendium, the straight-forward, non-invasive OpenAPI generator for Ktor.
|
||||||
|
|
||||||
## How to install
|
## How to install
|
||||||
|
|
||||||
@ -21,21 +21,74 @@ In addition to publishing releases to Maven Central, a snapshot version gets pub
|
|||||||
to `main`. These can be consumed by adding the repository to your gradle build file. Instructions can be
|
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)
|
found [here](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-gradle-registry#using-a-published-package)
|
||||||
|
|
||||||
## Setting up the Kompendium Plugin
|
## Setting up Kompendium
|
||||||
|
|
||||||
Kompendium is instantiated as a Ktor Feature/Plugin. It can be added to your API as follows
|
Kompendium's core features are comprised of a singular application level plugin and a collection of route level plugins.
|
||||||
|
The former sets up your OpenApi spec along with various cross-route metadata and overrides such as custom types (useful
|
||||||
|
for things like datetime libraries)
|
||||||
|
|
||||||
|
### `NotarizedApplication` plugin
|
||||||
|
|
||||||
|
The notarized application plugin is installed at (surprise!) the app level
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
private fun Application.mainModule() {
|
private fun Application.mainModule() {
|
||||||
// Installs the Kompendium Plugin and sets up baseline server metadata
|
install(NotarizedApplication()) {
|
||||||
install(Kompendium) {
|
spec = OpenApiSpec(
|
||||||
spec = OpenApiSpec(/*..*/)
|
// spec details go here ...
|
||||||
|
)
|
||||||
}
|
}
|
||||||
// ...
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Notarization
|
### `NotarizedRoute` plugin
|
||||||
|
|
||||||
The concept of notarizing routes / exceptions / etc. is central to Kompendium. More details on _how_ to notarize your
|
Notarized routes take advantage of Ktor 2's [route specific plugin](https://ktor.io/docs/plugins.html#install-route)
|
||||||
API can be found in the kompendium-core module.
|
feature. This allows us to take individual routes, document them, and feed them back in to the application level plugin.
|
||||||
|
|
||||||
|
This also allows you to adopt Kompendium incrementally. Individual routes can be documented at your leisure, and is
|
||||||
|
purely
|
||||||
|
additive, meaning that you do not need to modify existing code to get documentation working, you just need new code!
|
||||||
|
|
||||||
|
Non-invasive FTW 🚀
|
||||||
|
|
||||||
|
Documenting a simple `GET` endpoint would look something like this
|
||||||
|
|
||||||
|
```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 😱")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
route("/{id}") {
|
||||||
|
documentation()
|
||||||
|
get {
|
||||||
|
call.respond(HttpStatusCode.OK, ExampleResponse(true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Full details on application and route notarization can be found in the `core` module
|
||||||
|
|
||||||
|
## The Playground
|
||||||
|
|
||||||
|
In addition to the documentation available here, Kompendium has a number of out-of-the-box examples available in the
|
||||||
|
playground module. Go ahead and fork the repo and run them directly on your machine to get a sense of what Kompendium
|
||||||
|
can do!
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm") version "1.7.10" apply false
|
kotlin("jvm") version "1.7.10" apply false
|
||||||
kotlin("plugin.serialization") version "1.7.10" apply false
|
kotlin("plugin.serialization") version "1.7.10" apply false
|
||||||
id("io.bkbn.sourdough.library.jvm") version "0.9.0" apply false
|
id("io.bkbn.sourdough.library.jvm") version "0.9.1" apply false
|
||||||
id("io.bkbn.sourdough.application.jvm") version "0.9.0" apply false
|
id("io.bkbn.sourdough.application.jvm") version "0.9.1" apply false
|
||||||
id("io.bkbn.sourdough.root") version "0.9.0"
|
id("io.bkbn.sourdough.root") version "0.9.1"
|
||||||
id("com.github.jakemarsden.git-hooks") version "0.0.2"
|
id("com.github.jakemarsden.git-hooks") version "0.0.2"
|
||||||
id("org.jetbrains.dokka") version "1.7.10"
|
id("org.jetbrains.dokka") version "1.7.10"
|
||||||
id("org.jetbrains.kotlinx.kover") version "0.5.1"
|
id("org.jetbrains.kotlinx.kover") version "0.5.1"
|
||||||
|
@ -6,12 +6,14 @@ It is also the only mandatory client-facing module for a basic setup.
|
|||||||
|
|
||||||
# Package io.bkbn.kompendium.core
|
# Package io.bkbn.kompendium.core
|
||||||
|
|
||||||
The root package contains several objects that power Kompendium, including the Kompendium Ktor Plugin, route
|
## Plugins
|
||||||
notarization methods, and the reflection engine that analyzes method info type parameters.
|
|
||||||
|
|
||||||
## Plugin
|
As mentioned in the root documentation, there are two core Kompendium plugins.
|
||||||
|
|
||||||
The Kompendium plugin is an extremely light-weight plugin, with only a couple areas of customization.
|
1. The application level plugin that handles app level metadata, configuring up your OpenAPI spec, managing custom data
|
||||||
|
types, etc.
|
||||||
|
2. The route level plugin, which is how users declare the documentation for the given route. It _must_ be installed on
|
||||||
|
every route you wish to document
|
||||||
|
|
||||||
### Serialization
|
### Serialization
|
||||||
|
|
||||||
@ -29,76 +31,18 @@ serializer module will convert any `Any` serialization to be `Contextual`. This
|
|||||||
only way to get Kotlinx to play nice with serializing `Any`. If you come up with a better solution, definitely go ahead
|
only way to get Kotlinx to play nice with serializing `Any`. If you come up with a better solution, definitely go ahead
|
||||||
and open up a PR!
|
and open up a PR!
|
||||||
|
|
||||||
## Notarization
|
## NotarizedApplication
|
||||||
|
|
||||||
Central to Kompendium is the concept of notarization.
|
TODO
|
||||||
|
|
||||||
Notarizing a route is the mechanism by which Kompendium analyzes your route types, along with provided metadata, and
|
## NotarizedRoute
|
||||||
converts to the expected OpenAPI format.
|
|
||||||
|
|
||||||
Before jumping into notarization, lets first look at a standard Ktor route
|
TODO
|
||||||
|
|
||||||
```kotlin
|
|
||||||
routing {
|
|
||||||
get {
|
|
||||||
call.respond(HttpStatusCode.OK, BasicResponse(c = UUID.randomUUID().toString()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Now, let's compare this to the same functionality, but notarized using Kompendium
|
|
||||||
|
|
||||||
```kotlin
|
|
||||||
routing {
|
|
||||||
notarizedGet(simpleGetExample) {
|
|
||||||
call.respond(HttpStatusCode.OK, BasicResponse(c = UUID.randomUUID().toString()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Pretty simple huh. But hold on... what is this `simpleGetExample`? How can I know that it is so "simple". Let's take a
|
|
||||||
look
|
|
||||||
|
|
||||||
```kotlin
|
|
||||||
val simpleGetExample = GetInfo<Unit, BasicResponse>(
|
|
||||||
summary = "Simple, Documented GET Request",
|
|
||||||
description = "This is to showcase just how easy it is to document your Ktor API!",
|
|
||||||
responseInfo = ResponseInfo(
|
|
||||||
status = HttpStatusCode.OK,
|
|
||||||
description = "This means everything went as expected!",
|
|
||||||
examples = mapOf("demo" to BasicResponse(c = "52c099d7-8642-46cc-b34e-22f39b923cf4"))
|
|
||||||
),
|
|
||||||
tags = setOf("Simple")
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
See, not so bad 😄 `GetInfo<*,*>` is an implementation of `MethodInfo<TParam, TResp>`, a sealed interface designed to
|
|
||||||
encapsulate all the metadata required for documenting an API route. Kompendium leverages this data, along with the
|
|
||||||
provided type parameters `TParam` and `TResp` to construct the full OpenAPI Specification for your route.
|
|
||||||
|
|
||||||
Additionally, just as a backup, each notarization method includes a "post-processing' hook that will allow you to have
|
|
||||||
final say in the generated route info prior to being attached to the spec. This can be accessed via the optional
|
|
||||||
parameter
|
|
||||||
|
|
||||||
```kotlin
|
|
||||||
routing {
|
|
||||||
notarizedGet(simpleGetExample, postProcess = { spec -> spec }) {
|
|
||||||
call.respond(HttpStatusCode.OK, BasicResponse(c = UUID.randomUUID().toString()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This should only be used in _extremely_ rare scenarios, but it is nice to know it is there if you need it.
|
|
||||||
|
|
||||||
# Package io.bkbn.kompendium.core.metadata
|
# Package io.bkbn.kompendium.core.metadata
|
||||||
|
|
||||||
Houses all interfaces and types related to describing route metadata.
|
Houses all interfaces and types related to describing route metadata.
|
||||||
|
|
||||||
# Package io.bkbn.kompendium.core.parser
|
|
||||||
|
|
||||||
Responsible for the parse of method information. Base implementation is an interface to support extensibility as shown
|
|
||||||
in the `kompendium-locations` module.
|
|
||||||
|
|
||||||
# Package io.bkbn.kompendium.core.routes
|
# Package io.bkbn.kompendium.core.routes
|
||||||
|
|
||||||
Houses any routes provided by the core module. At the moment the only supported route is to enable ReDoc support.
|
Houses any routes provided by the core module. At the moment the only supported route is to enable ReDoc support.
|
||||||
|
@ -4,44 +4,32 @@ import io.bkbn.kompendium.core.attribute.KompendiumAttributes
|
|||||||
import io.bkbn.kompendium.core.metadata.DeleteInfo
|
import io.bkbn.kompendium.core.metadata.DeleteInfo
|
||||||
import io.bkbn.kompendium.core.metadata.GetInfo
|
import io.bkbn.kompendium.core.metadata.GetInfo
|
||||||
import io.bkbn.kompendium.core.metadata.HeadInfo
|
import io.bkbn.kompendium.core.metadata.HeadInfo
|
||||||
import io.bkbn.kompendium.core.metadata.MethodInfo
|
|
||||||
import io.bkbn.kompendium.core.metadata.MethodInfoWithRequest
|
|
||||||
import io.bkbn.kompendium.core.metadata.OptionsInfo
|
import io.bkbn.kompendium.core.metadata.OptionsInfo
|
||||||
import io.bkbn.kompendium.core.metadata.PatchInfo
|
import io.bkbn.kompendium.core.metadata.PatchInfo
|
||||||
import io.bkbn.kompendium.core.metadata.PostInfo
|
import io.bkbn.kompendium.core.metadata.PostInfo
|
||||||
import io.bkbn.kompendium.core.metadata.PutInfo
|
import io.bkbn.kompendium.core.metadata.PutInfo
|
||||||
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
import io.bkbn.kompendium.core.util.Helpers.addToSpec
|
||||||
import io.bkbn.kompendium.core.util.Helpers.getReferenceSlug
|
import io.bkbn.kompendium.core.util.SpecConfig
|
||||||
import io.bkbn.kompendium.core.util.Helpers.getSimpleSlug
|
|
||||||
import io.bkbn.kompendium.json.schema.SchemaGenerator
|
|
||||||
import io.bkbn.kompendium.json.schema.definition.ReferenceDefinition
|
|
||||||
import io.bkbn.kompendium.oas.OpenApiSpec
|
|
||||||
import io.bkbn.kompendium.oas.path.Path
|
import io.bkbn.kompendium.oas.path.Path
|
||||||
import io.bkbn.kompendium.oas.path.PathOperation
|
|
||||||
import io.bkbn.kompendium.oas.payload.MediaType
|
|
||||||
import io.bkbn.kompendium.oas.payload.Parameter
|
import io.bkbn.kompendium.oas.payload.Parameter
|
||||||
import io.bkbn.kompendium.oas.payload.Request
|
|
||||||
import io.bkbn.kompendium.oas.payload.Response
|
|
||||||
import io.ktor.server.application.ApplicationCallPipeline
|
import io.ktor.server.application.ApplicationCallPipeline
|
||||||
import io.ktor.server.application.Hook
|
import io.ktor.server.application.Hook
|
||||||
import io.ktor.server.application.createRouteScopedPlugin
|
import io.ktor.server.application.createRouteScopedPlugin
|
||||||
import io.ktor.server.routing.Route
|
import io.ktor.server.routing.Route
|
||||||
import kotlin.reflect.KClass
|
|
||||||
import kotlin.reflect.KType
|
|
||||||
|
|
||||||
object NotarizedRoute {
|
object NotarizedRoute {
|
||||||
|
|
||||||
class Config {
|
class Config : SpecConfig {
|
||||||
var tags: Set<String> = emptySet()
|
override var tags: Set<String> = emptySet()
|
||||||
var parameters: List<Parameter> = emptyList()
|
override var parameters: List<Parameter> = emptyList()
|
||||||
var get: GetInfo? = null
|
override var get: GetInfo? = null
|
||||||
var post: PostInfo? = null
|
override var post: PostInfo? = null
|
||||||
var put: PutInfo? = null
|
override var put: PutInfo? = null
|
||||||
var delete: DeleteInfo? = null
|
override var delete: DeleteInfo? = null
|
||||||
var patch: PatchInfo? = null
|
override var patch: PatchInfo? = null
|
||||||
var head: HeadInfo? = null
|
override var head: HeadInfo? = null
|
||||||
var options: OptionsInfo? = null
|
override var options: OptionsInfo? = null
|
||||||
var security: Map<String, List<String>>? = null
|
override var security: Map<String, List<String>>? = null
|
||||||
internal var path: Path? = null
|
internal var path: Path? = null
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,86 +74,5 @@ object NotarizedRoute {
|
|||||||
pluginConfig.path = path
|
pluginConfig.path = path
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun MethodInfo.addToSpec(path: Path, spec: OpenApiSpec, config: Config) {
|
|
||||||
SchemaGenerator.fromTypeOrUnit(this.response.responseType, spec.components.schemas)?.let { schema ->
|
|
||||||
spec.components.schemas[this.response.responseType.getSimpleSlug()] = schema
|
|
||||||
}
|
|
||||||
|
|
||||||
errors.forEach { error ->
|
|
||||||
SchemaGenerator.fromTypeOrUnit(error.responseType, spec.components.schemas)?.let { schema ->
|
|
||||||
spec.components.schemas[error.responseType.getSimpleSlug()] = schema
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
when (this) {
|
|
||||||
is MethodInfoWithRequest -> {
|
|
||||||
SchemaGenerator.fromTypeOrUnit(this.request.requestType, spec.components.schemas)?.let { schema ->
|
|
||||||
spec.components.schemas[this.request.requestType.getSimpleSlug()] = schema
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
|
|
||||||
val operations = this.toPathOperation(config)
|
|
||||||
|
|
||||||
when (this) {
|
|
||||||
is DeleteInfo -> path.delete = operations
|
|
||||||
is GetInfo -> path.get = operations
|
|
||||||
is HeadInfo -> path.head = operations
|
|
||||||
is PatchInfo -> path.patch = operations
|
|
||||||
is PostInfo -> path.post = operations
|
|
||||||
is PutInfo -> path.put = operations
|
|
||||||
is OptionsInfo -> path.options = operations
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun MethodInfo.toPathOperation(config: Config) = PathOperation(
|
|
||||||
tags = config.tags.plus(this.tags),
|
|
||||||
summary = this.summary,
|
|
||||||
description = this.description,
|
|
||||||
externalDocs = this.externalDocumentation,
|
|
||||||
operationId = this.operationId,
|
|
||||||
deprecated = this.deprecated,
|
|
||||||
parameters = this.parameters,
|
|
||||||
security = config.security
|
|
||||||
?.map { (k, v) -> k to v }
|
|
||||||
?.map { listOf(it).toMap() }
|
|
||||||
?.toList(),
|
|
||||||
requestBody = when (this) {
|
|
||||||
is MethodInfoWithRequest -> Request(
|
|
||||||
description = this.request.description,
|
|
||||||
content = this.request.requestType.toReferenceContent(this.request.examples),
|
|
||||||
required = true
|
|
||||||
)
|
|
||||||
|
|
||||||
else -> null
|
|
||||||
},
|
|
||||||
responses = mapOf(
|
|
||||||
this.response.responseCode.value to Response(
|
|
||||||
description = this.response.description,
|
|
||||||
content = this.response.responseType.toReferenceContent(this.response.examples)
|
|
||||||
)
|
|
||||||
).plus(this.errors.toResponseMap())
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun List<ResponseInfo>.toResponseMap(): Map<Int, Response> = associate { error ->
|
|
||||||
error.responseCode.value to Response(
|
|
||||||
description = error.description,
|
|
||||||
content = error.responseType.toReferenceContent(error.examples)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun KType.toReferenceContent(examples: Map<String, MediaType.Example>?): Map<String, MediaType>? =
|
|
||||||
when (this.classifier as KClass<*>) {
|
|
||||||
Unit::class -> null
|
|
||||||
else -> mapOf(
|
|
||||||
"application/json" to MediaType(
|
|
||||||
schema = ReferenceDefinition(this.getReferenceSlug()),
|
|
||||||
examples = examples
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Route.calculateRoutePath() = toString().replace(Regex("/\\(.+\\)"), "")
|
private fun Route.calculateRoutePath() = toString().replace(Regex("/\\(.+\\)"), "")
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ package io.bkbn.kompendium.core.routes
|
|||||||
import io.ktor.http.HttpStatusCode
|
import io.ktor.http.HttpStatusCode
|
||||||
import io.ktor.server.application.call
|
import io.ktor.server.application.call
|
||||||
import io.ktor.server.html.respondHtml
|
import io.ktor.server.html.respondHtml
|
||||||
import io.ktor.server.routing.Routing
|
import io.ktor.server.routing.Route
|
||||||
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 kotlinx.html.body
|
import kotlinx.html.body
|
||||||
@ -20,7 +20,7 @@ import kotlinx.html.unsafe
|
|||||||
* @param pageTitle Webpage title you wish to be displayed on your docs
|
* @param pageTitle Webpage title you wish to be displayed on your docs
|
||||||
* @param specUrl url to point ReDoc to the OpenAPI json document
|
* @param specUrl url to point ReDoc to the OpenAPI json document
|
||||||
*/
|
*/
|
||||||
fun Routing.redoc(pageTitle: String = "Docs", specUrl: String = "/openapi.json") {
|
fun Route.redoc(pageTitle: String = "Docs", specUrl: String = "/openapi.json") {
|
||||||
route("/docs") {
|
route("/docs") {
|
||||||
get {
|
get {
|
||||||
call.respondHtml(HttpStatusCode.OK) {
|
call.respondHtml(HttpStatusCode.OK) {
|
||||||
|
@ -1,35 +1,108 @@
|
|||||||
package io.bkbn.kompendium.core.util
|
package io.bkbn.kompendium.core.util
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.core.metadata.DeleteInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.GetInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.HeadInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.MethodInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.MethodInfoWithRequest
|
||||||
|
import io.bkbn.kompendium.core.metadata.OptionsInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.PatchInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.PostInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.PutInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
||||||
|
import io.bkbn.kompendium.json.schema.SchemaGenerator
|
||||||
|
import io.bkbn.kompendium.json.schema.definition.ReferenceDefinition
|
||||||
|
import io.bkbn.kompendium.json.schema.util.Helpers.getReferenceSlug
|
||||||
|
import io.bkbn.kompendium.json.schema.util.Helpers.getSimpleSlug
|
||||||
|
import io.bkbn.kompendium.oas.OpenApiSpec
|
||||||
|
import io.bkbn.kompendium.oas.path.Path
|
||||||
|
import io.bkbn.kompendium.oas.path.PathOperation
|
||||||
|
import io.bkbn.kompendium.oas.payload.MediaType
|
||||||
|
import io.bkbn.kompendium.oas.payload.Request
|
||||||
|
import io.bkbn.kompendium.oas.payload.Response
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
import kotlin.reflect.KProperty
|
|
||||||
import kotlin.reflect.KType
|
import kotlin.reflect.KType
|
||||||
import kotlin.reflect.full.createType
|
|
||||||
import kotlin.reflect.jvm.javaField
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import java.lang.reflect.ParameterizedType
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
object Helpers {
|
object Helpers {
|
||||||
|
|
||||||
private const val COMPONENT_SLUG = "#/components/schemas"
|
fun MethodInfo.addToSpec(path: Path, spec: OpenApiSpec, config: SpecConfig) {
|
||||||
|
SchemaGenerator.fromTypeOrUnit(this.response.responseType, spec.components.schemas)?.let { schema ->
|
||||||
fun KType.getSimpleSlug(): String = when {
|
spec.components.schemas[this.response.responseType.getSimpleSlug()] = schema
|
||||||
this.arguments.isNotEmpty() -> genericNameAdapter(this, classifier as KClass<*>)
|
|
||||||
else -> (classifier as KClass<*>).simpleName ?: error("Could not determine simple name for $this")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun KType.getReferenceSlug(): String = when {
|
errors.forEach { error ->
|
||||||
arguments.isNotEmpty() -> "$COMPONENT_SLUG/${genericNameAdapter(this, classifier as KClass<*>)}"
|
SchemaGenerator.fromTypeOrUnit(error.responseType, spec.components.schemas)?.let { schema ->
|
||||||
else -> "$COMPONENT_SLUG/${(classifier as KClass<*>).simpleName}"
|
spec.components.schemas[error.responseType.getSimpleSlug()] = schema
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
when (this) {
|
||||||
* Adapts a class with type parameters into a reference friendly string
|
is MethodInfoWithRequest -> {
|
||||||
*/
|
SchemaGenerator.fromTypeOrUnit(this.request.requestType, spec.components.schemas)?.let { schema ->
|
||||||
private fun genericNameAdapter(type: KType, clazz: KClass<*>): String {
|
spec.components.schemas[this.request.requestType.getSimpleSlug()] = schema
|
||||||
val classNames = type.arguments
|
}
|
||||||
.map { it.type?.classifier as KClass<*> }
|
}
|
||||||
.map { it.simpleName }
|
|
||||||
return classNames.joinToString(separator = "-", prefix = "${clazz.simpleName}-")
|
else -> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
val operations = this.toPathOperation(config)
|
||||||
|
|
||||||
|
when (this) {
|
||||||
|
is DeleteInfo -> path.delete = operations
|
||||||
|
is GetInfo -> path.get = operations
|
||||||
|
is HeadInfo -> path.head = operations
|
||||||
|
is PatchInfo -> path.patch = operations
|
||||||
|
is PostInfo -> path.post = operations
|
||||||
|
is PutInfo -> path.put = operations
|
||||||
|
is OptionsInfo -> path.options = operations
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun MethodInfo.toPathOperation(config: SpecConfig) = PathOperation(
|
||||||
|
tags = config.tags.plus(this.tags),
|
||||||
|
summary = this.summary,
|
||||||
|
description = this.description,
|
||||||
|
externalDocs = this.externalDocumentation,
|
||||||
|
operationId = this.operationId,
|
||||||
|
deprecated = this.deprecated,
|
||||||
|
parameters = this.parameters,
|
||||||
|
security = config.security
|
||||||
|
?.map { (k, v) -> k to v }
|
||||||
|
?.map { listOf(it).toMap() }
|
||||||
|
?.toList(),
|
||||||
|
requestBody = when (this) {
|
||||||
|
is MethodInfoWithRequest -> Request(
|
||||||
|
description = this.request.description,
|
||||||
|
content = this.request.requestType.toReferenceContent(this.request.examples),
|
||||||
|
required = true
|
||||||
|
)
|
||||||
|
|
||||||
|
else -> null
|
||||||
|
},
|
||||||
|
responses = mapOf(
|
||||||
|
this.response.responseCode.value to Response(
|
||||||
|
description = this.response.description,
|
||||||
|
content = this.response.responseType.toReferenceContent(this.response.examples)
|
||||||
|
)
|
||||||
|
).plus(this.errors.toResponseMap())
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun List<ResponseInfo>.toResponseMap(): Map<Int, Response> = associate { error ->
|
||||||
|
error.responseCode.value to Response(
|
||||||
|
description = error.description,
|
||||||
|
content = error.responseType.toReferenceContent(error.examples)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KType.toReferenceContent(examples: Map<String, MediaType.Example>?): Map<String, MediaType>? =
|
||||||
|
when (this.classifier as KClass<*>) {
|
||||||
|
Unit::class -> null
|
||||||
|
else -> mapOf(
|
||||||
|
"application/json" to MediaType(
|
||||||
|
schema = ReferenceDefinition(this.getReferenceSlug()),
|
||||||
|
examples = examples
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
package io.bkbn.kompendium.core.util
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.core.metadata.DeleteInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.GetInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.HeadInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.OptionsInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.PatchInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.PostInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.PutInfo
|
||||||
|
import io.bkbn.kompendium.oas.payload.Parameter
|
||||||
|
|
||||||
|
interface SpecConfig {
|
||||||
|
var tags: Set<String>
|
||||||
|
var parameters: List<Parameter>
|
||||||
|
var get: GetInfo?
|
||||||
|
var post: PostInfo?
|
||||||
|
var put: PutInfo?
|
||||||
|
var delete: DeleteInfo?
|
||||||
|
var patch: PatchInfo?
|
||||||
|
var head: HeadInfo?
|
||||||
|
var options: OptionsInfo?
|
||||||
|
var security: Map<String, List<String>>?
|
||||||
|
}
|
@ -14,9 +14,13 @@ import io.bkbn.kompendium.core.util.TestModules.singleException
|
|||||||
import io.bkbn.kompendium.core.util.TestModules.genericException
|
import io.bkbn.kompendium.core.util.TestModules.genericException
|
||||||
import io.bkbn.kompendium.core.util.TestModules.genericPolymorphicResponse
|
import io.bkbn.kompendium.core.util.TestModules.genericPolymorphicResponse
|
||||||
import io.bkbn.kompendium.core.util.TestModules.genericPolymorphicResponseMultipleImpls
|
import io.bkbn.kompendium.core.util.TestModules.genericPolymorphicResponseMultipleImpls
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.gnarlyGenericResponse
|
||||||
import io.bkbn.kompendium.core.util.TestModules.headerParameter
|
import io.bkbn.kompendium.core.util.TestModules.headerParameter
|
||||||
import io.bkbn.kompendium.core.util.TestModules.multipleExceptions
|
import io.bkbn.kompendium.core.util.TestModules.multipleExceptions
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.nestedGenericCollection
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.nestedGenericMultipleParamsCollection
|
||||||
import io.bkbn.kompendium.core.util.TestModules.nestedGenericResponse
|
import io.bkbn.kompendium.core.util.TestModules.nestedGenericResponse
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.nestedTypeName
|
||||||
import io.bkbn.kompendium.core.util.TestModules.nonRequiredParam
|
import io.bkbn.kompendium.core.util.TestModules.nonRequiredParam
|
||||||
import io.bkbn.kompendium.core.util.TestModules.polymorphicException
|
import io.bkbn.kompendium.core.util.TestModules.polymorphicException
|
||||||
import io.bkbn.kompendium.core.util.TestModules.notarizedHead
|
import io.bkbn.kompendium.core.util.TestModules.notarizedHead
|
||||||
@ -27,6 +31,7 @@ import io.bkbn.kompendium.core.util.TestModules.notarizedPut
|
|||||||
import io.bkbn.kompendium.core.util.TestModules.nullableEnumField
|
import io.bkbn.kompendium.core.util.TestModules.nullableEnumField
|
||||||
import io.bkbn.kompendium.core.util.TestModules.nullableField
|
import io.bkbn.kompendium.core.util.TestModules.nullableField
|
||||||
import io.bkbn.kompendium.core.util.TestModules.nullableNestedObject
|
import io.bkbn.kompendium.core.util.TestModules.nullableNestedObject
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.nullableReference
|
||||||
import io.bkbn.kompendium.core.util.TestModules.polymorphicCollectionResponse
|
import io.bkbn.kompendium.core.util.TestModules.polymorphicCollectionResponse
|
||||||
import io.bkbn.kompendium.core.util.TestModules.polymorphicMapResponse
|
import io.bkbn.kompendium.core.util.TestModules.polymorphicMapResponse
|
||||||
import io.bkbn.kompendium.core.util.TestModules.polymorphicResponse
|
import io.bkbn.kompendium.core.util.TestModules.polymorphicResponse
|
||||||
@ -37,6 +42,7 @@ import io.bkbn.kompendium.core.util.TestModules.returnsList
|
|||||||
import io.bkbn.kompendium.core.util.TestModules.rootRoute
|
import io.bkbn.kompendium.core.util.TestModules.rootRoute
|
||||||
import io.bkbn.kompendium.core.util.TestModules.simpleGenericResponse
|
import io.bkbn.kompendium.core.util.TestModules.simpleGenericResponse
|
||||||
import io.bkbn.kompendium.core.util.TestModules.simplePathParsing
|
import io.bkbn.kompendium.core.util.TestModules.simplePathParsing
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.simpleRecursive
|
||||||
import io.bkbn.kompendium.core.util.TestModules.trailingSlash
|
import io.bkbn.kompendium.core.util.TestModules.trailingSlash
|
||||||
import io.bkbn.kompendium.core.util.TestModules.withOperationId
|
import io.bkbn.kompendium.core.util.TestModules.withOperationId
|
||||||
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
||||||
@ -157,6 +163,15 @@ class KompendiumTest : DescribeSpec({
|
|||||||
it("Can handle an absolutely psycho inheritance test") {
|
it("Can handle an absolutely psycho inheritance test") {
|
||||||
openApiTestAllSerializers("T0033__crazy_polymorphic_example.json") { genericPolymorphicResponseMultipleImpls() }
|
openApiTestAllSerializers("T0033__crazy_polymorphic_example.json") { genericPolymorphicResponseMultipleImpls() }
|
||||||
}
|
}
|
||||||
|
it("Can support nested generic collections") {
|
||||||
|
openApiTestAllSerializers("T0039__nested_generic_collection.json") { nestedGenericCollection() }
|
||||||
|
}
|
||||||
|
it("Can support nested generics with multiple type parameters") {
|
||||||
|
openApiTestAllSerializers("T0040__nested_generic_multiple_type_params.json") { nestedGenericMultipleParamsCollection() }
|
||||||
|
}
|
||||||
|
it("Can handle a really gnarly generic example") {
|
||||||
|
openApiTestAllSerializers("T0043__gnarly_generic_example.json") { gnarlyGenericResponse() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
describe("Miscellaneous") {
|
describe("Miscellaneous") {
|
||||||
xit("Can generate the necessary ReDoc home page") {
|
xit("Can generate the necessary ReDoc home page") {
|
||||||
@ -174,8 +189,8 @@ class KompendiumTest : DescribeSpec({
|
|||||||
xit("Can override field name") {
|
xit("Can override field name") {
|
||||||
// TODO Assess strategies here
|
// TODO Assess strategies here
|
||||||
}
|
}
|
||||||
xit("Can serialize a recursive type") {
|
it("Can serialize a recursive type") {
|
||||||
// TODO openApiTestAllSerializers("simple_recursive.json") { simpleRecursive() }
|
openApiTestAllSerializers("T0042__simple_recursive.json") { simpleRecursive() }
|
||||||
}
|
}
|
||||||
it("Nullable fields do not lead to doom") {
|
it("Nullable fields do not lead to doom") {
|
||||||
openApiTestAllSerializers("T0036__nullable_fields.json") { nullableNestedObject() }
|
openApiTestAllSerializers("T0036__nullable_fields.json") { nullableNestedObject() }
|
||||||
@ -183,6 +198,12 @@ class KompendiumTest : DescribeSpec({
|
|||||||
it("Can have a nullable enum as a member field") {
|
it("Can have a nullable enum as a member field") {
|
||||||
openApiTestAllSerializers("T0037__nullable_enum_field.json") { nullableEnumField() }
|
openApiTestAllSerializers("T0037__nullable_enum_field.json") { nullableEnumField() }
|
||||||
}
|
}
|
||||||
|
it("Can have a nullable reference without impacting base type") {
|
||||||
|
openApiTestAllSerializers("T0041__nullable_reference.json") { nullableReference() }
|
||||||
|
}
|
||||||
|
it("Can handle nested type names") {
|
||||||
|
openApiTestAllSerializers("T0044__nested_type_name.json") { nestedTypeName() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
describe("Constraints") {
|
describe("Constraints") {
|
||||||
// TODO Assess strategies here
|
// TODO Assess strategies here
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package io.bkbn.kompendium.core.util
|
package io.bkbn.kompendium.core.util
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.core.fixtures.Barzo
|
||||||
import io.bkbn.kompendium.core.fixtures.ColumnSchema
|
import io.bkbn.kompendium.core.fixtures.ColumnSchema
|
||||||
import io.bkbn.kompendium.core.fixtures.ComplexRequest
|
import io.bkbn.kompendium.core.fixtures.ComplexRequest
|
||||||
import io.bkbn.kompendium.core.fixtures.DateTimeString
|
import io.bkbn.kompendium.core.fixtures.DateTimeString
|
||||||
@ -7,9 +8,14 @@ import io.bkbn.kompendium.core.fixtures.DefaultField
|
|||||||
import io.bkbn.kompendium.core.fixtures.ExceptionResponse
|
import io.bkbn.kompendium.core.fixtures.ExceptionResponse
|
||||||
import io.bkbn.kompendium.core.fixtures.Flibbity
|
import io.bkbn.kompendium.core.fixtures.Flibbity
|
||||||
import io.bkbn.kompendium.core.fixtures.FlibbityGibbit
|
import io.bkbn.kompendium.core.fixtures.FlibbityGibbit
|
||||||
|
import io.bkbn.kompendium.core.fixtures.Foosy
|
||||||
import io.bkbn.kompendium.core.fixtures.Gibbity
|
import io.bkbn.kompendium.core.fixtures.Gibbity
|
||||||
|
import io.bkbn.kompendium.core.fixtures.ManyThings
|
||||||
|
import io.bkbn.kompendium.core.fixtures.MultiNestedGenerics
|
||||||
|
import io.bkbn.kompendium.core.fixtures.Nested
|
||||||
import io.bkbn.kompendium.core.fixtures.NullableEnum
|
import io.bkbn.kompendium.core.fixtures.NullableEnum
|
||||||
import io.bkbn.kompendium.core.fixtures.NullableField
|
import io.bkbn.kompendium.core.fixtures.NullableField
|
||||||
|
import io.bkbn.kompendium.core.fixtures.Page
|
||||||
import io.bkbn.kompendium.core.fixtures.ProfileUpdateRequest
|
import io.bkbn.kompendium.core.fixtures.ProfileUpdateRequest
|
||||||
import io.bkbn.kompendium.core.fixtures.TestCreatedResponse
|
import io.bkbn.kompendium.core.fixtures.TestCreatedResponse
|
||||||
import io.bkbn.kompendium.core.fixtures.TestNested
|
import io.bkbn.kompendium.core.fixtures.TestNested
|
||||||
@ -561,28 +567,40 @@ object TestModules {
|
|||||||
|
|
||||||
fun Routing.simpleGenericResponse() = basicGetGenerator<Gibbity<String>>()
|
fun Routing.simpleGenericResponse() = basicGetGenerator<Gibbity<String>>()
|
||||||
|
|
||||||
|
fun Routing.gnarlyGenericResponse() = basicGetGenerator<Foosy<Barzo<Int>, String>>()
|
||||||
|
|
||||||
fun Routing.nestedGenericResponse() = basicGetGenerator<Gibbity<Map<String, String>>>()
|
fun Routing.nestedGenericResponse() = basicGetGenerator<Gibbity<Map<String, String>>>()
|
||||||
|
|
||||||
fun Routing.genericPolymorphicResponse() = basicGetGenerator<Flibbity<Double>>()
|
fun Routing.genericPolymorphicResponse() = basicGetGenerator<Flibbity<Double>>()
|
||||||
|
|
||||||
fun Routing.genericPolymorphicResponseMultipleImpls() = basicGetGenerator<Flibbity<FlibbityGibbit>>()
|
fun Routing.genericPolymorphicResponseMultipleImpls() = basicGetGenerator<Flibbity<FlibbityGibbit>>()
|
||||||
|
|
||||||
|
fun Routing.nestedGenericCollection() = basicGetGenerator<Page<Int>>()
|
||||||
|
|
||||||
|
fun Routing.nestedGenericMultipleParamsCollection() = basicGetGenerator<MultiNestedGenerics<String, ComplexRequest>>()
|
||||||
|
|
||||||
fun Routing.withOperationId() = basicGetGenerator<TestResponse>(operationId = "getThisDude")
|
fun Routing.withOperationId() = basicGetGenerator<TestResponse>(operationId = "getThisDude")
|
||||||
|
|
||||||
fun Routing.nullableNestedObject() = basicGetGenerator<ProfileUpdateRequest>()
|
fun Routing.nullableNestedObject() = basicGetGenerator<ProfileUpdateRequest>()
|
||||||
|
|
||||||
fun Routing.nullableEnumField() = basicGetGenerator<NullableEnum>()
|
fun Routing.nullableEnumField() = basicGetGenerator<NullableEnum>()
|
||||||
|
|
||||||
|
fun Routing.nullableReference() = basicGetGenerator<ManyThings>()
|
||||||
|
|
||||||
fun Routing.dateTimeString() = basicGetGenerator<DateTimeString>()
|
fun Routing.dateTimeString() = basicGetGenerator<DateTimeString>()
|
||||||
|
|
||||||
fun Routing.headerParameter() = basicGetGenerator<TestResponse>( params = listOf(
|
fun Routing.headerParameter() = basicGetGenerator<TestResponse>(
|
||||||
|
params = listOf(
|
||||||
Parameter(
|
Parameter(
|
||||||
name = "X-User-Email",
|
name = "X-User-Email",
|
||||||
`in` = Parameter.Location.header,
|
`in` = Parameter.Location.header,
|
||||||
schema = TypeDefinition.STRING,
|
schema = TypeDefinition.STRING,
|
||||||
required = true
|
required = true
|
||||||
)
|
)
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Routing.nestedTypeName() = basicGetGenerator<Nested.Response>()
|
||||||
|
|
||||||
fun Routing.simpleRecursive() = basicGetGenerator<ColumnSchema>()
|
fun Routing.simpleRecursive() = basicGetGenerator<ColumnSchema>()
|
||||||
|
|
||||||
|
@ -62,30 +62,7 @@
|
|||||||
"type": "null"
|
"type": "null"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "object",
|
"$ref": "#/components/schemas/ProfileMetadataUpdateRequest"
|
||||||
"properties": {
|
|
||||||
"isPrivate": {
|
|
||||||
"oneOf": [
|
|
||||||
{
|
|
||||||
"type": "null"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "boolean"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"otherThing": {
|
|
||||||
"oneOf": [
|
|
||||||
{
|
|
||||||
"type": "null"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": []
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -112,6 +89,39 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": []
|
"required": []
|
||||||
|
},
|
||||||
|
"ProfileMetadataUpdateRequest": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"isPrivate": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"otherThing": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": []
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"securitySchemes": {}
|
"securitySchemes": {}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"openapi": "3.0.3",
|
"openapi": "3.1.0",
|
||||||
|
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"info": {
|
"info": {
|
||||||
"title": "Test API",
|
"title": "Test API",
|
||||||
"version": "1.33.7",
|
"version": "1.33.7",
|
||||||
@ -26,72 +27,71 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"/test/test/{name}": {
|
"/": {
|
||||||
"post": {
|
"get": {
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"summary": "Location Test",
|
"summary": "Great Summary!",
|
||||||
"description": "A cool test",
|
"description": "testing more",
|
||||||
"parameters": [
|
"parameters": [],
|
||||||
{
|
|
||||||
"name": "name",
|
|
||||||
"in": "path",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"required": true,
|
|
||||||
"deprecated": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"requestBody": {
|
|
||||||
"description": "Cool stuff",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/SimpleRequest"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "A successful endeavor",
|
"description": "A Successful Endeavor",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/components/schemas/SimpleResponse"
|
"$ref": "#/components/schemas/Page-Int"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"deprecated": false
|
"deprecated": false
|
||||||
}
|
},
|
||||||
|
"parameters": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"webhooks": {},
|
||||||
"components": {
|
"components": {
|
||||||
"schemas": {
|
"schemas": {
|
||||||
"SimpleRequest": {
|
"Page-Int": {
|
||||||
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"input": {
|
"content": {
|
||||||
"type": "string"
|
"items": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"number": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"numberOfElements": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"totalElements": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"totalPages": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "int32"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"input"
|
"content",
|
||||||
],
|
"number",
|
||||||
"type": "object"
|
"numberOfElements",
|
||||||
},
|
"size",
|
||||||
"SimpleResponse": {
|
"totalElements",
|
||||||
"properties": {
|
"totalPages"
|
||||||
"result": {
|
]
|
||||||
"type": "boolean"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"result"
|
|
||||||
],
|
|
||||||
"type": "object"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"securitySchemes": {}
|
"securitySchemes": {}
|
@ -0,0 +1,129 @@
|
|||||||
|
{
|
||||||
|
"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": {
|
||||||
|
"/": {
|
||||||
|
"get": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Great Summary!",
|
||||||
|
"description": "testing more",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A Successful Endeavor",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/MultiNestedGenerics-String-ComplexRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"CrazyItem": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"enumeration": {
|
||||||
|
"enum": [
|
||||||
|
"ONE",
|
||||||
|
"TWO"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"enumeration"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"NestedComplexItem": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"alias": {
|
||||||
|
"additionalProperties": {
|
||||||
|
"$ref": "#/components/schemas/CrazyItem"
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"alias",
|
||||||
|
"name"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ComplexRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"amazingField": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"org": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"tables": {
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/NestedComplexItem"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"amazingField",
|
||||||
|
"org",
|
||||||
|
"tables"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"MultiNestedGenerics-String-ComplexRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"content": {
|
||||||
|
"additionalProperties": {
|
||||||
|
"$ref": "#/components/schemas/ComplexRequest"
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"content"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"openapi": "3.0.3",
|
"openapi": "3.1.0",
|
||||||
|
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"info": {
|
"info": {
|
||||||
"title": "Test API",
|
"title": "Test API",
|
||||||
"version": "1.33.7",
|
"version": "1.33.7",
|
||||||
@ -26,72 +27,68 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"/test/test/{name}": {
|
"/": {
|
||||||
"put": {
|
"get": {
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"summary": "Location Test",
|
"summary": "Great Summary!",
|
||||||
"description": "A cool test",
|
"description": "testing more",
|
||||||
"parameters": [
|
"parameters": [],
|
||||||
{
|
|
||||||
"name": "name",
|
|
||||||
"in": "path",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"required": true,
|
|
||||||
"deprecated": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"requestBody": {
|
|
||||||
"description": "Cool stuff",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/SimpleRequest"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "A successful endeavor",
|
"description": "A Successful Endeavor",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/components/schemas/SimpleResponse"
|
"$ref": "#/components/schemas/ManyThings"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"deprecated": false
|
"deprecated": false
|
||||||
}
|
},
|
||||||
|
"parameters": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"webhooks": {},
|
||||||
"components": {
|
"components": {
|
||||||
"schemas": {
|
"schemas": {
|
||||||
"SimpleRequest": {
|
"Something": {
|
||||||
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"input": {
|
"a": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"b": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "int32"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"input"
|
"a",
|
||||||
],
|
"b"
|
||||||
"type": "object"
|
]
|
||||||
},
|
},
|
||||||
"SimpleResponse": {
|
"ManyThings": {
|
||||||
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"result": {
|
"someA": {
|
||||||
"type": "boolean"
|
"$ref": "#/components/schemas/Something"
|
||||||
|
},
|
||||||
|
"someB": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/Something"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"result"
|
"someA"
|
||||||
],
|
]
|
||||||
"type": "object"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"securitySchemes": {}
|
"securitySchemes": {}
|
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"openapi": "3.0.3",
|
"openapi": "3.1.0",
|
||||||
|
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"info": {
|
"info": {
|
||||||
"title": "Test API",
|
"title": "Test API",
|
||||||
"version": "1.33.7",
|
"version": "1.33.7",
|
||||||
@ -26,59 +27,64 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"/test/test/{name}/nesty": {
|
"/": {
|
||||||
"delete": {
|
"get": {
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"summary": "Location Test",
|
"summary": "Great Summary!",
|
||||||
"description": "A cool test",
|
"description": "testing more",
|
||||||
"parameters": [
|
"parameters": [],
|
||||||
{
|
|
||||||
"name": "isCool",
|
|
||||||
"in": "query",
|
|
||||||
"schema": {
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"required": true,
|
|
||||||
"deprecated": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "name",
|
|
||||||
"in": "path",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"required": true,
|
|
||||||
"deprecated": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "A successful endeavor",
|
"description": "A Successful Endeavor",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/components/schemas/SimpleResponse"
|
"$ref": "#/components/schemas/ColumnSchema"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"deprecated": false
|
"deprecated": false
|
||||||
}
|
},
|
||||||
|
"parameters": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"webhooks": {},
|
||||||
"components": {
|
"components": {
|
||||||
"schemas": {
|
"schemas": {
|
||||||
"SimpleResponse": {
|
"ColumnSchema": {
|
||||||
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"result": {
|
"description": {
|
||||||
"type": "boolean"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"mode": {
|
||||||
|
"enum": [
|
||||||
|
"NULLABLE",
|
||||||
|
"REQUIRED",
|
||||||
|
"REPEATED"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"subColumns": {
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/ColumnSchema"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"result"
|
"description",
|
||||||
],
|
"mode",
|
||||||
"type": "object"
|
"name",
|
||||||
|
"type"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"securitySchemes": {}
|
"securitySchemes": {}
|
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"openapi": "3.0.3",
|
"openapi": "3.1.0",
|
||||||
|
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"info": {
|
"info": {
|
||||||
"title": "Test API",
|
"title": "Test API",
|
||||||
"version": "1.33.7",
|
"version": "1.33.7",
|
||||||
@ -26,59 +27,61 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"/test/test/{name}/nesty": {
|
"/": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"summary": "Location Test",
|
"summary": "Great Summary!",
|
||||||
"description": "A cool test",
|
"description": "testing more",
|
||||||
"parameters": [
|
"parameters": [],
|
||||||
{
|
|
||||||
"name": "isCool",
|
|
||||||
"in": "query",
|
|
||||||
"schema": {
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"required": true,
|
|
||||||
"deprecated": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "name",
|
|
||||||
"in": "path",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"required": true,
|
|
||||||
"deprecated": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "A successful endeavor",
|
"description": "A Successful Endeavor",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/components/schemas/SimpleResponse"
|
"$ref": "#/components/schemas/Foosy-Barzo-String"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"deprecated": false
|
"deprecated": false
|
||||||
}
|
},
|
||||||
|
"parameters": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"webhooks": {},
|
||||||
"components": {
|
"components": {
|
||||||
"schemas": {
|
"schemas": {
|
||||||
"SimpleResponse": {
|
"Foosy-Barzo-String": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"otherThing": {
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"$ref": "#/components/schemas/Barzo-Int"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"otherThing",
|
||||||
|
"test"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Barzo-Int": {
|
||||||
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"result": {
|
"result": {
|
||||||
"type": "boolean"
|
"type": "number",
|
||||||
|
"format": "int32"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"result"
|
"result"
|
||||||
],
|
]
|
||||||
"type": "object"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"securitySchemes": {}
|
"securitySchemes": {}
|
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"openapi": "3.0.3",
|
"openapi": "3.1.0",
|
||||||
|
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"info": {
|
"info": {
|
||||||
"title": "Test API",
|
"title": "Test API",
|
||||||
"version": "1.33.7",
|
"version": "1.33.7",
|
||||||
@ -26,50 +27,42 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"/test/test/{name}": {
|
"/": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"summary": "Location Test",
|
"summary": "Great Summary!",
|
||||||
"description": "A cool test",
|
"description": "testing more",
|
||||||
"parameters": [
|
"parameters": [],
|
||||||
{
|
|
||||||
"name": "name",
|
|
||||||
"in": "path",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"required": true,
|
|
||||||
"deprecated": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "A successful endeavor",
|
"description": "A Successful Endeavor",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/components/schemas/SimpleResponse"
|
"$ref": "#/components/schemas/NestedResponse"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"deprecated": false
|
"deprecated": false
|
||||||
}
|
},
|
||||||
|
"parameters": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"webhooks": {},
|
||||||
"components": {
|
"components": {
|
||||||
"schemas": {
|
"schemas": {
|
||||||
"SimpleResponse": {
|
"NestedResponse": {
|
||||||
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"result": {
|
"idk": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"result"
|
"idk"
|
||||||
],
|
]
|
||||||
"type": "object"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"securitySchemes": {}
|
"securitySchemes": {}
|
@ -23,6 +23,7 @@ import io.ktor.http.HttpStatusCode
|
|||||||
import io.ktor.serialization.gson.gson
|
import io.ktor.serialization.gson.gson
|
||||||
import io.ktor.serialization.jackson.jackson
|
import io.ktor.serialization.jackson.jackson
|
||||||
import io.ktor.serialization.kotlinx.json.json
|
import io.ktor.serialization.kotlinx.json.json
|
||||||
|
import io.ktor.server.application.Application
|
||||||
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
|
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
|
||||||
import io.ktor.server.routing.Routing
|
import io.ktor.server.routing.Routing
|
||||||
import io.ktor.server.testing.ApplicationTestBuilder
|
import io.ktor.server.testing.ApplicationTestBuilder
|
||||||
@ -63,17 +64,19 @@ object TestHelpers {
|
|||||||
fun openApiTestAllSerializers(
|
fun openApiTestAllSerializers(
|
||||||
snapshotName: String,
|
snapshotName: String,
|
||||||
customTypes: Map<KType, JsonSchema> = emptyMap(),
|
customTypes: Map<KType, JsonSchema> = emptyMap(),
|
||||||
|
applicationSetup: Application.() -> Unit = { },
|
||||||
routeUnderTest: Routing.() -> Unit
|
routeUnderTest: Routing.() -> Unit
|
||||||
) {
|
) {
|
||||||
openApiTest(snapshotName, SupportedSerializer.KOTLINX, routeUnderTest, customTypes)
|
openApiTest(snapshotName, SupportedSerializer.KOTLINX, routeUnderTest, applicationSetup, customTypes)
|
||||||
openApiTest(snapshotName, SupportedSerializer.JACKSON, routeUnderTest, customTypes)
|
openApiTest(snapshotName, SupportedSerializer.JACKSON, routeUnderTest, applicationSetup, customTypes)
|
||||||
openApiTest(snapshotName, SupportedSerializer.GSON, routeUnderTest, customTypes)
|
openApiTest(snapshotName, SupportedSerializer.GSON, routeUnderTest, applicationSetup, customTypes)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openApiTest(
|
private fun openApiTest(
|
||||||
snapshotName: String,
|
snapshotName: String,
|
||||||
serializer: SupportedSerializer,
|
serializer: SupportedSerializer,
|
||||||
routeUnderTest: Routing.() -> Unit,
|
routeUnderTest: Routing.() -> Unit,
|
||||||
|
applicationSetup: Application.() -> Unit,
|
||||||
typeOverrides: Map<KType, JsonSchema> = emptyMap()
|
typeOverrides: Map<KType, JsonSchema> = emptyMap()
|
||||||
) = testApplication {
|
) = testApplication {
|
||||||
install(NotarizedApplication()) {
|
install(NotarizedApplication()) {
|
||||||
@ -95,6 +98,7 @@ object TestHelpers {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
application(applicationSetup)
|
||||||
routing {
|
routing {
|
||||||
redoc()
|
redoc()
|
||||||
routeUnderTest()
|
routeUnderTest()
|
||||||
|
@ -118,3 +118,31 @@ public data class ProfileMetadataUpdateRequest(
|
|||||||
public val isPrivate: Boolean?,
|
public val isPrivate: Boolean?,
|
||||||
public val otherThing: String?
|
public val otherThing: String?
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class Page<T>(
|
||||||
|
val content: List<T>,
|
||||||
|
val totalElements: Long,
|
||||||
|
val totalPages: Int,
|
||||||
|
val numberOfElements: Int,
|
||||||
|
val number: Int,
|
||||||
|
val size: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
data class MultiNestedGenerics<T, E>(
|
||||||
|
val content: Map<T, E>
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Something(val a: String, val b: Int)
|
||||||
|
|
||||||
|
data class ManyThings(
|
||||||
|
val someA: Something,
|
||||||
|
val someB: Something?
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Foosy<T, K>(val test: T, val otherThing: List<K>)
|
||||||
|
data class Barzo<G>(val result: G)
|
||||||
|
|
||||||
|
object Nested {
|
||||||
|
@Serializable
|
||||||
|
data class Response(val idk: Boolean)
|
||||||
|
}
|
||||||
|
@ -1 +1 @@
|
|||||||
<meta http-equiv="refresh" content="0; url=./2.3.4" />
|
<meta http-equiv="refresh" content="0; url=./3.0.0-beta-SNAPSHOT" />
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Kompendium
|
# Kompendium
|
||||||
project.version=3.0.0-alpha
|
project.version=3.0.0
|
||||||
# Kotlin
|
# Kotlin
|
||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
# Gradle
|
# Gradle
|
||||||
|
3
json-schema/Module.md
Normal file
3
json-schema/Module.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Module kompendium-json-schema
|
||||||
|
|
||||||
|
This module handles converting Kotlin data classes to compliant [JsonSchema](https://json-schema.org)
|
@ -14,6 +14,7 @@ import kotlin.reflect.KClass
|
|||||||
import kotlin.reflect.KType
|
import kotlin.reflect.KType
|
||||||
import kotlin.reflect.full.isSubclassOf
|
import kotlin.reflect.full.isSubclassOf
|
||||||
import kotlin.reflect.typeOf
|
import kotlin.reflect.typeOf
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
object SchemaGenerator {
|
object SchemaGenerator {
|
||||||
inline fun <reified T : Any?> fromTypeToSchema(cache: MutableMap<String, JsonSchema> = mutableMapOf()) =
|
inline fun <reified T : Any?> fromTypeToSchema(cache: MutableMap<String, JsonSchema> = mutableMapOf()) =
|
||||||
@ -37,6 +38,7 @@ object SchemaGenerator {
|
|||||||
Float::class -> checkForNull(type, TypeDefinition.FLOAT)
|
Float::class -> checkForNull(type, TypeDefinition.FLOAT)
|
||||||
String::class -> checkForNull(type, TypeDefinition.STRING)
|
String::class -> checkForNull(type, TypeDefinition.STRING)
|
||||||
Boolean::class -> checkForNull(type, TypeDefinition.BOOLEAN)
|
Boolean::class -> checkForNull(type, TypeDefinition.BOOLEAN)
|
||||||
|
UUID::class -> checkForNull(type, TypeDefinition.UUID)
|
||||||
else -> when {
|
else -> when {
|
||||||
clazz.isSubclassOf(Enum::class) -> EnumHandler.handle(type, clazz)
|
clazz.isSubclassOf(Enum::class) -> EnumHandler.handle(type, clazz)
|
||||||
clazz.isSubclassOf(Collection::class) -> CollectionHandler.handle(type, cache)
|
clazz.isSubclassOf(Collection::class) -> CollectionHandler.handle(type, cache)
|
||||||
|
@ -40,6 +40,11 @@ data class TypeDefinition(
|
|||||||
type = "string"
|
type = "string"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val UUID = TypeDefinition(
|
||||||
|
type = "string",
|
||||||
|
format = "uuid"
|
||||||
|
)
|
||||||
|
|
||||||
val BOOLEAN = TypeDefinition(
|
val BOOLEAN = TypeDefinition(
|
||||||
type = "boolean"
|
type = "boolean"
|
||||||
)
|
)
|
||||||
|
@ -13,21 +13,32 @@ import kotlin.reflect.KProperty
|
|||||||
import kotlin.reflect.KType
|
import kotlin.reflect.KType
|
||||||
import kotlin.reflect.KTypeParameter
|
import kotlin.reflect.KTypeParameter
|
||||||
import kotlin.reflect.KTypeProjection
|
import kotlin.reflect.KTypeProjection
|
||||||
|
import kotlin.reflect.full.createType
|
||||||
import kotlin.reflect.full.memberProperties
|
import kotlin.reflect.full.memberProperties
|
||||||
import kotlin.reflect.full.primaryConstructor
|
import kotlin.reflect.full.primaryConstructor
|
||||||
|
|
||||||
object SimpleObjectHandler {
|
object SimpleObjectHandler {
|
||||||
|
|
||||||
fun handle(type: KType, clazz: KClass<*>, cache: MutableMap<String, JsonSchema>): JsonSchema {
|
fun handle(type: KType, clazz: KClass<*>, cache: MutableMap<String, JsonSchema>): JsonSchema {
|
||||||
// cache[type.getSimpleSlug()] = ReferenceDefinition("RECURSION_PLACEHOLDER")
|
|
||||||
|
cache[type.getSimpleSlug()] = ReferenceDefinition(type.getReferenceSlug())
|
||||||
|
|
||||||
val typeMap = clazz.typeParameters.zip(type.arguments).toMap()
|
val typeMap = clazz.typeParameters.zip(type.arguments).toMap()
|
||||||
val props = clazz.memberProperties.associate { prop ->
|
val props = clazz.memberProperties.associate { prop ->
|
||||||
val schema = when (typeMap.containsKey(prop.returnType.classifier)) {
|
val schema = when (prop.needsToInjectGenerics(typeMap)) {
|
||||||
|
true -> handleNestedGenerics(typeMap, prop, cache)
|
||||||
|
false -> when (typeMap.containsKey(prop.returnType.classifier)) {
|
||||||
true -> handleGenericProperty(prop, typeMap, cache)
|
true -> handleGenericProperty(prop, typeMap, cache)
|
||||||
false -> handleProperty(prop, cache)
|
false -> handleProperty(prop, cache)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
prop.name to schema
|
val nullCheckSchema = when (prop.returnType.isMarkedNullable && !schema.isNullable()) {
|
||||||
|
true -> OneOfDefinition(NullableDefinition(), schema)
|
||||||
|
false -> schema
|
||||||
|
}
|
||||||
|
|
||||||
|
prop.name to nullCheckSchema
|
||||||
}
|
}
|
||||||
|
|
||||||
val required = clazz.memberProperties.filterNot { prop -> prop.returnType.isMarkedNullable }
|
val required = clazz.memberProperties.filterNot { prop -> prop.returnType.isMarkedNullable }
|
||||||
@ -48,6 +59,34 @@ object SimpleObjectHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun KProperty<*>.needsToInjectGenerics(
|
||||||
|
typeMap: Map<KTypeParameter, KTypeProjection>
|
||||||
|
): Boolean {
|
||||||
|
val typeSymbols = returnType.arguments.map { it.type.toString() }
|
||||||
|
return typeMap.any { (k, _) -> typeSymbols.contains(k.name) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleNestedGenerics(
|
||||||
|
typeMap: Map<KTypeParameter, KTypeProjection>,
|
||||||
|
prop: KProperty<*>,
|
||||||
|
cache: MutableMap<String, JsonSchema>
|
||||||
|
): JsonSchema {
|
||||||
|
val propClass = prop.returnType.classifier as KClass<*>
|
||||||
|
val types = prop.returnType.arguments.map {
|
||||||
|
val typeSymbol = it.type.toString()
|
||||||
|
typeMap.filterKeys { k -> k.name == typeSymbol }.values.first()
|
||||||
|
}
|
||||||
|
val constructedType = propClass.createType(types)
|
||||||
|
return SchemaGenerator.fromTypeToSchema(constructedType, cache).let {
|
||||||
|
if (it.isOrContainsObjectDef()) {
|
||||||
|
cache[constructedType.getSimpleSlug()] = it
|
||||||
|
ReferenceDefinition(prop.returnType.getReferenceSlug())
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleGenericProperty(
|
private fun handleGenericProperty(
|
||||||
prop: KProperty<*>,
|
prop: KProperty<*>,
|
||||||
typeMap: Map<KTypeParameter, KTypeProjection>,
|
typeMap: Map<KTypeParameter, KTypeProjection>,
|
||||||
@ -55,9 +94,9 @@ object SimpleObjectHandler {
|
|||||||
): JsonSchema {
|
): JsonSchema {
|
||||||
val type = typeMap[prop.returnType.classifier]?.type!!
|
val type = typeMap[prop.returnType.classifier]?.type!!
|
||||||
return SchemaGenerator.fromTypeToSchema(type, cache).let {
|
return SchemaGenerator.fromTypeToSchema(type, cache).let {
|
||||||
if (it is TypeDefinition && it.type == "object") {
|
if (it.isOrContainsObjectDef()) {
|
||||||
cache[type.getSimpleSlug()] = it
|
cache[type.getSimpleSlug()] = it
|
||||||
ReferenceDefinition(prop.returnType.getReferenceSlug())
|
ReferenceDefinition(type.getReferenceSlug())
|
||||||
} else {
|
} else {
|
||||||
it
|
it
|
||||||
}
|
}
|
||||||
@ -66,11 +105,19 @@ object SimpleObjectHandler {
|
|||||||
|
|
||||||
private fun handleProperty(prop: KProperty<*>, cache: MutableMap<String, JsonSchema>): JsonSchema =
|
private fun handleProperty(prop: KProperty<*>, cache: MutableMap<String, JsonSchema>): JsonSchema =
|
||||||
SchemaGenerator.fromTypeToSchema(prop.returnType, cache).let {
|
SchemaGenerator.fromTypeToSchema(prop.returnType, cache).let {
|
||||||
if (it is TypeDefinition && it.type == "object") {
|
if (it.isOrContainsObjectDef()) {
|
||||||
cache[prop.returnType.getSimpleSlug()] = it
|
cache[prop.returnType.getSimpleSlug()] = it
|
||||||
ReferenceDefinition(prop.returnType.getReferenceSlug())
|
ReferenceDefinition(prop.returnType.getReferenceSlug())
|
||||||
} else {
|
} else {
|
||||||
it
|
it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun JsonSchema.isOrContainsObjectDef(): Boolean {
|
||||||
|
val isTypeDef = this is TypeDefinition && type == "object"
|
||||||
|
val isTypeDefOneOf = this is OneOfDefinition && this.oneOf.any { js -> js is TypeDefinition && js.type == "object" }
|
||||||
|
return isTypeDef || isTypeDefOneOf
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun JsonSchema.isNullable(): Boolean = this is OneOfDefinition && this.oneOf.any{ it is NullableDefinition }
|
||||||
}
|
}
|
||||||
|
@ -9,21 +9,26 @@ object Helpers {
|
|||||||
|
|
||||||
fun KType.getSimpleSlug(): String = when {
|
fun KType.getSimpleSlug(): String = when {
|
||||||
this.arguments.isNotEmpty() -> genericNameAdapter(this, classifier as KClass<*>)
|
this.arguments.isNotEmpty() -> genericNameAdapter(this, classifier as KClass<*>)
|
||||||
else -> (classifier as KClass<*>).simpleName ?: error("Could not determine simple name for $this")
|
else -> (classifier as KClass<*>).kompendiumSlug() ?: error("Could not determine simple name for $this")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun KType.getReferenceSlug(): String = when {
|
fun KType.getReferenceSlug(): String = when {
|
||||||
arguments.isNotEmpty() -> "$COMPONENT_SLUG/${genericNameAdapter(this, classifier as KClass<*>)}"
|
arguments.isNotEmpty() -> "$COMPONENT_SLUG/${genericNameAdapter(this, classifier as KClass<*>)}"
|
||||||
else -> "$COMPONENT_SLUG/${(classifier as KClass<*>).simpleName}"
|
else -> "$COMPONENT_SLUG/${(classifier as KClass<*>).kompendiumSlug()}"
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("ReturnCount")
|
||||||
|
private fun KClass<*>.kompendiumSlug(): String? {
|
||||||
|
if (java.packageName == "java.lang") return simpleName
|
||||||
|
if (java.packageName == "java.util") return simpleName
|
||||||
|
val pkg = java.packageName
|
||||||
|
return qualifiedName?.replace(pkg, "")?.replace(".", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Adapts a class with type parameters into a reference friendly string
|
|
||||||
*/
|
|
||||||
private fun genericNameAdapter(type: KType, clazz: KClass<*>): String {
|
private fun genericNameAdapter(type: KType, clazz: KClass<*>): String {
|
||||||
val classNames = type.arguments
|
val classNames = type.arguments
|
||||||
.map { it.type?.classifier as KClass<*> }
|
.map { it.type?.classifier as KClass<*> }
|
||||||
.map { it.simpleName }
|
.map { it.kompendiumSlug() }
|
||||||
return classNames.joinToString(separator = "-", prefix = "${clazz.simpleName}-")
|
return classNames.joinToString(separator = "-", prefix = "${clazz.kompendiumSlug()}-")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import io.kotest.assertions.json.shouldEqualJson
|
|||||||
import io.kotest.assertions.throwables.shouldThrow
|
import io.kotest.assertions.throwables.shouldThrow
|
||||||
import io.kotest.core.spec.style.DescribeSpec
|
import io.kotest.core.spec.style.DescribeSpec
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
class SchemaGeneratorTest : DescribeSpec({
|
class SchemaGeneratorTest : DescribeSpec({
|
||||||
describe("Scalars") {
|
describe("Scalars") {
|
||||||
@ -24,6 +25,9 @@ class SchemaGeneratorTest : DescribeSpec({
|
|||||||
it("Can generate the schema for a String") {
|
it("Can generate the schema for a String") {
|
||||||
jsonSchemaTest<String>("T0003__scalar_string.json")
|
jsonSchemaTest<String>("T0003__scalar_string.json")
|
||||||
}
|
}
|
||||||
|
it("Can generate the schema for a UUID") {
|
||||||
|
jsonSchemaTest<UUID>("T0017__scalar_uuid.json")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
describe("Objects") {
|
describe("Objects") {
|
||||||
it("Can generate the schema for a simple object") {
|
it("Can generate the schema for a simple object") {
|
||||||
|
4
json-schema/src/test/resources/T0017__scalar_uuid.json
Normal file
4
json-schema/src/test/resources/T0017__scalar_uuid.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid"
|
||||||
|
}
|
@ -1,4 +1,3 @@
|
|||||||
# Module kompendium-locations
|
# Module kompendium-locations
|
||||||
|
|
||||||
Adds support for Ktor [Locations](https://ktor.io/docs/locations.html) API. Any notarized location _must_ be provided
|
Adds support for Ktor [Locations](https://ktor.io/docs/locations.html) API.
|
||||||
with a `TParam` annotated with `@Location`. Nested Locations are supported
|
|
||||||
|
@ -1,84 +0,0 @@
|
|||||||
package io.bkbn.kompendium.locations
|
|
||||||
|
|
||||||
//import io.bkbn.kompendium.annotations.Param
|
|
||||||
//import io.bkbn.kompendium.core.Kompendium
|
|
||||||
//import io.bkbn.kompendium.core.metadata.method.MethodInfo
|
|
||||||
//import io.bkbn.kompendium.core.parser.IMethodParser
|
|
||||||
//import io.bkbn.kompendium.oas.path.Path
|
|
||||||
//import io.bkbn.kompendium.oas.path.PathOperation
|
|
||||||
//import io.bkbn.kompendium.oas.payload.Parameter
|
|
||||||
//import io.ktor.application.feature
|
|
||||||
//import io.ktor.locations.KtorExperimentalLocationsAPI
|
|
||||||
//import io.ktor.locations.Location
|
|
||||||
//import io.ktor.routing.Route
|
|
||||||
//import io.ktor.routing.application
|
|
||||||
//import kotlin.reflect.KAnnotatedElement
|
|
||||||
//import kotlin.reflect.KClass
|
|
||||||
//import kotlin.reflect.KClassifier
|
|
||||||
//import kotlin.reflect.KType
|
|
||||||
//import kotlin.reflect.full.createType
|
|
||||||
//import kotlin.reflect.full.findAnnotation
|
|
||||||
//import kotlin.reflect.full.hasAnnotation
|
|
||||||
//import kotlin.reflect.full.memberProperties
|
|
||||||
//
|
|
||||||
//@OptIn(KtorExperimentalLocationsAPI::class)
|
|
||||||
//object LocationMethodParser : IMethodParser {
|
|
||||||
// override fun KType.toParameterSpec(info: MethodInfo<*, *>, feature: Kompendium): List<Parameter> {
|
|
||||||
// val clazzList = determineLocationParents(classifier!!)
|
|
||||||
// return clazzList.associateWith { it.memberProperties }
|
|
||||||
// .flatMap { (clazz, memberProperties) -> memberProperties.associateWith { clazz }.toList() }
|
|
||||||
// .filter { (prop, _) -> prop.hasAnnotation<Param>() }
|
|
||||||
// .map { (prop, clazz) -> prop.toParameter(info, clazz.createType(), clazz, feature) }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private fun determineLocationParents(classifier: KClassifier): List<KClass<*>> {
|
|
||||||
// var clazz: KClass<*>? = classifier as KClass<*>
|
|
||||||
// val clazzList = mutableListOf<KClass<*>>()
|
|
||||||
// while (clazz != null) {
|
|
||||||
// clazzList.add(clazz)
|
|
||||||
// clazz = getLocationParent(clazz)
|
|
||||||
// }
|
|
||||||
// return clazzList
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private fun getLocationParent(clazz: KClass<*>): KClass<*>? {
|
|
||||||
// val parent = clazz.memberProperties
|
|
||||||
// .find { (it.returnType.classifier as KAnnotatedElement).hasAnnotation<Location>() }
|
|
||||||
// return parent?.returnType?.classifier as? KClass<*>
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// fun KClass<*>.calculateLocationPath(suffix: String = ""): String {
|
|
||||||
// val locationAnnotation = this.findAnnotation<Location>()
|
|
||||||
// require(locationAnnotation != null) { "Location annotation must be present to leverage notarized location api" }
|
|
||||||
// val parent = this.java.declaringClass?.kotlin?.takeIf { it.hasAnnotation<Location>() }
|
|
||||||
// val newSuffix = locationAnnotation.path.plus(suffix)
|
|
||||||
// return when (parent) {
|
|
||||||
// null -> newSuffix
|
|
||||||
// else -> parent.calculateLocationPath(newSuffix)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// inline fun <reified TParam : Any> processBaseInfo(
|
|
||||||
// paramType: KType,
|
|
||||||
// requestType: KType,
|
|
||||||
// responseType: KType,
|
|
||||||
// info: MethodInfo<*, *>,
|
|
||||||
// route: Route
|
|
||||||
// ): LocationBaseInfo {
|
|
||||||
// val locationAnnotation = TParam::class.findAnnotation<Location>()
|
|
||||||
// require(locationAnnotation != null) { "Location annotation must be present to leverage notarized location api" }
|
|
||||||
// val path = route.calculateRoutePath()
|
|
||||||
// val locationPath = TParam::class.calculateLocationPath()
|
|
||||||
// val pathWithLocation = path.plus(locationPath)
|
|
||||||
// val feature = route.application.feature(Kompendium)
|
|
||||||
// feature.config.spec.paths.getOrPut(pathWithLocation) { Path() }
|
|
||||||
// val baseInfo = parseMethodInfo(info, paramType, requestType, responseType, feature)
|
|
||||||
// return LocationBaseInfo(baseInfo, feature, pathWithLocation)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// data class LocationBaseInfo(
|
|
||||||
// val op: PathOperation,
|
|
||||||
// val feature: Kompendium,
|
|
||||||
// val path: String
|
|
||||||
// )
|
|
||||||
//}
|
|
@ -1,110 +0,0 @@
|
|||||||
package io.bkbn.kompendium.locations
|
|
||||||
|
|
||||||
//import io.bkbn.kompendium.core.KompendiumPreFlight.methodNotarizationPreFlight
|
|
||||||
//import io.bkbn.kompendium.core.metadata.method.DeleteInfo
|
|
||||||
//import io.bkbn.kompendium.core.metadata.method.GetInfo
|
|
||||||
//import io.bkbn.kompendium.core.metadata.method.PostInfo
|
|
||||||
//import io.bkbn.kompendium.core.metadata.method.PutInfo
|
|
||||||
//import io.bkbn.kompendium.oas.path.PathOperation
|
|
||||||
//import io.ktor.application.ApplicationCall
|
|
||||||
//import io.ktor.http.HttpMethod
|
|
||||||
//import io.ktor.locations.KtorExperimentalLocationsAPI
|
|
||||||
//import io.ktor.locations.handle
|
|
||||||
//import io.ktor.locations.location
|
|
||||||
//import io.ktor.routing.Route
|
|
||||||
//import io.ktor.routing.method
|
|
||||||
//import io.ktor.util.pipeline.PipelineContext
|
|
||||||
//
|
|
||||||
///**
|
|
||||||
// * This version of notarized routes leverages the Ktor [io.ktor.locations.Locations] plugin to provide type safe access
|
|
||||||
// * to all path and query parameters.
|
|
||||||
// */
|
|
||||||
//@KtorExperimentalLocationsAPI
|
|
||||||
//object NotarizedLocation {
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Notarization for an HTTP GET request leveraging the Ktor [io.ktor.locations.Locations] plugin
|
|
||||||
// * @param TParam The class containing all parameter fields.
|
|
||||||
// * Each field must be annotated with @[io.bkbn.kompendium.annotations.Param].
|
|
||||||
// * Additionally, the class must be annotated with @[io.ktor.locations.Location].
|
|
||||||
// * @param TResp Class detailing the expected API response
|
|
||||||
// * @param info Route metadata
|
|
||||||
// * @param postProcess Adds an optional callback hook to perform manual overrides on the generated [PathOperation]
|
|
||||||
// */
|
|
||||||
// inline fun <reified TParam : Any, reified TResp : Any> Route.notarizedGet(
|
|
||||||
// info: GetInfo<TParam, TResp>,
|
|
||||||
// postProcess: (PathOperation) -> PathOperation = { p -> p },
|
|
||||||
// noinline body: suspend PipelineContext<Unit, ApplicationCall>.(TParam) -> Unit
|
|
||||||
// ): Route = methodNotarizationPreFlight<TParam, Unit, TResp>() { paramType, requestType, responseType ->
|
|
||||||
// val lbi = LocationMethodParser.processBaseInfo<TParam>(paramType, requestType, responseType, info, this)
|
|
||||||
// lbi.feature.config.spec.paths[lbi.path]?.get = postProcess(lbi.op)
|
|
||||||
// return location(TParam::class) {
|
|
||||||
// method(HttpMethod.Get) { handle(body) }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Notarization for an HTTP POST request leveraging the Ktor [io.ktor.locations.Locations] plugin
|
|
||||||
// * @param TParam The class containing all parameter fields.
|
|
||||||
// * Each field must be annotated with @[io.bkbn.kompendium.annotations.Param]
|
|
||||||
// * Additionally, the class must be annotated with @[io.ktor.locations.Location].
|
|
||||||
// * @param TReq Class detailing the expected API request body
|
|
||||||
// * @param TResp Class detailing the expected API response
|
|
||||||
// * @param info Route metadata
|
|
||||||
// * @param postProcess Adds an optional callback hook to perform manual overrides on the generated [PathOperation]
|
|
||||||
// */
|
|
||||||
// inline fun <reified TParam : Any, reified TReq : Any, reified TResp : Any> Route.notarizedPost(
|
|
||||||
// info: PostInfo<TParam, TReq, TResp>,
|
|
||||||
// postProcess: (PathOperation) -> PathOperation = { p -> p },
|
|
||||||
// noinline body: suspend PipelineContext<Unit, ApplicationCall>.(TParam) -> Unit
|
|
||||||
// ): Route = methodNotarizationPreFlight<TParam, TReq, TResp>() { paramType, requestType, responseType ->
|
|
||||||
// val lbi = LocationMethodParser.processBaseInfo<TParam>(paramType, requestType, responseType, info, this)
|
|
||||||
// lbi.feature.config.spec.paths[lbi.path]?.post = postProcess(lbi.op)
|
|
||||||
// return location(TParam::class) {
|
|
||||||
// method(HttpMethod.Post) { handle(body) }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Notarization for an HTTP Delete request leveraging the Ktor [io.ktor.locations.Locations] plugin
|
|
||||||
// * @param TParam The class containing all parameter fields.
|
|
||||||
// * Each field must be annotated with @[io.bkbn.kompendium.annotations.Param]
|
|
||||||
// * Additionally, the class must be annotated with @[io.ktor.locations.Location].
|
|
||||||
// * @param TReq Class detailing the expected API request body
|
|
||||||
// * @param TResp Class detailing the expected API response
|
|
||||||
// * @param info Route metadata
|
|
||||||
// * @param postProcess Adds an optional callback hook to perform manual overrides on the generated [PathOperation]
|
|
||||||
// */
|
|
||||||
// inline fun <reified TParam : Any, reified TReq : Any, reified TResp : Any> Route.notarizedPut(
|
|
||||||
// info: PutInfo<TParam, TReq, TResp>,
|
|
||||||
// postProcess: (PathOperation) -> PathOperation = { p -> p },
|
|
||||||
// noinline body: suspend PipelineContext<Unit, ApplicationCall>.(TParam) -> Unit
|
|
||||||
// ): Route = methodNotarizationPreFlight<TParam, TReq, TResp>() { paramType, requestType, responseType ->
|
|
||||||
// val lbi = LocationMethodParser.processBaseInfo<TParam>(paramType, requestType, responseType, info, this)
|
|
||||||
// lbi.feature.config.spec.paths[lbi.path]?.put = postProcess(lbi.op)
|
|
||||||
// return location(TParam::class) {
|
|
||||||
// method(HttpMethod.Put) { handle(body) }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Notarization for an HTTP POST request leveraging the Ktor [io.ktor.locations.Locations] plugin
|
|
||||||
// * @param TParam The class containing all parameter fields.
|
|
||||||
// * Each field must be annotated with @[io.bkbn.kompendium.annotations.Param]
|
|
||||||
// * Additionally, the class must be annotated with @[io.ktor.locations.Location].
|
|
||||||
// * @param TResp Class detailing the expected API response
|
|
||||||
// * @param info Route metadata
|
|
||||||
// * @param postProcess Adds an optional callback hook to perform manual overrides on the generated [PathOperation]
|
|
||||||
// */
|
|
||||||
// inline fun <reified TParam : Any, reified TResp : Any> Route.notarizedDelete(
|
|
||||||
// info: DeleteInfo<TParam, TResp>,
|
|
||||||
// postProcess: (PathOperation) -> PathOperation = { p -> p },
|
|
||||||
// noinline body: suspend PipelineContext<Unit, ApplicationCall>.(TParam) -> Unit
|
|
||||||
// ): Route = methodNotarizationPreFlight<TParam, Unit, TResp> { paramType, requestType, responseType ->
|
|
||||||
// val lbi = LocationMethodParser.processBaseInfo<TParam>(paramType, requestType, responseType, info, this)
|
|
||||||
// lbi.feature.config.spec.paths[lbi.path]?.delete = postProcess(lbi.op)
|
|
||||||
// return location(TParam::class) {
|
|
||||||
// method(HttpMethod.Delete) { handle(body) }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
@ -0,0 +1,79 @@
|
|||||||
|
package io.bkbn.kompendium.locations
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.core.attribute.KompendiumAttributes
|
||||||
|
import io.bkbn.kompendium.core.metadata.DeleteInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.GetInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.HeadInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.OptionsInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.PatchInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.PostInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.PutInfo
|
||||||
|
import io.bkbn.kompendium.core.util.Helpers.addToSpec
|
||||||
|
import io.bkbn.kompendium.core.util.SpecConfig
|
||||||
|
import io.bkbn.kompendium.oas.path.Path
|
||||||
|
import io.bkbn.kompendium.oas.payload.Parameter
|
||||||
|
import io.ktor.server.application.createApplicationPlugin
|
||||||
|
import io.ktor.server.locations.KtorExperimentalLocationsAPI
|
||||||
|
import io.ktor.server.locations.Location
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
import kotlin.reflect.full.findAnnotation
|
||||||
|
import kotlin.reflect.full.hasAnnotation
|
||||||
|
import kotlin.reflect.full.memberProperties
|
||||||
|
|
||||||
|
object NotarizedLocations {
|
||||||
|
|
||||||
|
data class LocationMetadata(
|
||||||
|
override var tags: Set<String> = emptySet(),
|
||||||
|
override var parameters: List<Parameter> = emptyList(),
|
||||||
|
override var get: GetInfo? = null,
|
||||||
|
override var post: PostInfo? = null,
|
||||||
|
override var put: PutInfo? = null,
|
||||||
|
override var delete: DeleteInfo? = null,
|
||||||
|
override var patch: PatchInfo? = null,
|
||||||
|
override var head: HeadInfo? = null,
|
||||||
|
override var options: OptionsInfo? = null,
|
||||||
|
override var security: Map<String, List<String>>? = null,
|
||||||
|
) : SpecConfig
|
||||||
|
|
||||||
|
class Config {
|
||||||
|
lateinit var locations: Map<KClass<*>, LocationMetadata>
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun invoke() = createApplicationPlugin(
|
||||||
|
name = "NotarizedLocations",
|
||||||
|
createConfiguration = ::Config
|
||||||
|
) {
|
||||||
|
val spec = application.attributes[KompendiumAttributes.openApiSpec]
|
||||||
|
pluginConfig.locations.forEach { (k, v) ->
|
||||||
|
val path = Path()
|
||||||
|
path.parameters = v.parameters
|
||||||
|
v.get?.addToSpec(path, spec, v)
|
||||||
|
v.delete?.addToSpec(path, spec, v)
|
||||||
|
v.head?.addToSpec(path, spec, v)
|
||||||
|
v.options?.addToSpec(path, spec, v)
|
||||||
|
v.post?.addToSpec(path, spec, v)
|
||||||
|
v.put?.addToSpec(path, spec, v)
|
||||||
|
v.patch?.addToSpec(path, spec, v)
|
||||||
|
|
||||||
|
val location = k.getLocationFromClass()
|
||||||
|
spec.paths[location] = path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(KtorExperimentalLocationsAPI::class)
|
||||||
|
private fun KClass<*>.getLocationFromClass(): String {
|
||||||
|
// todo if parent
|
||||||
|
|
||||||
|
val location = findAnnotation<Location>()
|
||||||
|
?: error("Cannot notarize a location without annotating with @Location")
|
||||||
|
|
||||||
|
val path = location.path
|
||||||
|
val parent = memberProperties.map { it.returnType.classifier as KClass<*> }.find { it.hasAnnotation<Location>() }
|
||||||
|
|
||||||
|
return if (parent == null) {
|
||||||
|
path
|
||||||
|
} else {
|
||||||
|
parent.getLocationFromClass() + path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,82 +1,119 @@
|
|||||||
package io.bkbn.kompendium.locations
|
package io.bkbn.kompendium.locations
|
||||||
|
|
||||||
//import io.bkbn.kompendium.core.fixtures.TestHelpers.openApiTestAllSerializers
|
import Listing
|
||||||
//import io.bkbn.kompendium.locations.util.locationsConfig
|
import io.bkbn.kompendium.core.fixtures.TestHelpers.openApiTestAllSerializers
|
||||||
//import io.bkbn.kompendium.locations.util.notarizedDeleteNestedLocation
|
import io.bkbn.kompendium.core.fixtures.TestResponse
|
||||||
//import io.bkbn.kompendium.locations.util.notarizedDeleteSimpleLocation
|
import io.bkbn.kompendium.core.metadata.GetInfo
|
||||||
//import io.bkbn.kompendium.locations.util.notarizedGetNestedLocation
|
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
||||||
//import io.bkbn.kompendium.locations.util.notarizedGetNestedLocationFromNonLocationClass
|
import io.bkbn.kompendium.oas.payload.Parameter
|
||||||
//import io.bkbn.kompendium.locations.util.notarizedGetSimpleLocation
|
import io.kotest.core.spec.style.DescribeSpec
|
||||||
//import io.bkbn.kompendium.locations.util.notarizedPostNestedLocation
|
import io.ktor.http.HttpStatusCode
|
||||||
//import io.bkbn.kompendium.locations.util.notarizedPostSimpleLocation
|
import io.ktor.server.application.call
|
||||||
//import io.bkbn.kompendium.locations.util.notarizedPutNestedLocation
|
import io.ktor.server.application.install
|
||||||
//import io.bkbn.kompendium.locations.util.notarizedPutSimpleLocation
|
import io.ktor.server.locations.Locations
|
||||||
//import io.kotest.core.spec.style.DescribeSpec
|
import io.ktor.server.locations.get
|
||||||
//
|
import io.ktor.server.response.respondText
|
||||||
//class KompendiumLocationsTest : DescribeSpec({
|
|
||||||
// describe("Locations") {
|
class KompendiumLocationsTest : DescribeSpec({
|
||||||
// it("Can notarize a get request with a simple location") {
|
describe("Location Tests") {
|
||||||
// // act
|
it("Can notarize a simple location") {
|
||||||
// openApiTestAllSerializers("notarized_get_simple_location.json") {
|
openApiTestAllSerializers(
|
||||||
// locationsConfig()
|
snapshotName = "T0001__simple_location.json",
|
||||||
// notarizedGetSimpleLocation()
|
applicationSetup = {
|
||||||
// }
|
install(Locations)
|
||||||
// }
|
install(NotarizedLocations()) {
|
||||||
// it("Can notarize a get request with a nested location") {
|
locations = mapOf(
|
||||||
// // act
|
Listing::class to NotarizedLocations.LocationMetadata(
|
||||||
// openApiTestAllSerializers("notarized_get_nested_location.json") {
|
parameters = listOf(
|
||||||
// locationsConfig()
|
Parameter(
|
||||||
// notarizedGetNestedLocation()
|
name = "name",
|
||||||
// }
|
`in` = Parameter.Location.path,
|
||||||
// }
|
schema = TypeDefinition.STRING
|
||||||
// it("Can notarize a post with a simple location") {
|
),
|
||||||
// // act
|
Parameter(
|
||||||
// openApiTestAllSerializers("notarized_post_simple_location.json") {
|
name = "page",
|
||||||
// locationsConfig()
|
`in` = Parameter.Location.path,
|
||||||
// notarizedPostSimpleLocation()
|
schema = TypeDefinition.INT
|
||||||
// }
|
)
|
||||||
// }
|
),
|
||||||
// it("Can notarize a post with a nested location") {
|
get = GetInfo.builder {
|
||||||
// // act
|
summary("Location")
|
||||||
// openApiTestAllSerializers("notarized_post_nested_location.json") {
|
description("example location")
|
||||||
// locationsConfig()
|
response {
|
||||||
// notarizedPostNestedLocation()
|
responseCode(HttpStatusCode.OK)
|
||||||
// }
|
responseType<TestResponse>()
|
||||||
// }
|
description("does great things")
|
||||||
// it("Can notarize a put with a simple location") {
|
}
|
||||||
// // act
|
}
|
||||||
// openApiTestAllSerializers("notarized_put_simple_location.json") {
|
),
|
||||||
// locationsConfig()
|
)
|
||||||
// notarizedPutSimpleLocation()
|
}
|
||||||
// }
|
}
|
||||||
// }
|
) {
|
||||||
// it("Can notarize a put with a nested location") {
|
get<Listing> { listing ->
|
||||||
// // act
|
call.respondText("Listing ${listing.name}, page ${listing.page}")
|
||||||
// openApiTestAllSerializers("notarized_put_nested_location.json") {
|
}
|
||||||
// locationsConfig()
|
}
|
||||||
// notarizedPutNestedLocation()
|
}
|
||||||
// }
|
it("Can notarize nested locations") {
|
||||||
// }
|
openApiTestAllSerializers(
|
||||||
// it("Can notarize a delete with a simple location") {
|
snapshotName = "T0002__nested_locations.json",
|
||||||
// // act
|
applicationSetup = {
|
||||||
// openApiTestAllSerializers("notarized_delete_simple_location.json") {
|
install(Locations)
|
||||||
// locationsConfig()
|
install(NotarizedLocations()) {
|
||||||
// notarizedDeleteSimpleLocation()
|
locations = mapOf(
|
||||||
// }
|
Type.Edit::class to NotarizedLocations.LocationMetadata(
|
||||||
// }
|
parameters = listOf(
|
||||||
// it("Can notarize a delete with a nested location") {
|
Parameter(
|
||||||
// // act
|
name = "name",
|
||||||
// openApiTestAllSerializers("notarized_delete_nested_location.json") {
|
`in` = Parameter.Location.path,
|
||||||
// locationsConfig()
|
schema = TypeDefinition.STRING
|
||||||
// notarizedDeleteNestedLocation()
|
)
|
||||||
// }
|
),
|
||||||
// }
|
get = GetInfo.builder {
|
||||||
// it("Can notarize a get with a nested location nested in a non-location class") {
|
summary("Edit")
|
||||||
// // act
|
description("example location")
|
||||||
// openApiTestAllSerializers("notarized_get_nested_location_from_non_location_class.json") {
|
response {
|
||||||
// locationsConfig()
|
responseCode(HttpStatusCode.OK)
|
||||||
// notarizedGetNestedLocationFromNonLocationClass()
|
responseType<TestResponse>()
|
||||||
// }
|
description("does great things")
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
//})
|
),
|
||||||
|
Type.Other::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("Other")
|
||||||
|
description("example location")
|
||||||
|
response {
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<TestResponse>()
|
||||||
|
description("does great things")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
get<Type.Edit> { edit ->
|
||||||
|
call.respondText("Listing ${edit.parent.name}")
|
||||||
|
}
|
||||||
|
get<Type.Other> { other ->
|
||||||
|
call.respondText("Listing ${other.parent.name}, page ${other.page}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
@ -1,22 +1,12 @@
|
|||||||
package io.bkbn.kompendium.locations.util
|
import io.ktor.server.locations.Location
|
||||||
|
|
||||||
//import io.bkbn.kompendium.annotations.Param
|
@Location("/list/{name}/page/{page}")
|
||||||
//import io.bkbn.kompendium.annotations.ParamType
|
data class Listing(val name: String, val page: Int)
|
||||||
//import io.ktor.locations.Location
|
|
||||||
//
|
@Location("/type/{name}")
|
||||||
//@Location("/test/{name}")
|
data class Type(val name: String) {
|
||||||
//data class SimpleLoc(@Param(ParamType.PATH) val name: String) {
|
@Location("/edit")
|
||||||
// @Location("/nesty")
|
data class Edit(val parent: Type)
|
||||||
// data class NestedLoc(@Param(ParamType.QUERY) val isCool: Boolean, val parent: SimpleLoc)
|
@Location("/other/{page}")
|
||||||
//}
|
data class Other(val parent: Type, val page: Int)
|
||||||
//
|
}
|
||||||
//object NonLocationObject {
|
|
||||||
// @Location("/test/{name}")
|
|
||||||
// data class SimpleLoc(@Param(ParamType.PATH) val name: String) {
|
|
||||||
// @Location("/nesty")
|
|
||||||
// data class NestedLoc(@Param(ParamType.QUERY) val isCool: Boolean, val parent: SimpleLoc)
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//data class SimpleResponse(val result: Boolean)
|
|
||||||
//data class SimpleRequest(val input: String)
|
|
||||||
|
@ -1,107 +0,0 @@
|
|||||||
package io.bkbn.kompendium.locations.util
|
|
||||||
|
|
||||||
//import io.bkbn.kompendium.locations.NotarizedLocation.notarizedDelete
|
|
||||||
//import io.bkbn.kompendium.locations.NotarizedLocation.notarizedGet
|
|
||||||
//import io.bkbn.kompendium.locations.NotarizedLocation.notarizedPost
|
|
||||||
//import io.bkbn.kompendium.locations.NotarizedLocation.notarizedPut
|
|
||||||
//import io.ktor.application.Application
|
|
||||||
//import io.ktor.application.call
|
|
||||||
//import io.ktor.application.install
|
|
||||||
//import io.ktor.locations.Locations
|
|
||||||
//import io.ktor.response.respondText
|
|
||||||
//import io.ktor.routing.route
|
|
||||||
//import io.ktor.routing.routing
|
|
||||||
//
|
|
||||||
//fun Application.locationsConfig() {
|
|
||||||
// install(Locations)
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//fun Application.notarizedGetSimpleLocation() {
|
|
||||||
// routing {
|
|
||||||
// route("/test") {
|
|
||||||
// notarizedGet(TestResponseInfo.testGetSimpleLocation) {
|
|
||||||
// call.respondText { "hey dude ‼️ congratz on the get request" }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//fun Application.notarizedGetNestedLocation() {
|
|
||||||
// routing {
|
|
||||||
// route("/test") {
|
|
||||||
// notarizedGet(TestResponseInfo.testGetNestedLocation) {
|
|
||||||
// call.respondText { "hey dude ‼️ congratz on the get request" }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//fun Application.notarizedPostSimpleLocation() {
|
|
||||||
// routing {
|
|
||||||
// route("/test") {
|
|
||||||
// notarizedPost(TestResponseInfo.testPostSimpleLocation) {
|
|
||||||
// call.respondText { "hey dude ‼️ congratz on the get request" }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//fun Application.notarizedPostNestedLocation() {
|
|
||||||
// routing {
|
|
||||||
// route("/test") {
|
|
||||||
// notarizedPost(TestResponseInfo.testPostNestedLocation) {
|
|
||||||
// call.respondText { "hey dude ‼️ congratz on the get request" }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//fun Application.notarizedPutSimpleLocation() {
|
|
||||||
// routing {
|
|
||||||
// route("/test") {
|
|
||||||
// notarizedPut(TestResponseInfo.testPutSimpleLocation) {
|
|
||||||
// call.respondText { "hey dude ‼️ congratz on the get request" }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//fun Application.notarizedPutNestedLocation() {
|
|
||||||
// routing {
|
|
||||||
// route("/test") {
|
|
||||||
// notarizedPut(TestResponseInfo.testPutNestedLocation) {
|
|
||||||
// call.respondText { "hey dude ‼️ congratz on the get request" }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//fun Application.notarizedDeleteSimpleLocation() {
|
|
||||||
// routing {
|
|
||||||
// route("/test") {
|
|
||||||
// notarizedDelete(TestResponseInfo.testDeleteSimpleLocation) {
|
|
||||||
// call.respondText { "hey dude ‼️ congratz on the get request" }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//fun Application.notarizedDeleteNestedLocation() {
|
|
||||||
// routing {
|
|
||||||
// route("/test") {
|
|
||||||
// notarizedDelete(TestResponseInfo.testDeleteNestedLocation) {
|
|
||||||
// call.respondText { "hey dude ‼️ congratz on the get request" }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//fun Application.notarizedGetNestedLocationFromNonLocationClass() {
|
|
||||||
// routing {
|
|
||||||
// route("/test") {
|
|
||||||
// notarizedGet(TestResponseInfo.testGetNestedLocationFromNonLocationClass) {
|
|
||||||
// call.respondText { "hey dude ‼️ congratz on the get request" }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
@ -1,97 +0,0 @@
|
|||||||
package io.bkbn.kompendium.locations.util
|
|
||||||
|
|
||||||
//import io.bkbn.kompendium.core.legacy.metadata.RequestInfo
|
|
||||||
//import io.bkbn.kompendium.core.legacy.metadata.ResponseInfo
|
|
||||||
//import io.bkbn.kompendium.core.legacy.metadata.method.DeleteInfo
|
|
||||||
//import io.bkbn.kompendium.core.legacy.metadata.method.GetInfo
|
|
||||||
//import io.bkbn.kompendium.core.legacy.metadata.method.PostInfo
|
|
||||||
//import io.bkbn.kompendium.core.legacy.metadata.method.PutInfo
|
|
||||||
//import io.ktor.http.HttpStatusCode
|
|
||||||
//
|
|
||||||
//object TestResponseInfo {
|
|
||||||
// val testGetSimpleLocation = GetInfo<SimpleLoc, SimpleResponse>(
|
|
||||||
// summary = "Location Test",
|
|
||||||
// description = "A cool test",
|
|
||||||
// responseInfo = ResponseInfo(
|
|
||||||
// status = HttpStatusCode.OK,
|
|
||||||
// description = "A successful endeavor"
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
// val testPostSimpleLocation = PostInfo<SimpleLoc, SimpleRequest, SimpleResponse>(
|
|
||||||
// summary = "Location Test",
|
|
||||||
// description = "A cool test",
|
|
||||||
// requestInfo = RequestInfo(
|
|
||||||
// description = "Cool stuff"
|
|
||||||
// ),
|
|
||||||
// responseInfo = ResponseInfo(
|
|
||||||
// status = HttpStatusCode.OK,
|
|
||||||
// description = "A successful endeavor"
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
// val testPutSimpleLocation = PutInfo<SimpleLoc, SimpleRequest, SimpleResponse>(
|
|
||||||
// summary = "Location Test",
|
|
||||||
// description = "A cool test",
|
|
||||||
// requestInfo = RequestInfo(
|
|
||||||
// description = "Cool stuff"
|
|
||||||
// ),
|
|
||||||
// responseInfo = ResponseInfo(
|
|
||||||
// status = HttpStatusCode.OK,
|
|
||||||
// description = "A successful endeavor"
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
// val testDeleteSimpleLocation = DeleteInfo<SimpleLoc, SimpleResponse>(
|
|
||||||
// summary = "Location Test",
|
|
||||||
// description = "A cool test",
|
|
||||||
// responseInfo = ResponseInfo(
|
|
||||||
// status = HttpStatusCode.OK,
|
|
||||||
// description = "A successful endeavor"
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
// val testGetNestedLocation = GetInfo<SimpleLoc.NestedLoc, SimpleResponse>(
|
|
||||||
// summary = "Location Test",
|
|
||||||
// description = "A cool test",
|
|
||||||
// responseInfo = ResponseInfo(
|
|
||||||
// status = HttpStatusCode.OK,
|
|
||||||
// description = "A successful endeavor"
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
// val testPostNestedLocation = PostInfo<SimpleLoc.NestedLoc, SimpleRequest, SimpleResponse>(
|
|
||||||
// summary = "Location Test",
|
|
||||||
// description = "A cool test",
|
|
||||||
// requestInfo = RequestInfo(
|
|
||||||
// description = "Cool stuff"
|
|
||||||
// ),
|
|
||||||
// responseInfo = ResponseInfo(
|
|
||||||
// status = HttpStatusCode.OK,
|
|
||||||
// description = "A successful endeavor"
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
// val testPutNestedLocation = PutInfo<SimpleLoc.NestedLoc, SimpleRequest, SimpleResponse>(
|
|
||||||
// summary = "Location Test",
|
|
||||||
// description = "A cool test",
|
|
||||||
// requestInfo = RequestInfo(
|
|
||||||
// description = "Cool stuff"
|
|
||||||
// ),
|
|
||||||
// responseInfo = ResponseInfo(
|
|
||||||
// status = HttpStatusCode.OK,
|
|
||||||
// description = "A successful endeavor"
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
// val testDeleteNestedLocation = DeleteInfo<SimpleLoc.NestedLoc, SimpleResponse>(
|
|
||||||
// summary = "Location Test",
|
|
||||||
// description = "A cool test",
|
|
||||||
// responseInfo = ResponseInfo(
|
|
||||||
// status = HttpStatusCode.OK,
|
|
||||||
// description = "A successful endeavor"
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
// val testGetNestedLocationFromNonLocationClass = GetInfo<NonLocationObject.SimpleLoc.NestedLoc, SimpleResponse>(
|
|
||||||
// summary = "Location Test",
|
|
||||||
// description = "A cool test",
|
|
||||||
// responseInfo = ResponseInfo(
|
|
||||||
// status = HttpStatusCode.OK,
|
|
||||||
// description = "A successful endeavor"
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
//}
|
|
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"openapi": "3.0.3",
|
"openapi": "3.1.0",
|
||||||
|
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"info": {
|
"info": {
|
||||||
"title": "Test API",
|
"title": "Test API",
|
||||||
"version": "1.33.7",
|
"version": "1.33.7",
|
||||||
@ -26,21 +27,27 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"/test/test/{name}/nesty": {
|
"/list/{name}/page/{page}": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"summary": "Location Test",
|
"summary": "Location",
|
||||||
"description": "A cool test",
|
"description": "example location",
|
||||||
"parameters": [
|
"parameters": [],
|
||||||
{
|
"responses": {
|
||||||
"name": "isCool",
|
"200": {
|
||||||
"in": "query",
|
"description": "does great things",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "boolean"
|
"$ref": "#/components/schemas/TestResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"required": true,
|
|
||||||
"deprecated": false
|
"deprecated": false
|
||||||
},
|
},
|
||||||
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "name",
|
"name": "name",
|
||||||
"in": "path",
|
"in": "path",
|
||||||
@ -49,36 +56,33 @@
|
|||||||
},
|
},
|
||||||
"required": true,
|
"required": true,
|
||||||
"deprecated": false
|
"deprecated": false
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "A successful endeavor",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/SimpleResponse"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "page",
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
"deprecated": false
|
"deprecated": false
|
||||||
}
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"webhooks": {},
|
||||||
"components": {
|
"components": {
|
||||||
"schemas": {
|
"schemas": {
|
||||||
"SimpleResponse": {
|
"TestResponse": {
|
||||||
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"result": {
|
"c": {
|
||||||
"type": "boolean"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"result"
|
"c"
|
||||||
],
|
]
|
||||||
"type": "object"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"securitySchemes": {}
|
"securitySchemes": {}
|
124
locations/src/test/resources/T0002__nested_locations.json
Normal file
124
locations/src/test/resources/T0002__nested_locations.json
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
{
|
||||||
|
"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": {
|
||||||
|
"/type/{name}/edit": {
|
||||||
|
"get": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Edit",
|
||||||
|
"description": "example location",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "does great things",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"deprecated": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"/type/{name}/other/{page}": {
|
||||||
|
"get": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Other",
|
||||||
|
"description": "example location",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "does great things",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "page",
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"deprecated": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"TestResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"c": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"c"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
@ -1,79 +0,0 @@
|
|||||||
{
|
|
||||||
"openapi": "3.0.3",
|
|
||||||
"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": {
|
|
||||||
"/test/test/{name}": {
|
|
||||||
"delete": {
|
|
||||||
"tags": [],
|
|
||||||
"summary": "Location Test",
|
|
||||||
"description": "A cool test",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "name",
|
|
||||||
"in": "path",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"required": true,
|
|
||||||
"deprecated": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "A successful endeavor",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/SimpleResponse"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"deprecated": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"components": {
|
|
||||||
"schemas": {
|
|
||||||
"SimpleResponse": {
|
|
||||||
"properties": {
|
|
||||||
"result": {
|
|
||||||
"type": "boolean"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"result"
|
|
||||||
],
|
|
||||||
"type": "object"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"securitySchemes": {}
|
|
||||||
},
|
|
||||||
"security": [],
|
|
||||||
"tags": []
|
|
||||||
}
|
|
@ -1,110 +0,0 @@
|
|||||||
{
|
|
||||||
"openapi": "3.0.3",
|
|
||||||
"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": {
|
|
||||||
"/test/test/{name}/nesty": {
|
|
||||||
"post": {
|
|
||||||
"tags": [],
|
|
||||||
"summary": "Location Test",
|
|
||||||
"description": "A cool test",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "isCool",
|
|
||||||
"in": "query",
|
|
||||||
"schema": {
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"required": true,
|
|
||||||
"deprecated": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "name",
|
|
||||||
"in": "path",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"required": true,
|
|
||||||
"deprecated": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"requestBody": {
|
|
||||||
"description": "Cool stuff",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/SimpleRequest"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "A successful endeavor",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/SimpleResponse"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"deprecated": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"components": {
|
|
||||||
"schemas": {
|
|
||||||
"SimpleRequest": {
|
|
||||||
"properties": {
|
|
||||||
"input": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"input"
|
|
||||||
],
|
|
||||||
"type": "object"
|
|
||||||
},
|
|
||||||
"SimpleResponse": {
|
|
||||||
"properties": {
|
|
||||||
"result": {
|
|
||||||
"type": "boolean"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"result"
|
|
||||||
],
|
|
||||||
"type": "object"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"securitySchemes": {}
|
|
||||||
},
|
|
||||||
"security": [],
|
|
||||||
"tags": []
|
|
||||||
}
|
|
@ -1,110 +0,0 @@
|
|||||||
{
|
|
||||||
"openapi": "3.0.3",
|
|
||||||
"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": {
|
|
||||||
"/test/test/{name}/nesty": {
|
|
||||||
"put": {
|
|
||||||
"tags": [],
|
|
||||||
"summary": "Location Test",
|
|
||||||
"description": "A cool test",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "isCool",
|
|
||||||
"in": "query",
|
|
||||||
"schema": {
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"required": true,
|
|
||||||
"deprecated": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "name",
|
|
||||||
"in": "path",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"required": true,
|
|
||||||
"deprecated": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"requestBody": {
|
|
||||||
"description": "Cool stuff",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/SimpleRequest"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "A successful endeavor",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/SimpleResponse"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"deprecated": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"components": {
|
|
||||||
"schemas": {
|
|
||||||
"SimpleRequest": {
|
|
||||||
"properties": {
|
|
||||||
"input": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"input"
|
|
||||||
],
|
|
||||||
"type": "object"
|
|
||||||
},
|
|
||||||
"SimpleResponse": {
|
|
||||||
"properties": {
|
|
||||||
"result": {
|
|
||||||
"type": "boolean"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"result"
|
|
||||||
],
|
|
||||||
"type": "object"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"securitySchemes": {}
|
|
||||||
},
|
|
||||||
"security": [],
|
|
||||||
"tags": []
|
|
||||||
}
|
|
@ -31,7 +31,7 @@ dependencies {
|
|||||||
|
|
||||||
// Logging
|
// Logging
|
||||||
implementation("org.apache.logging.log4j:log4j-api-kotlin:1.2.0")
|
implementation("org.apache.logging.log4j:log4j-api-kotlin:1.2.0")
|
||||||
implementation("org.apache.logging.log4j:log4j-api:2.17.2")
|
implementation("org.apache.logging.log4j:log4j-api:2.18.0")
|
||||||
implementation("org.apache.logging.log4j:log4j-core:2.18.0")
|
implementation("org.apache.logging.log4j:log4j-core:2.18.0")
|
||||||
implementation("org.slf4j:slf4j-api:1.7.36")
|
implementation("org.slf4j:slf4j-api:1.7.36")
|
||||||
implementation("org.slf4j:slf4j-simple:1.7.36")
|
implementation("org.slf4j:slf4j-simple:1.7.36")
|
||||||
|
@ -30,10 +30,6 @@ import io.ktor.server.routing.route
|
|||||||
import io.ktor.server.routing.routing
|
import io.ktor.server.routing.routing
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
/**
|
|
||||||
* Application entrypoint. Run this and head on over to `localhost:8081/docs`
|
|
||||||
* to see a very simple yet beautifully documented API
|
|
||||||
*/
|
|
||||||
fun main() {
|
fun main() {
|
||||||
embeddedServer(
|
embeddedServer(
|
||||||
Netty,
|
Netty,
|
||||||
@ -43,7 +39,6 @@ fun main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun Application.mainModule() {
|
private fun Application.mainModule() {
|
||||||
// Installs Simple JSON Content Negotiation
|
|
||||||
install(ContentNegotiation) {
|
install(ContentNegotiation) {
|
||||||
json(Json {
|
json(Json {
|
||||||
serializersModule = KompendiumSerializersModule.module
|
serializersModule = KompendiumSerializersModule.module
|
||||||
@ -75,12 +70,8 @@ private fun Application.mainModule() {
|
|||||||
routing {
|
routing {
|
||||||
redoc(pageTitle = "Simple API Docs")
|
redoc(pageTitle = "Simple API Docs")
|
||||||
authenticate("basic") {
|
authenticate("basic") {
|
||||||
// This adds ReDoc support at the `/docs` endpoint.
|
|
||||||
// By default, it will point at the `/openapi.json` created by Kompendium
|
|
||||||
|
|
||||||
// Route with a get handler
|
|
||||||
route("/{id}") {
|
route("/{id}") {
|
||||||
idDocumentation()
|
locationDocumentation()
|
||||||
get {
|
get {
|
||||||
call.respond(HttpStatusCode.OK, ExampleResponse(true))
|
call.respond(HttpStatusCode.OK, ExampleResponse(true))
|
||||||
}
|
}
|
||||||
@ -89,8 +80,7 @@ private fun Application.mainModule() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Documentation for our route
|
private fun Route.locationDocumentation() {
|
||||||
private fun Route.idDocumentation() {
|
|
||||||
install(NotarizedRoute()) {
|
install(NotarizedRoute()) {
|
||||||
parameters = listOf(
|
parameters = listOf(
|
||||||
Parameter(
|
Parameter(
|
||||||
|
@ -47,7 +47,7 @@ private fun Application.mainModule() {
|
|||||||
redoc(pageTitle = "Simple API Docs")
|
redoc(pageTitle = "Simple API Docs")
|
||||||
|
|
||||||
route("/{id}") {
|
route("/{id}") {
|
||||||
idDocumentation()
|
documentation()
|
||||||
get {
|
get {
|
||||||
call.respond(HttpStatusCode.OK, ExampleResponse(true))
|
call.respond(HttpStatusCode.OK, ExampleResponse(true))
|
||||||
}
|
}
|
||||||
@ -55,7 +55,7 @@ private fun Application.mainModule() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Route.idDocumentation() {
|
private fun Route.documentation() {
|
||||||
install(NotarizedRoute()) {
|
install(NotarizedRoute()) {
|
||||||
parameters = listOf(
|
parameters = listOf(
|
||||||
Parameter(
|
Parameter(
|
||||||
|
@ -54,7 +54,7 @@ private fun Application.mainModule() {
|
|||||||
redoc(pageTitle = "Simple API Docs")
|
redoc(pageTitle = "Simple API Docs")
|
||||||
|
|
||||||
route("/{id}") {
|
route("/{id}") {
|
||||||
idDocumentation()
|
locationDocumentation()
|
||||||
get {
|
get {
|
||||||
call.respond(
|
call.respond(
|
||||||
HttpStatusCode.OK,
|
HttpStatusCode.OK,
|
||||||
@ -68,7 +68,7 @@ private fun Application.mainModule() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Route.idDocumentation() {
|
private fun Route.locationDocumentation() {
|
||||||
install(NotarizedRoute()) {
|
install(NotarizedRoute()) {
|
||||||
parameters = listOf(
|
parameters = listOf(
|
||||||
Parameter(
|
Parameter(
|
||||||
|
@ -13,7 +13,6 @@ import io.bkbn.kompendium.playground.util.Util.baseSpec
|
|||||||
import io.ktor.http.HttpStatusCode
|
import io.ktor.http.HttpStatusCode
|
||||||
import io.ktor.serialization.kotlinx.json.json
|
import io.ktor.serialization.kotlinx.json.json
|
||||||
import io.ktor.server.application.Application
|
import io.ktor.server.application.Application
|
||||||
import io.ktor.server.application.call
|
|
||||||
import io.ktor.server.application.install
|
import io.ktor.server.application.install
|
||||||
import io.ktor.server.engine.embeddedServer
|
import io.ktor.server.engine.embeddedServer
|
||||||
import io.ktor.server.netty.Netty
|
import io.ktor.server.netty.Netty
|
||||||
@ -55,7 +54,7 @@ private fun Application.mainModule() {
|
|||||||
redoc(pageTitle = "Simple API Docs")
|
redoc(pageTitle = "Simple API Docs")
|
||||||
|
|
||||||
route("/{id}") {
|
route("/{id}") {
|
||||||
idDocumentation()
|
locationDocumentation()
|
||||||
get {
|
get {
|
||||||
throw RuntimeException("This wasn't your fault I promise <3")
|
throw RuntimeException("This wasn't your fault I promise <3")
|
||||||
}
|
}
|
||||||
@ -63,7 +62,7 @@ private fun Application.mainModule() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Route.idDocumentation() {
|
private fun Route.locationDocumentation() {
|
||||||
install(NotarizedRoute()) {
|
install(NotarizedRoute()) {
|
||||||
parameters = listOf(
|
parameters = listOf(
|
||||||
Parameter(
|
Parameter(
|
||||||
|
@ -43,7 +43,7 @@ private fun Application.mainModule() {
|
|||||||
redoc(pageTitle = "Simple API Docs")
|
redoc(pageTitle = "Simple API Docs")
|
||||||
|
|
||||||
route("/{id}") {
|
route("/{id}") {
|
||||||
idDocumentation()
|
locationDocumentation()
|
||||||
get {
|
get {
|
||||||
call.respond(HttpStatusCode.OK, ExampleResponse(true))
|
call.respond(HttpStatusCode.OK, ExampleResponse(true))
|
||||||
}
|
}
|
||||||
@ -51,7 +51,7 @@ private fun Application.mainModule() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Route.idDocumentation() {
|
private fun Route.locationDocumentation() {
|
||||||
install(NotarizedRoute()) {
|
install(NotarizedRoute()) {
|
||||||
parameters = listOf(
|
parameters = listOf(
|
||||||
Parameter(
|
Parameter(
|
||||||
|
@ -0,0 +1,116 @@
|
|||||||
|
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
|
||||||
|
import io.bkbn.kompendium.core.routes.redoc
|
||||||
|
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
||||||
|
import io.bkbn.kompendium.oas.component.Components
|
||||||
|
import io.bkbn.kompendium.oas.payload.Parameter
|
||||||
|
import io.bkbn.kompendium.oas.security.BasicAuth
|
||||||
|
import io.bkbn.kompendium.oas.serialization.KompendiumSerializersModule
|
||||||
|
import io.bkbn.kompendium.playground.util.ExampleResponse
|
||||||
|
import io.bkbn.kompendium.playground.util.Util.baseSpec
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.serialization.kotlinx.json.json
|
||||||
|
import io.ktor.server.application.Application
|
||||||
|
import io.ktor.server.application.call
|
||||||
|
import io.ktor.server.application.install
|
||||||
|
import io.ktor.server.auth.Authentication
|
||||||
|
import io.ktor.server.auth.UserIdPrincipal
|
||||||
|
import io.ktor.server.auth.authenticate
|
||||||
|
import io.ktor.server.auth.basic
|
||||||
|
import io.ktor.server.engine.embeddedServer
|
||||||
|
import io.ktor.server.netty.Netty
|
||||||
|
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
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
embeddedServer(
|
||||||
|
Netty,
|
||||||
|
port = 8081,
|
||||||
|
module = Application::mainModule
|
||||||
|
).start(wait = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Application.mainModule() {
|
||||||
|
install(ContentNegotiation) {
|
||||||
|
json(Json {
|
||||||
|
serializersModule = KompendiumSerializersModule.module
|
||||||
|
encodeDefaults = true
|
||||||
|
explicitNulls = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
install(Authentication) {
|
||||||
|
basic("basic") {
|
||||||
|
realm = "Ktor Server"
|
||||||
|
validate { credentials ->
|
||||||
|
if (credentials.name == credentials.password) {
|
||||||
|
UserIdPrincipal(credentials.name)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
install(NotarizedApplication()) {
|
||||||
|
spec = baseSpec.copy(
|
||||||
|
components = Components(
|
||||||
|
securitySchemes = mutableMapOf(
|
||||||
|
"basic" to BasicAuth()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
openApiJson = {
|
||||||
|
authenticate("basic") {
|
||||||
|
route("/openapi.json") {
|
||||||
|
get {
|
||||||
|
call.respond(HttpStatusCode.OK, this@route.application.attributes[KompendiumAttributes.openApiSpec])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
routing {
|
||||||
|
authenticate("basic") {
|
||||||
|
redoc(pageTitle = "Simple API Docs")
|
||||||
|
route("/{id}") {
|
||||||
|
locationDocumentation()
|
||||||
|
get {
|
||||||
|
call.respond(HttpStatusCode.OK, ExampleResponse(true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Route.locationDocumentation() {
|
||||||
|
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!")
|
||||||
|
security = mapOf(
|
||||||
|
"basic" to emptyList()
|
||||||
|
)
|
||||||
|
response {
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<ExampleResponse>()
|
||||||
|
description("Will return whether or not the user is real 😱")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -46,7 +46,7 @@ private fun Application.mainModule() {
|
|||||||
redoc(pageTitle = "Simple API Docs")
|
redoc(pageTitle = "Simple API Docs")
|
||||||
|
|
||||||
route("/{id}") {
|
route("/{id}") {
|
||||||
idDocumentation()
|
locationDocumentation()
|
||||||
get {
|
get {
|
||||||
call.respond(HttpStatusCode.OK, ExampleResponse(true))
|
call.respond(HttpStatusCode.OK, ExampleResponse(true))
|
||||||
}
|
}
|
||||||
@ -54,7 +54,7 @@ private fun Application.mainModule() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Route.idDocumentation() {
|
private fun Route.locationDocumentation() {
|
||||||
install(NotarizedRoute()) {
|
install(NotarizedRoute()) {
|
||||||
parameters = listOf(
|
parameters = listOf(
|
||||||
Parameter(
|
Parameter(
|
||||||
|
@ -0,0 +1,80 @@
|
|||||||
|
package io.bkbn.kompendium.playground
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.core.metadata.GetInfo
|
||||||
|
import io.bkbn.kompendium.core.plugin.NotarizedApplication
|
||||||
|
import io.bkbn.kompendium.core.routes.redoc
|
||||||
|
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
||||||
|
import io.bkbn.kompendium.locations.NotarizedLocations
|
||||||
|
import io.bkbn.kompendium.oas.payload.Parameter
|
||||||
|
import io.bkbn.kompendium.oas.serialization.KompendiumSerializersModule
|
||||||
|
import io.bkbn.kompendium.playground.util.ExampleResponse
|
||||||
|
import io.bkbn.kompendium.playground.util.Listing
|
||||||
|
import io.bkbn.kompendium.playground.util.Util.baseSpec
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.serialization.kotlinx.json.json
|
||||||
|
import io.ktor.server.application.Application
|
||||||
|
import io.ktor.server.application.call
|
||||||
|
import io.ktor.server.application.install
|
||||||
|
import io.ktor.server.engine.embeddedServer
|
||||||
|
import io.ktor.server.locations.Locations
|
||||||
|
import io.ktor.server.locations.get
|
||||||
|
import io.ktor.server.netty.Netty
|
||||||
|
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
|
||||||
|
import io.ktor.server.response.respondText
|
||||||
|
import io.ktor.server.routing.routing
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
embeddedServer(
|
||||||
|
Netty,
|
||||||
|
port = 8081,
|
||||||
|
module = Application::mainModule
|
||||||
|
).start(wait = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Application.mainModule() {
|
||||||
|
install(Locations)
|
||||||
|
install(ContentNegotiation) {
|
||||||
|
json(Json {
|
||||||
|
serializersModule = KompendiumSerializersModule.module
|
||||||
|
encodeDefaults = true
|
||||||
|
explicitNulls = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
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 😱")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
routing {
|
||||||
|
redoc(pageTitle = "Simple API Docs")
|
||||||
|
get<Listing> { listing ->
|
||||||
|
call.respondText("Listing ${listing.name}, page ${listing.page}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,7 @@
|
|||||||
package io.bkbn.kompendium.playground.util
|
package io.bkbn.kompendium.playground.util
|
||||||
|
|
||||||
|
import io.ktor.server.locations.KtorExperimentalLocationsAPI
|
||||||
|
import io.ktor.server.locations.Location
|
||||||
import kotlinx.datetime.Instant
|
import kotlinx.datetime.Instant
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@ -14,3 +16,12 @@ data class CustomTypeResponse(
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ExceptionResponse(val message: String)
|
data class ExceptionResponse(val message: String)
|
||||||
|
|
||||||
|
@Location("/list/{name}/page/{page}")
|
||||||
|
data class Listing(val name: String, val page: Int)
|
||||||
|
|
||||||
|
@Location("/type/{name}") data class Type(val name: String) {
|
||||||
|
// In these classes we have to include the `name` property matching the parent.
|
||||||
|
@Location("/edit") data class Edit(val parent: Type)
|
||||||
|
@Location("/other/{page}") data class Other(val parent: Type, val page: Int)
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user