Compare commits
114 Commits
v3.6.0
...
v4.0.0-alp
Author | SHA1 | Date | |
---|---|---|---|
f2786fec08 | |||
904cc6816d | |||
a06821a910 | |||
a9dffcf8a5 | |||
548b1ff4bc | |||
6f85ee476a | |||
326bbb4419 | |||
438eb52dc8 | |||
1fd59db4e1 | |||
238e372460 | |||
473e2c22af | |||
7fb7e688ac | |||
9f2c27aa0f | |||
dc762d1d3e | |||
b039803246 | |||
6ec240d7cd | |||
dd57347b90 | |||
43c8fd1338 | |||
6c2725680e | |||
84dfe4e6fe | |||
a0c1abf011 | |||
25820d0920 | |||
7c18283ac8 | |||
c5f3b7fa8b | |||
8b81010d72 | |||
c07ffa6a14 | |||
7793908ab6 | |||
bf6d08c2bd | |||
68bae4918e | |||
592c116c3b | |||
c7fe5c288f | |||
e059633055 | |||
884a50fc83 | |||
588e52c9df | |||
f49fcb2a22 | |||
0a9475a7ab | |||
f8fbb7ad25 | |||
f792fb5d1f | |||
845d1a971d | |||
9396b2ecfe | |||
d74b7b3f28 | |||
e783630845 | |||
d907ba004a | |||
5c7ec4fdb4 | |||
097413067b | |||
eb7360b8d2 | |||
3ec467c1d8 | |||
152476c26e | |||
c4df4c7b2c | |||
87fa0b5602 | |||
53c76d6037 | |||
44324ca3a4 | |||
9364fb19e4 | |||
ca1f632665 | |||
9bb3184735 | |||
9a139b5713 | |||
9861c8e258 | |||
02f8ed04f2 | |||
3e23939386 | |||
d0575944db | |||
ccc81c2813 | |||
e8da52fd75 | |||
34c14e26da | |||
209b5e38a3 | |||
902faa9471 | |||
c7ef70a844 | |||
f83c3d203d | |||
bd05dbcfcb | |||
cb5c0e71a3 | |||
a209cafa74 | |||
3d27d30a31 | |||
8cc229667e | |||
54d12de67a | |||
9b93c887a2 | |||
19d828956b | |||
eabe90acfc | |||
7c2a2a9c9d | |||
15cdfb229d | |||
5a40f37f81 | |||
64f2516f19 | |||
2e5e39d3b2 | |||
5342cf00d1 | |||
8c0b658033 | |||
b7b1171685 | |||
1f730869a8 | |||
377a60614e | |||
e34bea1769 | |||
91bf93a866 | |||
73fb8b137f | |||
d3d6e79329 | |||
f2b7d924e0 | |||
f62010b4e7 | |||
5bd1534270 | |||
4c151ffeea | |||
08a0d2e47c | |||
f19476d3a3 | |||
8ede53fd5c | |||
31a9f44ec1 | |||
0eb2f126b3 | |||
0d658bd6a8 | |||
e924671c2b | |||
04996631b9 | |||
82d80873c8 | |||
487eaba741 | |||
b935cc1e1d | |||
9f2d2dd4e3 | |||
20f8a4f08f | |||
7b39e448bf | |||
3c57c8f5e4 | |||
01b6b59cf5 | |||
2c1c8fbcdc | |||
0dc2b9538f | |||
1ca7abaf0d | |||
a5376cfa82 |
12
.github/workflows/autoupdate.yml
vendored
12
.github/workflows/autoupdate.yml
vendored
@ -1,12 +0,0 @@
|
|||||||
name: autoupdate
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ main ]
|
|
||||||
jobs:
|
|
||||||
autoupdate:
|
|
||||||
name: autoupdate
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: docker://chinthakagodawita/autoupdate-action:v1
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
|
|
100
CHANGELOG.md
100
CHANGELOG.md
@ -12,6 +12,106 @@
|
|||||||
|
|
||||||
## Released
|
## Released
|
||||||
|
|
||||||
|
## [4.0.0-alpha] - September 3rd, 2023
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Support for `type` on sealed interfaces
|
||||||
|
- Ability to provide custom serializers
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Exception thrown when inheriting variable
|
||||||
|
- Notarized routes not discarded on test completion
|
||||||
|
- Data classes with property members breaks schema generation
|
||||||
|
- Security cannot be applied to individual path operations
|
||||||
|
- Serialization fails on generic response
|
||||||
|
- Parameter example descriptions not being applied
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- Out of the box support for Jackson and Gson (can still be implemented through custom schema configurators)
|
||||||
|
|
||||||
|
## [3.14.4] - June 5th, 2023
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Components definitions were not in the proper schema section. Prefixed the path with component slug in `protobuf java converter`.
|
||||||
|
|
||||||
|
## [3.14.3] - May 22nd, 2023
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added `required` parameter in request info builder.
|
||||||
|
|
||||||
|
## [3.14.2] - May 8rd, 2023
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Fixed bug where routes were not resolving when a parameter was declared via the ktor SDK
|
||||||
|
|
||||||
|
## [3.14.1] - April 28th, 2023
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Generating enrichments for generic classes no longer throws the `Slugs should not be generated for field enrichments` error.
|
||||||
|
|
||||||
|
## [3.14.0] - April 6th, 2023
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add support for response headers
|
||||||
|
|
||||||
|
## [3.13.0] - March 15th, 2023
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Post, Put, and Patch now support not providing request info
|
||||||
|
|
||||||
|
## [3.12.0] - March 14th, 2023
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add support for swagger documentation
|
||||||
|
|
||||||
|
## [3.11.0] - January 5th, 2023
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Support for type constraints.
|
||||||
|
|
||||||
|
## [3.10.0] - January 4th, 2023
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Support for type enrichments! `deprecated` and `description` to start
|
||||||
|
|
||||||
|
## [3.9.0] - November 15th, 2022
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `protobuf-java-converter` module for converting generated protobuf objects to `JsonSchema` representations
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Application `rootPath` is no longer prefixed to serialized route path when `NotarizedRoute` is resolved
|
||||||
|
|
||||||
|
## [3.8.0] - November 9th, 2022
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add support for NotarizedResource plugin scoped to route
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Support registering same path with different authentication and methods
|
||||||
|
|
||||||
|
## [3.7.0] - November 5th, 2022
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Allow users to override media type in request and response
|
||||||
|
|
||||||
## [3.6.0] - November 5th, 2022
|
## [3.6.0] - November 5th, 2022
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
@ -3,3 +3,7 @@
|
|||||||
[](https://search.maven.org/search?q=io.bkbn%20kompendium)
|
[](https://search.maven.org/search?q=io.bkbn%20kompendium)
|
||||||
|
|
||||||
Documentation is stored in the `docs` folder and is hosted [here](https://bkbn.gitbook.io/kompendium)
|
Documentation is stored in the `docs` folder and is hosted [here](https://bkbn.gitbook.io/kompendium)
|
||||||
|
|
||||||
|
## Showcase
|
||||||
|
|
||||||
|
If you would like to showcase docs that you build using Kompendium, add them to [the showcase discussion](https://github.com/bkbnio/kompendium/discussions/444)
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm") version "1.7.20" apply false
|
kotlin("jvm") version "1.9.10" apply false
|
||||||
kotlin("plugin.serialization") version "1.7.20" apply false
|
kotlin("plugin.serialization") version "1.9.10" apply false
|
||||||
id("io.bkbn.sourdough.library.jvm") version "0.12.0" apply false
|
id("io.bkbn.sourdough.library.jvm") version "0.12.0" apply false
|
||||||
id("io.bkbn.sourdough.application.jvm") version "0.12.0" apply false
|
id("io.bkbn.sourdough.application.jvm") version "0.12.0" apply false
|
||||||
id("io.bkbn.sourdough.root") version "0.12.0"
|
id("io.bkbn.sourdough.root") version "0.12.0"
|
||||||
id("com.github.jakemarsden.git-hooks") version "0.0.2"
|
id("com.github.jakemarsden.git-hooks") version "0.0.2"
|
||||||
id("org.jetbrains.kotlinx.kover") version "0.6.1"
|
id("org.jetbrains.kotlinx.kover") version "0.6.1"
|
||||||
id("io.github.gradle-nexus.publish-plugin") version "1.1.0"
|
id("io.github.gradle-nexus.publish-plugin") version "1.3.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
gitHooks {
|
gitHooks {
|
||||||
|
@ -33,7 +33,6 @@ dependencies {
|
|||||||
implementation("io.ktor:ktor-server-html-builder:$ktorVersion")
|
implementation("io.ktor:ktor-server-html-builder:$ktorVersion")
|
||||||
implementation("io.ktor:ktor-server-content-negotiation:$ktorVersion")
|
implementation("io.ktor:ktor-server-content-negotiation:$ktorVersion")
|
||||||
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
|
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
|
||||||
implementation("ch.qos.logback:logback-classic:1.4.4")
|
|
||||||
|
|
||||||
// Formatting
|
// Formatting
|
||||||
detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:$detektVersion")
|
detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:$detektVersion")
|
||||||
@ -49,8 +48,6 @@ dependencies {
|
|||||||
testFixturesApi("io.ktor:ktor-server-core:$ktorVersion")
|
testFixturesApi("io.ktor:ktor-server-core:$ktorVersion")
|
||||||
testFixturesApi("io.ktor:ktor-server-test-host:$ktorVersion")
|
testFixturesApi("io.ktor:ktor-server-test-host:$ktorVersion")
|
||||||
testFixturesApi("io.ktor:ktor-serialization:$ktorVersion")
|
testFixturesApi("io.ktor:ktor-serialization:$ktorVersion")
|
||||||
testFixturesApi("io.ktor:ktor-serialization-jackson:$ktorVersion")
|
|
||||||
testFixturesApi("io.ktor:ktor-serialization-gson:$ktorVersion")
|
|
||||||
testFixturesApi("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
|
testFixturesApi("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
|
||||||
testFixturesApi("io.ktor:ktor-server-content-negotiation:$ktorVersion")
|
testFixturesApi("io.ktor:ktor-server-content-negotiation:$ktorVersion")
|
||||||
testFixturesApi("io.ktor:ktor-server-auth:$ktorVersion")
|
testFixturesApi("io.ktor:ktor-server-auth:$ktorVersion")
|
||||||
@ -58,9 +55,9 @@ dependencies {
|
|||||||
testFixturesApi("io.ktor:ktor-client:$ktorVersion")
|
testFixturesApi("io.ktor:ktor-client:$ktorVersion")
|
||||||
testFixturesApi("io.ktor:ktor-client-cio:$ktorVersion")
|
testFixturesApi("io.ktor:ktor-client-cio:$ktorVersion")
|
||||||
|
|
||||||
testFixturesApi("dev.forst:ktor-api-key:2.1.3")
|
testFixturesApi("dev.forst:ktor-api-key:2.2.4")
|
||||||
|
|
||||||
testFixturesApi("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1")
|
testFixturesApi("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")
|
||||||
}
|
}
|
||||||
|
|
||||||
testing {
|
testing {
|
||||||
|
@ -10,6 +10,7 @@ class DeleteInfo private constructor(
|
|||||||
override val summary: String,
|
override val summary: String,
|
||||||
override val description: String,
|
override val description: String,
|
||||||
override val externalDocumentation: ExternalDocumentation?,
|
override val externalDocumentation: ExternalDocumentation?,
|
||||||
|
override val security: Map<String, List<String>>?,
|
||||||
override val operationId: String?,
|
override val operationId: String?,
|
||||||
override val deprecated: Boolean,
|
override val deprecated: Boolean,
|
||||||
override val parameters: List<Parameter>
|
override val parameters: List<Parameter>
|
||||||
@ -33,7 +34,8 @@ class DeleteInfo private constructor(
|
|||||||
externalDocumentation = externalDocumentation,
|
externalDocumentation = externalDocumentation,
|
||||||
operationId = operationId,
|
operationId = operationId,
|
||||||
deprecated = deprecated,
|
deprecated = deprecated,
|
||||||
parameters = parameters
|
parameters = parameters,
|
||||||
|
security = security
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,8 @@ class GetInfo private constructor(
|
|||||||
override val externalDocumentation: ExternalDocumentation?,
|
override val externalDocumentation: ExternalDocumentation?,
|
||||||
override val operationId: String?,
|
override val operationId: String?,
|
||||||
override val deprecated: Boolean,
|
override val deprecated: Boolean,
|
||||||
override val parameters: List<Parameter>
|
override val parameters: List<Parameter>,
|
||||||
|
override val security: Map<String, List<String>>?
|
||||||
) : MethodInfo {
|
) : MethodInfo {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -33,7 +34,8 @@ class GetInfo private constructor(
|
|||||||
externalDocumentation = externalDocumentation,
|
externalDocumentation = externalDocumentation,
|
||||||
operationId = operationId,
|
operationId = operationId,
|
||||||
deprecated = deprecated,
|
deprecated = deprecated,
|
||||||
parameters = parameters
|
parameters = parameters,
|
||||||
|
security = security
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,8 @@ class HeadInfo private constructor(
|
|||||||
override val externalDocumentation: ExternalDocumentation?,
|
override val externalDocumentation: ExternalDocumentation?,
|
||||||
override val operationId: String?,
|
override val operationId: String?,
|
||||||
override val deprecated: Boolean,
|
override val deprecated: Boolean,
|
||||||
override val parameters: List<Parameter>
|
override val parameters: List<Parameter>,
|
||||||
|
override val security: Map<String, List<String>>?,
|
||||||
) : MethodInfo {
|
) : MethodInfo {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -33,7 +34,8 @@ class HeadInfo private constructor(
|
|||||||
externalDocumentation = externalDocumentation,
|
externalDocumentation = externalDocumentation,
|
||||||
operationId = operationId,
|
operationId = operationId,
|
||||||
deprecated = deprecated,
|
deprecated = deprecated,
|
||||||
parameters = parameters
|
parameters = parameters,
|
||||||
|
security = security,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,9 @@ sealed interface MethodInfo {
|
|||||||
val tags: Set<String>
|
val tags: Set<String>
|
||||||
val summary: String
|
val summary: String
|
||||||
val description: String
|
val description: String
|
||||||
|
|
||||||
|
val security: Map<String, List<String>>?
|
||||||
|
get() = null
|
||||||
val externalDocumentation: ExternalDocumentation?
|
val externalDocumentation: ExternalDocumentation?
|
||||||
get() = null
|
get() = null
|
||||||
val operationId: String?
|
val operationId: String?
|
||||||
@ -28,6 +31,7 @@ sealed interface MethodInfo {
|
|||||||
internal var tags: Set<String> = emptySet()
|
internal var tags: Set<String> = emptySet()
|
||||||
internal var parameters: List<Parameter> = emptyList()
|
internal var parameters: List<Parameter> = emptyList()
|
||||||
internal var errors: MutableList<ResponseInfo> = mutableListOf()
|
internal var errors: MutableList<ResponseInfo> = mutableListOf()
|
||||||
|
internal var security: Map<String, List<String>>? = null
|
||||||
|
|
||||||
fun response(init: ResponseInfo.Builder.() -> Unit) = apply {
|
fun response(init: ResponseInfo.Builder.() -> Unit) = apply {
|
||||||
val builder = ResponseInfo.Builder()
|
val builder = ResponseInfo.Builder()
|
||||||
@ -59,6 +63,8 @@ sealed interface MethodInfo {
|
|||||||
|
|
||||||
fun parameters(vararg parameters: Parameter) = apply { this.parameters = parameters.toList() }
|
fun parameters(vararg parameters: Parameter) = apply { this.parameters = parameters.toList() }
|
||||||
|
|
||||||
|
fun security(security: Map<String, List<String>>) = apply { this.security = security }
|
||||||
|
|
||||||
abstract fun build(): T
|
abstract fun build(): T
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package io.bkbn.kompendium.core.metadata
|
package io.bkbn.kompendium.core.metadata
|
||||||
|
|
||||||
sealed interface MethodInfoWithRequest : MethodInfo {
|
sealed interface MethodInfoWithRequest : MethodInfo {
|
||||||
val request: RequestInfo
|
val request: RequestInfo?
|
||||||
|
|
||||||
abstract class Builder<T : MethodInfoWithRequest> : MethodInfo.Builder<T>() {
|
abstract class Builder<T : MethodInfoWithRequest> : MethodInfo.Builder<T>() {
|
||||||
internal var request: RequestInfo? = null
|
internal var request: RequestInfo? = null
|
||||||
|
@ -12,7 +12,8 @@ class OptionsInfo private constructor(
|
|||||||
override val externalDocumentation: ExternalDocumentation?,
|
override val externalDocumentation: ExternalDocumentation?,
|
||||||
override val operationId: String?,
|
override val operationId: String?,
|
||||||
override val deprecated: Boolean,
|
override val deprecated: Boolean,
|
||||||
override val parameters: List<Parameter>
|
override val parameters: List<Parameter>,
|
||||||
|
override val security: Map<String, List<String>>?,
|
||||||
) : MethodInfo {
|
) : MethodInfo {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -33,7 +34,8 @@ class OptionsInfo private constructor(
|
|||||||
externalDocumentation = externalDocumentation,
|
externalDocumentation = externalDocumentation,
|
||||||
operationId = operationId,
|
operationId = operationId,
|
||||||
deprecated = deprecated,
|
deprecated = deprecated,
|
||||||
parameters = parameters
|
parameters = parameters,
|
||||||
|
security = security,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import io.bkbn.kompendium.oas.common.ExternalDocumentation
|
|||||||
import io.bkbn.kompendium.oas.payload.Parameter
|
import io.bkbn.kompendium.oas.payload.Parameter
|
||||||
|
|
||||||
class PatchInfo private constructor(
|
class PatchInfo private constructor(
|
||||||
override val request: RequestInfo,
|
override val request: RequestInfo?,
|
||||||
override val errors: MutableList<ResponseInfo>,
|
override val errors: MutableList<ResponseInfo>,
|
||||||
override val response: ResponseInfo,
|
override val response: ResponseInfo,
|
||||||
override val tags: Set<String>,
|
override val tags: Set<String>,
|
||||||
@ -13,7 +13,8 @@ class PatchInfo private constructor(
|
|||||||
override val externalDocumentation: ExternalDocumentation?,
|
override val externalDocumentation: ExternalDocumentation?,
|
||||||
override val operationId: String?,
|
override val operationId: String?,
|
||||||
override val deprecated: Boolean,
|
override val deprecated: Boolean,
|
||||||
override val parameters: List<Parameter>
|
override val parameters: List<Parameter>,
|
||||||
|
override val security: Map<String, List<String>>?,
|
||||||
) : MethodInfoWithRequest {
|
) : MethodInfoWithRequest {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -26,7 +27,7 @@ class PatchInfo private constructor(
|
|||||||
|
|
||||||
class Builder : MethodInfoWithRequest.Builder<PatchInfo>() {
|
class Builder : MethodInfoWithRequest.Builder<PatchInfo>() {
|
||||||
override fun build() = PatchInfo(
|
override fun build() = PatchInfo(
|
||||||
request = request ?: error("request info must be present"),
|
request = request,
|
||||||
errors = errors,
|
errors = errors,
|
||||||
response = response ?: error("response info must be present"),
|
response = response ?: error("response info must be present"),
|
||||||
tags = tags,
|
tags = tags,
|
||||||
@ -35,7 +36,8 @@ class PatchInfo private constructor(
|
|||||||
externalDocumentation = externalDocumentation,
|
externalDocumentation = externalDocumentation,
|
||||||
operationId = operationId,
|
operationId = operationId,
|
||||||
deprecated = deprecated,
|
deprecated = deprecated,
|
||||||
parameters = parameters
|
parameters = parameters,
|
||||||
|
security = security,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import io.bkbn.kompendium.oas.common.ExternalDocumentation
|
|||||||
import io.bkbn.kompendium.oas.payload.Parameter
|
import io.bkbn.kompendium.oas.payload.Parameter
|
||||||
|
|
||||||
class PostInfo private constructor(
|
class PostInfo private constructor(
|
||||||
override val request: RequestInfo,
|
override val request: RequestInfo?,
|
||||||
override val errors: MutableList<ResponseInfo>,
|
override val errors: MutableList<ResponseInfo>,
|
||||||
override val response: ResponseInfo,
|
override val response: ResponseInfo,
|
||||||
override val tags: Set<String>,
|
override val tags: Set<String>,
|
||||||
@ -13,7 +13,8 @@ class PostInfo private constructor(
|
|||||||
override val externalDocumentation: ExternalDocumentation?,
|
override val externalDocumentation: ExternalDocumentation?,
|
||||||
override val operationId: String?,
|
override val operationId: String?,
|
||||||
override val deprecated: Boolean,
|
override val deprecated: Boolean,
|
||||||
override val parameters: List<Parameter>
|
override val parameters: List<Parameter>,
|
||||||
|
override val security: Map<String, List<String>>?,
|
||||||
) : MethodInfoWithRequest {
|
) : MethodInfoWithRequest {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -26,7 +27,7 @@ class PostInfo private constructor(
|
|||||||
|
|
||||||
class Builder : MethodInfoWithRequest.Builder<PostInfo>() {
|
class Builder : MethodInfoWithRequest.Builder<PostInfo>() {
|
||||||
override fun build() = PostInfo(
|
override fun build() = PostInfo(
|
||||||
request = request ?: error("request info must be present"),
|
request = request,
|
||||||
errors = errors,
|
errors = errors,
|
||||||
response = response ?: error("response info must be present"),
|
response = response ?: error("response info must be present"),
|
||||||
tags = tags,
|
tags = tags,
|
||||||
@ -35,7 +36,8 @@ class PostInfo private constructor(
|
|||||||
externalDocumentation = externalDocumentation,
|
externalDocumentation = externalDocumentation,
|
||||||
operationId = operationId,
|
operationId = operationId,
|
||||||
deprecated = deprecated,
|
deprecated = deprecated,
|
||||||
parameters = parameters
|
parameters = parameters,
|
||||||
|
security = security,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import io.bkbn.kompendium.oas.common.ExternalDocumentation
|
|||||||
import io.bkbn.kompendium.oas.payload.Parameter
|
import io.bkbn.kompendium.oas.payload.Parameter
|
||||||
|
|
||||||
class PutInfo private constructor(
|
class PutInfo private constructor(
|
||||||
override val request: RequestInfo,
|
override val request: RequestInfo?,
|
||||||
override val errors: MutableList<ResponseInfo>,
|
override val errors: MutableList<ResponseInfo>,
|
||||||
override val response: ResponseInfo,
|
override val response: ResponseInfo,
|
||||||
override val tags: Set<String>,
|
override val tags: Set<String>,
|
||||||
@ -13,7 +13,8 @@ class PutInfo private constructor(
|
|||||||
override val externalDocumentation: ExternalDocumentation?,
|
override val externalDocumentation: ExternalDocumentation?,
|
||||||
override val operationId: String?,
|
override val operationId: String?,
|
||||||
override val deprecated: Boolean,
|
override val deprecated: Boolean,
|
||||||
override val parameters: List<Parameter>
|
override val parameters: List<Parameter>,
|
||||||
|
override val security: Map<String, List<String>>?,
|
||||||
) : MethodInfoWithRequest {
|
) : MethodInfoWithRequest {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -26,7 +27,7 @@ class PutInfo private constructor(
|
|||||||
|
|
||||||
class Builder : MethodInfoWithRequest.Builder<PutInfo>() {
|
class Builder : MethodInfoWithRequest.Builder<PutInfo>() {
|
||||||
override fun build() = PutInfo(
|
override fun build() = PutInfo(
|
||||||
request = request ?: error("request info must be present"),
|
request = request,
|
||||||
errors = errors,
|
errors = errors,
|
||||||
response = response ?: error("response info must be present"),
|
response = response ?: error("response info must be present"),
|
||||||
tags = tags,
|
tags = tags,
|
||||||
@ -35,7 +36,8 @@ class PutInfo private constructor(
|
|||||||
externalDocumentation = externalDocumentation,
|
externalDocumentation = externalDocumentation,
|
||||||
operationId = operationId,
|
operationId = operationId,
|
||||||
deprecated = deprecated,
|
deprecated = deprecated,
|
||||||
parameters = parameters
|
parameters = parameters,
|
||||||
|
security = security,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,17 @@
|
|||||||
package io.bkbn.kompendium.core.metadata
|
package io.bkbn.kompendium.core.metadata
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.enrichment.TypeEnrichment
|
||||||
import io.bkbn.kompendium.oas.payload.MediaType
|
import io.bkbn.kompendium.oas.payload.MediaType
|
||||||
import kotlin.reflect.KType
|
import kotlin.reflect.KType
|
||||||
import kotlin.reflect.typeOf
|
import kotlin.reflect.typeOf
|
||||||
|
|
||||||
class RequestInfo private constructor(
|
class RequestInfo private constructor(
|
||||||
val requestType: KType,
|
val requestType: KType,
|
||||||
|
val typeEnrichment: TypeEnrichment<*>?,
|
||||||
val description: String,
|
val description: String,
|
||||||
val examples: Map<String, MediaType.Example>?
|
val examples: Map<String, MediaType.Example>?,
|
||||||
|
val mediaTypes: Set<String>,
|
||||||
|
val required: Boolean
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -20,25 +24,46 @@ class RequestInfo private constructor(
|
|||||||
|
|
||||||
class Builder {
|
class Builder {
|
||||||
private var requestType: KType? = null
|
private var requestType: KType? = null
|
||||||
|
private var typeEnrichment: TypeEnrichment<*>? = null
|
||||||
private var description: String? = null
|
private var description: String? = null
|
||||||
private var examples: Map<String, MediaType.Example>? = null
|
private var examples: Map<String, MediaType.Example>? = null
|
||||||
|
private var mediaTypes: Set<String>? = null
|
||||||
|
private var required: Boolean? = null
|
||||||
|
|
||||||
|
fun required(r: Boolean) = apply {
|
||||||
|
this.required = r
|
||||||
|
}
|
||||||
|
|
||||||
fun requestType(t: KType) = apply {
|
fun requestType(t: KType) = apply {
|
||||||
this.requestType = t
|
this.requestType = t
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <reified T> requestType() = apply { requestType(typeOf<T>()) }
|
fun enrichment(t: TypeEnrichment<*>) = apply {
|
||||||
|
this.typeEnrichment = t
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T> requestType(enrichment: TypeEnrichment<T>? = null) = apply {
|
||||||
|
requestType(typeOf<T>())
|
||||||
|
enrichment?.let { enrichment(it) }
|
||||||
|
}
|
||||||
|
|
||||||
fun description(s: String) = apply { this.description = s }
|
fun description(s: String) = apply { this.description = s }
|
||||||
|
|
||||||
fun examples(vararg e: Pair<String, Any>) = apply {
|
fun examples(vararg e: Pair<String, MediaType.Example>) = apply {
|
||||||
this.examples = e.toMap().mapValues { (_, v) -> MediaType.Example(v) }
|
this.examples = e.toMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun mediaTypes(vararg m: String) = apply {
|
||||||
|
this.mediaTypes = m.toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun build() = RequestInfo(
|
fun build() = RequestInfo(
|
||||||
requestType = requestType ?: error("Request type must be present"),
|
requestType = requestType ?: error("Request type must be present"),
|
||||||
description = description ?: error("Description must be present"),
|
description = description ?: error("Description must be present"),
|
||||||
examples = examples
|
typeEnrichment = typeEnrichment,
|
||||||
|
examples = examples,
|
||||||
|
mediaTypes = mediaTypes ?: setOf("application/json"),
|
||||||
|
required = required ?: true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package io.bkbn.kompendium.core.metadata
|
package io.bkbn.kompendium.core.metadata
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.enrichment.TypeEnrichment
|
||||||
|
import io.bkbn.kompendium.oas.payload.Header
|
||||||
import io.bkbn.kompendium.oas.payload.MediaType
|
import io.bkbn.kompendium.oas.payload.MediaType
|
||||||
import io.ktor.http.HttpStatusCode
|
import io.ktor.http.HttpStatusCode
|
||||||
import kotlin.reflect.KType
|
import kotlin.reflect.KType
|
||||||
@ -8,8 +10,11 @@ import kotlin.reflect.typeOf
|
|||||||
class ResponseInfo private constructor(
|
class ResponseInfo private constructor(
|
||||||
val responseCode: HttpStatusCode,
|
val responseCode: HttpStatusCode,
|
||||||
val responseType: KType,
|
val responseType: KType,
|
||||||
|
val typeEnrichment: TypeEnrichment<*>?,
|
||||||
val description: String,
|
val description: String,
|
||||||
val examples: Map<String, MediaType.Example>?
|
val examples: Map<String, MediaType.Example>?,
|
||||||
|
val mediaTypes: Set<String>,
|
||||||
|
val responseHeaders: Map<String, Header>?
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -23,8 +28,15 @@ class ResponseInfo private constructor(
|
|||||||
class Builder {
|
class Builder {
|
||||||
private var responseCode: HttpStatusCode? = null
|
private var responseCode: HttpStatusCode? = null
|
||||||
private var responseType: KType? = null
|
private var responseType: KType? = null
|
||||||
|
private var typeEnrichment: TypeEnrichment<*>? = null
|
||||||
private var description: String? = null
|
private var description: String? = null
|
||||||
private var examples: Map<String, MediaType.Example>? = null
|
private var examples: Map<String, MediaType.Example>? = null
|
||||||
|
private var mediaTypes: Set<String>? = null
|
||||||
|
private var responseHeaders: Map<String, Header>? = null
|
||||||
|
|
||||||
|
fun responseHeaders(headers: Map<String, Header>) = apply {
|
||||||
|
this.responseHeaders = headers
|
||||||
|
}
|
||||||
|
|
||||||
fun responseCode(code: HttpStatusCode) = apply {
|
fun responseCode(code: HttpStatusCode) = apply {
|
||||||
this.responseCode = code
|
this.responseCode = code
|
||||||
@ -34,19 +46,33 @@ class ResponseInfo private constructor(
|
|||||||
this.responseType = t
|
this.responseType = t
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <reified T> responseType() = apply { responseType(typeOf<T>()) }
|
fun enrichment(t: TypeEnrichment<*>) = apply {
|
||||||
|
this.typeEnrichment = t
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T> responseType(enrichment: TypeEnrichment<T>? = null) = apply {
|
||||||
|
responseType(typeOf<T>())
|
||||||
|
enrichment?.let { enrichment(it) }
|
||||||
|
}
|
||||||
|
|
||||||
fun description(s: String) = apply { this.description = s }
|
fun description(s: String) = apply { this.description = s }
|
||||||
|
|
||||||
fun examples(vararg e: Pair<String, Any>) = apply {
|
fun examples(vararg e: Pair<String, MediaType.Example>) = apply {
|
||||||
this.examples = e.toMap().mapValues { (_, v) -> MediaType.Example(v) }
|
this.examples = e.toMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun mediaTypes(vararg m: String) = apply {
|
||||||
|
this.mediaTypes = m.toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun build() = ResponseInfo(
|
fun build() = ResponseInfo(
|
||||||
responseCode = responseCode ?: error("You must provide a response code in order to build a Response!"),
|
responseCode = responseCode ?: error("You must provide a response code in order to build a Response!"),
|
||||||
responseType = responseType ?: error("You must provide a response type in order to build a Response!"),
|
responseType = responseType ?: error("You must provide a response type in order to build a Response!"),
|
||||||
description = description ?: error("You must provide a description in order to build a Response!"),
|
description = description ?: error("You must provide a description in order to build a Response!"),
|
||||||
examples = examples
|
typeEnrichment = typeEnrichment,
|
||||||
|
examples = examples,
|
||||||
|
mediaTypes = mediaTypes ?: setOf("application/json"),
|
||||||
|
responseHeaders = responseHeaders
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,15 @@
|
|||||||
package io.bkbn.kompendium.core.plugin
|
package io.bkbn.kompendium.core.plugin
|
||||||
|
|
||||||
import io.bkbn.kompendium.core.attribute.KompendiumAttributes
|
import io.bkbn.kompendium.core.attribute.KompendiumAttributes
|
||||||
|
import io.bkbn.kompendium.json.schema.KotlinXSchemaConfigurator
|
||||||
import io.bkbn.kompendium.json.schema.SchemaConfigurator
|
import io.bkbn.kompendium.json.schema.SchemaConfigurator
|
||||||
import io.bkbn.kompendium.json.schema.definition.JsonSchema
|
import io.bkbn.kompendium.json.schema.definition.JsonSchema
|
||||||
import io.bkbn.kompendium.json.schema.util.Helpers.getSimpleSlug
|
import io.bkbn.kompendium.json.schema.util.Helpers.getSimpleSlug
|
||||||
import io.bkbn.kompendium.oas.OpenApiSpec
|
import io.bkbn.kompendium.oas.OpenApiSpec
|
||||||
import io.ktor.http.HttpStatusCode
|
|
||||||
import io.ktor.server.application.call
|
import io.ktor.server.application.call
|
||||||
import io.ktor.server.application.createApplicationPlugin
|
import io.ktor.server.application.createApplicationPlugin
|
||||||
import io.ktor.server.response.respond
|
import io.ktor.server.response.respond
|
||||||
import io.ktor.server.routing.Routing
|
import io.ktor.server.routing.Routing
|
||||||
import io.ktor.server.routing.application
|
|
||||||
import io.ktor.server.routing.get
|
import io.ktor.server.routing.get
|
||||||
import io.ktor.server.routing.route
|
import io.ktor.server.routing.route
|
||||||
import io.ktor.server.routing.routing
|
import io.ktor.server.routing.routing
|
||||||
@ -19,25 +18,26 @@ import kotlin.reflect.KType
|
|||||||
object NotarizedApplication {
|
object NotarizedApplication {
|
||||||
|
|
||||||
class Config {
|
class Config {
|
||||||
lateinit var spec: OpenApiSpec
|
lateinit var spec: () -> OpenApiSpec
|
||||||
var openApiJson: Routing.() -> Unit = {
|
var specRoute: (OpenApiSpec, Routing) -> Unit = { spec, routing ->
|
||||||
route("/openapi.json") {
|
routing.route("/openapi.json") {
|
||||||
get {
|
get {
|
||||||
call.respond(HttpStatusCode.OK, this@route.application.attributes[KompendiumAttributes.openApiSpec])
|
call.respond(spec)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var customTypes: Map<KType, JsonSchema> = emptyMap()
|
var customTypes: Map<KType, JsonSchema> = emptyMap()
|
||||||
var schemaConfigurator: SchemaConfigurator = SchemaConfigurator.Default()
|
var schemaConfigurator: SchemaConfigurator = KotlinXSchemaConfigurator()
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun invoke() = createApplicationPlugin(
|
operator fun invoke() = createApplicationPlugin(
|
||||||
name = "NotarizedApplication",
|
name = "NotarizedApplication",
|
||||||
createConfiguration = ::Config
|
createConfiguration = ::Config
|
||||||
) {
|
) {
|
||||||
val spec = pluginConfig.spec
|
val spec = pluginConfig.spec()
|
||||||
val routing = application.routing { }
|
val routing = application.routing {}
|
||||||
pluginConfig.openApiJson(routing)
|
this@createApplicationPlugin.pluginConfig.specRoute(spec, routing)
|
||||||
|
// pluginConfig.openApiJson(routing)
|
||||||
pluginConfig.customTypes.forEach { (type, schema) ->
|
pluginConfig.customTypes.forEach { (type, schema) ->
|
||||||
spec.components.schemas[type.getSimpleSlug()] = schema
|
spec.components.schemas[type.getSimpleSlug()] = schema
|
||||||
}
|
}
|
||||||
|
@ -10,16 +10,17 @@ 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.util.Helpers.addToSpec
|
import io.bkbn.kompendium.core.util.Helpers.addToSpec
|
||||||
import io.bkbn.kompendium.core.util.SpecConfig
|
import io.bkbn.kompendium.core.util.SpecConfig
|
||||||
|
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.Parameter
|
import io.bkbn.kompendium.oas.payload.Parameter
|
||||||
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.PluginBuilder
|
||||||
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 io.ktor.server.routing.application
|
||||||
|
|
||||||
object NotarizedRoute {
|
object NotarizedRoute {
|
||||||
|
|
||||||
class Config : SpecConfig {
|
class Config : SpecConfig {
|
||||||
override var tags: Set<String> = emptySet()
|
override var tags: Set<String> = emptySet()
|
||||||
override var parameters: List<Parameter> = emptyList()
|
override var parameters: List<Parameter> = emptyList()
|
||||||
@ -31,7 +32,6 @@ object NotarizedRoute {
|
|||||||
override var head: HeadInfo? = null
|
override var head: HeadInfo? = null
|
||||||
override var options: OptionsInfo? = null
|
override var options: OptionsInfo? = null
|
||||||
override var security: Map<String, List<String>>? = null
|
override var security: Map<String, List<String>>? = null
|
||||||
internal var path: Path? = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private object InstallHook : Hook<(ApplicationCallPipeline) -> Unit> {
|
private object InstallHook : Hook<(ApplicationCallPipeline) -> Unit> {
|
||||||
@ -44,69 +44,54 @@ object NotarizedRoute {
|
|||||||
name = "NotarizedRoute",
|
name = "NotarizedRoute",
|
||||||
createConfiguration = ::Config
|
createConfiguration = ::Config
|
||||||
) {
|
) {
|
||||||
|
|
||||||
// This is required in order to introspect the route path and authentication
|
// This is required in order to introspect the route path and authentication
|
||||||
on(InstallHook) {
|
on(InstallHook) {
|
||||||
val route = it as? Route ?: return@on
|
val route = it as? Route ?: return@on
|
||||||
val spec = application.attributes[KompendiumAttributes.openApiSpec]
|
val spec = application.attributes[KompendiumAttributes.openApiSpec]
|
||||||
val routePath = route.calculateRoutePath()
|
val routePath = route.calculateRoutePath()
|
||||||
val authMethods = route.collectAuthMethods()
|
val authMethods = route.collectAuthMethods()
|
||||||
pluginConfig.path?.addDefaultAuthMethods(authMethods)
|
|
||||||
require(spec.paths[routePath] == null) {
|
addToSpec(spec, routePath, authMethods)
|
||||||
"""
|
|
||||||
The specified path ${Parameter.Location.path} has already been documented!
|
|
||||||
Please make sure that all notarized paths are unique
|
|
||||||
""".trimIndent()
|
|
||||||
}
|
|
||||||
spec.paths[routePath] = pluginConfig.path
|
|
||||||
?: error("This indicates a bug in Kompendium. Please file a GitHub issue!")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val spec = application.attributes[KompendiumAttributes.openApiSpec]
|
|
||||||
val serializableReader = application.attributes[KompendiumAttributes.schemaConfigurator]
|
|
||||||
|
|
||||||
val path = Path()
|
|
||||||
path.parameters = pluginConfig.parameters
|
|
||||||
|
|
||||||
pluginConfig.get?.addToSpec(path, spec, pluginConfig, serializableReader)
|
|
||||||
pluginConfig.delete?.addToSpec(path, spec, pluginConfig, serializableReader)
|
|
||||||
pluginConfig.head?.addToSpec(path, spec, pluginConfig, serializableReader)
|
|
||||||
pluginConfig.options?.addToSpec(path, spec, pluginConfig, serializableReader)
|
|
||||||
pluginConfig.post?.addToSpec(path, spec, pluginConfig, serializableReader)
|
|
||||||
pluginConfig.put?.addToSpec(path, spec, pluginConfig, serializableReader)
|
|
||||||
pluginConfig.patch?.addToSpec(path, spec, pluginConfig, serializableReader)
|
|
||||||
|
|
||||||
pluginConfig.path = path
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Route.calculateRoutePath() = toString().replace(Regex("/\\(.+\\)"), "")
|
fun <T : SpecConfig> PluginBuilder<T>.addToSpec(
|
||||||
private fun Route.collectAuthMethods() = toString()
|
spec: OpenApiSpec,
|
||||||
|
fullPath: String,
|
||||||
|
authMethods: List<String>
|
||||||
|
) {
|
||||||
|
val path = spec.paths[fullPath] ?: Path()
|
||||||
|
|
||||||
|
path.parameters = path.parameters?.plus(pluginConfig.parameters) ?: pluginConfig.parameters
|
||||||
|
val serializableReader = application.attributes[KompendiumAttributes.schemaConfigurator]
|
||||||
|
|
||||||
|
pluginConfig.get?.addToSpec(path, spec, pluginConfig, serializableReader, fullPath, authMethods)
|
||||||
|
pluginConfig.delete?.addToSpec(path, spec, pluginConfig, serializableReader, fullPath, authMethods)
|
||||||
|
pluginConfig.head?.addToSpec(path, spec, pluginConfig, serializableReader, fullPath, authMethods)
|
||||||
|
pluginConfig.options?.addToSpec(path, spec, pluginConfig, serializableReader, fullPath, authMethods)
|
||||||
|
pluginConfig.post?.addToSpec(path, spec, pluginConfig, serializableReader, fullPath, authMethods)
|
||||||
|
pluginConfig.put?.addToSpec(path, spec, pluginConfig, serializableReader, fullPath, authMethods)
|
||||||
|
pluginConfig.patch?.addToSpec(path, spec, pluginConfig, serializableReader, fullPath, authMethods)
|
||||||
|
|
||||||
|
spec.paths[fullPath] = path
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Route.calculateRoutePath() = toString()
|
||||||
|
.let {
|
||||||
|
application.environment.rootPath.takeIf { root -> root.isNotEmpty() }
|
||||||
|
?.let { root ->
|
||||||
|
val sanitizedRoute = if (root.startsWith("/")) root else "/$root"
|
||||||
|
it.replace(sanitizedRoute, "")
|
||||||
|
}
|
||||||
|
?: it
|
||||||
|
}
|
||||||
|
.replace(Regex("/\\(.+\\)"), "")
|
||||||
|
.replace(Regex("/\\[.+\\]"), "")
|
||||||
|
|
||||||
|
fun Route.collectAuthMethods() = toString()
|
||||||
.split("/")
|
.split("/")
|
||||||
.filter { it.contains(Regex("\\(authenticate .*\\)")) }
|
.filter { it.contains(Regex("\\(authenticate .*\\)")) }
|
||||||
.map { it.replace("(authenticate ", "").replace(")", "") }
|
.map { it.replace("(authenticate ", "").replace(")", "") }
|
||||||
.map { it.split(", ") }
|
.map { it.split(", ") }
|
||||||
.flatten()
|
.flatten()
|
||||||
|
|
||||||
private fun Path.addDefaultAuthMethods(methods: List<String>) {
|
|
||||||
get?.addDefaultAuthMethods(methods)
|
|
||||||
put?.addDefaultAuthMethods(methods)
|
|
||||||
post?.addDefaultAuthMethods(methods)
|
|
||||||
delete?.addDefaultAuthMethods(methods)
|
|
||||||
options?.addDefaultAuthMethods(methods)
|
|
||||||
head?.addDefaultAuthMethods(methods)
|
|
||||||
patch?.addDefaultAuthMethods(methods)
|
|
||||||
trace?.addDefaultAuthMethods(methods)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun PathOperation.addDefaultAuthMethods(methods: List<String>) {
|
|
||||||
methods.forEach { m ->
|
|
||||||
if (security == null || security?.all { s -> !s.containsKey(m) } == true) {
|
|
||||||
if (security == null) {
|
|
||||||
security = mutableListOf(mapOf(m to emptyList()))
|
|
||||||
} else {
|
|
||||||
security?.add(mapOf(m to emptyList()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ import kotlinx.html.unsafe
|
|||||||
/**
|
/**
|
||||||
* Provides an out-of-the-box route to view docs using ReDoc on the specified [path].
|
* Provides an out-of-the-box route to view docs using ReDoc on the specified [path].
|
||||||
* @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 route path to docs resource
|
* @param path path to docs resource
|
||||||
* @param specUrl url to point ReDoc to the OpenAPI json document
|
* @param specUrl url to point ReDoc to the OpenAPI json document
|
||||||
*/
|
*/
|
||||||
fun Route.redoc(pageTitle: String = "Docs", path: String = "/docs", specUrl: String = "/openapi.json") {
|
fun Route.redoc(pageTitle: String = "Docs", path: String = "/docs", specUrl: String = "/openapi.json") {
|
||||||
|
@ -0,0 +1,91 @@
|
|||||||
|
package io.bkbn.kompendium.core.routes
|
||||||
|
|
||||||
|
import io.ktor.server.application.call
|
||||||
|
import io.ktor.server.html.respondHtml
|
||||||
|
import io.ktor.server.routing.Route
|
||||||
|
import io.ktor.server.routing.get
|
||||||
|
import io.ktor.server.routing.route
|
||||||
|
import kotlinx.html.body
|
||||||
|
import kotlinx.html.div
|
||||||
|
import kotlinx.html.head
|
||||||
|
import kotlinx.html.id
|
||||||
|
import kotlinx.html.link
|
||||||
|
import kotlinx.html.meta
|
||||||
|
import kotlinx.html.script
|
||||||
|
import kotlinx.html.title
|
||||||
|
import kotlinx.html.unsafe
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides an out-of-the-box route to view docs using Swagger
|
||||||
|
* @see <a href="https://swagger.io/specification/">Swagger OpenApi Specification</a>
|
||||||
|
* for the latest supported open api version.
|
||||||
|
* @param pageTitle Webpage title you wish to be displayed on your docs
|
||||||
|
* @param path path to docs resource
|
||||||
|
* @param specUrl url to point Swagger to the OpenAPI json document
|
||||||
|
* @param swaggerVersion version of swagger-ui distribution
|
||||||
|
*/
|
||||||
|
fun Route.swagger(
|
||||||
|
pageTitle: String = "Docs",
|
||||||
|
path: String = "/swagger-ui",
|
||||||
|
specUrl: String = "/openapi.json",
|
||||||
|
swaggerVersion: String? = null
|
||||||
|
) {
|
||||||
|
val swaggerVersionSuffix = if (swaggerVersion == null) "" else "@$swaggerVersion"
|
||||||
|
|
||||||
|
route(path) {
|
||||||
|
get {
|
||||||
|
call.respondHtml {
|
||||||
|
head {
|
||||||
|
title {
|
||||||
|
+pageTitle
|
||||||
|
}
|
||||||
|
meta {
|
||||||
|
charset = "utf-8"
|
||||||
|
}
|
||||||
|
meta {
|
||||||
|
name = "viewport"
|
||||||
|
content = "width=device-width, initial-scale=1"
|
||||||
|
}
|
||||||
|
link {
|
||||||
|
href = "https://unpkg.com/swagger-ui-dist$swaggerVersionSuffix/swagger-ui.css"
|
||||||
|
rel = "stylesheet"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
div {
|
||||||
|
id = "swagger-ui"
|
||||||
|
}
|
||||||
|
script {
|
||||||
|
src = "https://unpkg.com/swagger-ui-dist$swaggerVersionSuffix/swagger-ui-standalone-preset.js"
|
||||||
|
}
|
||||||
|
script {
|
||||||
|
src = "https://unpkg.com/swagger-ui-dist$swaggerVersionSuffix/swagger-ui-bundle.js"
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
+"""
|
||||||
|
<script>
|
||||||
|
window.onload = function () {
|
||||||
|
// Build a system
|
||||||
|
const ui = SwaggerUIBundle({
|
||||||
|
url: "$specUrl",
|
||||||
|
dom_id: '#swagger-ui',
|
||||||
|
deepLinking: true,
|
||||||
|
presets: [
|
||||||
|
SwaggerUIBundle.presets.apis,
|
||||||
|
SwaggerUIStandalonePreset
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
SwaggerUIBundle.plugins.DownloadUrl
|
||||||
|
],
|
||||||
|
layout: "StandaloneLayout",
|
||||||
|
})
|
||||||
|
window.ui = ui
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
""".trimIndent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -10,13 +10,14 @@ 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.metadata.ResponseInfo
|
||||||
|
import io.bkbn.kompendium.enrichment.TypeEnrichment
|
||||||
import io.bkbn.kompendium.json.schema.SchemaConfigurator
|
import io.bkbn.kompendium.json.schema.SchemaConfigurator
|
||||||
import io.bkbn.kompendium.json.schema.SchemaGenerator
|
import io.bkbn.kompendium.json.schema.SchemaGenerator
|
||||||
import io.bkbn.kompendium.json.schema.definition.NullableDefinition
|
import io.bkbn.kompendium.json.schema.definition.NullableDefinition
|
||||||
import io.bkbn.kompendium.json.schema.definition.OneOfDefinition
|
import io.bkbn.kompendium.json.schema.definition.OneOfDefinition
|
||||||
import io.bkbn.kompendium.json.schema.definition.ReferenceDefinition
|
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.getReferenceSlug
|
||||||
import io.bkbn.kompendium.json.schema.util.Helpers.getSimpleSlug
|
import io.bkbn.kompendium.json.schema.util.Helpers.getSlug
|
||||||
import io.bkbn.kompendium.oas.OpenApiSpec
|
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.path.PathOperation
|
||||||
@ -24,32 +25,62 @@ import io.bkbn.kompendium.oas.payload.MediaType
|
|||||||
import io.bkbn.kompendium.oas.payload.Request
|
import io.bkbn.kompendium.oas.payload.Request
|
||||||
import io.bkbn.kompendium.oas.payload.Response
|
import io.bkbn.kompendium.oas.payload.Response
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
import kotlin.reflect.KMutableProperty1
|
||||||
import kotlin.reflect.KType
|
import kotlin.reflect.KType
|
||||||
|
|
||||||
object Helpers {
|
object Helpers {
|
||||||
|
|
||||||
fun MethodInfo.addToSpec(path: Path, spec: OpenApiSpec, config: SpecConfig, schemaConfigurator: SchemaConfigurator) {
|
private fun PathOperation.addDefaultAuthMethods(methods: List<String>) {
|
||||||
|
methods.forEach { m ->
|
||||||
|
if (security == null || security?.all { s -> !s.containsKey(m) } == true) {
|
||||||
|
if (security == null) {
|
||||||
|
security = mutableListOf(mapOf(m to emptyList()))
|
||||||
|
} else {
|
||||||
|
security?.add(mapOf(m to emptyList()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun MethodInfo.addToSpec(
|
||||||
|
path: Path,
|
||||||
|
spec: OpenApiSpec,
|
||||||
|
config: SpecConfig,
|
||||||
|
schemaConfigurator: SchemaConfigurator,
|
||||||
|
routePath: String,
|
||||||
|
authMethods: List<String> = emptyList()
|
||||||
|
) {
|
||||||
SchemaGenerator.fromTypeOrUnit(
|
SchemaGenerator.fromTypeOrUnit(
|
||||||
this.response.responseType,
|
type = this.response.responseType,
|
||||||
spec.components.schemas, schemaConfigurator
|
cache = spec.components.schemas,
|
||||||
|
schemaConfigurator = schemaConfigurator,
|
||||||
|
enrichment = this.response.typeEnrichment,
|
||||||
)?.let { schema ->
|
)?.let { schema ->
|
||||||
spec.components.schemas[this.response.responseType.getSimpleSlug()] = schema
|
spec.components.schemas[this.response.responseType.getSlug(this.response.typeEnrichment)] = schema
|
||||||
}
|
}
|
||||||
|
|
||||||
errors.forEach { error ->
|
errors.forEach { error ->
|
||||||
SchemaGenerator.fromTypeOrUnit(error.responseType, spec.components.schemas, schemaConfigurator)?.let { schema ->
|
SchemaGenerator.fromTypeOrUnit(
|
||||||
spec.components.schemas[error.responseType.getSimpleSlug()] = schema
|
type = error.responseType,
|
||||||
|
cache = spec.components.schemas,
|
||||||
|
schemaConfigurator = schemaConfigurator,
|
||||||
|
enrichment = error.typeEnrichment,
|
||||||
|
)?.let { schema ->
|
||||||
|
spec.components.schemas[error.responseType.getSlug(error.typeEnrichment)] = schema
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
when (this) {
|
when (this) {
|
||||||
is MethodInfoWithRequest -> {
|
is MethodInfoWithRequest -> {
|
||||||
SchemaGenerator.fromTypeOrUnit(
|
this.request?.let { reqInfo ->
|
||||||
this.request.requestType,
|
SchemaGenerator.fromTypeOrUnit(
|
||||||
spec.components.schemas,
|
type = reqInfo.requestType,
|
||||||
schemaConfigurator
|
cache = spec.components.schemas,
|
||||||
)?.let { schema ->
|
schemaConfigurator = schemaConfigurator,
|
||||||
spec.components.schemas[this.request.requestType.getSimpleSlug()] = schema
|
enrichment = reqInfo.typeEnrichment,
|
||||||
|
)?.let { schema ->
|
||||||
|
spec.components.schemas[reqInfo.requestType.getSlug(reqInfo.typeEnrichment)] = schema
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,15 +88,25 @@ object Helpers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val operations = this.toPathOperation(config)
|
val operations = this.toPathOperation(config)
|
||||||
|
operations.addDefaultAuthMethods(authMethods)
|
||||||
|
|
||||||
when (this) {
|
fun setOperation(
|
||||||
is DeleteInfo -> path.delete = operations
|
property: KMutableProperty1<Path, PathOperation?>
|
||||||
is GetInfo -> path.get = operations
|
) {
|
||||||
is HeadInfo -> path.head = operations
|
require(property.get(path) == null) {
|
||||||
is PatchInfo -> path.patch = operations
|
"A route has already been registered for path: $routePath and method: ${property.name.uppercase()}"
|
||||||
is PostInfo -> path.post = operations
|
}
|
||||||
is PutInfo -> path.put = operations
|
property.set(path, operations)
|
||||||
is OptionsInfo -> path.options = operations
|
}
|
||||||
|
|
||||||
|
return when (this) {
|
||||||
|
is DeleteInfo -> setOperation(Path::delete)
|
||||||
|
is GetInfo -> setOperation(Path::get)
|
||||||
|
is HeadInfo -> setOperation(Path::head)
|
||||||
|
is PatchInfo -> setOperation(Path::patch)
|
||||||
|
is PostInfo -> setOperation(Path::post)
|
||||||
|
is PutInfo -> setOperation(Path::put)
|
||||||
|
is OptionsInfo -> setOperation(Path::options)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,45 +118,85 @@ object Helpers {
|
|||||||
operationId = this.operationId,
|
operationId = this.operationId,
|
||||||
deprecated = this.deprecated,
|
deprecated = this.deprecated,
|
||||||
parameters = this.parameters,
|
parameters = this.parameters,
|
||||||
security = config.security
|
security = this.createCombinedSecurityContext(config),
|
||||||
?.map { (k, v) -> k to v }
|
|
||||||
?.map { listOf(it).toMap() }
|
|
||||||
?.toMutableList(),
|
|
||||||
requestBody = when (this) {
|
requestBody = when (this) {
|
||||||
is MethodInfoWithRequest -> Request(
|
is MethodInfoWithRequest -> this.request?.let { reqInfo ->
|
||||||
description = this.request.description,
|
Request(
|
||||||
content = this.request.requestType.toReferenceContent(this.request.examples),
|
description = reqInfo.description,
|
||||||
required = true
|
content = reqInfo.requestType.toReferenceContent(
|
||||||
)
|
examples = reqInfo.examples,
|
||||||
|
mediaTypes = reqInfo.mediaTypes,
|
||||||
|
enrichment = reqInfo.typeEnrichment
|
||||||
|
),
|
||||||
|
required = reqInfo.required
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
else -> null
|
else -> null
|
||||||
},
|
},
|
||||||
responses = mapOf(
|
responses = mapOf(
|
||||||
this.response.responseCode.value to Response(
|
this.response.responseCode.value to Response(
|
||||||
description = this.response.description,
|
description = this.response.description,
|
||||||
content = this.response.responseType.toReferenceContent(this.response.examples)
|
headers = this.response.responseHeaders,
|
||||||
|
content = this.response.responseType.toReferenceContent(
|
||||||
|
examples = this.response.examples,
|
||||||
|
mediaTypes = this.response.mediaTypes,
|
||||||
|
enrichment = this.response.typeEnrichment
|
||||||
|
)
|
||||||
)
|
)
|
||||||
).plus(this.errors.toResponseMap())
|
).plus(this.errors.toResponseMap())
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private fun MethodInfo.createCombinedSecurityContext(config: SpecConfig): MutableList<Map<String, List<String>>>? {
|
||||||
|
val configSecurity = config.security
|
||||||
|
?.map { (k, v) -> k to v }
|
||||||
|
?.map { listOf(it).toMap() }
|
||||||
|
?.toMutableList()
|
||||||
|
|
||||||
|
val methodSecurity = this.security
|
||||||
|
?.map { (k, v) -> k to v }
|
||||||
|
?.map { listOf(it).toMap() }
|
||||||
|
?.toMutableList()
|
||||||
|
|
||||||
|
return when {
|
||||||
|
configSecurity == null && methodSecurity == null -> null
|
||||||
|
configSecurity == null -> methodSecurity
|
||||||
|
methodSecurity == null -> configSecurity
|
||||||
|
else -> configSecurity.plus(methodSecurity).toMutableList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun List<ResponseInfo>.toResponseMap(): Map<Int, Response> = associate { error ->
|
private fun List<ResponseInfo>.toResponseMap(): Map<Int, Response> = associate { error ->
|
||||||
error.responseCode.value to Response(
|
error.responseCode.value to Response(
|
||||||
description = error.description,
|
description = error.description,
|
||||||
content = error.responseType.toReferenceContent(error.examples)
|
headers = error.responseHeaders,
|
||||||
|
content = error.responseType.toReferenceContent(
|
||||||
|
examples = error.examples,
|
||||||
|
mediaTypes = error.mediaTypes,
|
||||||
|
enrichment = error.typeEnrichment
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun KType.toReferenceContent(examples: Map<String, MediaType.Example>?): Map<String, MediaType>? =
|
private fun KType.toReferenceContent(
|
||||||
|
examples: Map<String, MediaType.Example>?,
|
||||||
|
mediaTypes: Set<String>,
|
||||||
|
enrichment: TypeEnrichment<*>?
|
||||||
|
): Map<String, MediaType>? =
|
||||||
when (this.classifier as KClass<*>) {
|
when (this.classifier as KClass<*>) {
|
||||||
Unit::class -> null
|
Unit::class -> null
|
||||||
else -> mapOf(
|
else -> mediaTypes.associateWith {
|
||||||
"application/json" to MediaType(
|
MediaType(
|
||||||
schema = if (this.isMarkedNullable) OneOfDefinition(
|
schema = if (this.isMarkedNullable) {
|
||||||
NullableDefinition(),
|
OneOfDefinition(
|
||||||
ReferenceDefinition(this.getReferenceSlug())
|
NullableDefinition(),
|
||||||
) else ReferenceDefinition(this.getReferenceSlug()),
|
ReferenceDefinition(this.getReferenceSlug(enrichment))
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
ReferenceDefinition(this.getReferenceSlug(enrichment))
|
||||||
|
},
|
||||||
examples = examples
|
examples = examples
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,57 +2,80 @@ package io.bkbn.kompendium.core
|
|||||||
|
|
||||||
import dev.forst.ktor.apikey.apiKey
|
import dev.forst.ktor.apikey.apiKey
|
||||||
import io.bkbn.kompendium.core.fixtures.TestHelpers.openApiTestAllSerializers
|
import io.bkbn.kompendium.core.fixtures.TestHelpers.openApiTestAllSerializers
|
||||||
import io.bkbn.kompendium.core.util.TestModules.complexRequest
|
import io.bkbn.kompendium.core.util.arrayConstraints
|
||||||
import io.bkbn.kompendium.core.util.TestModules.customAuthConfig
|
import io.bkbn.kompendium.core.util.complexRequest
|
||||||
import io.bkbn.kompendium.core.util.TestModules.customFieldNameResponse
|
import io.bkbn.kompendium.core.util.customAuthConfig
|
||||||
import io.bkbn.kompendium.core.util.TestModules.dateTimeString
|
import io.bkbn.kompendium.core.util.customFieldNameResponse
|
||||||
import io.bkbn.kompendium.core.util.TestModules.defaultAuthConfig
|
import io.bkbn.kompendium.core.util.customScopesOnSiblingPathOperations
|
||||||
import io.bkbn.kompendium.core.util.TestModules.defaultField
|
import io.bkbn.kompendium.core.util.dateTimeString
|
||||||
import io.bkbn.kompendium.core.util.TestModules.defaultParameter
|
import io.bkbn.kompendium.core.util.defaultAuthConfig
|
||||||
import io.bkbn.kompendium.core.util.TestModules.exampleParams
|
import io.bkbn.kompendium.core.util.defaultField
|
||||||
import io.bkbn.kompendium.core.util.TestModules.genericException
|
import io.bkbn.kompendium.core.util.defaultParameter
|
||||||
import io.bkbn.kompendium.core.util.TestModules.genericPolymorphicResponse
|
import io.bkbn.kompendium.core.util.doubleConstraints
|
||||||
import io.bkbn.kompendium.core.util.TestModules.genericPolymorphicResponseMultipleImpls
|
import io.bkbn.kompendium.core.util.enrichedComplexGenericType
|
||||||
import io.bkbn.kompendium.core.util.TestModules.gnarlyGenericResponse
|
import io.bkbn.kompendium.core.util.enrichedGenericResponse
|
||||||
import io.bkbn.kompendium.core.util.TestModules.headerParameter
|
import io.bkbn.kompendium.core.util.enrichedNestedCollection
|
||||||
import io.bkbn.kompendium.core.util.TestModules.ignoredFieldsResponse
|
import io.bkbn.kompendium.core.util.enrichedSimpleRequest
|
||||||
import io.bkbn.kompendium.core.util.TestModules.multipleAuthStrategies
|
import io.bkbn.kompendium.core.util.enrichedSimpleResponse
|
||||||
import io.bkbn.kompendium.core.util.TestModules.multipleExceptions
|
import io.bkbn.kompendium.core.util.exampleParams
|
||||||
import io.bkbn.kompendium.core.util.TestModules.nestedGenericCollection
|
import io.bkbn.kompendium.core.util.exampleSummaryAndDescription
|
||||||
import io.bkbn.kompendium.core.util.TestModules.nestedGenericMultipleParamsCollection
|
import io.bkbn.kompendium.core.util.fieldOutsideConstructor
|
||||||
import io.bkbn.kompendium.core.util.TestModules.nestedGenericResponse
|
import io.bkbn.kompendium.core.util.genericException
|
||||||
import io.bkbn.kompendium.core.util.TestModules.nestedTypeName
|
import io.bkbn.kompendium.core.util.genericPolymorphicResponse
|
||||||
import io.bkbn.kompendium.core.util.TestModules.nestedUnderRoot
|
import io.bkbn.kompendium.core.util.genericPolymorphicResponseMultipleImpls
|
||||||
import io.bkbn.kompendium.core.util.TestModules.nonRequiredParam
|
import io.bkbn.kompendium.core.util.gnarlyGenericResponse
|
||||||
import io.bkbn.kompendium.core.util.TestModules.nonRequiredParams
|
import io.bkbn.kompendium.core.util.headerParameter
|
||||||
import io.bkbn.kompendium.core.util.TestModules.notarizedDelete
|
import io.bkbn.kompendium.core.util.ignoredFieldsResponse
|
||||||
import io.bkbn.kompendium.core.util.TestModules.notarizedGet
|
import io.bkbn.kompendium.core.util.intConstraints
|
||||||
import io.bkbn.kompendium.core.util.TestModules.notarizedHead
|
import io.bkbn.kompendium.core.util.multipleAuthStrategies
|
||||||
import io.bkbn.kompendium.core.util.TestModules.notarizedOptions
|
import io.bkbn.kompendium.core.util.multipleExceptions
|
||||||
import io.bkbn.kompendium.core.util.TestModules.notarizedPatch
|
import io.bkbn.kompendium.core.util.nestedGenericCollection
|
||||||
import io.bkbn.kompendium.core.util.TestModules.notarizedPost
|
import io.bkbn.kompendium.core.util.nestedGenericMultipleParamsCollection
|
||||||
import io.bkbn.kompendium.core.util.TestModules.notarizedPut
|
import io.bkbn.kompendium.core.util.nestedGenericResponse
|
||||||
import io.bkbn.kompendium.core.util.TestModules.nullableEnumField
|
import io.bkbn.kompendium.core.util.nestedTypeName
|
||||||
import io.bkbn.kompendium.core.util.TestModules.nullableField
|
import io.bkbn.kompendium.core.util.nestedUnderRoot
|
||||||
import io.bkbn.kompendium.core.util.TestModules.nullableNestedObject
|
import io.bkbn.kompendium.core.util.nonRequiredParam
|
||||||
import io.bkbn.kompendium.core.util.TestModules.nullableReference
|
import io.bkbn.kompendium.core.util.nonRequiredParams
|
||||||
import io.bkbn.kompendium.core.util.TestModules.polymorphicCollectionResponse
|
import io.bkbn.kompendium.core.util.notarizedDelete
|
||||||
import io.bkbn.kompendium.core.util.TestModules.polymorphicException
|
import io.bkbn.kompendium.core.util.notarizedGet
|
||||||
import io.bkbn.kompendium.core.util.TestModules.polymorphicMapResponse
|
import io.bkbn.kompendium.core.util.notarizedHead
|
||||||
import io.bkbn.kompendium.core.util.TestModules.polymorphicResponse
|
import io.bkbn.kompendium.core.util.notarizedOptions
|
||||||
import io.bkbn.kompendium.core.util.TestModules.primitives
|
import io.bkbn.kompendium.core.util.notarizedPatch
|
||||||
import io.bkbn.kompendium.core.util.TestModules.reqRespExamples
|
import io.bkbn.kompendium.core.util.notarizedPost
|
||||||
import io.bkbn.kompendium.core.util.TestModules.requiredParams
|
import io.bkbn.kompendium.core.util.notarizedPut
|
||||||
import io.bkbn.kompendium.core.util.TestModules.returnsList
|
import io.bkbn.kompendium.core.util.nullableEnumField
|
||||||
import io.bkbn.kompendium.core.util.TestModules.rootRoute
|
import io.bkbn.kompendium.core.util.nullableField
|
||||||
import io.bkbn.kompendium.core.util.TestModules.simpleGenericResponse
|
import io.bkbn.kompendium.core.util.nullableNestedObject
|
||||||
import io.bkbn.kompendium.core.util.TestModules.simplePathParsing
|
import io.bkbn.kompendium.core.util.nullableReference
|
||||||
import io.bkbn.kompendium.core.util.TestModules.simpleRecursive
|
import io.bkbn.kompendium.core.util.optionalReqExample
|
||||||
import io.bkbn.kompendium.core.util.TestModules.singleException
|
import io.bkbn.kompendium.core.util.overrideMediaTypes
|
||||||
import io.bkbn.kompendium.core.util.TestModules.topLevelNullable
|
import io.bkbn.kompendium.core.util.overrideSealedTypeIdentifier
|
||||||
import io.bkbn.kompendium.core.util.TestModules.trailingSlash
|
import io.bkbn.kompendium.core.util.paramWrapper
|
||||||
import io.bkbn.kompendium.core.util.TestModules.unbackedFieldsResponse
|
import io.bkbn.kompendium.core.util.polymorphicCollectionResponse
|
||||||
import io.bkbn.kompendium.core.util.TestModules.withOperationId
|
import io.bkbn.kompendium.core.util.polymorphicException
|
||||||
|
import io.bkbn.kompendium.core.util.polymorphicMapResponse
|
||||||
|
import io.bkbn.kompendium.core.util.polymorphicResponse
|
||||||
|
import io.bkbn.kompendium.core.util.postNoReqBody
|
||||||
|
import io.bkbn.kompendium.core.util.primitives
|
||||||
|
import io.bkbn.kompendium.core.util.reqRespExamples
|
||||||
|
import io.bkbn.kompendium.core.util.requiredParams
|
||||||
|
import io.bkbn.kompendium.core.util.responseHeaders
|
||||||
|
import io.bkbn.kompendium.core.util.returnsList
|
||||||
|
import io.bkbn.kompendium.core.util.rootRoute
|
||||||
|
import io.bkbn.kompendium.core.util.samePathDifferentMethodsAndAuth
|
||||||
|
import io.bkbn.kompendium.core.util.samePathSameMethod
|
||||||
|
import io.bkbn.kompendium.core.util.simpleGenericResponse
|
||||||
|
import io.bkbn.kompendium.core.util.simplePathParsing
|
||||||
|
import io.bkbn.kompendium.core.util.simpleRecursive
|
||||||
|
import io.bkbn.kompendium.core.util.singleException
|
||||||
|
import io.bkbn.kompendium.core.util.stringConstraints
|
||||||
|
import io.bkbn.kompendium.core.util.stringContentEncodingConstraints
|
||||||
|
import io.bkbn.kompendium.core.util.stringPatternConstraints
|
||||||
|
import io.bkbn.kompendium.core.util.subtypeNotCompleteSetOfParentProperties
|
||||||
|
import io.bkbn.kompendium.core.util.topLevelNullable
|
||||||
|
import io.bkbn.kompendium.core.util.trailingSlash
|
||||||
|
import io.bkbn.kompendium.core.util.unbackedFieldsResponse
|
||||||
|
import io.bkbn.kompendium.core.util.withOperationId
|
||||||
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
||||||
import io.bkbn.kompendium.json.schema.exception.UnknownSchemaException
|
import io.bkbn.kompendium.json.schema.exception.UnknownSchemaException
|
||||||
import io.bkbn.kompendium.oas.component.Components
|
import io.bkbn.kompendium.oas.component.Components
|
||||||
@ -60,6 +83,7 @@ import io.bkbn.kompendium.oas.security.ApiKeyAuth
|
|||||||
import io.bkbn.kompendium.oas.security.BasicAuth
|
import io.bkbn.kompendium.oas.security.BasicAuth
|
||||||
import io.bkbn.kompendium.oas.security.BearerAuth
|
import io.bkbn.kompendium.oas.security.BearerAuth
|
||||||
import io.bkbn.kompendium.oas.security.OAuth
|
import io.bkbn.kompendium.oas.security.OAuth
|
||||||
|
import io.bkbn.kompendium.oas.serialization.KompendiumSerializersModule
|
||||||
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 io.kotest.matchers.should
|
import io.kotest.matchers.should
|
||||||
@ -67,6 +91,8 @@ import io.kotest.matchers.string.startWith
|
|||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
import io.ktor.client.engine.cio.CIO
|
import io.ktor.client.engine.cio.CIO
|
||||||
import io.ktor.http.HttpMethod
|
import io.ktor.http.HttpMethod
|
||||||
|
import io.ktor.serialization.kotlinx.json.json
|
||||||
|
import io.ktor.server.application.call
|
||||||
import io.ktor.server.application.install
|
import io.ktor.server.application.install
|
||||||
import io.ktor.server.auth.Authentication
|
import io.ktor.server.auth.Authentication
|
||||||
import io.ktor.server.auth.OAuthServerSettings
|
import io.ktor.server.auth.OAuthServerSettings
|
||||||
@ -74,8 +100,14 @@ import io.ktor.server.auth.UserIdPrincipal
|
|||||||
import io.ktor.server.auth.basic
|
import io.ktor.server.auth.basic
|
||||||
import io.ktor.server.auth.jwt.jwt
|
import io.ktor.server.auth.jwt.jwt
|
||||||
import io.ktor.server.auth.oauth
|
import io.ktor.server.auth.oauth
|
||||||
import kotlin.reflect.typeOf
|
import io.ktor.server.response.respondText
|
||||||
|
import io.ktor.server.routing.get
|
||||||
|
import io.ktor.server.routing.route
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import java.net.URI
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
import kotlin.reflect.typeOf
|
||||||
|
|
||||||
class KompendiumTest : DescribeSpec({
|
class KompendiumTest : DescribeSpec({
|
||||||
describe("Notarized Open API Metadata Tests") {
|
describe("Notarized Open API Metadata Tests") {
|
||||||
@ -112,6 +144,15 @@ class KompendiumTest : DescribeSpec({
|
|||||||
it("Can notarize a route with non-required params") {
|
it("Can notarize a route with non-required params") {
|
||||||
openApiTestAllSerializers("T0011__non_required_params.json") { nonRequiredParams() }
|
openApiTestAllSerializers("T0011__non_required_params.json") { nonRequiredParams() }
|
||||||
}
|
}
|
||||||
|
it("Can override media types") {
|
||||||
|
openApiTestAllSerializers("T0052__override_media_types.json") { overrideMediaTypes() }
|
||||||
|
}
|
||||||
|
it("Can support a post request with no request body") {
|
||||||
|
openApiTestAllSerializers("T0065__post_no_req_body.json") { postNoReqBody() }
|
||||||
|
}
|
||||||
|
it("Can notarize a route with response headers") {
|
||||||
|
openApiTestAllSerializers("T0066__notarized_get_with_response_headers.json") { responseHeaders() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
describe("Route Parsing") {
|
describe("Route Parsing") {
|
||||||
it("Can parse a simple path and store it under the expected route") {
|
it("Can parse a simple path and store it under the expected route") {
|
||||||
@ -126,6 +167,9 @@ class KompendiumTest : DescribeSpec({
|
|||||||
it("Can notarize a route with a trailing slash") {
|
it("Can notarize a route with a trailing slash") {
|
||||||
openApiTestAllSerializers("T0015__trailing_slash.json") { trailingSlash() }
|
openApiTestAllSerializers("T0015__trailing_slash.json") { trailingSlash() }
|
||||||
}
|
}
|
||||||
|
it("Can notarize a route with a parameter") {
|
||||||
|
openApiTestAllSerializers("T0068__param_wrapper.json") { paramWrapper() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
describe("Exceptions") {
|
describe("Exceptions") {
|
||||||
it("Can add an exception status code to a response") {
|
it("Can add an exception status code to a response") {
|
||||||
@ -148,6 +192,12 @@ class KompendiumTest : DescribeSpec({
|
|||||||
it("Can describe example parameters") {
|
it("Can describe example parameters") {
|
||||||
openApiTestAllSerializers("T0021__example_parameters.json") { exampleParams() }
|
openApiTestAllSerializers("T0021__example_parameters.json") { exampleParams() }
|
||||||
}
|
}
|
||||||
|
it("Can generate example optional request body") {
|
||||||
|
openApiTestAllSerializers("T0069__example_optional_req.json") { optionalReqExample() }
|
||||||
|
}
|
||||||
|
it("Can generate example summary and description") {
|
||||||
|
openApiTestAllSerializers("T0075__example_summary_and_description.json") { exampleSummaryAndDescription() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
describe("Defaults") {
|
describe("Defaults") {
|
||||||
it("Can generate a default parameter value") {
|
it("Can generate a default parameter value") {
|
||||||
@ -201,167 +251,341 @@ class KompendiumTest : DescribeSpec({
|
|||||||
it("Can handle a really gnarly generic example") {
|
it("Can handle a really gnarly generic example") {
|
||||||
openApiTestAllSerializers("T0043__gnarly_generic_example.json") { gnarlyGenericResponse() }
|
openApiTestAllSerializers("T0043__gnarly_generic_example.json") { gnarlyGenericResponse() }
|
||||||
}
|
}
|
||||||
}
|
it("Can override the type name for a sealed interface implementation") {
|
||||||
describe("Custom Serializable Reader tests") {
|
openApiTestAllSerializers("T0070__sealed_interface_type_name_override.json") { overrideSealedTypeIdentifier() }
|
||||||
it("Can support ignoring fields") {
|
|
||||||
openApiTestAllSerializers("T0048__ignored_property.json") { ignoredFieldsResponse() }
|
|
||||||
}
|
}
|
||||||
it("Can support un-backed fields") {
|
it("Can serialize an object where the subtype is not a complete set of parent properties") {
|
||||||
openApiTestAllSerializers("T0049__unbacked_property.json") { unbackedFieldsResponse() }
|
openApiTestAllSerializers("T0071__subtype_not_complete_set_of_parent_properties.json") {
|
||||||
|
subtypeNotCompleteSetOfParentProperties()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
it("Can support custom named fields") {
|
describe("Custom Serializable Reader tests") {
|
||||||
openApiTestAllSerializers("T0050__custom_named_property.json") { customFieldNameResponse() }
|
it("Can support ignoring fields") {
|
||||||
|
openApiTestAllSerializers("T0048__ignored_property.json") { ignoredFieldsResponse() }
|
||||||
|
}
|
||||||
|
it("Can support un-backed fields") {
|
||||||
|
openApiTestAllSerializers("T0049__unbacked_property.json") { unbackedFieldsResponse() }
|
||||||
|
}
|
||||||
|
it("Can support custom named fields") {
|
||||||
|
openApiTestAllSerializers("T0050__custom_named_property.json") { customFieldNameResponse() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
describe("Miscellaneous") {
|
||||||
describe("Miscellaneous") {
|
xit("Can generate the necessary ReDoc home page") {
|
||||||
xit("Can generate the necessary ReDoc home page") {
|
// TODO apiFunctionalityTest(getFileSnapshot("redoc.html"), "/docs") { returnsList() }
|
||||||
// TODO apiFunctionalityTest(getFileSnapshot("redoc.html"), "/docs") { returnsList() }
|
}
|
||||||
}
|
it("Can add an operation id to a notarized route") {
|
||||||
it("Can add an operation id to a notarized route") {
|
openApiTestAllSerializers("T0034__notarized_get_with_operation_id.json") { withOperationId() }
|
||||||
openApiTestAllSerializers("T0034__notarized_get_with_operation_id.json") { withOperationId() }
|
}
|
||||||
}
|
xit("Can add an undeclared field") {
|
||||||
xit("Can add an undeclared field") {
|
// TODO openApiTestAllSerializers("undeclared_field.json") { undeclaredType() }
|
||||||
// TODO openApiTestAllSerializers("undeclared_field.json") { undeclaredType() }
|
}
|
||||||
}
|
it("Can add a custom header parameter with a name override") {
|
||||||
it("Can add a custom header parameter with a name override") {
|
openApiTestAllSerializers("T0035__override_parameter_name.json") { headerParameter() }
|
||||||
openApiTestAllSerializers("T0035__override_parameter_name.json") { headerParameter() }
|
}
|
||||||
}
|
xit("Can override field name") {
|
||||||
xit("Can override field name") {
|
// TODO Assess strategies here
|
||||||
// TODO Assess strategies here
|
}
|
||||||
}
|
it("Can serialize a recursive type") {
|
||||||
it("Can serialize a recursive type") {
|
openApiTestAllSerializers("T0042__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() }
|
}
|
||||||
}
|
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") {
|
||||||
it("Can have a nullable reference without impacting base type") {
|
openApiTestAllSerializers("T0041__nullable_reference.json") { nullableReference() }
|
||||||
openApiTestAllSerializers("T0041__nullable_reference.json") { nullableReference() }
|
}
|
||||||
}
|
it("Can handle nested type names") {
|
||||||
it("Can handle nested type names") {
|
openApiTestAllSerializers("T0044__nested_type_name.json") { nestedTypeName() }
|
||||||
openApiTestAllSerializers("T0044__nested_type_name.json") { nestedTypeName() }
|
}
|
||||||
}
|
it("Can handle top level nullable types") {
|
||||||
it("Can handle top level nullable types") {
|
openApiTestAllSerializers("T0051__top_level_nullable.json") { topLevelNullable() }
|
||||||
openApiTestAllSerializers("T0051__top_level_nullable.json") { topLevelNullable() }
|
}
|
||||||
}
|
it("Can handle multiple registrations for different methods with the same path and different auth") {
|
||||||
}
|
openApiTestAllSerializers(
|
||||||
describe("Error Handling") {
|
"T0053__same_path_different_methods_and_auth.json",
|
||||||
it("Throws a clear exception when an unidentified type is encountered") {
|
applicationSetup = {
|
||||||
val exception = shouldThrow<UnknownSchemaException> { openApiTestAllSerializers("") { dateTimeString() } }
|
install(Authentication) {
|
||||||
exception.message should startWith("An unknown type was encountered: class java.time.Instant")
|
basic("basic") {
|
||||||
}
|
realm = "Ktor Server"
|
||||||
}
|
validate { UserIdPrincipal("Placeholder") }
|
||||||
describe("Constraints") {
|
}
|
||||||
// TODO Assess strategies here
|
|
||||||
}
|
|
||||||
describe("Formats") {
|
|
||||||
it("Can set a format for a simple type schema") {
|
|
||||||
openApiTestAllSerializers(
|
|
||||||
snapshotName = "T0038__formatted_date_time_string.json",
|
|
||||||
customTypes = mapOf(typeOf<Instant>() to TypeDefinition(type = "string", format = "date"))
|
|
||||||
) { dateTimeString() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
describe("Free Form") {
|
|
||||||
// todo Assess strategies here
|
|
||||||
}
|
|
||||||
describe("Authentication") {
|
|
||||||
it("Can add a default auth config by default") {
|
|
||||||
openApiTestAllSerializers(
|
|
||||||
snapshotName = "T0045__default_auth_config.json",
|
|
||||||
applicationSetup = {
|
|
||||||
install(Authentication) {
|
|
||||||
basic("basic") {
|
|
||||||
realm = "Ktor Server"
|
|
||||||
validate { UserIdPrincipal("Placeholder") }
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
specOverrides = {
|
||||||
specOverrides = {
|
this.copy(
|
||||||
this.copy(
|
components = Components(
|
||||||
components = Components(
|
securitySchemes = mutableMapOf(
|
||||||
securitySchemes = mutableMapOf(
|
"basic" to BasicAuth()
|
||||||
"basic" to BasicAuth()
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
) { defaultAuthConfig() }
|
|
||||||
}
|
|
||||||
it("Can provide custom auth config with proper scopes") {
|
|
||||||
openApiTestAllSerializers(
|
|
||||||
snapshotName = "T0046__custom_auth_config.json",
|
|
||||||
applicationSetup = {
|
|
||||||
install(Authentication) {
|
|
||||||
oauth("auth-oauth-google") {
|
|
||||||
urlProvider = { "http://localhost:8080/callback" }
|
|
||||||
providerLookup = {
|
|
||||||
OAuthServerSettings.OAuth2ServerSettings(
|
|
||||||
name = "google",
|
|
||||||
authorizeUrl = "https://accounts.google.com/o/oauth2/auth",
|
|
||||||
accessTokenUrl = "https://accounts.google.com/o/oauth2/token",
|
|
||||||
requestMethod = HttpMethod.Post,
|
|
||||||
clientId = "DUMMY_VAL",
|
|
||||||
clientSecret = "DUMMY_VAL",
|
|
||||||
defaultScopes = listOf("https://www.googleapis.com/auth/userinfo.profile"),
|
|
||||||
extraTokenParameters = listOf("access_type" to "offline")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
client = HttpClient(CIO)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
) { samePathDifferentMethodsAndAuth() }
|
||||||
specOverrides = {
|
}
|
||||||
this.copy(
|
it("Can generate paths without application root-path") {
|
||||||
components = Components(
|
openApiTestAllSerializers(
|
||||||
securitySchemes = mutableMapOf(
|
"T0054__app_with_rootpath.json",
|
||||||
"auth-oauth-google" to OAuth(
|
applicationEnvironmentBuilder = {
|
||||||
flows = OAuth.Flows(
|
rootPath = "/example"
|
||||||
implicit = OAuth.Flows.Implicit(
|
},
|
||||||
authorizationUrl = "https://accounts.google.com/o/oauth2/auth",
|
specOverrides = {
|
||||||
scopes = mapOf(
|
copy(
|
||||||
"write:pets" to "modify pets in your account",
|
servers = servers.map { it.copy(url = URI("${it.url}/example")) }.toMutableList()
|
||||||
"read:pets" to "read your pets"
|
)
|
||||||
|
}
|
||||||
|
) { notarizedGet() }
|
||||||
|
}
|
||||||
|
it("Can apply a custom serialization strategy to the openapi document") {
|
||||||
|
val customJsonEncoder = Json {
|
||||||
|
serializersModule = KompendiumSerializersModule.module
|
||||||
|
encodeDefaults = true
|
||||||
|
explicitNulls = false
|
||||||
|
}
|
||||||
|
openApiTestAllSerializers(
|
||||||
|
snapshotName = "T0072__custom_serialization_strategy.json",
|
||||||
|
notarizedApplicationConfigOverrides = {
|
||||||
|
specRoute = { spec, routing ->
|
||||||
|
routing {
|
||||||
|
route("/openapi.json") {
|
||||||
|
get {
|
||||||
|
call.response.headers.append("Content-Type", "application/json")
|
||||||
|
call.respondText { customJsonEncoder.encodeToString(spec) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
contentNegotiation = {
|
||||||
|
json(
|
||||||
|
Json {
|
||||||
|
encodeDefaults = true
|
||||||
|
explicitNulls = true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { notarizedGet() }
|
||||||
|
}
|
||||||
|
it("Can serialize a data class with a field outside of the constructor") {
|
||||||
|
openApiTestAllSerializers("T0073__data_class_with_field_outside_constructor.json") { fieldOutsideConstructor() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
describe("Error Handling") {
|
||||||
|
it("Throws a clear exception when an unidentified type is encountered") {
|
||||||
|
val exception = shouldThrow<UnknownSchemaException> { openApiTestAllSerializers("") { dateTimeString() } }
|
||||||
|
exception.message should startWith("An unknown type was encountered: class java.time.Instant")
|
||||||
|
}
|
||||||
|
it("Throws an exception when same method for same path has been previously registered") {
|
||||||
|
val exception = shouldThrow<IllegalArgumentException> {
|
||||||
|
openApiTestAllSerializers(
|
||||||
|
snapshotName = "",
|
||||||
|
applicationSetup = {
|
||||||
|
install(Authentication) {
|
||||||
|
basic("basic") {
|
||||||
|
realm = "Ktor Server"
|
||||||
|
validate { UserIdPrincipal("Placeholder") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
samePathSameMethod()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exception.message should startWith("A route has already been registered for path: /test/{a} and method: GET")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
describe("Formats") {
|
||||||
|
it("Can set a format for a simple type schema") {
|
||||||
|
openApiTestAllSerializers(
|
||||||
|
snapshotName = "T0038__formatted_date_time_string.json",
|
||||||
|
customTypes = mapOf(typeOf<Instant>() to TypeDefinition(type = "string", format = "date"))
|
||||||
|
) { dateTimeString() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
describe("Free Form") {
|
||||||
|
// todo Assess strategies here
|
||||||
|
}
|
||||||
|
describe("Authentication") {
|
||||||
|
it("Can add a default auth config by default") {
|
||||||
|
openApiTestAllSerializers(
|
||||||
|
snapshotName = "T0045__default_auth_config.json",
|
||||||
|
applicationSetup = {
|
||||||
|
install(Authentication) {
|
||||||
|
basic("basic") {
|
||||||
|
realm = "Ktor Server"
|
||||||
|
validate { UserIdPrincipal("Placeholder") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
specOverrides = {
|
||||||
|
this.copy(
|
||||||
|
components = Components(
|
||||||
|
securitySchemes = mutableMapOf(
|
||||||
|
"basic" to BasicAuth()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { defaultAuthConfig() }
|
||||||
|
}
|
||||||
|
it("Can provide custom auth config with proper scopes") {
|
||||||
|
openApiTestAllSerializers(
|
||||||
|
snapshotName = "T0046__custom_auth_config.json",
|
||||||
|
applicationSetup = {
|
||||||
|
install(Authentication) {
|
||||||
|
oauth("auth-oauth-google") {
|
||||||
|
urlProvider = { "http://localhost:8080/callback" }
|
||||||
|
providerLookup = {
|
||||||
|
OAuthServerSettings.OAuth2ServerSettings(
|
||||||
|
name = "google",
|
||||||
|
authorizeUrl = "https://accounts.google.com/o/oauth2/auth",
|
||||||
|
accessTokenUrl = "https://accounts.google.com/o/oauth2/token",
|
||||||
|
requestMethod = HttpMethod.Post,
|
||||||
|
clientId = "DUMMY_VAL",
|
||||||
|
clientSecret = "DUMMY_VAL",
|
||||||
|
defaultScopes = listOf("https://www.googleapis.com/auth/userinfo.profile"),
|
||||||
|
extraTokenParameters = listOf("access_type" to "offline")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
client = HttpClient(CIO)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
specOverrides = {
|
||||||
|
this.copy(
|
||||||
|
components = Components(
|
||||||
|
securitySchemes = mutableMapOf(
|
||||||
|
"auth-oauth-google" to OAuth(
|
||||||
|
flows = OAuth.Flows(
|
||||||
|
implicit = OAuth.Flows.Implicit(
|
||||||
|
authorizationUrl = "https://accounts.google.com/o/oauth2/auth",
|
||||||
|
scopes = mapOf(
|
||||||
|
"write:pets" to "modify pets in your account",
|
||||||
|
"read:pets" to "read your pets"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
}
|
) { customAuthConfig() }
|
||||||
) { customAuthConfig() }
|
}
|
||||||
}
|
it("Can provide multiple authentication strategies") {
|
||||||
it("Can provide multiple authentication strategies") {
|
openApiTestAllSerializers(
|
||||||
openApiTestAllSerializers(
|
snapshotName = "T0047__multiple_auth_strategies.json",
|
||||||
snapshotName = "T0047__multiple_auth_strategies.json",
|
applicationSetup = {
|
||||||
applicationSetup = {
|
install(Authentication) {
|
||||||
install(Authentication) {
|
apiKey("api-key") {
|
||||||
apiKey("api-key") {
|
headerName = "X-API-KEY"
|
||||||
headerName = "X-API-KEY"
|
validate {
|
||||||
validate {
|
UserIdPrincipal("Placeholder")
|
||||||
UserIdPrincipal("Placeholder")
|
}
|
||||||
|
}
|
||||||
|
jwt("jwt") {
|
||||||
|
realm = "Server"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jwt("jwt") {
|
},
|
||||||
realm = "Server"
|
specOverrides = {
|
||||||
}
|
this.copy(
|
||||||
}
|
components = Components(
|
||||||
},
|
securitySchemes = mutableMapOf(
|
||||||
specOverrides = {
|
"jwt" to BearerAuth("JWT"),
|
||||||
this.copy(
|
"api-key" to ApiKeyAuth(ApiKeyAuth.ApiKeyLocation.HEADER, "X-API-KEY")
|
||||||
components = Components(
|
)
|
||||||
securitySchemes = mutableMapOf(
|
|
||||||
"jwt" to BearerAuth("JWT"),
|
|
||||||
"api-key" to ApiKeyAuth(ApiKeyAuth.ApiKeyLocation.HEADER, "X-API-KEY")
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
|
) { multipleAuthStrategies() }
|
||||||
|
}
|
||||||
|
it("Can provide different scopes on path operations in the same route") {
|
||||||
|
openApiTestAllSerializers(
|
||||||
|
snapshotName = "T0074__auth_on_specific_path_operation.json",
|
||||||
|
applicationSetup = {
|
||||||
|
install(Authentication) {
|
||||||
|
oauth("auth-oauth-google") {
|
||||||
|
urlProvider = { "http://localhost:8080/callback" }
|
||||||
|
providerLookup = {
|
||||||
|
OAuthServerSettings.OAuth2ServerSettings(
|
||||||
|
name = "google",
|
||||||
|
authorizeUrl = "https://accounts.google.com/o/oauth2/auth",
|
||||||
|
accessTokenUrl = "https://accounts.google.com/o/oauth2/token",
|
||||||
|
requestMethod = HttpMethod.Post,
|
||||||
|
clientId = "DUMMY_VAL",
|
||||||
|
clientSecret = "DUMMY_VAL",
|
||||||
|
defaultScopes = listOf("https://www.googleapis.com/auth/userinfo.profile"),
|
||||||
|
extraTokenParameters = listOf("access_type" to "offline")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
client = HttpClient(CIO)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
specOverrides = {
|
||||||
|
this.copy(
|
||||||
|
components = Components(
|
||||||
|
securitySchemes = mutableMapOf(
|
||||||
|
"auth-oauth-google" to OAuth(
|
||||||
|
flows = OAuth.Flows(
|
||||||
|
implicit = OAuth.Flows.Implicit(
|
||||||
|
authorizationUrl = "https://accounts.google.com/o/oauth2/auth",
|
||||||
|
scopes = mapOf(
|
||||||
|
"write:pets" to "modify pets in your account",
|
||||||
|
"read:pets" to "read your pets"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { customScopesOnSiblingPathOperations() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
describe("Enrichment") {
|
||||||
|
it("Can enrich a simple request") {
|
||||||
|
openApiTestAllSerializers("T0055__enriched_simple_request.json") { enrichedSimpleRequest() }
|
||||||
|
}
|
||||||
|
it("Can enrich a simple response") {
|
||||||
|
openApiTestAllSerializers("T0058__enriched_simple_response.json") { enrichedSimpleResponse() }
|
||||||
|
}
|
||||||
|
it("Can enrich a nested collection") {
|
||||||
|
openApiTestAllSerializers("T0056__enriched_nested_collection.json") { enrichedNestedCollection() }
|
||||||
|
}
|
||||||
|
it("Can enrich a complex generic type") {
|
||||||
|
openApiTestAllSerializers("T0057__enriched_complex_generic_type.json") { enrichedComplexGenericType() }
|
||||||
|
}
|
||||||
|
it("Can enrich a generic object") {
|
||||||
|
openApiTestAllSerializers("T0067__enriched_generic_object.json") { enrichedGenericResponse() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
describe("Constraints") {
|
||||||
|
it("Can apply constraints to an int field") {
|
||||||
|
openApiTestAllSerializers("T0059__int_constraints.json") { intConstraints() }
|
||||||
|
}
|
||||||
|
it("Can apply constraints to a double field") {
|
||||||
|
openApiTestAllSerializers("T0060__double_constraints.json") { doubleConstraints() }
|
||||||
|
}
|
||||||
|
it("Can apply a min and max length to a string field") {
|
||||||
|
openApiTestAllSerializers("T0061__string_min_max_constraints.json") { stringConstraints() }
|
||||||
|
}
|
||||||
|
it("Can apply a pattern to a string field") {
|
||||||
|
openApiTestAllSerializers("T0062__string_pattern_constraints.json") { stringPatternConstraints() }
|
||||||
|
}
|
||||||
|
it("Can apply a content encoding and media type to a string field") {
|
||||||
|
openApiTestAllSerializers("T0063__string_content_encoding_constraints.json") {
|
||||||
|
stringContentEncodingConstraints()
|
||||||
}
|
}
|
||||||
) { multipleAuthStrategies() }
|
}
|
||||||
|
it("Can apply constraints to an array field") {
|
||||||
|
openApiTestAllSerializers("T0064__array_constraints.json") { arrayConstraints() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -0,0 +1,90 @@
|
|||||||
|
package io.bkbn.kompendium.core.util
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.core.fixtures.TestResponse
|
||||||
|
import io.bkbn.kompendium.core.metadata.GetInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.PostInfo
|
||||||
|
import io.bkbn.kompendium.core.plugin.NotarizedRoute
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultPathDescription
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultPathSummary
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultResponseDescription
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.rootPath
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.server.application.install
|
||||||
|
import io.ktor.server.auth.authenticate
|
||||||
|
import io.ktor.server.routing.Routing
|
||||||
|
import io.ktor.server.routing.post
|
||||||
|
import io.ktor.server.routing.route
|
||||||
|
|
||||||
|
fun Routing.defaultAuthConfig() {
|
||||||
|
authenticate("basic") {
|
||||||
|
route(rootPath) {
|
||||||
|
basicGetGenerator<TestResponse>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.customAuthConfig() {
|
||||||
|
authenticate("auth-oauth-google") {
|
||||||
|
route(rootPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
get = GetInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
response {
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<TestResponse>()
|
||||||
|
}
|
||||||
|
security = mapOf(
|
||||||
|
"auth-oauth-google" to listOf("read:pets")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.customScopesOnSiblingPathOperations() {
|
||||||
|
authenticate("auth-oauth-google") {
|
||||||
|
route(rootPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
get = GetInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
response {
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<TestResponse>()
|
||||||
|
}
|
||||||
|
security = mapOf(
|
||||||
|
"auth-oauth-google" to listOf("read:pets")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
post = PostInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
response {
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<TestResponse>()
|
||||||
|
}
|
||||||
|
request {
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
requestType<TestResponse>()
|
||||||
|
}
|
||||||
|
security = mapOf(
|
||||||
|
"auth-oauth-google" to listOf("write:pets")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.multipleAuthStrategies() {
|
||||||
|
authenticate("jwt", "api-key") {
|
||||||
|
route(rootPath) {
|
||||||
|
basicGetGenerator<TestResponse>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
160
core/src/test/kotlin/io/bkbn/kompendium/core/util/Constraints.kt
Normal file
160
core/src/test/kotlin/io/bkbn/kompendium/core/util/Constraints.kt
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
package io.bkbn.kompendium.core.util
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.core.fixtures.DoubleResponse
|
||||||
|
import io.bkbn.kompendium.core.fixtures.Page
|
||||||
|
import io.bkbn.kompendium.core.fixtures.TestCreatedResponse
|
||||||
|
import io.bkbn.kompendium.core.fixtures.TestNested
|
||||||
|
import io.bkbn.kompendium.core.metadata.GetInfo
|
||||||
|
import io.bkbn.kompendium.core.plugin.NotarizedRoute
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultPath
|
||||||
|
import io.bkbn.kompendium.enrichment.TypeEnrichment
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.server.application.install
|
||||||
|
import io.ktor.server.routing.Routing
|
||||||
|
import io.ktor.server.routing.route
|
||||||
|
|
||||||
|
fun Routing.intConstraints() {
|
||||||
|
route(defaultPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
get = GetInfo.builder {
|
||||||
|
summary("Get an int")
|
||||||
|
description("Get an int")
|
||||||
|
response {
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
description("An int")
|
||||||
|
responseType(
|
||||||
|
enrichment = TypeEnrichment("example") {
|
||||||
|
TestCreatedResponse::id {
|
||||||
|
minimum = 2
|
||||||
|
maximum = 100
|
||||||
|
multipleOf = 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.doubleConstraints() {
|
||||||
|
route(defaultPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
get = GetInfo.builder {
|
||||||
|
summary("Get a double")
|
||||||
|
description("Get a double")
|
||||||
|
response {
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
description("A double")
|
||||||
|
responseType(
|
||||||
|
enrichment = TypeEnrichment("example") {
|
||||||
|
DoubleResponse::payload {
|
||||||
|
minimum = 2.0
|
||||||
|
maximum = 100.0
|
||||||
|
multipleOf = 2.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.stringConstraints() {
|
||||||
|
route(defaultPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
get = GetInfo.builder {
|
||||||
|
summary("Get a string")
|
||||||
|
description("Get a string with constraints")
|
||||||
|
response {
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
description("A string")
|
||||||
|
responseType(
|
||||||
|
enrichment = TypeEnrichment("example") {
|
||||||
|
TestNested::nesty {
|
||||||
|
maxLength = 10
|
||||||
|
minLength = 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.stringPatternConstraints() {
|
||||||
|
route(defaultPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
get = GetInfo.builder {
|
||||||
|
summary("Get a string")
|
||||||
|
description("This is a description")
|
||||||
|
response {
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
description("A string")
|
||||||
|
responseType(
|
||||||
|
enrichment = TypeEnrichment("example") {
|
||||||
|
TestNested::nesty {
|
||||||
|
pattern = "[a-z]+"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.stringContentEncodingConstraints() {
|
||||||
|
route(defaultPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
get = GetInfo.builder {
|
||||||
|
summary("Get a string")
|
||||||
|
description("This is a description")
|
||||||
|
response {
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
description("A string")
|
||||||
|
responseType(
|
||||||
|
enrichment = TypeEnrichment("example") {
|
||||||
|
TestNested::nesty {
|
||||||
|
contentEncoding = "base64"
|
||||||
|
contentMediaType = "image/png"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.arrayConstraints() {
|
||||||
|
route(defaultPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
get = GetInfo.builder {
|
||||||
|
summary("Get an array")
|
||||||
|
description("Get an array of strings")
|
||||||
|
response {
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
description("An array")
|
||||||
|
responseType(
|
||||||
|
enrichment = TypeEnrichment("example") {
|
||||||
|
Page<String>::content {
|
||||||
|
minItems = 2
|
||||||
|
maxItems = 10
|
||||||
|
uniqueItems = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package io.bkbn.kompendium.core.util
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.core.fixtures.SerialNameObject
|
||||||
|
import io.bkbn.kompendium.core.fixtures.TransientObject
|
||||||
|
import io.bkbn.kompendium.core.fixtures.UnbackedObject
|
||||||
|
import io.ktor.server.routing.Routing
|
||||||
|
|
||||||
|
fun Routing.ignoredFieldsResponse() = basicGetGenerator<TransientObject>()
|
||||||
|
fun Routing.unbackedFieldsResponse() = basicGetGenerator<UnbackedObject>()
|
||||||
|
fun Routing.customFieldNameResponse() = basicGetGenerator<SerialNameObject>()
|
@ -0,0 +1,16 @@
|
|||||||
|
package io.bkbn.kompendium.core.util
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.core.fixtures.TestResponse
|
||||||
|
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
||||||
|
import io.bkbn.kompendium.oas.payload.Parameter
|
||||||
|
import io.ktor.server.routing.Routing
|
||||||
|
|
||||||
|
fun Routing.defaultParameter() = basicGetGenerator<TestResponse>(
|
||||||
|
params = listOf(
|
||||||
|
Parameter(
|
||||||
|
name = "id",
|
||||||
|
`in` = Parameter.Location.path,
|
||||||
|
schema = TypeDefinition.STRING.withDefault("IDK")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
168
core/src/test/kotlin/io/bkbn/kompendium/core/util/Enrichment.kt
Normal file
168
core/src/test/kotlin/io/bkbn/kompendium/core/util/Enrichment.kt
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
package io.bkbn.kompendium.core.util
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.core.fixtures.ComplexRequest
|
||||||
|
import io.bkbn.kompendium.core.fixtures.MultiNestedGenerics
|
||||||
|
import io.bkbn.kompendium.core.fixtures.NestedComplexItem
|
||||||
|
import io.bkbn.kompendium.core.fixtures.TestCreatedResponse
|
||||||
|
import io.bkbn.kompendium.core.fixtures.TestResponse
|
||||||
|
import io.bkbn.kompendium.core.fixtures.TestSimpleRequest
|
||||||
|
import io.bkbn.kompendium.core.fixtures.GenericObject
|
||||||
|
import io.bkbn.kompendium.core.metadata.GetInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.PostInfo
|
||||||
|
import io.bkbn.kompendium.core.plugin.NotarizedRoute
|
||||||
|
import io.bkbn.kompendium.enrichment.TypeEnrichment
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.server.application.install
|
||||||
|
import io.ktor.server.routing.Routing
|
||||||
|
import io.ktor.server.routing.route
|
||||||
|
|
||||||
|
fun Routing.enrichedSimpleResponse() {
|
||||||
|
route("/enriched") {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
get = GetInfo.builder {
|
||||||
|
summary(TestModules.defaultPathSummary)
|
||||||
|
description(TestModules.defaultPathDescription)
|
||||||
|
response {
|
||||||
|
responseType(
|
||||||
|
enrichment = TypeEnrichment("simple") {
|
||||||
|
TestResponse::c {
|
||||||
|
description = "A simple description"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
description("A good response")
|
||||||
|
responseCode(HttpStatusCode.Created)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.enrichedSimpleRequest() {
|
||||||
|
route("/example") {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
parameters = TestModules.defaultParams
|
||||||
|
post = PostInfo.builder {
|
||||||
|
summary(TestModules.defaultPathSummary)
|
||||||
|
description(TestModules.defaultPathDescription)
|
||||||
|
request {
|
||||||
|
requestType(
|
||||||
|
enrichment = TypeEnrichment("simple") {
|
||||||
|
TestSimpleRequest::a {
|
||||||
|
description = "A simple description"
|
||||||
|
}
|
||||||
|
TestSimpleRequest::b {
|
||||||
|
deprecated = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
description("A test request")
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
responseCode(HttpStatusCode.Created)
|
||||||
|
responseType<TestCreatedResponse>()
|
||||||
|
description(TestModules.defaultResponseDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.enrichedNestedCollection() {
|
||||||
|
route("/example") {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
parameters = TestModules.defaultParams
|
||||||
|
post = PostInfo.builder {
|
||||||
|
summary(TestModules.defaultPathSummary)
|
||||||
|
description(TestModules.defaultPathDescription)
|
||||||
|
request {
|
||||||
|
requestType(
|
||||||
|
enrichment = TypeEnrichment("simple") {
|
||||||
|
ComplexRequest::tables {
|
||||||
|
description = "A nested item"
|
||||||
|
typeEnrichment = TypeEnrichment("nested") {
|
||||||
|
NestedComplexItem::name {
|
||||||
|
description = "A nested description"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
description("A test request")
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
responseCode(HttpStatusCode.Created)
|
||||||
|
responseType<TestCreatedResponse>()
|
||||||
|
description(TestModules.defaultResponseDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.enrichedComplexGenericType() {
|
||||||
|
route("/example") {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
parameters = TestModules.defaultParams
|
||||||
|
post = PostInfo.builder {
|
||||||
|
summary(TestModules.defaultPathSummary)
|
||||||
|
description(TestModules.defaultPathDescription)
|
||||||
|
request {
|
||||||
|
requestType(
|
||||||
|
enrichment = TypeEnrichment("simple") {
|
||||||
|
MultiNestedGenerics<String, ComplexRequest>::content {
|
||||||
|
description = "Getting pretty crazy"
|
||||||
|
typeEnrichment = TypeEnrichment("nested") {
|
||||||
|
ComplexRequest::tables {
|
||||||
|
description = "A nested item"
|
||||||
|
typeEnrichment = TypeEnrichment("nested") {
|
||||||
|
NestedComplexItem::name {
|
||||||
|
description = "A nested description"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
description("A test request")
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
responseCode(HttpStatusCode.Created)
|
||||||
|
responseType<TestCreatedResponse>()
|
||||||
|
description(TestModules.defaultResponseDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.enrichedGenericResponse() {
|
||||||
|
route("/example") {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
get = GetInfo.builder {
|
||||||
|
summary(TestModules.defaultPathSummary)
|
||||||
|
description(TestModules.defaultPathDescription)
|
||||||
|
response {
|
||||||
|
responseType(
|
||||||
|
enrichment = TypeEnrichment("generic") {
|
||||||
|
GenericObject<TestSimpleRequest>::data {
|
||||||
|
description = "A simple description"
|
||||||
|
typeEnrichment = TypeEnrichment("simple") {
|
||||||
|
TestSimpleRequest::a {
|
||||||
|
description = "A simple description"
|
||||||
|
}
|
||||||
|
TestSimpleRequest::b {
|
||||||
|
deprecated = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
description("A good response")
|
||||||
|
responseCode(HttpStatusCode.Created)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package io.bkbn.kompendium.core.util
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.core.fixtures.TestResponse
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultPath
|
||||||
|
import io.ktor.server.auth.authenticate
|
||||||
|
import io.ktor.server.routing.Routing
|
||||||
|
import io.ktor.server.routing.route
|
||||||
|
|
||||||
|
fun Routing.samePathSameMethod() {
|
||||||
|
route(defaultPath) {
|
||||||
|
basicGetGenerator<TestResponse>()
|
||||||
|
authenticate("basic") {
|
||||||
|
basicGetGenerator<TestResponse>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
115
core/src/test/kotlin/io/bkbn/kompendium/core/util/Examples.kt
Normal file
115
core/src/test/kotlin/io/bkbn/kompendium/core/util/Examples.kt
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
package io.bkbn.kompendium.core.util
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.core.fixtures.TestNested
|
||||||
|
import io.bkbn.kompendium.core.fixtures.TestRequest
|
||||||
|
import io.bkbn.kompendium.core.fixtures.TestResponse
|
||||||
|
import io.bkbn.kompendium.core.metadata.PostInfo
|
||||||
|
import io.bkbn.kompendium.core.plugin.NotarizedRoute
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultPathDescription
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultPathSummary
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultRequestDescription
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultResponseDescription
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.rootPath
|
||||||
|
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
||||||
|
import io.bkbn.kompendium.oas.payload.MediaType
|
||||||
|
import io.bkbn.kompendium.oas.payload.Parameter
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.server.application.install
|
||||||
|
import io.ktor.server.routing.Routing
|
||||||
|
import io.ktor.server.routing.route
|
||||||
|
|
||||||
|
fun Routing.reqRespExamples() {
|
||||||
|
route(rootPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
post = PostInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
request {
|
||||||
|
description(defaultRequestDescription)
|
||||||
|
requestType<TestRequest>()
|
||||||
|
examples(
|
||||||
|
"Testerina" to MediaType.Example(TestRequest(TestNested("asdf"), 1.5, emptyList()))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<TestResponse>()
|
||||||
|
examples(
|
||||||
|
"Testerino" to MediaType.Example(TestResponse("Heya"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.exampleParams() = basicGetGenerator<TestResponse>(
|
||||||
|
params = listOf(
|
||||||
|
Parameter(
|
||||||
|
name = "id",
|
||||||
|
`in` = Parameter.Location.path,
|
||||||
|
schema = TypeDefinition.STRING,
|
||||||
|
examples = mapOf(
|
||||||
|
"foo" to MediaType.Example("testing")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Routing.optionalReqExample() {
|
||||||
|
route(rootPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
post = PostInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
request {
|
||||||
|
description(defaultRequestDescription)
|
||||||
|
requestType<TestRequest>()
|
||||||
|
examples(
|
||||||
|
"Testerina" to MediaType.Example(TestRequest(TestNested("asdf"), 1.5, emptyList()))
|
||||||
|
)
|
||||||
|
required(false)
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<TestResponse>()
|
||||||
|
examples(
|
||||||
|
"Testerino" to MediaType.Example(TestResponse("Heya"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.exampleSummaryAndDescription() {
|
||||||
|
route(rootPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
post = PostInfo.builder {
|
||||||
|
summary("This is a summary")
|
||||||
|
description("This is a description")
|
||||||
|
request {
|
||||||
|
description("This is a request description")
|
||||||
|
requestType<TestRequest>()
|
||||||
|
examples(
|
||||||
|
"Testerina" to MediaType.Example(
|
||||||
|
TestRequest(TestNested("asdf"), 1.5, emptyList()),
|
||||||
|
"summary",
|
||||||
|
"description"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
description("This is a response description")
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<TestResponse>()
|
||||||
|
examples(
|
||||||
|
"Testerino" to MediaType.Example(TestResponse("Heya"), "summary", "description")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
105
core/src/test/kotlin/io/bkbn/kompendium/core/util/Exceptions.kt
Normal file
105
core/src/test/kotlin/io/bkbn/kompendium/core/util/Exceptions.kt
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
package io.bkbn.kompendium.core.util
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.core.fixtures.ExceptionResponse
|
||||||
|
import io.bkbn.kompendium.core.fixtures.Flibbity
|
||||||
|
import io.bkbn.kompendium.core.fixtures.FlibbityGibbit
|
||||||
|
import io.bkbn.kompendium.core.fixtures.TestResponse
|
||||||
|
import io.bkbn.kompendium.core.metadata.GetInfo
|
||||||
|
import io.bkbn.kompendium.core.plugin.NotarizedRoute
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultPathDescription
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultPathSummary
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultResponseDescription
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.rootPath
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.server.application.install
|
||||||
|
import io.ktor.server.routing.Routing
|
||||||
|
import io.ktor.server.routing.route
|
||||||
|
|
||||||
|
fun Routing.singleException() {
|
||||||
|
route(rootPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
get = GetInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
response {
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<TestResponse>()
|
||||||
|
}
|
||||||
|
canRespond {
|
||||||
|
description("Bad Things Happened")
|
||||||
|
responseCode(HttpStatusCode.BadRequest)
|
||||||
|
responseType<ExceptionResponse>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.multipleExceptions() {
|
||||||
|
route(rootPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
get = GetInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
response {
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<TestResponse>()
|
||||||
|
}
|
||||||
|
canRespond {
|
||||||
|
description("Bad Things Happened")
|
||||||
|
responseCode(HttpStatusCode.BadRequest)
|
||||||
|
responseType<ExceptionResponse>()
|
||||||
|
}
|
||||||
|
canRespond {
|
||||||
|
description("Access Denied")
|
||||||
|
responseCode(HttpStatusCode.Forbidden)
|
||||||
|
responseType<ExceptionResponse>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.polymorphicException() {
|
||||||
|
route(rootPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
get = GetInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
response {
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<TestResponse>()
|
||||||
|
}
|
||||||
|
canRespond {
|
||||||
|
description("Bad Things Happened")
|
||||||
|
responseCode(HttpStatusCode.InternalServerError)
|
||||||
|
responseType<FlibbityGibbit>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.genericException() {
|
||||||
|
route(rootPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
get = GetInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
response {
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<TestResponse>()
|
||||||
|
}
|
||||||
|
canRespond {
|
||||||
|
description("Bad Things Happened")
|
||||||
|
responseCode(HttpStatusCode.BadRequest)
|
||||||
|
responseType<Flibbity<String>>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
package io.bkbn.kompendium.core.util
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.core.fixtures.ColumnSchema
|
||||||
|
import io.bkbn.kompendium.core.fixtures.DateTimeString
|
||||||
|
import io.bkbn.kompendium.core.fixtures.ManyThings
|
||||||
|
import io.bkbn.kompendium.core.fixtures.Nested
|
||||||
|
import io.bkbn.kompendium.core.fixtures.NullableEnum
|
||||||
|
import io.bkbn.kompendium.core.fixtures.ProfileUpdateRequest
|
||||||
|
import io.bkbn.kompendium.core.fixtures.TestCreatedResponse
|
||||||
|
import io.bkbn.kompendium.core.fixtures.TestResponse
|
||||||
|
import io.bkbn.kompendium.core.fixtures.TestSimpleRequest
|
||||||
|
import io.bkbn.kompendium.core.metadata.GetInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.PutInfo
|
||||||
|
import io.bkbn.kompendium.core.plugin.NotarizedRoute
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultParams
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultPath
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultPathDescription
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultPathSummary
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultRequestDescription
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultResponseDescription
|
||||||
|
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
||||||
|
import io.bkbn.kompendium.oas.payload.Parameter
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.server.application.install
|
||||||
|
import io.ktor.server.auth.authenticate
|
||||||
|
import io.ktor.server.routing.Routing
|
||||||
|
import io.ktor.server.routing.route
|
||||||
|
|
||||||
|
fun Routing.withOperationId() = basicGetGenerator<TestResponse>(operationId = "getThisDude")
|
||||||
|
fun Routing.nullableNestedObject() = basicGetGenerator<ProfileUpdateRequest>()
|
||||||
|
fun Routing.nullableEnumField() = basicGetGenerator<NullableEnum>()
|
||||||
|
fun Routing.nullableReference() = basicGetGenerator<ManyThings>()
|
||||||
|
fun Routing.dateTimeString() = basicGetGenerator<DateTimeString>()
|
||||||
|
fun Routing.headerParameter() = basicGetGenerator<TestResponse>(
|
||||||
|
params = listOf(
|
||||||
|
Parameter(
|
||||||
|
name = "X-User-Email",
|
||||||
|
`in` = Parameter.Location.header,
|
||||||
|
schema = TypeDefinition.STRING,
|
||||||
|
required = true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Routing.nestedTypeName() = basicGetGenerator<Nested.Response>()
|
||||||
|
fun Routing.topLevelNullable() = basicGetGenerator<TestResponse?>()
|
||||||
|
fun Routing.simpleRecursive() = basicGetGenerator<ColumnSchema>()
|
||||||
|
fun Routing.samePathDifferentMethodsAndAuth() {
|
||||||
|
route(defaultPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
parameters = defaultParams
|
||||||
|
get = GetInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
response {
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<TestResponse>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
authenticate("basic") {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
put = PutInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
request {
|
||||||
|
description(defaultRequestDescription)
|
||||||
|
requestType<TestSimpleRequest>()
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
responseCode(HttpStatusCode.Created)
|
||||||
|
responseType<TestCreatedResponse>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,372 @@
|
|||||||
|
package io.bkbn.kompendium.core.util
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.core.fixtures.ComplexRequest
|
||||||
|
import io.bkbn.kompendium.core.fixtures.SomethingSimilar
|
||||||
|
import io.bkbn.kompendium.core.fixtures.TestCreatedResponse
|
||||||
|
import io.bkbn.kompendium.core.fixtures.TestRequest
|
||||||
|
import io.bkbn.kompendium.core.fixtures.TestResponse
|
||||||
|
import io.bkbn.kompendium.core.fixtures.TestSimpleRequest
|
||||||
|
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.plugin.NotarizedRoute
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultParams
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultPath
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultPathDescription
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultPathSummary
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultResponseDescription
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.rootPath
|
||||||
|
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
||||||
|
import io.bkbn.kompendium.oas.payload.Header
|
||||||
|
import io.bkbn.kompendium.oas.payload.Parameter
|
||||||
|
import io.ktor.http.HttpHeaders
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.server.application.call
|
||||||
|
import io.ktor.server.application.install
|
||||||
|
import io.ktor.server.response.respond
|
||||||
|
import io.ktor.server.response.respondText
|
||||||
|
import io.ktor.server.routing.Routing
|
||||||
|
import io.ktor.server.routing.delete
|
||||||
|
import io.ktor.server.routing.get
|
||||||
|
import io.ktor.server.routing.head
|
||||||
|
import io.ktor.server.routing.options
|
||||||
|
import io.ktor.server.routing.patch
|
||||||
|
import io.ktor.server.routing.post
|
||||||
|
import io.ktor.server.routing.put
|
||||||
|
import io.ktor.server.routing.route
|
||||||
|
|
||||||
|
fun Routing.notarizedGet() {
|
||||||
|
route(defaultPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
parameters = defaultParams
|
||||||
|
get = GetInfo.builder {
|
||||||
|
response {
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<TestResponse>()
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
}
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
get {
|
||||||
|
call.respondText { "hey dude ‼️ congrats on the get request" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.responseHeaders() {
|
||||||
|
route(defaultPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
parameters = defaultParams
|
||||||
|
get = GetInfo.builder {
|
||||||
|
response {
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<TestResponse>()
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
responseHeaders(
|
||||||
|
mapOf(
|
||||||
|
HttpHeaders.ETag to Header(
|
||||||
|
TypeDefinition.STRING,
|
||||||
|
"https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag"
|
||||||
|
),
|
||||||
|
HttpHeaders.LastModified to Header(
|
||||||
|
TypeDefinition.STRING,
|
||||||
|
"https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Last-Modified"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
get {
|
||||||
|
call.respondText { "hey dude ‼️ congrats on the get request" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.notarizedPost() {
|
||||||
|
route(defaultPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
parameters = defaultParams
|
||||||
|
post = PostInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
request {
|
||||||
|
requestType<TestSimpleRequest>()
|
||||||
|
description("A Test request")
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
responseCode(HttpStatusCode.Created)
|
||||||
|
responseType<TestCreatedResponse>()
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post {
|
||||||
|
call.respondText { "hey dude ‼️ congrats on the post request" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.notarizedPut() {
|
||||||
|
route(defaultPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
parameters = defaultParams
|
||||||
|
put = PutInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
request {
|
||||||
|
requestType<TestSimpleRequest>()
|
||||||
|
description("A Test request")
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
responseCode(HttpStatusCode.Created)
|
||||||
|
responseType<TestCreatedResponse>()
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
put {
|
||||||
|
call.respondText { "hey dude ‼️ congrats on the post request" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.notarizedDelete() {
|
||||||
|
route(defaultPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
parameters = defaultParams
|
||||||
|
delete = DeleteInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
response {
|
||||||
|
responseCode(HttpStatusCode.NoContent)
|
||||||
|
responseType<Unit>()
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete {
|
||||||
|
call.respond(HttpStatusCode.NoContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.notarizedPatch() {
|
||||||
|
route(defaultPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
parameters = defaultParams
|
||||||
|
patch = PatchInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
request {
|
||||||
|
description("A Test request")
|
||||||
|
requestType<TestSimpleRequest>()
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
responseCode(HttpStatusCode.Created)
|
||||||
|
responseType<TestCreatedResponse>()
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
patch {
|
||||||
|
call.respond(HttpStatusCode.Created) { TestCreatedResponse(123, "Nice!") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.notarizedHead() {
|
||||||
|
route(defaultPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
parameters = defaultParams
|
||||||
|
head = HeadInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
|
||||||
|
response {
|
||||||
|
description("great!")
|
||||||
|
responseCode(HttpStatusCode.Created)
|
||||||
|
responseType<Unit>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
head {
|
||||||
|
call.respond(HttpStatusCode.OK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.notarizedOptions() {
|
||||||
|
route(defaultPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
parameters = defaultParams
|
||||||
|
options = OptionsInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
response {
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<TestResponse>()
|
||||||
|
description("nice")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
options {
|
||||||
|
call.respond(HttpStatusCode.NoContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.complexRequest() {
|
||||||
|
route(rootPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
put = PutInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
request {
|
||||||
|
requestType<ComplexRequest>()
|
||||||
|
description("A Complex request")
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
responseCode(HttpStatusCode.Created)
|
||||||
|
responseType<TestCreatedResponse>()
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
patch {
|
||||||
|
call.respond(HttpStatusCode.Created, TestCreatedResponse(123, "nice!"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.primitives() {
|
||||||
|
route(rootPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
put = PutInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
request {
|
||||||
|
requestType<Int>()
|
||||||
|
description("A Test Request")
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
responseCode(HttpStatusCode.Created)
|
||||||
|
responseType<Boolean>()
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.returnsList() {
|
||||||
|
route(defaultPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
parameters = defaultParams
|
||||||
|
get = GetInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
response {
|
||||||
|
description("A Successful List-y Endeavor")
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<List<TestResponse>>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.nonRequiredParams() {
|
||||||
|
route("/optional") {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
parameters = listOf(
|
||||||
|
Parameter(
|
||||||
|
name = "notRequired",
|
||||||
|
`in` = Parameter.Location.query,
|
||||||
|
schema = TypeDefinition.STRING,
|
||||||
|
required = false,
|
||||||
|
),
|
||||||
|
Parameter(
|
||||||
|
name = "required",
|
||||||
|
`in` = Parameter.Location.query,
|
||||||
|
schema = TypeDefinition.STRING
|
||||||
|
)
|
||||||
|
)
|
||||||
|
get = GetInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
response {
|
||||||
|
responseType<Unit>()
|
||||||
|
description("Empty")
|
||||||
|
responseCode(HttpStatusCode.NoContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.overrideMediaTypes() {
|
||||||
|
route("/media_types") {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
put = PutInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
request {
|
||||||
|
mediaTypes("multipart/form-data", "application/json")
|
||||||
|
requestType<TestRequest>()
|
||||||
|
description("A cool request")
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
mediaTypes("application/xml")
|
||||||
|
responseType<TestResponse>()
|
||||||
|
description("A good response")
|
||||||
|
responseCode(HttpStatusCode.Created)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.postNoReqBody() {
|
||||||
|
route("/no_req_body") {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
post = PostInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
response {
|
||||||
|
responseType<TestResponse>()
|
||||||
|
description("Cool response")
|
||||||
|
responseCode(HttpStatusCode.Created)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.fieldOutsideConstructor() {
|
||||||
|
route("/field_outside_constructor") {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
post = PostInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
request {
|
||||||
|
requestType<SomethingSimilar>()
|
||||||
|
description("A cool request")
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
responseType<TestResponse>()
|
||||||
|
description("Cool response")
|
||||||
|
responseCode(HttpStatusCode.Created)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
package io.bkbn.kompendium.core.util
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.core.fixtures.Barzo
|
||||||
|
import io.bkbn.kompendium.core.fixtures.ChillaxificationMaximization
|
||||||
|
import io.bkbn.kompendium.core.fixtures.ComplexRequest
|
||||||
|
import io.bkbn.kompendium.core.fixtures.Flibbity
|
||||||
|
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.Gizmo
|
||||||
|
import io.bkbn.kompendium.core.fixtures.MultiNestedGenerics
|
||||||
|
import io.bkbn.kompendium.core.fixtures.Page
|
||||||
|
import io.ktor.server.routing.Routing
|
||||||
|
|
||||||
|
fun Routing.polymorphicResponse() = basicGetGenerator<FlibbityGibbit>()
|
||||||
|
fun Routing.polymorphicCollectionResponse() = basicGetGenerator<List<FlibbityGibbit>>()
|
||||||
|
fun Routing.polymorphicMapResponse() = basicGetGenerator<Map<String, FlibbityGibbit>>()
|
||||||
|
fun Routing.simpleGenericResponse() = basicGetGenerator<Gibbity<String>>()
|
||||||
|
fun Routing.gnarlyGenericResponse() = basicGetGenerator<Foosy<Barzo<Int>, String>>()
|
||||||
|
fun Routing.nestedGenericResponse() = basicGetGenerator<Gibbity<Map<String, String>>>()
|
||||||
|
fun Routing.genericPolymorphicResponse() = basicGetGenerator<Flibbity<Double>>()
|
||||||
|
fun Routing.genericPolymorphicResponseMultipleImpls() = basicGetGenerator<Flibbity<FlibbityGibbit>>()
|
||||||
|
fun Routing.nestedGenericCollection() = basicGetGenerator<Page<Int>>()
|
||||||
|
fun Routing.nestedGenericMultipleParamsCollection() = basicGetGenerator<MultiNestedGenerics<String, ComplexRequest>>()
|
||||||
|
fun Routing.overrideSealedTypeIdentifier() = basicGetGenerator<ChillaxificationMaximization>()
|
||||||
|
fun Routing.subtypeNotCompleteSetOfParentProperties() = basicGetGenerator<Gizmo>()
|
@ -0,0 +1,32 @@
|
|||||||
|
package io.bkbn.kompendium.core.util
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.core.fixtures.DefaultField
|
||||||
|
import io.bkbn.kompendium.core.fixtures.NullableField
|
||||||
|
import io.bkbn.kompendium.core.fixtures.TestResponse
|
||||||
|
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
||||||
|
import io.bkbn.kompendium.oas.payload.Parameter
|
||||||
|
import io.ktor.server.routing.Routing
|
||||||
|
|
||||||
|
fun Routing.requiredParams() = basicGetGenerator<TestResponse>(
|
||||||
|
params = listOf(
|
||||||
|
Parameter(
|
||||||
|
name = "id",
|
||||||
|
`in` = Parameter.Location.path,
|
||||||
|
schema = TypeDefinition.STRING
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Routing.nonRequiredParam() = basicGetGenerator<TestResponse>(
|
||||||
|
params = listOf(
|
||||||
|
Parameter(
|
||||||
|
name = "id",
|
||||||
|
`in` = Parameter.Location.query,
|
||||||
|
schema = TypeDefinition.STRING,
|
||||||
|
required = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Routing.defaultField() = basicGetGenerator<DefaultField>()
|
||||||
|
fun Routing.nullableField() = basicGetGenerator<NullableField>()
|
@ -0,0 +1,132 @@
|
|||||||
|
package io.bkbn.kompendium.core.util
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.core.fixtures.TestResponse
|
||||||
|
import io.bkbn.kompendium.core.metadata.GetInfo
|
||||||
|
import io.bkbn.kompendium.core.plugin.NotarizedRoute
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultParams
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultPathDescription
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultPathSummary
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultResponseDescription
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.rootPath
|
||||||
|
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
||||||
|
import io.bkbn.kompendium.oas.payload.Parameter
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.server.application.install
|
||||||
|
import io.ktor.server.routing.Routing
|
||||||
|
import io.ktor.server.routing.route
|
||||||
|
import io.ktor.server.routing.param
|
||||||
|
|
||||||
|
fun Routing.simplePathParsing() {
|
||||||
|
route("/this") {
|
||||||
|
route("/is") {
|
||||||
|
route("/a") {
|
||||||
|
route("/complex") {
|
||||||
|
route("path") {
|
||||||
|
route("with/an/{id}") {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
get = GetInfo.builder {
|
||||||
|
parameters = listOf(
|
||||||
|
Parameter(
|
||||||
|
name = "id",
|
||||||
|
`in` = Parameter.Location.path,
|
||||||
|
schema = TypeDefinition.STRING
|
||||||
|
)
|
||||||
|
)
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
response {
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<TestResponse>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.rootRoute() {
|
||||||
|
route(rootPath) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
parameters = listOf(defaultParams.last())
|
||||||
|
get = GetInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
response {
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<TestResponse>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.nestedUnderRoot() {
|
||||||
|
route("/") {
|
||||||
|
route("/testerino") {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
get = GetInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
response {
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<TestResponse>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.trailingSlash() {
|
||||||
|
route("/test") {
|
||||||
|
route("/") {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
get = GetInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
response {
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<TestResponse>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.paramWrapper() {
|
||||||
|
route("/test") {
|
||||||
|
param("a") {
|
||||||
|
param("b") {
|
||||||
|
param("c") {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
parameters = listOf(
|
||||||
|
Parameter(
|
||||||
|
name = "test",
|
||||||
|
`in` = Parameter.Location.query,
|
||||||
|
schema = TypeDefinition.STRING
|
||||||
|
)
|
||||||
|
)
|
||||||
|
get = GetInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
response {
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<TestResponse>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,66 +1,29 @@
|
|||||||
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.ComplexRequest
|
|
||||||
import io.bkbn.kompendium.core.fixtures.DateTimeString
|
|
||||||
import io.bkbn.kompendium.core.fixtures.DefaultField
|
|
||||||
import io.bkbn.kompendium.core.fixtures.ExceptionResponse
|
|
||||||
import io.bkbn.kompendium.core.fixtures.Flibbity
|
|
||||||
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.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.NullableField
|
|
||||||
import io.bkbn.kompendium.core.fixtures.Page
|
|
||||||
import io.bkbn.kompendium.core.fixtures.ProfileUpdateRequest
|
|
||||||
import io.bkbn.kompendium.core.fixtures.SerialNameObject
|
|
||||||
import io.bkbn.kompendium.core.fixtures.TestCreatedResponse
|
|
||||||
import io.bkbn.kompendium.core.fixtures.TestNested
|
|
||||||
import io.bkbn.kompendium.core.fixtures.TestRequest
|
|
||||||
import io.bkbn.kompendium.core.fixtures.TestResponse
|
|
||||||
import io.bkbn.kompendium.core.fixtures.TestSimpleRequest
|
|
||||||
import io.bkbn.kompendium.core.fixtures.TransientObject
|
|
||||||
import io.bkbn.kompendium.core.fixtures.UnbackedObject
|
|
||||||
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.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.plugin.NotarizedRoute
|
import io.bkbn.kompendium.core.plugin.NotarizedRoute
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultPathDescription
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultPathSummary
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultResponseDescription
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.rootPath
|
||||||
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
||||||
import io.bkbn.kompendium.oas.payload.Parameter
|
import io.bkbn.kompendium.oas.payload.Parameter
|
||||||
import io.ktor.http.HttpStatusCode
|
import io.ktor.http.HttpStatusCode
|
||||||
import io.ktor.server.application.call
|
|
||||||
import io.ktor.server.application.install
|
import io.ktor.server.application.install
|
||||||
import io.ktor.server.auth.authenticate
|
|
||||||
import io.ktor.server.response.respond
|
|
||||||
import io.ktor.server.response.respondText
|
|
||||||
import io.ktor.server.routing.Route
|
import io.ktor.server.routing.Route
|
||||||
import io.ktor.server.routing.Routing
|
import io.ktor.server.routing.Routing
|
||||||
import io.ktor.server.routing.delete
|
|
||||||
import io.ktor.server.routing.get
|
|
||||||
import io.ktor.server.routing.head
|
|
||||||
import io.ktor.server.routing.options
|
|
||||||
import io.ktor.server.routing.patch
|
|
||||||
import io.ktor.server.routing.post
|
|
||||||
import io.ktor.server.routing.put
|
|
||||||
import io.ktor.server.routing.route
|
import io.ktor.server.routing.route
|
||||||
|
|
||||||
object TestModules {
|
object TestModules {
|
||||||
private const val defaultPath = "/test/{a}"
|
|
||||||
private const val rootPath = "/"
|
|
||||||
private const val defaultResponseDescription = "A Successful Endeavor"
|
|
||||||
private const val defaultRequestDescription = "You gotta send it"
|
|
||||||
private const val defaultPathSummary = "Great Summary!"
|
|
||||||
private const val defaultPathDescription = "testing more"
|
|
||||||
|
|
||||||
private val defaultParams = listOf(
|
const val defaultPath = "/test/{a}"
|
||||||
|
const val rootPath = "/"
|
||||||
|
const val defaultResponseDescription = "A Successful Endeavor"
|
||||||
|
const val defaultRequestDescription = "You gotta send it"
|
||||||
|
const val defaultPathSummary = "Great Summary!"
|
||||||
|
const val defaultPathDescription = "testing more"
|
||||||
|
|
||||||
|
val defaultParams = listOf(
|
||||||
Parameter(
|
Parameter(
|
||||||
name = "a",
|
name = "a",
|
||||||
`in` = Parameter.Location.path,
|
`in` = Parameter.Location.path,
|
||||||
@ -72,612 +35,31 @@ object TestModules {
|
|||||||
schema = TypeDefinition.INT
|
schema = TypeDefinition.INT
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun Routing.notarizedGet() {
|
internal inline fun <reified T> Routing.basicGetGenerator(
|
||||||
route(defaultPath) {
|
params: List<Parameter> = emptyList(),
|
||||||
install(NotarizedRoute()) {
|
operationId: String? = null
|
||||||
parameters = defaultParams
|
) {
|
||||||
get = GetInfo.builder {
|
route(rootPath) {
|
||||||
response {
|
basicGetGenerator<T>(params, operationId)
|
||||||
responseCode(HttpStatusCode.OK)
|
|
||||||
responseType<TestResponse>()
|
|
||||||
description(defaultResponseDescription)
|
|
||||||
}
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
get {
|
|
||||||
call.respondText { "hey dude ‼️ congrats on the get request" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun Routing.notarizedPost() {
|
internal inline fun <reified T> Route.basicGetGenerator(
|
||||||
route(defaultPath) {
|
params: List<Parameter> = emptyList(),
|
||||||
install(NotarizedRoute()) {
|
operationId: String? = null
|
||||||
parameters = defaultParams
|
) {
|
||||||
post = PostInfo.builder {
|
install(NotarizedRoute()) {
|
||||||
summary(defaultPathSummary)
|
get = GetInfo.builder {
|
||||||
description(defaultPathDescription)
|
summary(defaultPathSummary)
|
||||||
request {
|
description(defaultPathDescription)
|
||||||
requestType<TestSimpleRequest>()
|
operationId?.let { operationId(it) }
|
||||||
description("A Test request")
|
parameters = params
|
||||||
}
|
response {
|
||||||
response {
|
description(defaultResponseDescription)
|
||||||
responseCode(HttpStatusCode.Created)
|
responseCode(HttpStatusCode.OK)
|
||||||
responseType<TestCreatedResponse>()
|
responseType<T>()
|
||||||
description(defaultResponseDescription)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
post {
|
|
||||||
call.respondText { "hey dude ‼️ congrats on the post request" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.notarizedPut() {
|
|
||||||
route(defaultPath) {
|
|
||||||
install(NotarizedRoute()) {
|
|
||||||
parameters = defaultParams
|
|
||||||
put = PutInfo.builder {
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
request {
|
|
||||||
requestType<TestSimpleRequest>()
|
|
||||||
description("A Test request")
|
|
||||||
}
|
|
||||||
response {
|
|
||||||
responseCode(HttpStatusCode.Created)
|
|
||||||
responseType<TestCreatedResponse>()
|
|
||||||
description(defaultResponseDescription)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
put {
|
|
||||||
call.respondText { "hey dude ‼️ congrats on the post request" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.notarizedDelete() {
|
|
||||||
route(defaultPath) {
|
|
||||||
install(NotarizedRoute()) {
|
|
||||||
parameters = defaultParams
|
|
||||||
delete = DeleteInfo.builder {
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
response {
|
|
||||||
responseCode(HttpStatusCode.NoContent)
|
|
||||||
responseType<Unit>()
|
|
||||||
description(defaultResponseDescription)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delete {
|
|
||||||
call.respond(HttpStatusCode.NoContent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.notarizedPatch() {
|
|
||||||
route(defaultPath) {
|
|
||||||
install(NotarizedRoute()) {
|
|
||||||
parameters = defaultParams
|
|
||||||
patch = PatchInfo.builder {
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
request {
|
|
||||||
description("A Test request")
|
|
||||||
requestType<TestSimpleRequest>()
|
|
||||||
}
|
|
||||||
response {
|
|
||||||
responseCode(HttpStatusCode.Created)
|
|
||||||
responseType<TestCreatedResponse>()
|
|
||||||
description(defaultResponseDescription)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
patch {
|
|
||||||
call.respond(HttpStatusCode.Created) { TestCreatedResponse(123, "Nice!") }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.notarizedHead() {
|
|
||||||
route(defaultPath) {
|
|
||||||
install(NotarizedRoute()) {
|
|
||||||
parameters = defaultParams
|
|
||||||
head = HeadInfo.builder {
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
|
|
||||||
response {
|
|
||||||
description("great!")
|
|
||||||
responseCode(HttpStatusCode.Created)
|
|
||||||
responseType<Unit>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
head {
|
|
||||||
call.respond(HttpStatusCode.OK)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.notarizedOptions() {
|
|
||||||
route(defaultPath) {
|
|
||||||
install(NotarizedRoute()) {
|
|
||||||
parameters = defaultParams
|
|
||||||
options = OptionsInfo.builder {
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
response {
|
|
||||||
responseCode(HttpStatusCode.OK)
|
|
||||||
responseType<TestResponse>()
|
|
||||||
description("nice")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
options {
|
|
||||||
call.respond(HttpStatusCode.NoContent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.complexRequest() {
|
|
||||||
route(rootPath) {
|
|
||||||
install(NotarizedRoute()) {
|
|
||||||
put = PutInfo.builder {
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
request {
|
|
||||||
requestType<ComplexRequest>()
|
|
||||||
description("A Complex request")
|
|
||||||
}
|
|
||||||
response {
|
|
||||||
responseCode(HttpStatusCode.Created)
|
|
||||||
responseType<TestCreatedResponse>()
|
|
||||||
description(defaultResponseDescription)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
patch {
|
|
||||||
call.respond(HttpStatusCode.Created, TestCreatedResponse(123, "nice!"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.primitives() {
|
|
||||||
route(rootPath) {
|
|
||||||
install(NotarizedRoute()) {
|
|
||||||
put = PutInfo.builder {
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
request {
|
|
||||||
requestType<Int>()
|
|
||||||
description("A Test Request")
|
|
||||||
}
|
|
||||||
response {
|
|
||||||
responseCode(HttpStatusCode.Created)
|
|
||||||
responseType<Boolean>()
|
|
||||||
description(defaultResponseDescription)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.returnsList() {
|
|
||||||
route(defaultPath) {
|
|
||||||
install(NotarizedRoute()) {
|
|
||||||
parameters = defaultParams
|
|
||||||
get = GetInfo.builder {
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
response {
|
|
||||||
description("A Successful List-y Endeavor")
|
|
||||||
responseCode(HttpStatusCode.OK)
|
|
||||||
responseType<List<TestResponse>>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.nonRequiredParams() {
|
|
||||||
route("/optional") {
|
|
||||||
install(NotarizedRoute()) {
|
|
||||||
parameters = listOf(
|
|
||||||
Parameter(
|
|
||||||
name = "notRequired",
|
|
||||||
`in` = Parameter.Location.query,
|
|
||||||
schema = TypeDefinition.STRING,
|
|
||||||
required = false,
|
|
||||||
),
|
|
||||||
Parameter(
|
|
||||||
name = "required",
|
|
||||||
`in` = Parameter.Location.query,
|
|
||||||
schema = TypeDefinition.STRING
|
|
||||||
)
|
|
||||||
)
|
|
||||||
get = GetInfo.builder {
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
response {
|
|
||||||
responseType<Unit>()
|
|
||||||
description("Empty")
|
|
||||||
responseCode(HttpStatusCode.NoContent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.simplePathParsing() {
|
|
||||||
route("/this") {
|
|
||||||
route("/is") {
|
|
||||||
route("/a") {
|
|
||||||
route("/complex") {
|
|
||||||
route("path") {
|
|
||||||
route("with/an/{id}") {
|
|
||||||
install(NotarizedRoute()) {
|
|
||||||
get = GetInfo.builder {
|
|
||||||
parameters = listOf(
|
|
||||||
Parameter(
|
|
||||||
name = "id",
|
|
||||||
`in` = Parameter.Location.path,
|
|
||||||
schema = TypeDefinition.STRING
|
|
||||||
)
|
|
||||||
)
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
response {
|
|
||||||
description(defaultResponseDescription)
|
|
||||||
responseCode(HttpStatusCode.OK)
|
|
||||||
responseType<TestResponse>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.rootRoute() {
|
|
||||||
route(rootPath) {
|
|
||||||
install(NotarizedRoute()) {
|
|
||||||
parameters = listOf(defaultParams.last())
|
|
||||||
get = GetInfo.builder {
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
response {
|
|
||||||
description(defaultResponseDescription)
|
|
||||||
responseCode(HttpStatusCode.OK)
|
|
||||||
responseType<TestResponse>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.nestedUnderRoot() {
|
|
||||||
route("/") {
|
|
||||||
route("/testerino") {
|
|
||||||
install(NotarizedRoute()) {
|
|
||||||
get = GetInfo.builder {
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
response {
|
|
||||||
description(defaultResponseDescription)
|
|
||||||
responseCode(HttpStatusCode.OK)
|
|
||||||
responseType<TestResponse>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.trailingSlash() {
|
|
||||||
route("/test") {
|
|
||||||
route("/") {
|
|
||||||
install(NotarizedRoute()) {
|
|
||||||
get = GetInfo.builder {
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
response {
|
|
||||||
description(defaultResponseDescription)
|
|
||||||
responseCode(HttpStatusCode.OK)
|
|
||||||
responseType<TestResponse>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.singleException() {
|
|
||||||
route(rootPath) {
|
|
||||||
install(NotarizedRoute()) {
|
|
||||||
get = GetInfo.builder {
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
response {
|
|
||||||
description(defaultResponseDescription)
|
|
||||||
responseCode(HttpStatusCode.OK)
|
|
||||||
responseType<TestResponse>()
|
|
||||||
}
|
|
||||||
canRespond {
|
|
||||||
description("Bad Things Happened")
|
|
||||||
responseCode(HttpStatusCode.BadRequest)
|
|
||||||
responseType<ExceptionResponse>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.multipleExceptions() {
|
|
||||||
route(rootPath) {
|
|
||||||
install(NotarizedRoute()) {
|
|
||||||
get = GetInfo.builder {
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
response {
|
|
||||||
description(defaultResponseDescription)
|
|
||||||
responseCode(HttpStatusCode.OK)
|
|
||||||
responseType<TestResponse>()
|
|
||||||
}
|
|
||||||
canRespond {
|
|
||||||
description("Bad Things Happened")
|
|
||||||
responseCode(HttpStatusCode.BadRequest)
|
|
||||||
responseType<ExceptionResponse>()
|
|
||||||
}
|
|
||||||
canRespond {
|
|
||||||
description("Access Denied")
|
|
||||||
responseCode(HttpStatusCode.Forbidden)
|
|
||||||
responseType<ExceptionResponse>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.polymorphicException() {
|
|
||||||
route(rootPath) {
|
|
||||||
install(NotarizedRoute()) {
|
|
||||||
get = GetInfo.builder {
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
response {
|
|
||||||
description(defaultResponseDescription)
|
|
||||||
responseCode(HttpStatusCode.OK)
|
|
||||||
responseType<TestResponse>()
|
|
||||||
}
|
|
||||||
canRespond {
|
|
||||||
description("Bad Things Happened")
|
|
||||||
responseCode(HttpStatusCode.InternalServerError)
|
|
||||||
responseType<FlibbityGibbit>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.genericException() {
|
|
||||||
route(rootPath) {
|
|
||||||
install(NotarizedRoute()) {
|
|
||||||
get = GetInfo.builder {
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
response {
|
|
||||||
description(defaultResponseDescription)
|
|
||||||
responseCode(HttpStatusCode.OK)
|
|
||||||
responseType<TestResponse>()
|
|
||||||
}
|
|
||||||
canRespond {
|
|
||||||
description("Bad Things Happened")
|
|
||||||
responseCode(HttpStatusCode.BadRequest)
|
|
||||||
responseType<Flibbity<String>>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.reqRespExamples() {
|
|
||||||
route(rootPath) {
|
|
||||||
install(NotarizedRoute()) {
|
|
||||||
post = PostInfo.builder {
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
request {
|
|
||||||
description(defaultRequestDescription)
|
|
||||||
requestType<TestRequest>()
|
|
||||||
examples(
|
|
||||||
"Testerina" to TestRequest(TestNested("asdf"), 1.5, emptyList())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
response {
|
|
||||||
description(defaultResponseDescription)
|
|
||||||
responseCode(HttpStatusCode.OK)
|
|
||||||
responseType<TestResponse>()
|
|
||||||
examples(
|
|
||||||
"Testerino" to TestResponse("Heya")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.exampleParams() = basicGetGenerator<TestResponse>(
|
|
||||||
params = listOf(
|
|
||||||
Parameter(
|
|
||||||
name = "id",
|
|
||||||
`in` = Parameter.Location.path,
|
|
||||||
schema = TypeDefinition.STRING,
|
|
||||||
examples = mapOf(
|
|
||||||
"foo" to Parameter.Example("testing")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
fun Routing.defaultParameter() = basicGetGenerator<TestResponse>(
|
|
||||||
params = listOf(
|
|
||||||
Parameter(
|
|
||||||
name = "id",
|
|
||||||
`in` = Parameter.Location.path,
|
|
||||||
schema = TypeDefinition.STRING.withDefault("IDK")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
fun Routing.requiredParams() = basicGetGenerator<TestResponse>(
|
|
||||||
params = listOf(
|
|
||||||
Parameter(
|
|
||||||
name = "id",
|
|
||||||
`in` = Parameter.Location.path,
|
|
||||||
schema = TypeDefinition.STRING
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
fun Routing.nonRequiredParam() = basicGetGenerator<TestResponse>(
|
|
||||||
params = listOf(
|
|
||||||
Parameter(
|
|
||||||
name = "id",
|
|
||||||
`in` = Parameter.Location.query,
|
|
||||||
schema = TypeDefinition.STRING,
|
|
||||||
required = false
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
fun Routing.defaultField() = basicGetGenerator<DefaultField>()
|
|
||||||
|
|
||||||
fun Routing.nullableField() = basicGetGenerator<NullableField>()
|
|
||||||
|
|
||||||
fun Routing.polymorphicResponse() = basicGetGenerator<FlibbityGibbit>()
|
|
||||||
|
|
||||||
fun Routing.ignoredFieldsResponse() = basicGetGenerator<TransientObject>()
|
|
||||||
|
|
||||||
fun Routing.unbackedFieldsResponse() = basicGetGenerator<UnbackedObject>()
|
|
||||||
|
|
||||||
fun Routing.customFieldNameResponse() = basicGetGenerator<SerialNameObject>()
|
|
||||||
|
|
||||||
fun Routing.polymorphicCollectionResponse() = basicGetGenerator<List<FlibbityGibbit>>()
|
|
||||||
|
|
||||||
fun Routing.polymorphicMapResponse() = basicGetGenerator<Map<String, FlibbityGibbit>>()
|
|
||||||
|
|
||||||
fun Routing.simpleGenericResponse() = basicGetGenerator<Gibbity<String>>()
|
|
||||||
|
|
||||||
fun Routing.gnarlyGenericResponse() = basicGetGenerator<Foosy<Barzo<Int>, String>>()
|
|
||||||
|
|
||||||
fun Routing.nestedGenericResponse() = basicGetGenerator<Gibbity<Map<String, String>>>()
|
|
||||||
|
|
||||||
fun Routing.genericPolymorphicResponse() = basicGetGenerator<Flibbity<Double>>()
|
|
||||||
|
|
||||||
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.nullableNestedObject() = basicGetGenerator<ProfileUpdateRequest>()
|
|
||||||
|
|
||||||
fun Routing.nullableEnumField() = basicGetGenerator<NullableEnum>()
|
|
||||||
|
|
||||||
fun Routing.nullableReference() = basicGetGenerator<ManyThings>()
|
|
||||||
|
|
||||||
fun Routing.dateTimeString() = basicGetGenerator<DateTimeString>()
|
|
||||||
|
|
||||||
fun Routing.headerParameter() = basicGetGenerator<TestResponse>(
|
|
||||||
params = listOf(
|
|
||||||
Parameter(
|
|
||||||
name = "X-User-Email",
|
|
||||||
`in` = Parameter.Location.header,
|
|
||||||
schema = TypeDefinition.STRING,
|
|
||||||
required = true
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
fun Routing.nestedTypeName() = basicGetGenerator<Nested.Response>()
|
|
||||||
|
|
||||||
fun Routing.topLevelNullable() = basicGetGenerator<TestResponse?>()
|
|
||||||
|
|
||||||
fun Routing.simpleRecursive() = basicGetGenerator<ColumnSchema>()
|
|
||||||
|
|
||||||
fun Routing.defaultAuthConfig() {
|
|
||||||
authenticate("basic") {
|
|
||||||
route(rootPath) {
|
|
||||||
basicGetGenerator<TestResponse>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.customAuthConfig() {
|
|
||||||
authenticate("auth-oauth-google") {
|
|
||||||
route(rootPath) {
|
|
||||||
install(NotarizedRoute()) {
|
|
||||||
get = GetInfo.builder {
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
response {
|
|
||||||
description(defaultResponseDescription)
|
|
||||||
responseCode(HttpStatusCode.OK)
|
|
||||||
responseType<TestResponse>()
|
|
||||||
}
|
|
||||||
security = mapOf(
|
|
||||||
"auth-oauth-google" to listOf("read:pets")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Routing.multipleAuthStrategies() {
|
|
||||||
authenticate("jwt", "api-key") {
|
|
||||||
route(rootPath) {
|
|
||||||
basicGetGenerator<TestResponse>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private inline fun <reified T> Routing.basicGetGenerator(
|
|
||||||
params: List<Parameter> = emptyList(),
|
|
||||||
operationId: String? = null
|
|
||||||
) {
|
|
||||||
route(rootPath) {
|
|
||||||
basicGetGenerator<T>(params, operationId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private inline fun <reified T> Route.basicGetGenerator(
|
|
||||||
params: List<Parameter> = emptyList(),
|
|
||||||
operationId: String? = null
|
|
||||||
) {
|
|
||||||
install(NotarizedRoute()) {
|
|
||||||
get = GetInfo.builder {
|
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
|
||||||
operationId?.let { operationId(it) }
|
|
||||||
parameters = params
|
|
||||||
response {
|
|
||||||
description(defaultResponseDescription)
|
|
||||||
responseCode(HttpStatusCode.OK)
|
|
||||||
responseType<T>()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,12 +86,19 @@
|
|||||||
},
|
},
|
||||||
"z": {
|
"z": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"io.bkbn.kompendium.core.fixtures.ComplexGibbit"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"b",
|
"b",
|
||||||
"c",
|
"c",
|
||||||
"z"
|
"z",
|
||||||
|
"type"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"SimpleGibbit": {
|
"SimpleGibbit": {
|
||||||
@ -102,10 +109,17 @@
|
|||||||
},
|
},
|
||||||
"z": {
|
"z": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"io.bkbn.kompendium.core.fixtures.SimpleGibbit"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"a"
|
"a",
|
||||||
|
"type"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"FlibbityGibbit": {
|
"FlibbityGibbit": {
|
||||||
|
@ -82,11 +82,18 @@
|
|||||||
},
|
},
|
||||||
"f": {
|
"f": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"io.bkbn.kompendium.core.fixtures.Bibbity"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"b",
|
"b",
|
||||||
"f"
|
"f",
|
||||||
|
"type"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Gibbity-String": {
|
"Gibbity-String": {
|
||||||
@ -94,10 +101,17 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"a": {
|
"a": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"io.bkbn.kompendium.core.fixtures.Gibbity"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"a"
|
"a",
|
||||||
|
"type"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Flibbity-String": {
|
"Flibbity-String": {
|
||||||
|
@ -65,12 +65,19 @@
|
|||||||
},
|
},
|
||||||
"z": {
|
"z": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"io.bkbn.kompendium.core.fixtures.ComplexGibbit"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"b",
|
"b",
|
||||||
"c",
|
"c",
|
||||||
"z"
|
"z",
|
||||||
|
"type"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"SimpleGibbit": {
|
"SimpleGibbit": {
|
||||||
@ -81,10 +88,17 @@
|
|||||||
},
|
},
|
||||||
"z": {
|
"z": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"io.bkbn.kompendium.core.fixtures.SimpleGibbit"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"a"
|
"a",
|
||||||
|
"type"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"FlibbityGibbit": {
|
"FlibbityGibbit": {
|
||||||
|
@ -65,12 +65,19 @@
|
|||||||
},
|
},
|
||||||
"z": {
|
"z": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"io.bkbn.kompendium.core.fixtures.ComplexGibbit"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"b",
|
"b",
|
||||||
"c",
|
"c",
|
||||||
"z"
|
"z",
|
||||||
|
"type"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"SimpleGibbit": {
|
"SimpleGibbit": {
|
||||||
@ -81,10 +88,17 @@
|
|||||||
},
|
},
|
||||||
"z": {
|
"z": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"io.bkbn.kompendium.core.fixtures.SimpleGibbit"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"a"
|
"a",
|
||||||
|
"type"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"List-FlibbityGibbit": {
|
"List-FlibbityGibbit": {
|
||||||
|
@ -65,12 +65,19 @@
|
|||||||
},
|
},
|
||||||
"z": {
|
"z": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"io.bkbn.kompendium.core.fixtures.ComplexGibbit"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"b",
|
"b",
|
||||||
"c",
|
"c",
|
||||||
"z"
|
"z",
|
||||||
|
"type"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"SimpleGibbit": {
|
"SimpleGibbit": {
|
||||||
@ -81,10 +88,17 @@
|
|||||||
},
|
},
|
||||||
"z": {
|
"z": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"io.bkbn.kompendium.core.fixtures.SimpleGibbit"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"a"
|
"a",
|
||||||
|
"type"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Map-String-FlibbityGibbit": {
|
"Map-String-FlibbityGibbit": {
|
||||||
|
@ -62,11 +62,18 @@
|
|||||||
"f": {
|
"f": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"format": "double"
|
"format": "double"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"io.bkbn.kompendium.core.fixtures.Bibbity"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"b",
|
"b",
|
||||||
"f"
|
"f",
|
||||||
|
"type"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Gibbity-Double": {
|
"Gibbity-Double": {
|
||||||
@ -75,10 +82,17 @@
|
|||||||
"a": {
|
"a": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"format": "double"
|
"format": "double"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"io.bkbn.kompendium.core.fixtures.Gibbity"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"a"
|
"a",
|
||||||
|
"type"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Flibbity-Double": {
|
"Flibbity-Double": {
|
||||||
|
@ -53,40 +53,6 @@
|
|||||||
"webhooks": {},
|
"webhooks": {},
|
||||||
"components": {
|
"components": {
|
||||||
"schemas": {
|
"schemas": {
|
||||||
"ComplexGibbit": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"b": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"c": {
|
|
||||||
"type": "number",
|
|
||||||
"format": "int32"
|
|
||||||
},
|
|
||||||
"z": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"b",
|
|
||||||
"c",
|
|
||||||
"z"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"SimpleGibbit": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"a": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"z": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"a"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Bibbity-FlibbityGibbit": {
|
"Bibbity-FlibbityGibbit": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -102,11 +68,66 @@
|
|||||||
"$ref": "#/components/schemas/SimpleGibbit"
|
"$ref": "#/components/schemas/SimpleGibbit"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"io.bkbn.kompendium.core.fixtures.Bibbity"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"b",
|
"b",
|
||||||
"f"
|
"f",
|
||||||
|
"type"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ComplexGibbit": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"b": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"c": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"z": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"io.bkbn.kompendium.core.fixtures.ComplexGibbit"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"b",
|
||||||
|
"c",
|
||||||
|
"z",
|
||||||
|
"type"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"SimpleGibbit": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"a": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"z": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"io.bkbn.kompendium.core.fixtures.SimpleGibbit"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"a",
|
||||||
|
"type"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Gibbity-FlibbityGibbit": {
|
"Gibbity-FlibbityGibbit": {
|
||||||
@ -121,10 +142,17 @@
|
|||||||
"$ref": "#/components/schemas/SimpleGibbit"
|
"$ref": "#/components/schemas/SimpleGibbit"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"io.bkbn.kompendium.core.fixtures.Gibbity"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"a"
|
"a",
|
||||||
|
"type"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Flibbity-FlibbityGibbit": {
|
"Flibbity-FlibbityGibbit": {
|
||||||
|
123
core/src/test/resources/T0052__override_media_types.json
Normal file
123
core/src/test/resources/T0052__override_media_types.json
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
{
|
||||||
|
"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": {
|
||||||
|
"/media_types": {
|
||||||
|
"put": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Great Summary!",
|
||||||
|
"description": "testing more",
|
||||||
|
"parameters": [],
|
||||||
|
"requestBody": {
|
||||||
|
"description": "A cool request",
|
||||||
|
"content": {
|
||||||
|
"multipart/form-data": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestRequest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "A good response",
|
||||||
|
"content": {
|
||||||
|
"application/xml": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"TestResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"c": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"c"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"TestRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"aaa": {
|
||||||
|
"items": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"b": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"fieldName": {
|
||||||
|
"$ref": "#/components/schemas/TestNested"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"aaa",
|
||||||
|
"b",
|
||||||
|
"fieldName"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"TestNested": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"nesty": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"nesty"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
@ -0,0 +1,164 @@
|
|||||||
|
{
|
||||||
|
"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": {
|
||||||
|
"/test/{a}": {
|
||||||
|
"get": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Great Summary!",
|
||||||
|
"description": "testing more",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A Successful Endeavor",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Great Summary!",
|
||||||
|
"description": "testing more",
|
||||||
|
"parameters": [],
|
||||||
|
"requestBody": {
|
||||||
|
"description": "You gotta send it",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestSimpleRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "A Successful Endeavor",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestCreatedResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false,
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"basic": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "a",
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aa",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"deprecated": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"TestResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"c": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"c"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"TestCreatedResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"c": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "int32"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"c",
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"TestSimpleRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"a": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"b": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "int32"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"a",
|
||||||
|
"b"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {
|
||||||
|
"basic": {
|
||||||
|
"type": "http",
|
||||||
|
"scheme": "basic"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
92
core/src/test/resources/T0054__app_with_rootpath.json
Normal file
92
core/src/test/resources/T0054__app_with_rootpath.json
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
{
|
||||||
|
"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/example",
|
||||||
|
"description": "Production instance of my API"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://staging.myawesomeapi.com/example",
|
||||||
|
"description": "Where the fun stuff happens"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"/test/{a}": {
|
||||||
|
"get": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Great Summary!",
|
||||||
|
"description": "testing more",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A Successful Endeavor",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "a",
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aa",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"deprecated": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"TestResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"c": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"c"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
126
core/src/test/resources/T0055__enriched_simple_request.json
Normal file
126
core/src/test/resources/T0055__enriched_simple_request.json
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.1.0",
|
||||||
|
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"info": {
|
||||||
|
"title": "Test API",
|
||||||
|
"version": "1.33.7",
|
||||||
|
"description": "An amazing, fully-ish 😉 generated API spec",
|
||||||
|
"termsOfService": "https://example.com",
|
||||||
|
"contact": {
|
||||||
|
"name": "Homer Simpson",
|
||||||
|
"url": "https://gph.is/1NPUDiM",
|
||||||
|
"email": "chunkylover53@aol.com"
|
||||||
|
},
|
||||||
|
"license": {
|
||||||
|
"name": "MIT",
|
||||||
|
"url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"url": "https://myawesomeapi.com",
|
||||||
|
"description": "Production instance of my API"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://staging.myawesomeapi.com",
|
||||||
|
"description": "Where the fun stuff happens"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"/example": {
|
||||||
|
"post": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Great Summary!",
|
||||||
|
"description": "testing more",
|
||||||
|
"parameters": [],
|
||||||
|
"requestBody": {
|
||||||
|
"description": "A test request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestSimpleRequest-simple"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "A Successful Endeavor",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestCreatedResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "a",
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aa",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"deprecated": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"TestCreatedResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"c": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "int32"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"c",
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"TestSimpleRequest-simple": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"a": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "A simple description"
|
||||||
|
},
|
||||||
|
"b": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "int32",
|
||||||
|
"deprecated": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"a",
|
||||||
|
"b"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
168
core/src/test/resources/T0056__enriched_nested_collection.json
Normal file
168
core/src/test/resources/T0056__enriched_nested_collection.json
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.1.0",
|
||||||
|
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"info": {
|
||||||
|
"title": "Test API",
|
||||||
|
"version": "1.33.7",
|
||||||
|
"description": "An amazing, fully-ish 😉 generated API spec",
|
||||||
|
"termsOfService": "https://example.com",
|
||||||
|
"contact": {
|
||||||
|
"name": "Homer Simpson",
|
||||||
|
"url": "https://gph.is/1NPUDiM",
|
||||||
|
"email": "chunkylover53@aol.com"
|
||||||
|
},
|
||||||
|
"license": {
|
||||||
|
"name": "MIT",
|
||||||
|
"url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"url": "https://myawesomeapi.com",
|
||||||
|
"description": "Production instance of my API"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://staging.myawesomeapi.com",
|
||||||
|
"description": "Where the fun stuff happens"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"/example": {
|
||||||
|
"post": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Great Summary!",
|
||||||
|
"description": "testing more",
|
||||||
|
"parameters": [],
|
||||||
|
"requestBody": {
|
||||||
|
"description": "A test request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ComplexRequest-simple"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "A Successful Endeavor",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestCreatedResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "a",
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aa",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"deprecated": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"TestCreatedResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"c": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "int32"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"c",
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ComplexRequest-simple": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"amazingField": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"org": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"tables": {
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/NestedComplexItem-nested"
|
||||||
|
},
|
||||||
|
"description": "A nested item",
|
||||||
|
"type": "array"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"amazingField",
|
||||||
|
"org",
|
||||||
|
"tables"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"NestedComplexItem-nested": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"alias": {
|
||||||
|
"additionalProperties": {
|
||||||
|
"$ref": "#/components/schemas/CrazyItem"
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "A nested description"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"alias",
|
||||||
|
"name"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"CrazyItem": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"enumeration": {
|
||||||
|
"$ref": "#/components/schemas/SimpleEnum"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"enumeration"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"SimpleEnum": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"ONE",
|
||||||
|
"TWO"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
@ -0,0 +1,183 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.1.0",
|
||||||
|
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"info": {
|
||||||
|
"title": "Test API",
|
||||||
|
"version": "1.33.7",
|
||||||
|
"description": "An amazing, fully-ish 😉 generated API spec",
|
||||||
|
"termsOfService": "https://example.com",
|
||||||
|
"contact": {
|
||||||
|
"name": "Homer Simpson",
|
||||||
|
"url": "https://gph.is/1NPUDiM",
|
||||||
|
"email": "chunkylover53@aol.com"
|
||||||
|
},
|
||||||
|
"license": {
|
||||||
|
"name": "MIT",
|
||||||
|
"url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"url": "https://myawesomeapi.com",
|
||||||
|
"description": "Production instance of my API"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://staging.myawesomeapi.com",
|
||||||
|
"description": "Where the fun stuff happens"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"/example": {
|
||||||
|
"post": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Great Summary!",
|
||||||
|
"description": "testing more",
|
||||||
|
"parameters": [],
|
||||||
|
"requestBody": {
|
||||||
|
"description": "A test request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/MultiNestedGenerics-String-ComplexRequest-simple"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "A Successful Endeavor",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestCreatedResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "a",
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aa",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"deprecated": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"TestCreatedResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"c": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "int32"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"c",
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"MultiNestedGenerics-String-ComplexRequest-simple": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"content": {
|
||||||
|
"additionalProperties": {
|
||||||
|
"$ref": "#/components/schemas/ComplexRequest-nested"
|
||||||
|
},
|
||||||
|
"description": "Getting pretty crazy",
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"content"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ComplexRequest-nested": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"amazingField": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"org": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"tables": {
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/NestedComplexItem-nested"
|
||||||
|
},
|
||||||
|
"description": "A nested item",
|
||||||
|
"type": "array"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"amazingField",
|
||||||
|
"org",
|
||||||
|
"tables"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"NestedComplexItem-nested": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"alias": {
|
||||||
|
"additionalProperties": {
|
||||||
|
"$ref": "#/components/schemas/CrazyItem"
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "A nested description"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"alias",
|
||||||
|
"name"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"CrazyItem": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"enumeration": {
|
||||||
|
"$ref": "#/components/schemas/SimpleEnum"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"enumeration"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"SimpleEnum": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"ONE",
|
||||||
|
"TWO"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
73
core/src/test/resources/T0058__enriched_simple_response.json
Normal file
73
core/src/test/resources/T0058__enriched_simple_response.json
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
{
|
||||||
|
"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": {
|
||||||
|
"/enriched": {
|
||||||
|
"get": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Great Summary!",
|
||||||
|
"description": "testing more",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "A good response",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestResponse-simple"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"TestResponse-simple": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"c": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "A simple description"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"c"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
80
core/src/test/resources/T0059__int_constraints.json
Normal file
80
core/src/test/resources/T0059__int_constraints.json
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
{
|
||||||
|
"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": {
|
||||||
|
"/test/{a}": {
|
||||||
|
"get": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Get an int",
|
||||||
|
"description": "Get an int",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "An int",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestCreatedResponse-example"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"TestCreatedResponse-example": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"c": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "int32",
|
||||||
|
"multipleOf": 2,
|
||||||
|
"maximum": 100,
|
||||||
|
"minimum": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"c",
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
76
core/src/test/resources/T0060__double_constraints.json
Normal file
76
core/src/test/resources/T0060__double_constraints.json
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
{
|
||||||
|
"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": {
|
||||||
|
"/test/{a}": {
|
||||||
|
"get": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Get a double",
|
||||||
|
"description": "Get a double",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A double",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/DoubleResponse-example"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"DoubleResponse-example": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"payload": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double",
|
||||||
|
"multipleOf": 2.0,
|
||||||
|
"maximum": 100.0,
|
||||||
|
"minimum": 2.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"payload"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
{
|
||||||
|
"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": {
|
||||||
|
"/test/{a}": {
|
||||||
|
"get": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Get a string",
|
||||||
|
"description": "Get a string with constraints",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A string",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestNested-example"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"TestNested-example": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"nesty": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 10,
|
||||||
|
"minLength": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"nesty"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
{
|
||||||
|
"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": {
|
||||||
|
"/test/{a}": {
|
||||||
|
"get": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Get a string",
|
||||||
|
"description": "This is a description",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A string",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestNested-example"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"TestNested-example": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"nesty": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "[a-z]+"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"nesty"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
{
|
||||||
|
"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": {
|
||||||
|
"/test/{a}": {
|
||||||
|
"get": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Get a string",
|
||||||
|
"description": "This is a description",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A string",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestNested-example"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"TestNested-example": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"nesty": {
|
||||||
|
"type": "string",
|
||||||
|
"contentEncoding": "base64",
|
||||||
|
"contentMediaType": "image/png"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"nesty"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
103
core/src/test/resources/T0064__array_constraints.json
Normal file
103
core/src/test/resources/T0064__array_constraints.json
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.1.0",
|
||||||
|
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"info": {
|
||||||
|
"title": "Test API",
|
||||||
|
"version": "1.33.7",
|
||||||
|
"description": "An amazing, fully-ish 😉 generated API spec",
|
||||||
|
"termsOfService": "https://example.com",
|
||||||
|
"contact": {
|
||||||
|
"name": "Homer Simpson",
|
||||||
|
"url": "https://gph.is/1NPUDiM",
|
||||||
|
"email": "chunkylover53@aol.com"
|
||||||
|
},
|
||||||
|
"license": {
|
||||||
|
"name": "MIT",
|
||||||
|
"url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"url": "https://myawesomeapi.com",
|
||||||
|
"description": "Production instance of my API"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://staging.myawesomeapi.com",
|
||||||
|
"description": "Where the fun stuff happens"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"/test/{a}": {
|
||||||
|
"get": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Get an array",
|
||||||
|
"description": "Get an array of strings",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "An array",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Page-String-example"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"Page-String-example": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"content": {
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"maxItems": 10,
|
||||||
|
"minItems": 2,
|
||||||
|
"uniqueItems": true,
|
||||||
|
"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": [
|
||||||
|
"content",
|
||||||
|
"number",
|
||||||
|
"numberOfElements",
|
||||||
|
"size",
|
||||||
|
"totalElements",
|
||||||
|
"totalPages"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
72
core/src/test/resources/T0065__post_no_req_body.json
Normal file
72
core/src/test/resources/T0065__post_no_req_body.json
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
{
|
||||||
|
"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": {
|
||||||
|
"/no_req_body": {
|
||||||
|
"post": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Great Summary!",
|
||||||
|
"description": "testing more",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "Cool response",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"TestResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"c": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"c"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
@ -0,0 +1,110 @@
|
|||||||
|
{
|
||||||
|
"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": {
|
||||||
|
"/test/{a}": {
|
||||||
|
"get": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Great Summary!",
|
||||||
|
"description": "testing more",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A Successful Endeavor",
|
||||||
|
"headers": {
|
||||||
|
"ETag": {
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag",
|
||||||
|
"required": true,
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
"Last-Modified": {
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Last-Modified",
|
||||||
|
"required": true,
|
||||||
|
"deprecated": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "a",
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aa",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"deprecated": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"TestResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"c": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"c"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
91
core/src/test/resources/T0067__enriched_generic_object.json
Normal file
91
core/src/test/resources/T0067__enriched_generic_object.json
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.1.0",
|
||||||
|
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"info": {
|
||||||
|
"title": "Test API",
|
||||||
|
"version": "1.33.7",
|
||||||
|
"description": "An amazing, fully-ish 😉 generated API spec",
|
||||||
|
"termsOfService": "https://example.com",
|
||||||
|
"contact": {
|
||||||
|
"name": "Homer Simpson",
|
||||||
|
"url": "https://gph.is/1NPUDiM",
|
||||||
|
"email": "chunkylover53@aol.com"
|
||||||
|
},
|
||||||
|
"license": {
|
||||||
|
"name": "MIT",
|
||||||
|
"url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"url": "https://myawesomeapi.com",
|
||||||
|
"description": "Production instance of my API"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://staging.myawesomeapi.com",
|
||||||
|
"description": "Where the fun stuff happens"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"/example": {
|
||||||
|
"get": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Great Summary!",
|
||||||
|
"description": "testing more",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "A good response",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/GenericObject-TestSimpleRequest-generic"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"GenericObject-TestSimpleRequest-generic": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"$ref": "#/components/schemas/TestSimpleRequest-simple",
|
||||||
|
"description": "A simple description"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"data"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"TestSimpleRequest-simple": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"a": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "A simple description"
|
||||||
|
},
|
||||||
|
"b": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "int32",
|
||||||
|
"deprecated": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"a",
|
||||||
|
"b"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
82
core/src/test/resources/T0068__param_wrapper.json
Normal file
82
core/src/test/resources/T0068__param_wrapper.json
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
{
|
||||||
|
"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": {
|
||||||
|
"/test": {
|
||||||
|
"get": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Great Summary!",
|
||||||
|
"description": "testing more",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A Successful Endeavor",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "test",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"deprecated": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"TestResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"c": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"c"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
136
core/src/test/resources/T0069__example_optional_req.json
Normal file
136
core/src/test/resources/T0069__example_optional_req.json
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
{
|
||||||
|
"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": {
|
||||||
|
"/": {
|
||||||
|
"post": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Great Summary!",
|
||||||
|
"description": "testing more",
|
||||||
|
"parameters": [],
|
||||||
|
"requestBody": {
|
||||||
|
"description": "You gotta send it",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestRequest"
|
||||||
|
},
|
||||||
|
"examples": {
|
||||||
|
"Testerina": {
|
||||||
|
"value": {
|
||||||
|
"fieldName": {
|
||||||
|
"nesty": "asdf"
|
||||||
|
},
|
||||||
|
"b": 1.5,
|
||||||
|
"aaa": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A Successful Endeavor",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestResponse"
|
||||||
|
},
|
||||||
|
"examples": {
|
||||||
|
"Testerino": {
|
||||||
|
"value": {
|
||||||
|
"c": "Heya"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"TestResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"c": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"c"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"TestRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"aaa": {
|
||||||
|
"items": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"b": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"fieldName": {
|
||||||
|
"$ref": "#/components/schemas/TestNested"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"aaa",
|
||||||
|
"b",
|
||||||
|
"fieldName"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"TestNested": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"nesty": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"nesty"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
@ -0,0 +1,108 @@
|
|||||||
|
{
|
||||||
|
"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/ChillaxificationMaximization"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"Chillax": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"a": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"chillax"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"a",
|
||||||
|
"type"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ToDaMax": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"b": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"maximize"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"b",
|
||||||
|
"type"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ChillaxificationMaximization": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/Chillax"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/ToDaMax"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
{
|
||||||
|
"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/Gizmo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"Gizmo": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"title": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"title"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
@ -0,0 +1,92 @@
|
|||||||
|
{
|
||||||
|
"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": {
|
||||||
|
"/test/{a}": {
|
||||||
|
"get": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Great Summary!",
|
||||||
|
"description": "testing more",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A Successful Endeavor",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "a",
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aa",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"deprecated": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"TestResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"c": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"c"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
@ -0,0 +1,94 @@
|
|||||||
|
{
|
||||||
|
"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": {
|
||||||
|
"/field_outside_constructor": {
|
||||||
|
"post": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Great Summary!",
|
||||||
|
"description": "testing more",
|
||||||
|
"parameters": [],
|
||||||
|
"requestBody": {
|
||||||
|
"description": "A cool request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/SomethingSimilar"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "Cool response",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"TestResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"c": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"c"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"SomethingSimilar": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"a": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"a"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
@ -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/TestResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false,
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"auth-oauth-google": [
|
||||||
|
"read:pets"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Great Summary!",
|
||||||
|
"description": "testing more",
|
||||||
|
"parameters": [],
|
||||||
|
"requestBody": {
|
||||||
|
"description": "A Successful Endeavor",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A Successful Endeavor",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false,
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"auth-oauth-google": [
|
||||||
|
"write:pets"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"TestResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"c": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"c"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {
|
||||||
|
"auth-oauth-google": {
|
||||||
|
"flows": {
|
||||||
|
"implicit": {
|
||||||
|
"authorizationUrl": "https://accounts.google.com/o/oauth2/auth",
|
||||||
|
"scopes": {
|
||||||
|
"write:pets": "modify pets in your account",
|
||||||
|
"read:pets": "read your pets"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "oauth2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
@ -0,0 +1,140 @@
|
|||||||
|
{
|
||||||
|
"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": {
|
||||||
|
"/": {
|
||||||
|
"post": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "This is a summary",
|
||||||
|
"description": "This is a description",
|
||||||
|
"parameters": [],
|
||||||
|
"requestBody": {
|
||||||
|
"description": "This is a request description",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestRequest"
|
||||||
|
},
|
||||||
|
"examples": {
|
||||||
|
"Testerina": {
|
||||||
|
"value": {
|
||||||
|
"fieldName": {
|
||||||
|
"nesty": "asdf"
|
||||||
|
},
|
||||||
|
"b": 1.5,
|
||||||
|
"aaa": []
|
||||||
|
},
|
||||||
|
"summary": "summary",
|
||||||
|
"description": "description"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "This is a response description",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestResponse"
|
||||||
|
},
|
||||||
|
"examples": {
|
||||||
|
"Testerino": {
|
||||||
|
"value": {
|
||||||
|
"c": "Heya"
|
||||||
|
},
|
||||||
|
"summary": "summary",
|
||||||
|
"description": "description"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"TestResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"c": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"c"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"TestRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"aaa": {
|
||||||
|
"items": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"b": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"fieldName": {
|
||||||
|
"$ref": "#/components/schemas/TestNested"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"aaa",
|
||||||
|
"b",
|
||||||
|
"fieldName"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"TestNested": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"nesty": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"nesty"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
@ -1,54 +0,0 @@
|
|||||||
package io.bkbn.kompendium.core.fixtures
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty
|
|
||||||
import com.google.gson.annotations.Expose
|
|
||||||
import com.google.gson.annotations.SerializedName
|
|
||||||
import io.bkbn.kompendium.json.schema.SchemaConfigurator
|
|
||||||
import kotlin.reflect.KClass
|
|
||||||
import kotlin.reflect.KProperty1
|
|
||||||
import kotlin.reflect.full.memberProperties
|
|
||||||
import kotlin.reflect.jvm.javaField
|
|
||||||
|
|
||||||
/*
|
|
||||||
These are test implementation and may well be a good starting point for creating production ones.
|
|
||||||
Both Gson and Jackson are complex and can achieve this things is more than one way therefore
|
|
||||||
these will not always work hence why they are in the test package
|
|
||||||
*/
|
|
||||||
|
|
||||||
class GsonSchemaConfigurator: SchemaConfigurator {
|
|
||||||
|
|
||||||
override fun serializableMemberProperties(clazz: KClass<*>): Collection<KProperty1<out Any, *>> {
|
|
||||||
// NOTE: This is test logic Expose is set at a global Gson level so configure to match your Gson set up
|
|
||||||
val hasAnyExpose = clazz.memberProperties.any { it.hasJavaAnnotation<Expose>() }
|
|
||||||
return if(hasAnyExpose) {
|
|
||||||
clazz.memberProperties
|
|
||||||
.filter { it.hasJavaAnnotation<Expose>() }
|
|
||||||
} else clazz.memberProperties
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun serializableName(property: KProperty1<out Any, *>): String =
|
|
||||||
property.getJavaAnnotation<SerializedName>()?.value?: property.name
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class JacksonSchemaConfigurator: SchemaConfigurator {
|
|
||||||
|
|
||||||
override fun serializableMemberProperties(clazz: KClass<*>): Collection<KProperty1<out Any, *>> =
|
|
||||||
clazz.memberProperties
|
|
||||||
.filterNot {
|
|
||||||
it.hasJavaAnnotation<JsonIgnore>()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun serializableName(property: KProperty1<out Any, *>): String =
|
|
||||||
property.getJavaAnnotation<JsonProperty>()?.value?: property.name
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <reified T: Annotation> KProperty1<*, *>.hasJavaAnnotation(): Boolean {
|
|
||||||
return javaField?.isAnnotationPresent(T::class.java)?: false
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <reified T: Annotation> KProperty1<*, *>.getJavaAnnotation(): T? {
|
|
||||||
return javaField?.getDeclaredAnnotation(T::class.java)
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
package io.bkbn.kompendium.core.fixtures
|
|
||||||
|
|
||||||
enum class SupportedSerializer {
|
|
||||||
KOTLINX,
|
|
||||||
GSON,
|
|
||||||
JACKSON
|
|
||||||
}
|
|
@ -1,10 +1,9 @@
|
|||||||
package io.bkbn.kompendium.core.fixtures
|
package io.bkbn.kompendium.core.fixtures
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude
|
|
||||||
import com.fasterxml.jackson.databind.SerializationFeature
|
|
||||||
import io.bkbn.kompendium.core.fixtures.TestSpecs.defaultSpec
|
import io.bkbn.kompendium.core.fixtures.TestSpecs.defaultSpec
|
||||||
import io.bkbn.kompendium.core.plugin.NotarizedApplication
|
import io.bkbn.kompendium.core.plugin.NotarizedApplication
|
||||||
import io.bkbn.kompendium.core.routes.redoc
|
import io.bkbn.kompendium.core.routes.redoc
|
||||||
|
import io.bkbn.kompendium.core.routes.swagger
|
||||||
import io.bkbn.kompendium.json.schema.KotlinXSchemaConfigurator
|
import io.bkbn.kompendium.json.schema.KotlinXSchemaConfigurator
|
||||||
import io.bkbn.kompendium.json.schema.definition.JsonSchema
|
import io.bkbn.kompendium.json.schema.definition.JsonSchema
|
||||||
import io.bkbn.kompendium.oas.OpenApiSpec
|
import io.bkbn.kompendium.oas.OpenApiSpec
|
||||||
@ -15,19 +14,18 @@ import io.kotest.matchers.shouldNot
|
|||||||
import io.kotest.matchers.string.beBlank
|
import io.kotest.matchers.string.beBlank
|
||||||
import io.ktor.client.request.get
|
import io.ktor.client.request.get
|
||||||
import io.ktor.client.statement.bodyAsText
|
import io.ktor.client.statement.bodyAsText
|
||||||
import io.ktor.http.ContentType
|
|
||||||
import io.ktor.http.HttpStatusCode
|
import io.ktor.http.HttpStatusCode
|
||||||
import io.ktor.serialization.gson.gson
|
|
||||||
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.application.Application
|
||||||
|
import io.ktor.server.engine.ApplicationEngineEnvironmentBuilder
|
||||||
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
|
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
|
||||||
|
import io.ktor.server.plugins.contentnegotiation.ContentNegotiationConfig
|
||||||
import io.ktor.server.routing.Routing
|
import io.ktor.server.routing.Routing
|
||||||
import io.ktor.server.testing.ApplicationTestBuilder
|
import io.ktor.server.testing.ApplicationTestBuilder
|
||||||
import io.ktor.server.testing.testApplication
|
import io.ktor.server.testing.testApplication
|
||||||
import kotlin.reflect.KType
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import kotlin.reflect.KType
|
||||||
|
|
||||||
object TestHelpers {
|
object TestHelpers {
|
||||||
private const val OPEN_API_ENDPOINT = "/openapi.json"
|
private const val OPEN_API_ENDPOINT = "/openapi.json"
|
||||||
@ -43,8 +41,8 @@ object TestHelpers {
|
|||||||
* exists as expected, and that the content matches the expected blob found in the specified file
|
* exists as expected, and that the content matches the expected blob found in the specified file
|
||||||
* @param snapshotName The snapshot file to retrieve from the resources folder
|
* @param snapshotName The snapshot file to retrieve from the resources folder
|
||||||
*/
|
*/
|
||||||
private suspend fun ApplicationTestBuilder.compareOpenAPISpec(snapshotName: String) {
|
private suspend fun ApplicationTestBuilder.compareOpenAPISpec(rootPath: String, snapshotName: String) {
|
||||||
val response = client.get(OPEN_API_ENDPOINT)
|
val response = client.get("$rootPath$OPEN_API_ENDPOINT")
|
||||||
response shouldHaveStatus HttpStatusCode.OK
|
response shouldHaveStatus HttpStatusCode.OK
|
||||||
response.bodyAsText() shouldNot beBlank()
|
response.bodyAsText() shouldNot beBlank()
|
||||||
response.bodyAsText() shouldEqualJson getFileSnapshot(snapshotName)
|
response.bodyAsText() shouldEqualJson getFileSnapshot(snapshotName)
|
||||||
@ -53,7 +51,7 @@ object TestHelpers {
|
|||||||
/**
|
/**
|
||||||
* This will take a provided JSON snapshot file, retrieve it from the resource folder,
|
* This will take a provided JSON snapshot file, retrieve it from the resource folder,
|
||||||
* and build a test ktor server to compare the expected output with the output found in the default
|
* and build a test ktor server to compare the expected output with the output found in the default
|
||||||
* OpenAPI json endpoint. By default, this will run the same test with Gson, Kotlinx, and Jackson serializers
|
* OpenAPI json endpoint.
|
||||||
* @param snapshotName The snapshot file to retrieve from the resources folder
|
* @param snapshotName The snapshot file to retrieve from the resources folder
|
||||||
*/
|
*/
|
||||||
fun openApiTestAllSerializers(
|
fun openApiTestAllSerializers(
|
||||||
@ -61,50 +59,56 @@ object TestHelpers {
|
|||||||
customTypes: Map<KType, JsonSchema> = emptyMap(),
|
customTypes: Map<KType, JsonSchema> = emptyMap(),
|
||||||
applicationSetup: Application.() -> Unit = { },
|
applicationSetup: Application.() -> Unit = { },
|
||||||
specOverrides: OpenApiSpec.() -> OpenApiSpec = { this },
|
specOverrides: OpenApiSpec.() -> OpenApiSpec = { this },
|
||||||
|
applicationEnvironmentBuilder: ApplicationEngineEnvironmentBuilder.() -> Unit = {},
|
||||||
|
notarizedApplicationConfigOverrides: NotarizedApplication.Config.() -> Unit = {},
|
||||||
|
contentNegotiation: ContentNegotiationConfig.() -> Unit = {
|
||||||
|
json(Json {
|
||||||
|
encodeDefaults = true
|
||||||
|
explicitNulls = false
|
||||||
|
serializersModule = KompendiumSerializersModule.module
|
||||||
|
})
|
||||||
|
},
|
||||||
routeUnderTest: Routing.() -> Unit
|
routeUnderTest: Routing.() -> Unit
|
||||||
) {
|
) {
|
||||||
openApiTest(snapshotName, SupportedSerializer.KOTLINX, routeUnderTest, applicationSetup, specOverrides, customTypes)
|
openApiTest(
|
||||||
openApiTest(snapshotName, SupportedSerializer.JACKSON, routeUnderTest, applicationSetup, specOverrides, customTypes)
|
snapshotName,
|
||||||
openApiTest(snapshotName, SupportedSerializer.GSON, routeUnderTest, applicationSetup, specOverrides, customTypes)
|
routeUnderTest,
|
||||||
|
applicationSetup,
|
||||||
|
specOverrides,
|
||||||
|
customTypes,
|
||||||
|
notarizedApplicationConfigOverrides,
|
||||||
|
contentNegotiation,
|
||||||
|
applicationEnvironmentBuilder
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openApiTest(
|
private fun openApiTest(
|
||||||
snapshotName: String,
|
snapshotName: String,
|
||||||
serializer: SupportedSerializer,
|
|
||||||
routeUnderTest: Routing.() -> Unit,
|
routeUnderTest: Routing.() -> Unit,
|
||||||
applicationSetup: Application.() -> Unit,
|
applicationSetup: Application.() -> Unit,
|
||||||
specOverrides: OpenApiSpec.() -> OpenApiSpec,
|
specOverrides: OpenApiSpec.() -> OpenApiSpec,
|
||||||
typeOverrides: Map<KType, JsonSchema> = emptyMap()
|
typeOverrides: Map<KType, JsonSchema> = emptyMap(),
|
||||||
|
notarizedApplicationConfigOverrides: NotarizedApplication.Config.() -> Unit,
|
||||||
|
contentNegotiation: ContentNegotiationConfig.() -> Unit,
|
||||||
|
applicationBuilder: ApplicationEngineEnvironmentBuilder.() -> Unit
|
||||||
) = testApplication {
|
) = testApplication {
|
||||||
|
environment(applicationBuilder)
|
||||||
install(NotarizedApplication()) {
|
install(NotarizedApplication()) {
|
||||||
customTypes = typeOverrides
|
customTypes = typeOverrides
|
||||||
spec = defaultSpec().specOverrides()
|
spec = { specOverrides(defaultSpec()) }
|
||||||
schemaConfigurator = when (serializer) {
|
schemaConfigurator = KotlinXSchemaConfigurator()
|
||||||
SupportedSerializer.KOTLINX -> KotlinXSchemaConfigurator()
|
notarizedApplicationConfigOverrides()
|
||||||
SupportedSerializer.GSON -> GsonSchemaConfigurator()
|
|
||||||
SupportedSerializer.JACKSON -> JacksonSchemaConfigurator()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
install(ContentNegotiation) {
|
install(ContentNegotiation) {
|
||||||
when (serializer) {
|
contentNegotiation()
|
||||||
SupportedSerializer.KOTLINX -> json(Json {
|
|
||||||
encodeDefaults = true
|
|
||||||
explicitNulls = false
|
|
||||||
serializersModule = KompendiumSerializersModule.module
|
|
||||||
})
|
|
||||||
|
|
||||||
SupportedSerializer.GSON -> gson()
|
|
||||||
SupportedSerializer.JACKSON -> jackson(ContentType.Application.Json) {
|
|
||||||
enable(SerializationFeature.INDENT_OUTPUT)
|
|
||||||
setSerializationInclusion(JsonInclude.Include.NON_NULL)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
application(applicationSetup)
|
application(applicationSetup)
|
||||||
routing {
|
routing {
|
||||||
|
swagger()
|
||||||
redoc()
|
redoc()
|
||||||
routeUnderTest()
|
routeUnderTest()
|
||||||
}
|
}
|
||||||
compareOpenAPISpec(snapshotName)
|
val root = ApplicationEngineEnvironmentBuilder().apply(applicationBuilder).rootPath
|
||||||
|
compareOpenAPISpec(root, snapshotName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
package io.bkbn.kompendium.core.fixtures
|
package io.bkbn.kompendium.core.fixtures
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty
|
|
||||||
import com.google.gson.annotations.Expose
|
|
||||||
import com.google.gson.annotations.SerializedName
|
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.Transient
|
import kotlinx.serialization.Transient
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class TestNested(val nesty: String)
|
data class TestNested(val nesty: String)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class DoubleResponse(val payload: Double)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class TestRequest(
|
data class TestRequest(
|
||||||
val fieldName: TestNested,
|
val fieldName: TestNested,
|
||||||
@ -88,6 +89,25 @@ data class AnothaJamma(val b: Float) : SlammaJamma
|
|||||||
|
|
||||||
data class InsaneJamma(val c: SlammaJamma) : SlammaJamma
|
data class InsaneJamma(val c: SlammaJamma) : SlammaJamma
|
||||||
|
|
||||||
|
sealed interface ChillaxificationMaximization
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@SerialName("chillax")
|
||||||
|
data class Chillax(val a: String) : ChillaxificationMaximization
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@SerialName("maximize")
|
||||||
|
data class ToDaMax(val b: Int) : ChillaxificationMaximization
|
||||||
|
|
||||||
|
sealed class Gadget(
|
||||||
|
open val title: String,
|
||||||
|
open val description: String
|
||||||
|
)
|
||||||
|
|
||||||
|
class Gizmo(
|
||||||
|
override val title: String,
|
||||||
|
) : Gadget(title, "Just a gizmo")
|
||||||
|
|
||||||
sealed interface Flibbity<T>
|
sealed interface Flibbity<T>
|
||||||
|
|
||||||
data class Gibbity<T>(val a: T) : Flibbity<T>
|
data class Gibbity<T>(val a: T) : Flibbity<T>
|
||||||
@ -155,9 +175,7 @@ object Nested {
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class TransientObject(
|
data class TransientObject(
|
||||||
@field:Expose
|
|
||||||
val nonTransient: String,
|
val nonTransient: String,
|
||||||
@field:JsonIgnore
|
|
||||||
@Transient
|
@Transient
|
||||||
val transient: String = "transient"
|
val transient: String = "transient"
|
||||||
)
|
)
|
||||||
@ -171,12 +189,14 @@ data class UnbackedObject(
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class SerialNameObject(
|
data class SerialNameObject(
|
||||||
@field:JsonProperty("snake_case_name")
|
|
||||||
@field:SerializedName("snake_case_name")
|
|
||||||
@SerialName("snake_case_name")
|
@SerialName("snake_case_name")
|
||||||
val camelCaseName: String
|
val camelCaseName: String
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class GenericObject<T>(
|
||||||
|
val data: T
|
||||||
|
)
|
||||||
|
|
||||||
enum class Color {
|
enum class Color {
|
||||||
RED,
|
RED,
|
||||||
GREEN,
|
GREEN,
|
||||||
@ -187,3 +207,8 @@ enum class Color {
|
|||||||
data class ObjectWithEnum(
|
data class ObjectWithEnum(
|
||||||
val color: Color
|
val color: Color
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SomethingSimilar(val a: String) {
|
||||||
|
val b = "something else"
|
||||||
|
}
|
||||||
|
@ -22,6 +22,8 @@ formatting:
|
|||||||
indentSize: 2
|
indentSize: 2
|
||||||
ImportOrdering:
|
ImportOrdering:
|
||||||
active: false
|
active: false
|
||||||
|
EnumEntryNameCase:
|
||||||
|
active: false
|
||||||
naming:
|
naming:
|
||||||
ConstructorParameterNaming:
|
ConstructorParameterNaming:
|
||||||
active: false
|
active: false
|
||||||
|
@ -6,4 +6,8 @@
|
|||||||
* [Notarized Route](plugins/notarized_route.md)
|
* [Notarized Route](plugins/notarized_route.md)
|
||||||
* [Notarized Locations](plugins/notarized_locations.md)
|
* [Notarized Locations](plugins/notarized_locations.md)
|
||||||
* [Notarized Resources](plugins/notarized_resources.md)
|
* [Notarized Resources](plugins/notarized_resources.md)
|
||||||
|
* [Concepts](concepts/index.md)
|
||||||
|
* [Enrichment](concepts/enrichment.md)
|
||||||
|
* [Helpers](helpers/index.md)
|
||||||
|
* [Protobuf java converter](helpers/protobuf_java_converter.md)
|
||||||
* [The Playground](playground.md)
|
* [The Playground](playground.md)
|
||||||
|
106
docs/concepts/enrichment.md
Normal file
106
docs/concepts/enrichment.md
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
Kompendium allows users to enrich their data types with additional information. This can be done by defining a
|
||||||
|
`TypeEnrichment` object and passing it to the `enrichment` parameter of the relevant `requestType` or `responseType`.
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
data class SimpleData(val a: String, val b: Int? = null)
|
||||||
|
|
||||||
|
val myEnrichment = TypeEnrichment<SimpleData>(id = "simple-enrichment") {
|
||||||
|
SimpleData::a {
|
||||||
|
description = "This will update the field description"
|
||||||
|
}
|
||||||
|
SimpleData::b {
|
||||||
|
// Will indicate in the UI that the field will be removed soon
|
||||||
|
deprecated = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// In your route documentation
|
||||||
|
fun Routing.enrichedSimpleRequest() {
|
||||||
|
route("/example") {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
parameters = TestModules.defaultParams
|
||||||
|
post = PostInfo.builder {
|
||||||
|
summary(TestModules.defaultPathSummary)
|
||||||
|
description(TestModules.defaultPathDescription)
|
||||||
|
request {
|
||||||
|
requestType<SimpleData>(enrichment = myEnrichment) // Simply attach the enrichment to the request
|
||||||
|
description("A test request")
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
responseCode(HttpStatusCode.Created)
|
||||||
|
responseType<TestCreatedResponse>()
|
||||||
|
description(TestModules.defaultResponseDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
{% hint style="warning" %}
|
||||||
|
An enrichment must provide an `id` field that is unique to the data class that is being enriched. This is because
|
||||||
|
under the hood, Kompendium appends this id to the data class identifier in order to support multiple different
|
||||||
|
enrichments
|
||||||
|
on the same data class.
|
||||||
|
|
||||||
|
If you provide duplicate ids, all but the first enrichment will be ignored, as Kompendium will view that as a cache hit,
|
||||||
|
and skip analyzing the new enrichment.
|
||||||
|
{% endhint %}
|
||||||
|
|
||||||
|
### Nested Enrichments
|
||||||
|
|
||||||
|
Enrichments are portable and composable, meaning that we can take an enrichment for a child data class
|
||||||
|
and apply it inside a parent data class using the `typeEnrichment` property.
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
data class ParentData(val a: String, val b: ChildData)
|
||||||
|
data class ChildData(val c: String, val d: Int? = null)
|
||||||
|
|
||||||
|
val childEnrichment = TypeEnrichment<ChildData>(id = "child-enrichment") {
|
||||||
|
ChildData::c {
|
||||||
|
description = "This will update the field description of field c on child data"
|
||||||
|
}
|
||||||
|
ChildData::d {
|
||||||
|
description = "This will update the field description of field d on child data"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val parentEnrichment = TypeEnrichment<ParentData>(id = "parent-enrichment") {
|
||||||
|
ParentData::a {
|
||||||
|
description = "This will update the field description"
|
||||||
|
}
|
||||||
|
ParentData::b {
|
||||||
|
description = "This will update the field description of field b on parent data"
|
||||||
|
typeEnrichment = childEnrichment // Will apply the child enrichment to the internals of field b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Available Enrichments
|
||||||
|
|
||||||
|
All enrichments support the following properties:
|
||||||
|
|
||||||
|
- description -> Provides a reader friendly description of the field in the object
|
||||||
|
- deprecated -> Indicates that the field is deprecated and should not be used
|
||||||
|
|
||||||
|
### String
|
||||||
|
|
||||||
|
- minLength -> The minimum length of the string
|
||||||
|
- maxLength -> The maximum length of the string
|
||||||
|
- pattern -> A regex pattern that the string must match
|
||||||
|
- contentEncoding -> The encoding of the string
|
||||||
|
- contentMediaType -> The media type of the string
|
||||||
|
|
||||||
|
### Numbers
|
||||||
|
|
||||||
|
- minimum -> The minimum value of the number
|
||||||
|
- maximum -> The maximum value of the number
|
||||||
|
- exclusiveMinimum -> Indicates that the minimum value is exclusive
|
||||||
|
- exclusiveMaximum -> Indicates that the maximum value is exclusive
|
||||||
|
- multipleOf -> Indicates that the number must be a multiple of the provided value
|
||||||
|
|
||||||
|
### Arrays
|
||||||
|
|
||||||
|
- minItems -> The minimum number of items in the array
|
||||||
|
- maxItems -> The maximum number of items in the array
|
||||||
|
- uniqueItems -> Indicates that the array must contain unique items
|
2
docs/concepts/index.md
Normal file
2
docs/concepts/index.md
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Various concepts that are core to Kompendium but not necessarily exclusive
|
||||||
|
to any given module or plugin
|
7
docs/helpers/index.md
Normal file
7
docs/helpers/index.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
Helper modules help you to interact with Kompendium.
|
||||||
|
|
||||||
|
Some functionality is not possible or difficult to do with Kompendium by default. Modules in this folder help you to get
|
||||||
|
functionality that would otherwise be difficult.
|
||||||
|
|
||||||
|
The first one of which is [Protobuf java converter](protobuf_java_converter.md) which translates java protobuf classes
|
||||||
|
to `customTypes` entries.
|
153
docs/helpers/protobuf_java_converter.md
Normal file
153
docs/helpers/protobuf_java_converter.md
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
The `Protobuf java converter` functions allow you to generate documentation from your generated Java classes.
|
||||||
|
Since Kompendium relies a lot on `KProperties` we have yet to find a way to connect this with our Java.
|
||||||
|
For now the documentation is generated for the `customTypes` in `NotarizedApplication`.
|
||||||
|
|
||||||
|
## Usage with Kotlinx
|
||||||
|
|
||||||
|
setup:
|
||||||
|
```kotlin
|
||||||
|
install(ContentNegotiation) {
|
||||||
|
json(Json {
|
||||||
|
encodeDefaults = false
|
||||||
|
// Combine the kompendium serializers with your custom java proto serializers
|
||||||
|
serializersModule =
|
||||||
|
KompendiumSerializersModule.module + SerializersModule { serializersModule = yourCustomProtoSerializers }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For one message and all its nested sub messages:
|
||||||
|
```kotlin
|
||||||
|
private fun Application.mainModule() {
|
||||||
|
// ...
|
||||||
|
install(NotarizedApplication()) {
|
||||||
|
spec = baseSpec
|
||||||
|
customTypes = MyJavaProto.getDefaultInstance().createCustomTypesForTypeAndSubTypes().toMap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For multiple messages and their submesages:
|
||||||
|
```kotlin
|
||||||
|
private fun Application.mainModule() {
|
||||||
|
// ...
|
||||||
|
install(NotarizedApplication()) {
|
||||||
|
spec = baseSpec
|
||||||
|
customTypes = MyJavaProto.getDefaultInstance().createCustomTypesForTypeAndSubTypes()
|
||||||
|
.plus(AnotherJavaProto.getDefaultInstance().createCustomTypesForTypeAndSubTypes()).toMap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example User
|
||||||
|
|
||||||
|
The protobuf that is used on our endpoint
|
||||||
|
```proto
|
||||||
|
message User {
|
||||||
|
string id = 1;
|
||||||
|
string email = 2;
|
||||||
|
string mobile_phone = 3;
|
||||||
|
string name = 4;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
A custom serializer deserializer:
|
||||||
|
```kotlin
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
|
object UserSerializer : KSerializer<User> {
|
||||||
|
|
||||||
|
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("User") {
|
||||||
|
element("id", serialDescriptor<String>())
|
||||||
|
element("email", serialDescriptor<String>())
|
||||||
|
element("mobile_phone", serialDescriptor<String>())
|
||||||
|
element("name", serialDescriptor<String>())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deserialize(decoder: Decoder): User {
|
||||||
|
return decoder.decodeStructure(descriptor) {
|
||||||
|
var id: String? = null
|
||||||
|
var email: String? = null
|
||||||
|
var mobilePhone: String? = null
|
||||||
|
var name: String? = null
|
||||||
|
|
||||||
|
loop@ while (true) {
|
||||||
|
when (val index = decodeElementIndex(descriptor)) {
|
||||||
|
CompositeDecoder.DECODE_DONE -> break@loop
|
||||||
|
0 -> id = decodeStringElement(descriptor, index)
|
||||||
|
1 -> email = decodeStringElement(descriptor, index)
|
||||||
|
2 -> mobilePhone = decodeStringElement(descriptor, index)
|
||||||
|
3 -> name = decodeStringElement(descriptor, index)
|
||||||
|
else -> throw RuntimeException(
|
||||||
|
"Unexpected index field ${descriptor.getElementName(index)}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// building the protobuf object
|
||||||
|
val user = User.newBuilder().apply {
|
||||||
|
id?.let { v -> this.id = v }
|
||||||
|
email?.let { v -> this.email = v }
|
||||||
|
mobilePhone?.let { v -> this.mobilePhone = v }
|
||||||
|
name?.let { v -> this.name = v }
|
||||||
|
}.build()
|
||||||
|
user
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun serialize(encoder: Encoder, value: User) {
|
||||||
|
encoder.encodeStructure(descriptor) {
|
||||||
|
encodeStringElement(descriptor, 0, value.id)
|
||||||
|
encodeStringElement(descriptor, 1, value.email)
|
||||||
|
encodeStringElement(descriptor, 2, value.mobilePhone)
|
||||||
|
encodeStringElement(descriptor, 3, value.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Setting the content type:
|
||||||
|
```kotlin
|
||||||
|
install(ContentNegotiation) {
|
||||||
|
json(Json {
|
||||||
|
encodeDefaults = false
|
||||||
|
// Combine the kompendium serializers with your custom java proto serializers
|
||||||
|
serializersModule =
|
||||||
|
KompendiumSerializersModule.module + SerializersModule {
|
||||||
|
serializersModule = SerializersModule {
|
||||||
|
contextual(UserSerializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
The installation of the noterized application:
|
||||||
|
```kotlin
|
||||||
|
install(NotarizedApplication()) {
|
||||||
|
spec = baseSpec
|
||||||
|
customTypes = User.getDefaultInstance().createCustomTypesForTypeAndSubTypes().toMap()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Route configuration as you normally would with one exception which is `createType()` to create kotlin type from a javaClass.
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
private fun Route.userDocumentation() {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
post = PostInfo.builder {
|
||||||
|
summary("My User API")
|
||||||
|
description("Create a user")
|
||||||
|
request {
|
||||||
|
requestType(User::class.createType())
|
||||||
|
description("My user creation object")
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType(CreateUserResponse::class.createType())
|
||||||
|
description("Returns simulation object")
|
||||||
|
}
|
||||||
|
canRespond {
|
||||||
|
responseCode(HttpStatusCode.NotFound)
|
||||||
|
responseType<String>()
|
||||||
|
description("Indicates that the user could not be found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
@ -4,6 +4,12 @@ You can read more about it [here](https://ktor.io/docs/type-safe-routing.html).
|
|||||||
|
|
||||||
Kompendium supports Ktor-Resources through an ancillary module `kompendium-resources`
|
Kompendium supports Ktor-Resources through an ancillary module `kompendium-resources`
|
||||||
|
|
||||||
|
{% hint style="warning" %}
|
||||||
|
The resources module contains _two_ plugins: `KompendiumResources` and `KompendiumResource`. You will find more
|
||||||
|
information on both below, but in a nutshell, the former is an application level plugin intended to define your entire
|
||||||
|
application, while the latter is a route level approach should you wish to split out your route definitions.
|
||||||
|
{% endhint %}
|
||||||
|
|
||||||
## Adding the Artifact
|
## Adding the Artifact
|
||||||
|
|
||||||
Prior to documenting your resources, you will need to add the artifact to your gradle build file.
|
Prior to documenting your resources, you will need to add the artifact to your gradle build file.
|
||||||
@ -14,9 +20,11 @@ dependencies {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Installing Plugin
|
## NotarizedResources
|
||||||
|
|
||||||
Once you have installed the dependency, you can install the plugin. The `NotarizedResources` plugin is an _application_ level plugin, and **must** be install after both the `NotarizedApplication` plugin and the Ktor `Resources` plugin.
|
The `NotarizedResources` plugin is an _application_ level plugin, and **must** be installed after both the
|
||||||
|
`NotarizedApplication` plugin and the Ktor `Resources` plugin. It is intended to be used to document your entire
|
||||||
|
application in a single block.
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
private fun Application.mainModule() {
|
private fun Application.mainModule() {
|
||||||
@ -54,7 +62,64 @@ private fun Application.mainModule() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Here, the `resources` property is a map of `KClass<*>` to `ResourceMetadata` instance describing that resource. This metadata is functionally identical to how a standard `NotarizedRoute` is defined.
|
Here, the `resources` property is a map of `KClass<*>` to `ResourceMetadata` instance describing that resource. This
|
||||||
|
metadata is functionally identical to how a standard `NotarizedRoute` is defined.
|
||||||
|
|
||||||
> ⚠️ If you try to map a class that is not annotated with the ktor `@Resource` annotation, you will get a runtime
|
{% hint style="danger" %}
|
||||||
> exception!
|
If you try to map a class that is not annotated with the ktor `@Resource` annotation, you will get a runtime exception!
|
||||||
|
{% endhint %}
|
||||||
|
|
||||||
|
## NotarizedResource
|
||||||
|
|
||||||
|
If you prefer a route-based approach similar to `NotarizedRoute`, you can use the `NotarizedResource<MyResourceType>()`
|
||||||
|
plugin instead of `NotarizedResources`. It will combine paths from any parent route with the route defined in the
|
||||||
|
resource, exactly as Ktor itself does:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
@Serializable
|
||||||
|
@Resource("/list/{name}/page/{page}")
|
||||||
|
data class Listing(val name: String, val page: Int)
|
||||||
|
|
||||||
|
private fun Application.mainModule() {
|
||||||
|
install(Resources)
|
||||||
|
route("/api") {
|
||||||
|
listingDocumentation()
|
||||||
|
get<Listing> { listing ->
|
||||||
|
call.respondText("Listing ${listing.name}, page ${listing.page}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Route.listingDocumentation() {
|
||||||
|
install(NotarizedResource<Listing>()) {
|
||||||
|
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 😱")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In this case, the generated path will be `/api/list/{name}/page/{page}`, combining the route prefix with the path in the
|
||||||
|
resource.
|
||||||
|
|
||||||
|
{% hint style="danger" %}
|
||||||
|
If you try to map a class that is not annotated with the ktor `@Resource` annotation, you will get a runtime exception!
|
||||||
|
{% endhint %}
|
||||||
|
@ -165,3 +165,42 @@ get = GetInfo.builder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Media Types
|
||||||
|
|
||||||
|
By default, Kompendium will set the only media type to "application/json". If you would like to override the media type
|
||||||
|
for a specific request or response (including errors), you can do so with the `mediaTypes` method
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
get = GetInfo.builder {
|
||||||
|
summary("Get user by id")
|
||||||
|
description("A very neat endpoint!")
|
||||||
|
response {
|
||||||
|
mediaTypes("application/xml")
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<ExampleResponse>()
|
||||||
|
description("Will return whether or not the user is real 😱")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Partial Authentication
|
||||||
|
|
||||||
|
One might want to have a public GET endpoint but a protected PUT endpoint. This can be achieved by registering two
|
||||||
|
separate notarized routes. Note that you will get an error if you try to register the same method twice, as each path
|
||||||
|
can only have one registration per method. Example:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
route("/user/{id}") {
|
||||||
|
get = GetInfo.builder {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
// ...
|
||||||
|
authenticate {
|
||||||
|
put = PutInfo.builder {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
34
enrichment/build.gradle.kts
Normal file
34
enrichment/build.gradle.kts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
plugins {
|
||||||
|
kotlin("jvm")
|
||||||
|
id("io.bkbn.sourdough.library.jvm")
|
||||||
|
id("io.gitlab.arturbosch.detekt")
|
||||||
|
id("com.adarshr.test-logger")
|
||||||
|
id("maven-publish")
|
||||||
|
id("java-library")
|
||||||
|
id("signing")
|
||||||
|
id("org.jetbrains.kotlinx.kover")
|
||||||
|
}
|
||||||
|
|
||||||
|
sourdoughLibrary {
|
||||||
|
libraryName.set("Kompendium Type Enrichment")
|
||||||
|
libraryDescription.set("Utility library for creating portable type enrichments")
|
||||||
|
compilerArgs.set(listOf("-opt-in=kotlin.RequiresOptIn"))
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
// Versions
|
||||||
|
val detektVersion: String by project
|
||||||
|
|
||||||
|
// Formatting
|
||||||
|
detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:$detektVersion")
|
||||||
|
|
||||||
|
testImplementation(testFixtures(projects.kompendiumCore))
|
||||||
|
}
|
||||||
|
|
||||||
|
testing {
|
||||||
|
suites {
|
||||||
|
named("test", JvmTestSuite::class) {
|
||||||
|
useJUnitJupiter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
package io.bkbn.kompendium.enrichment
|
||||||
|
|
||||||
|
sealed interface Enrichment
|
@ -0,0 +1,36 @@
|
|||||||
|
package io.bkbn.kompendium.enrichment
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference https://json-schema.org/draft/2020-12/json-schema-validation.html#name-multipleof
|
||||||
|
*/
|
||||||
|
class PropertyEnrichment : Enrichment {
|
||||||
|
// Metadata
|
||||||
|
var deprecated: Boolean? = null
|
||||||
|
var description: String? = null
|
||||||
|
var typeEnrichment: TypeEnrichment<*>? = null
|
||||||
|
|
||||||
|
// Number and Integer Constraints
|
||||||
|
var multipleOf: Number? = null
|
||||||
|
var maximum: Number? = null
|
||||||
|
var exclusiveMaximum: Number? = null
|
||||||
|
var minimum: Number? = null
|
||||||
|
var exclusiveMinimum: Number? = null
|
||||||
|
|
||||||
|
// String constraints
|
||||||
|
var maxLength: Int? = null
|
||||||
|
var minLength: Int? = null
|
||||||
|
var pattern: String? = null
|
||||||
|
var contentEncoding: String? = null
|
||||||
|
var contentMediaType: String? = null
|
||||||
|
// TODO how to handle contentSchema?
|
||||||
|
|
||||||
|
// Array constraints
|
||||||
|
var maxItems: Int? = null
|
||||||
|
var minItems: Int? = null
|
||||||
|
var uniqueItems: Boolean? = null
|
||||||
|
// TODO How to handle contains, minContains, maxContains?
|
||||||
|
|
||||||
|
// Object constraints
|
||||||
|
var maxProperties: Int? = null
|
||||||
|
var minProperties: Int? = null
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package io.bkbn.kompendium.enrichment
|
||||||
|
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
import kotlin.reflect.KProperty1
|
||||||
|
|
||||||
|
class TypeEnrichment<T>(val id: String) : Enrichment {
|
||||||
|
|
||||||
|
private val enrichments: MutableMap<KProperty1<*, *>, Enrichment> = mutableMapOf()
|
||||||
|
|
||||||
|
fun getEnrichmentForProperty(property: KProperty<*>): Enrichment? = enrichments[property]
|
||||||
|
|
||||||
|
operator fun <R> KProperty1<T, R>.invoke(init: PropertyEnrichment.() -> Unit) {
|
||||||
|
require(!enrichments.containsKey(this)) { "${this.name} has already been registered" }
|
||||||
|
val propertyEnrichment = PropertyEnrichment()
|
||||||
|
init.invoke(propertyEnrichment)
|
||||||
|
enrichments[this] = propertyEnrichment
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
inline operator fun <reified T> invoke(id: String, init: TypeEnrichment<T>.() -> Unit): TypeEnrichment<T> {
|
||||||
|
val builder = TypeEnrichment<T>(id)
|
||||||
|
return builder.apply(init)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,14 @@
|
|||||||
# Kompendium
|
# Kompendium
|
||||||
project.version=3.6.0
|
project.version=4.0.0-alpha
|
||||||
# Kotlin
|
# Kotlin
|
||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
# Gradle
|
# Gradle
|
||||||
org.gradle.vfs.watch=true
|
org.gradle.vfs.watch=true
|
||||||
org.gradle.vfs.verbose=true
|
org.gradle.vfs.verbose=true
|
||||||
org.gradle.jvmargs=-Xmx2000m
|
org.gradle.jvmargs=-Xmx2000m
|
||||||
|
org.gradle.parallel=true
|
||||||
|
|
||||||
# Dependencies
|
# Dependencies
|
||||||
ktorVersion=2.1.3
|
ktorVersion=2.3.4
|
||||||
kotestVersion=5.5.4
|
kotestVersion=5.7.1
|
||||||
detektVersion=1.21.0
|
detektVersion=1.22.0
|
||||||
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,5 +1,7 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
|
||||||
|
networkTimeout=10000
|
||||||
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
31
gradlew
vendored
31
gradlew
vendored
@ -55,7 +55,7 @@
|
|||||||
# Darwin, MinGW, and NonStop.
|
# Darwin, MinGW, and NonStop.
|
||||||
#
|
#
|
||||||
# (3) This script is generated from the Groovy template
|
# (3) This script is generated from the Groovy template
|
||||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
# within the Gradle project.
|
# within the Gradle project.
|
||||||
#
|
#
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
@ -80,13 +80,11 @@ do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
# This is normally unused
|
||||||
|
# shellcheck disable=SC2034
|
||||||
APP_NAME="Gradle"
|
|
||||||
APP_BASE_NAME=${0##*/}
|
APP_BASE_NAME=${0##*/}
|
||||||
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD=maximum
|
||||||
@ -133,22 +131,29 @@ location of your Java installation."
|
|||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
JAVACMD=java
|
JAVACMD=java
|
||||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
if ! command -v java >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
location of your Java installation."
|
location of your Java installation."
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
case $MAX_FD in #(
|
case $MAX_FD in #(
|
||||||
max*)
|
max*)
|
||||||
|
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC3045
|
||||||
MAX_FD=$( ulimit -H -n ) ||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
warn "Could not query maximum file descriptor limit"
|
warn "Could not query maximum file descriptor limit"
|
||||||
esac
|
esac
|
||||||
case $MAX_FD in #(
|
case $MAX_FD in #(
|
||||||
'' | soft) :;; #(
|
'' | soft) :;; #(
|
||||||
*)
|
*)
|
||||||
|
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC3045
|
||||||
ulimit -n "$MAX_FD" ||
|
ulimit -n "$MAX_FD" ||
|
||||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
esac
|
esac
|
||||||
@ -193,6 +198,10 @@ if "$cygwin" || "$msys" ; then
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
# Collect all arguments for the java command;
|
# Collect all arguments for the java command;
|
||||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||||
# shell script including quotes and variable substitutions, so put them in
|
# shell script including quotes and variable substitutions, so put them in
|
||||||
@ -205,6 +214,12 @@ set -- \
|
|||||||
org.gradle.wrapper.GradleWrapperMain \
|
org.gradle.wrapper.GradleWrapperMain \
|
||||||
"$@"
|
"$@"
|
||||||
|
|
||||||
|
# Stop when "xargs" is not available.
|
||||||
|
if ! command -v xargs >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "xargs is not available"
|
||||||
|
fi
|
||||||
|
|
||||||
# Use "xargs" to parse quoted args.
|
# Use "xargs" to parse quoted args.
|
||||||
#
|
#
|
||||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
15
gradlew.bat
vendored
15
gradlew.bat
vendored
@ -14,7 +14,7 @@
|
|||||||
@rem limitations under the License.
|
@rem limitations under the License.
|
||||||
@rem
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%" == "" @echo off
|
@if "%DEBUG%"=="" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@rem
|
@rem
|
||||||
@rem Gradle startup script for Windows
|
@rem Gradle startup script for Windows
|
||||||
@ -25,7 +25,8 @@
|
|||||||
if "%OS%"=="Windows_NT" setlocal
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%" == "" set DIRNAME=.
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
|
@rem This is normally unused
|
||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
|||||||
|
|
||||||
set JAVA_EXE=java.exe
|
set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if "%ERRORLEVEL%" == "0" goto execute
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
|||||||
|
|
||||||
:end
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||||
|
|
||||||
:fail
|
:fail
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
rem the _cmd.exe /c_ return code!
|
rem the _cmd.exe /c_ return code!
|
||||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
set EXIT_CODE=%ERRORLEVEL%
|
||||||
exit /b 1
|
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||||
|
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||||
|
exit /b %EXIT_CODE%
|
||||||
|
|
||||||
:mainEnd
|
:mainEnd
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
@ -20,8 +20,11 @@ dependencies {
|
|||||||
// Versions
|
// Versions
|
||||||
val detektVersion: String by project
|
val detektVersion: String by project
|
||||||
|
|
||||||
implementation("org.jetbrains.kotlin:kotlin-reflect:1.7.20")
|
// Kompendium
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1")
|
api(projects.kompendiumEnrichment)
|
||||||
|
|
||||||
|
implementation("org.jetbrains.kotlin:kotlin-reflect:1.9.10")
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")
|
||||||
|
|
||||||
// Formatting
|
// Formatting
|
||||||
detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:$detektVersion")
|
detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:$detektVersion")
|
||||||
|
@ -1,20 +1,51 @@
|
|||||||
package io.bkbn.kompendium.json.schema
|
package io.bkbn.kompendium.json.schema
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.json.schema.definition.EnumDefinition
|
||||||
|
import io.bkbn.kompendium.json.schema.definition.JsonSchema
|
||||||
|
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Transient
|
import kotlinx.serialization.Transient
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
import kotlin.reflect.KProperty1
|
import kotlin.reflect.KProperty1
|
||||||
|
import kotlin.reflect.KType
|
||||||
|
import kotlin.reflect.full.findAnnotation
|
||||||
import kotlin.reflect.full.hasAnnotation
|
import kotlin.reflect.full.hasAnnotation
|
||||||
import kotlin.reflect.full.memberProperties
|
import kotlin.reflect.full.memberProperties
|
||||||
|
import kotlin.reflect.full.primaryConstructor
|
||||||
|
|
||||||
class KotlinXSchemaConfigurator : SchemaConfigurator {
|
class KotlinXSchemaConfigurator : SchemaConfigurator {
|
||||||
|
|
||||||
override fun serializableMemberProperties(clazz: KClass<*>): Collection<KProperty1<out Any, *>> =
|
override fun serializableMemberProperties(clazz: KClass<*>): Collection<KProperty1<out Any, *>> {
|
||||||
clazz.memberProperties
|
return clazz.memberProperties
|
||||||
.filterNot { it.hasAnnotation<Transient>() }
|
.filterNot { it.hasAnnotation<Transient>() }
|
||||||
|
.filter { clazz.primaryConstructor?.parameters?.map { it.name }?.contains(it.name) ?: true }
|
||||||
|
}
|
||||||
|
|
||||||
override fun serializableName(property: KProperty1<out Any, *>): String =
|
override fun serializableName(property: KProperty1<out Any, *>): String =
|
||||||
property.annotations
|
property.annotations
|
||||||
.filterIsInstance<SerialName>()
|
.filterIsInstance<SerialName>()
|
||||||
.firstOrNull()?.value ?: property.name
|
.firstOrNull()?.value ?: property.name
|
||||||
|
|
||||||
|
override fun sealedTypeEnrichment(
|
||||||
|
implementationType: KType,
|
||||||
|
implementationSchema: JsonSchema,
|
||||||
|
): JsonSchema {
|
||||||
|
return if (implementationSchema is TypeDefinition && implementationSchema.type == "object") {
|
||||||
|
implementationSchema.copy(
|
||||||
|
required = implementationSchema.required?.plus("type"),
|
||||||
|
properties = implementationSchema.properties?.plus(
|
||||||
|
mapOf(
|
||||||
|
"type" to EnumDefinition("string", enum = setOf(determineTypeQualifier(implementationType)))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
implementationSchema
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun determineTypeQualifier(type: KType): String {
|
||||||
|
val nameOverrideAnnotation = (type.classifier as KClass<*>).findAnnotation<SerialName>()
|
||||||
|
return nameOverrideAnnotation?.value ?: (type.classifier as KClass<*>).qualifiedName!!
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,16 @@
|
|||||||
package io.bkbn.kompendium.json.schema
|
package io.bkbn.kompendium.json.schema
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.json.schema.definition.JsonSchema
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
import kotlin.reflect.KProperty1
|
import kotlin.reflect.KProperty1
|
||||||
import kotlin.reflect.full.memberProperties
|
import kotlin.reflect.KType
|
||||||
|
|
||||||
interface SchemaConfigurator {
|
interface SchemaConfigurator {
|
||||||
fun serializableMemberProperties(clazz: KClass<*>): Collection<KProperty1<out Any, *>>
|
fun serializableMemberProperties(clazz: KClass<*>): Collection<KProperty1<out Any, *>>
|
||||||
fun serializableName(property: KProperty1<out Any, *>): String
|
fun serializableName(property: KProperty1<out Any, *>): String
|
||||||
|
|
||||||
open class Default : SchemaConfigurator {
|
fun sealedTypeEnrichment(
|
||||||
override fun serializableMemberProperties(clazz: KClass<*>): Collection<KProperty1<out Any, *>> =
|
implementationType: KType,
|
||||||
clazz.memberProperties
|
implementationSchema: JsonSchema
|
||||||
|
): JsonSchema
|
||||||
override fun serializableName(property: KProperty1<out Any, *>): String = property.name
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package io.bkbn.kompendium.json.schema
|
package io.bkbn.kompendium.json.schema
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.enrichment.TypeEnrichment
|
||||||
import io.bkbn.kompendium.json.schema.definition.JsonSchema
|
import io.bkbn.kompendium.json.schema.definition.JsonSchema
|
||||||
import io.bkbn.kompendium.json.schema.definition.NullableDefinition
|
import io.bkbn.kompendium.json.schema.definition.NullableDefinition
|
||||||
import io.bkbn.kompendium.json.schema.definition.OneOfDefinition
|
import io.bkbn.kompendium.json.schema.definition.OneOfDefinition
|
||||||
@ -9,28 +10,26 @@ import io.bkbn.kompendium.json.schema.handler.EnumHandler
|
|||||||
import io.bkbn.kompendium.json.schema.handler.MapHandler
|
import io.bkbn.kompendium.json.schema.handler.MapHandler
|
||||||
import io.bkbn.kompendium.json.schema.handler.SealedObjectHandler
|
import io.bkbn.kompendium.json.schema.handler.SealedObjectHandler
|
||||||
import io.bkbn.kompendium.json.schema.handler.SimpleObjectHandler
|
import io.bkbn.kompendium.json.schema.handler.SimpleObjectHandler
|
||||||
import io.bkbn.kompendium.json.schema.util.Helpers.getSimpleSlug
|
import io.bkbn.kompendium.json.schema.util.Helpers.getSlug
|
||||||
|
import java.util.UUID
|
||||||
import kotlin.reflect.KClass
|
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 java.util.UUID
|
|
||||||
|
|
||||||
object SchemaGenerator {
|
object SchemaGenerator {
|
||||||
|
|
||||||
inline fun <reified T : Any?> fromTypeToSchema(
|
|
||||||
cache: MutableMap<String, JsonSchema> = mutableMapOf(),
|
|
||||||
schemaConfigurator: SchemaConfigurator = SchemaConfigurator.Default()
|
|
||||||
) = fromTypeToSchema(typeOf<T>(), cache, schemaConfigurator)
|
|
||||||
|
|
||||||
fun fromTypeToSchema(
|
fun fromTypeToSchema(
|
||||||
type: KType,
|
type: KType,
|
||||||
cache: MutableMap<String, JsonSchema>,
|
cache: MutableMap<String, JsonSchema>,
|
||||||
schemaConfigurator: SchemaConfigurator
|
schemaConfigurator: SchemaConfigurator,
|
||||||
|
enrichment: TypeEnrichment<*>? = null
|
||||||
): JsonSchema {
|
): JsonSchema {
|
||||||
cache[type.getSimpleSlug()]?.let {
|
val slug = type.getSlug(enrichment)
|
||||||
|
|
||||||
|
cache[slug]?.let {
|
||||||
return it
|
return it
|
||||||
}
|
}
|
||||||
|
|
||||||
return when (val clazz = type.classifier as KClass<*>) {
|
return when (val clazz = type.classifier as KClass<*>) {
|
||||||
Unit::class -> error(
|
Unit::class -> error(
|
||||||
"""
|
"""
|
||||||
@ -48,14 +47,14 @@ object SchemaGenerator {
|
|||||||
Boolean::class -> checkForNull(type, TypeDefinition.BOOLEAN)
|
Boolean::class -> checkForNull(type, TypeDefinition.BOOLEAN)
|
||||||
UUID::class -> checkForNull(type, TypeDefinition.UUID)
|
UUID::class -> checkForNull(type, TypeDefinition.UUID)
|
||||||
else -> when {
|
else -> when {
|
||||||
clazz.isSubclassOf(Enum::class) -> EnumHandler.handle(type, clazz, cache)
|
clazz.isSubclassOf(Enum::class) -> EnumHandler.handle(type, clazz, cache, enrichment)
|
||||||
clazz.isSubclassOf(Collection::class) -> CollectionHandler.handle(type, cache, schemaConfigurator)
|
clazz.isSubclassOf(Collection::class) -> CollectionHandler.handle(type, cache, schemaConfigurator, enrichment)
|
||||||
clazz.isSubclassOf(Map::class) -> MapHandler.handle(type, cache, schemaConfigurator)
|
clazz.isSubclassOf(Map::class) -> MapHandler.handle(type, cache, schemaConfigurator, enrichment)
|
||||||
else -> {
|
else -> {
|
||||||
if (clazz.isSealed) {
|
if (clazz.isSealed) {
|
||||||
SealedObjectHandler.handle(type, clazz, cache, schemaConfigurator)
|
SealedObjectHandler.handle(type, clazz, cache, schemaConfigurator, enrichment)
|
||||||
} else {
|
} else {
|
||||||
SimpleObjectHandler.handle(type, clazz, cache, schemaConfigurator)
|
SimpleObjectHandler.handle(type, clazz, cache, schemaConfigurator, enrichment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -65,11 +64,12 @@ object SchemaGenerator {
|
|||||||
fun fromTypeOrUnit(
|
fun fromTypeOrUnit(
|
||||||
type: KType,
|
type: KType,
|
||||||
cache: MutableMap<String, JsonSchema> = mutableMapOf(),
|
cache: MutableMap<String, JsonSchema> = mutableMapOf(),
|
||||||
schemaConfigurator: SchemaConfigurator
|
schemaConfigurator: SchemaConfigurator,
|
||||||
|
enrichment: TypeEnrichment<*>? = null
|
||||||
): JsonSchema? =
|
): JsonSchema? =
|
||||||
when (type.classifier as KClass<*>) {
|
when (type.classifier as KClass<*>) {
|
||||||
Unit::class -> null
|
Unit::class -> null
|
||||||
else -> fromTypeToSchema(type, cache, schemaConfigurator)
|
else -> fromTypeToSchema(type, cache, schemaConfigurator, enrichment)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkForNull(type: KType, schema: JsonSchema): JsonSchema = when (type.isMarkedNullable) {
|
private fun checkForNull(type: KType, schema: JsonSchema): JsonSchema = when (type.isMarkedNullable) {
|
||||||
|
@ -3,4 +3,8 @@ package io.bkbn.kompendium.json.schema.definition
|
|||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class AnyOfDefinition(val anyOf: Set<JsonSchema>) : JsonSchema
|
data class AnyOfDefinition(
|
||||||
|
val anyOf: Set<JsonSchema>,
|
||||||
|
override val deprecated: Boolean? = null,
|
||||||
|
override val description: String? = null,
|
||||||
|
) : JsonSchema
|
||||||
|
@ -4,7 +4,14 @@ import kotlinx.serialization.Serializable
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ArrayDefinition(
|
data class ArrayDefinition(
|
||||||
val items: JsonSchema
|
val items: JsonSchema,
|
||||||
|
override val deprecated: Boolean? = null,
|
||||||
|
override val description: String? = null,
|
||||||
|
|
||||||
|
// Constraints
|
||||||
|
val maxItems: Int? = null,
|
||||||
|
val minItems: Int? = null,
|
||||||
|
val uniqueItems: Boolean? = null,
|
||||||
) : JsonSchema {
|
) : JsonSchema {
|
||||||
val type: String = "array"
|
val type: String = "array"
|
||||||
}
|
}
|
||||||
|
@ -5,5 +5,7 @@ import kotlinx.serialization.Serializable
|
|||||||
@Serializable
|
@Serializable
|
||||||
data class EnumDefinition(
|
data class EnumDefinition(
|
||||||
val type: String,
|
val type: String,
|
||||||
val enum: Set<String>
|
val enum: Set<String>,
|
||||||
|
override val deprecated: Boolean? = null,
|
||||||
|
override val description: String? = null,
|
||||||
) : JsonSchema
|
) : JsonSchema
|
||||||
|
@ -11,6 +11,9 @@ import kotlinx.serialization.encoding.Encoder
|
|||||||
@Serializable(with = JsonSchema.Serializer::class)
|
@Serializable(with = JsonSchema.Serializer::class)
|
||||||
sealed interface JsonSchema {
|
sealed interface JsonSchema {
|
||||||
|
|
||||||
|
val description: String?
|
||||||
|
val deprecated: Boolean?
|
||||||
|
|
||||||
object Serializer : KSerializer<JsonSchema> {
|
object Serializer : KSerializer<JsonSchema> {
|
||||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("JsonSchema", PrimitiveKind.STRING)
|
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("JsonSchema", PrimitiveKind.STRING)
|
||||||
override fun deserialize(decoder: Decoder): JsonSchema {
|
override fun deserialize(decoder: Decoder): JsonSchema {
|
||||||
|
@ -4,7 +4,9 @@ import kotlinx.serialization.Serializable
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class MapDefinition(
|
data class MapDefinition(
|
||||||
val additionalProperties: JsonSchema
|
val additionalProperties: JsonSchema,
|
||||||
|
override val deprecated: Boolean? = null,
|
||||||
|
override val description: String? = null,
|
||||||
) : JsonSchema {
|
) : JsonSchema {
|
||||||
val type: String = "object"
|
val type: String = "object"
|
||||||
}
|
}
|
||||||
|
@ -3,4 +3,8 @@ package io.bkbn.kompendium.json.schema.definition
|
|||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class NullableDefinition(val type: String = "null") : JsonSchema
|
data class NullableDefinition(
|
||||||
|
val type: String = "null",
|
||||||
|
override val deprecated: Boolean? = null,
|
||||||
|
override val description: String? = null,
|
||||||
|
) : JsonSchema
|
||||||
|
@ -3,6 +3,10 @@ package io.bkbn.kompendium.json.schema.definition
|
|||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class OneOfDefinition(val oneOf: Set<JsonSchema>) : JsonSchema {
|
data class OneOfDefinition(
|
||||||
|
val oneOf: Set<JsonSchema>,
|
||||||
|
override val deprecated: Boolean? = null,
|
||||||
|
override val description: String? = null,
|
||||||
|
) : JsonSchema {
|
||||||
constructor(vararg types: JsonSchema) : this(types.toSet())
|
constructor(vararg types: JsonSchema) : this(types.toSet())
|
||||||
}
|
}
|
||||||
|
@ -3,4 +3,8 @@ package io.bkbn.kompendium.json.schema.definition
|
|||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ReferenceDefinition(val `$ref`: String) : JsonSchema
|
data class ReferenceDefinition(
|
||||||
|
val `$ref`: String,
|
||||||
|
override val deprecated: Boolean? = null,
|
||||||
|
override val description: String? = null,
|
||||||
|
) : JsonSchema
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user