Compare commits
28 Commits
v2.0.0-alp
...
v2.0.1
Author | SHA1 | Date | |
---|---|---|---|
d97717f8f8 | |||
3c585c06a3 | |||
7bfd168d74 | |||
19298f4deb | |||
54bdf107e2 | |||
721302d651 | |||
3ffda43a52 | |||
ae2a1b578a | |||
147c7e7fb0 | |||
906b329c2e | |||
1bf81cfd82 | |||
c9f173d6b0 | |||
c43fafae1b | |||
3b2fa72d26 | |||
91d4ec10b7 | |||
aa1b898b22 | |||
aa21c1219b | |||
bc380077fb | |||
fc9929e9cc | |||
a26ad72b67 | |||
38a70e4979 | |||
4a1425b73b | |||
01c118373e | |||
7535d67661 | |||
eb369dcdc8 | |||
da104d0a63 | |||
c6ed261fe4 | |||
012db5ad26 |
6
.github/workflows/pr_checks.yml
vendored
6
.github/workflows/pr_checks.yml
vendored
@ -15,9 +15,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
gradle-version: wrapper
|
gradle-version: wrapper
|
||||||
arguments: detekt
|
arguments: detekt
|
||||||
properties: |
|
|
||||||
org.gradle.vfs.watch=false
|
|
||||||
org.gradle.vfs.verbose=false
|
|
||||||
unit:
|
unit:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
@ -32,6 +29,3 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
gradle-version: wrapper
|
gradle-version: wrapper
|
||||||
arguments: test koverCollectReports
|
arguments: test koverCollectReports
|
||||||
properties: |
|
|
||||||
org.gradle.vfs.watch=false
|
|
||||||
org.gradle.vfs.verbose=false
|
|
||||||
|
5
.github/workflows/publish.yml
vendored
5
.github/workflows/publish.yml
vendored
@ -2,6 +2,8 @@ name: Publish to GitHub Packages
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ main ]
|
branches: [ main ]
|
||||||
|
paths-ignore:
|
||||||
|
- docs/**
|
||||||
env:
|
env:
|
||||||
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.SONATYPE_SIGNING_KEY }}
|
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.SONATYPE_SIGNING_KEY }}
|
||||||
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.SONATYPE_SIGNING_PASSWORD }}
|
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.SONATYPE_SIGNING_PASSWORD }}
|
||||||
@ -19,8 +21,5 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
gradle-version: wrapper
|
gradle-version: wrapper
|
||||||
arguments: publishAllPublicationsToGithubPackagesRepository
|
arguments: publishAllPublicationsToGithubPackagesRepository
|
||||||
properties: |
|
|
||||||
org.gradle.vfs.watch=false
|
|
||||||
org.gradle.vfs.verbose=false
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
42
.github/workflows/release.yml
vendored
42
.github/workflows/release.yml
vendored
@ -8,23 +8,6 @@ env:
|
|||||||
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.SONATYPE_SIGNING_KEY }}
|
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.SONATYPE_SIGNING_KEY }}
|
||||||
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.SONATYPE_SIGNING_PASSWORD }}
|
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.SONATYPE_SIGNING_PASSWORD }}
|
||||||
jobs:
|
jobs:
|
||||||
publish-to-github:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- uses: actions/setup-java@v2
|
|
||||||
with:
|
|
||||||
distribution: 'adopt'
|
|
||||||
java-version: '17'
|
|
||||||
- name: Publish to GitHub Packages
|
|
||||||
uses: burrunan/gradle-cache-action@v1
|
|
||||||
with:
|
|
||||||
gradle-version: wrapper
|
|
||||||
arguments: publishAllPublicationsToGithubPackagesRepository
|
|
||||||
properties: |
|
|
||||||
release=true
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
publish-to-nexus:
|
publish-to-nexus:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
@ -43,3 +26,28 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.SONATYPE_USER }}
|
ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.SONATYPE_USER }}
|
||||||
ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.SONATYPE_PASSWORD }}
|
ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.SONATYPE_PASSWORD }}
|
||||||
|
build-documentation:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- publish-to-nexus
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
||||||
|
- uses: actions/setup-java@v2
|
||||||
|
with:
|
||||||
|
distribution: 'adopt'
|
||||||
|
java-version: '17'
|
||||||
|
- name: Build New Documentation
|
||||||
|
uses: burrunan/gradle-cache-action@v1
|
||||||
|
with:
|
||||||
|
gradle-version: wrapper
|
||||||
|
arguments: dokkaHtmlMultiModule
|
||||||
|
properties: |
|
||||||
|
release=true
|
||||||
|
- name: Push New Documentation
|
||||||
|
uses: EndBug/add-and-commit@v8
|
||||||
|
with:
|
||||||
|
default_author: github_actions
|
||||||
|
new_branch: main
|
||||||
|
message: 'doc: Added Latest Documentation ✨'
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,5 +1,3 @@
|
|||||||
.gradle
|
.gradle
|
||||||
build
|
build
|
||||||
.idea
|
.idea
|
||||||
dokka
|
|
||||||
wiki
|
|
||||||
|
59
CHANGELOG.md
59
CHANGELOG.md
@ -1,6 +1,7 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
@ -11,8 +12,24 @@
|
|||||||
|
|
||||||
## Released
|
## Released
|
||||||
|
|
||||||
## [2.0.0-alpha] - January 2nd, 2021
|
## [2.0.1] - January 23rd, 2022
|
||||||
|
|
||||||
|
### Change
|
||||||
|
- Fix bug in documentation publishing pipeline
|
||||||
|
|
||||||
|
## [2.0.0] - January 23rd, 2022
|
||||||
|
|
||||||
|
Major Release 🎉 As we head towards the Ktor 2 release, this library will be kept compatible with Ktor 1. A future
|
||||||
|
Kompendium 2 repository will be created soon, porting much of the changes you see here, with some awesome Ktor 2 twists
|
||||||
|
😉
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
- Support for HTTP Patch, Head, and Options methods
|
||||||
|
- Support for including parameter examples via `MethodInfo`
|
||||||
|
- Dokka Pipeline Generation
|
||||||
|
- GitHub Pages integration
|
||||||
|
- Sourdough Gradle updates
|
||||||
- Support for OAuth authentication
|
- Support for OAuth authentication
|
||||||
- Gradle Toolchain feature to ensure match between local JDK and compile target
|
- Gradle Toolchain feature to ensure match between local JDK and compile target
|
||||||
- Dokka integration
|
- Dokka integration
|
||||||
@ -22,6 +39,10 @@
|
|||||||
- Ability to document expected unstructured data
|
- Ability to document expected unstructured data
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
- Kompendium now leverages the chosen API serializer. Supports Jackson, Gson and Kotlinx Serialization
|
||||||
|
- Fixed bug where overridden field names were not reflected in serialized object and required array
|
||||||
|
- Fixed bug where Ktor Location parents were not being scanned for parameters
|
||||||
- `$ref` types are no longer generated, instead all objects are defined explicitly
|
- `$ref` types are no longer generated, instead all objects are defined explicitly
|
||||||
- All OpenAPI domain models moved to a separate module `kompendium-oas`
|
- All OpenAPI domain models moved to a separate module `kompendium-oas`
|
||||||
- Moved all files in `kompendium-core` into `io.bkbn.kompendium.core` package from `io.bkbn.kompendium`
|
- Moved all files in `kompendium-core` into `io.bkbn.kompendium.core` package from `io.bkbn.kompendium`
|
||||||
@ -29,7 +50,8 @@
|
|||||||
- Gradle build logic offloaded to Sourdough Plugin
|
- Gradle build logic offloaded to Sourdough Plugin
|
||||||
- Minimum supported Java version is now 11
|
- Minimum supported Java version is now 11
|
||||||
- Bumped Kotlin to 1.6
|
- Bumped Kotlin to 1.6
|
||||||
- Annotations now live in a separate module. (Should not impact end users as module is imported as api dependency by core).
|
- Annotations now live in a separate module. (Should not impact end users as module is imported as api dependency by
|
||||||
|
core).
|
||||||
- Kotest as the testing framework of choice
|
- Kotest as the testing framework of choice
|
||||||
- Path calculation removed in favor of built-in route toString
|
- Path calculation removed in favor of built-in route toString
|
||||||
- Ktor to 1.6.7
|
- Ktor to 1.6.7
|
||||||
@ -55,11 +77,15 @@
|
|||||||
- Dropped ASDF tool manifest
|
- Dropped ASDF tool manifest
|
||||||
|
|
||||||
## [1.11.1] - November 25th, 2021
|
## [1.11.1] - November 25th, 2021
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Documentation showing how to add header names using Kotlin backtick convention
|
- Documentation showing how to add header names using Kotlin backtick convention
|
||||||
|
|
||||||
## [1.11.0] - November 25th, 2021
|
## [1.11.0] - November 25th, 2021
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Support for Ktor Location Plugin
|
- Support for Ktor Location Plugin
|
||||||
|
|
||||||
## [1.10.0] - November 25th, 2021
|
## [1.10.0] - November 25th, 2021
|
||||||
@ -79,7 +105,7 @@
|
|||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Code Coverage removed from PR checks due to limitations with GitHub workflows
|
- Code Coverage removed from PR checks due to limitations with GitHub workflows
|
||||||
- Minor linting fixes
|
- Minor linting fixes
|
||||||
- Detekt now builds off of default config
|
- Detekt now builds off of default config
|
||||||
|
|
||||||
@ -89,7 +115,6 @@
|
|||||||
|
|
||||||
- ByteArray added to the set of default types
|
- ByteArray added to the set of default types
|
||||||
|
|
||||||
|
|
||||||
## [1.8.1] - October 4th, 2021
|
## [1.8.1] - October 4th, 2021
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
@ -203,13 +228,13 @@ This is just to get my repo back to normal now that I have confirmed sonatype pu
|
|||||||
|
|
||||||
- In order to facilitate default parameters, a couple changes were needed
|
- In order to facilitate default parameters, a couple changes were needed
|
||||||
- `KompendiumParam` was added in replacement of the four parameter annotations
|
- `KompendiumParam` was added in replacement of the four parameter annotations
|
||||||
- Specs now explicitly declare type of parameter rather than a reference in order to not override default values.
|
- Specs now explicitly declare type of parameter rather than a reference in order to not override default values.
|
||||||
|
|
||||||
## [0.8.0] - May 4th, 2021
|
## [0.8.0] - May 4th, 2021
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Support for example request and response bodies. Parameter examples / defaults are a separate issue for later.
|
- Support for example request and response bodies. Parameter examples / defaults are a separate issue for later.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
@ -225,7 +250,7 @@ This is just to get my repo back to normal now that I have confirmed sonatype pu
|
|||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Refactored `kompendium-core` to break up the `Kompendium` object into slightly more manageable chunks
|
- Refactored `kompendium-core` to break up the `Kompendium` object into slightly more manageable chunks
|
||||||
- Notarization Parameters can now be inferred from method info
|
- Notarization Parameters can now be inferred from method info
|
||||||
|
|
||||||
## [0.6.2] - April 23rd, 2021
|
## [0.6.2] - April 23rd, 2021
|
||||||
|
|
||||||
@ -242,23 +267,24 @@ This is just to get my repo back to normal now that I have confirmed sonatype pu
|
|||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Set jvm target to 11
|
- Set jvm target to 11
|
||||||
- Resolved bug for empty params and/or empty response body
|
- Resolved bug for empty params and/or empty response body
|
||||||
|
|
||||||
## [0.6.0] - April 21st, 2021
|
## [0.6.0] - April 21st, 2021
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added basic and jwt security scheme support with the new module kompendium-auth
|
- Added basic and jwt security scheme support with the new module kompendium-auth
|
||||||
|
|
||||||
## [0.5.2] - April 19th, 2021
|
## [0.5.2] - April 19th, 2021
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
- Removed `Route.calculatePath`
|
- Removed `Route.calculatePath`
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- Added an explicit `PathCalculator` interface to allow for easier handling of routes external to the core set of Ktor route selectors.
|
### Added
|
||||||
|
|
||||||
|
- Added an explicit `PathCalculator` interface to allow for easier handling of routes external to the core set of Ktor
|
||||||
|
route selectors.
|
||||||
|
|
||||||
## [0.5.1] - April 19th, 2021
|
## [0.5.1] - April 19th, 2021
|
||||||
|
|
||||||
@ -270,7 +296,7 @@ This is just to get my repo back to normal now that I have confirmed sonatype pu
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Expose `/openapi.json` and `/docs` as opt-in pre-built Routes
|
- Expose `/openapi.json` and `/docs` as opt-in pre-built Routes
|
||||||
|
|
||||||
## [0.4.0] - April 17th, 2021
|
## [0.4.0] - April 17th, 2021
|
||||||
|
|
||||||
@ -349,12 +375,13 @@ This is just to get my repo back to normal now that I have confirmed sonatype pu
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Beginning of an implementation. Currently, able to generate a rough outline of the API at runtime, along with generating
|
- Beginning of an implementation. Currently, able to generate a rough outline of the API at runtime, along with
|
||||||
full data classes represented by JSON Schema.
|
generating full data classes represented by JSON Schema.
|
||||||
|
|
||||||
## [0.0.1] - April 11th, 2021
|
## [0.0.1] - April 11th, 2021
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added _most_ of the data classes necessary for generating an [Open API Spec](https://swagger.io/specification)
|
- Added _most_ of the data classes necessary for generating an [Open API Spec](https://swagger.io/specification)
|
||||||
- Added playground to allow users to tinker with a live Ktor api in conjunction with development
|
- Added playground to allow users to tinker with a live Ktor api in conjunction with development
|
||||||
- Added all standard OSS files
|
- Added all standard OSS files
|
||||||
|
41
Project.md
Normal file
41
Project.md
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# Kompendium
|
||||||
|
|
||||||
|
Welcome to Kompendium, the straight-forward, minimally-invasive OpenAPI generator for Ktor.
|
||||||
|
|
||||||
|
## How to install
|
||||||
|
|
||||||
|
Kompendium publishes all releases to Maven Central. As such, using the release versions of `Kompendium` is as simple as
|
||||||
|
declaring it as an implementation dependency in your `build.gradle.kts`
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation("io.bkbn:kompendium-core:latest.release")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In addition to publishing releases to Maven Central, a snapshot version gets published to GitHub Packages on every merge
|
||||||
|
to `main`. These can be consumed by adding the repository to your gradle build file. Instructions can be
|
||||||
|
found [here](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-gradle-registry#using-a-published-package)
|
||||||
|
|
||||||
|
## Setting up the Kompendium Plugin
|
||||||
|
|
||||||
|
Kompendium is instantiated as a Ktor Feature/Plugin. It can be added to your API as follows
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
private fun Application.mainModule() {
|
||||||
|
// Installs the Kompendium Plugin and sets up baseline server metadata
|
||||||
|
install(Kompendium) {
|
||||||
|
spec = OpenApiSpec(/*..*/)
|
||||||
|
}
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notarization
|
||||||
|
|
||||||
|
The concept of notarizing routes / exceptions / etc. is central to Kompendium. More details on _how_ to notarize your
|
||||||
|
API can be found in the kompendium-core module.
|
@ -31,15 +31,13 @@ dependencies {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The last two dependencies are optional.
|
|
||||||
|
|
||||||
In addition to publishing releases to Maven Central, a snapshot version gets published to GitHub Packages on every merge
|
In addition to publishing releases to Maven Central, a snapshot version gets published to GitHub Packages on every merge
|
||||||
to `main`. These can be consumed by adding the repository to your gradle build file. Instructions can be
|
to `main`. These can be consumed by adding the repository to your gradle build file. Instructions can be
|
||||||
found [here](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-gradle-registry#using-a-published-package)
|
found [here](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-gradle-registry#using-a-published-package)
|
||||||
|
|
||||||
# Library Details
|
# Library Details
|
||||||
|
|
||||||
Details on how to use Kompendium can be found in the Wiki (WIP, this is new for V2 and will be fleshed out prior to release)
|
Forthcoming, more details on V2 will be published soon :)
|
||||||
|
|
||||||
## Local Development
|
## Local Development
|
||||||
|
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
import io.bkbn.sourdough.gradle.core.extension.SourdoughLibraryExtension
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("io.bkbn.sourdough.root") version "0.3.0"
|
kotlin("jvm") version "1.6.10" apply false
|
||||||
|
kotlin("plugin.serialization") version "1.6.10" apply false
|
||||||
|
id("io.bkbn.sourdough.library.jvm") version "0.6.0" apply false
|
||||||
|
id("io.bkbn.sourdough.application.jvm") version "0.6.0" apply false
|
||||||
|
id("io.bkbn.sourdough.root") version "0.6.0"
|
||||||
id("com.github.jakemarsden.git-hooks") version "0.0.2"
|
id("com.github.jakemarsden.git-hooks") version "0.0.2"
|
||||||
}
|
id("org.jetbrains.dokka") version "1.6.10"
|
||||||
|
id("org.jetbrains.kotlinx.kover") version "0.5.0-RC"
|
||||||
sourdough {
|
id("io.github.gradle-nexus.publish-plugin") version "1.1.0"
|
||||||
toolChainJavaVersion.set(JavaLanguageVersion.of(JavaVersion.VERSION_17.majorVersion))
|
|
||||||
jvmTarget.set(JavaVersion.VERSION_11.majorVersion)
|
|
||||||
compilerArgs.set(listOf("-opt-in=kotlin.RequiresOptIn"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
gitHooks {
|
gitHooks {
|
||||||
@ -33,17 +32,13 @@ allprojects {
|
|||||||
}
|
}
|
||||||
|
|
||||||
subprojects {
|
subprojects {
|
||||||
if (this.name != "kompendium-playground") {
|
plugins.withType(io.bkbn.sourdough.gradle.library.jvm.LibraryJvmPlugin::class) {
|
||||||
apply(plugin = "io.bkbn.sourdough.library")
|
extensions.configure(io.bkbn.sourdough.gradle.library.jvm.LibraryJvmExtension::class) {
|
||||||
|
|
||||||
configure<SourdoughLibraryExtension> {
|
|
||||||
githubOrg.set("bkbnio")
|
githubOrg.set("bkbnio")
|
||||||
githubRepo.set("kompendium")
|
githubRepo.set("kompendium")
|
||||||
libraryName.set("Kompendium")
|
|
||||||
libraryDescription.set("A minimally invasive OpenAPI spec generator for Ktor")
|
|
||||||
licenseName.set("MIT License")
|
licenseName.set("MIT License")
|
||||||
licenseUrl.set("https://mit-license.org")
|
licenseUrl.set("https://mit-license.org")
|
||||||
developerId.set("bkbnio")
|
developerId.set("unredundant")
|
||||||
developerName.set("Ryan Brink")
|
developerName.set("Ryan Brink")
|
||||||
developerEmail.set("admin@bkbn.io")
|
developerEmail.set("admin@bkbn.io")
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Kompendium
|
# Kompendium
|
||||||
project.version=2.0.0-alpha
|
project.version=2.0.1
|
||||||
# Kotlin
|
# Kotlin
|
||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
# Gradle
|
# Gradle
|
||||||
@ -9,4 +9,4 @@ org.gradle.jvmargs=-Xmx2000m
|
|||||||
|
|
||||||
# Dependencies
|
# Dependencies
|
||||||
ktorVersion=1.6.7
|
ktorVersion=1.6.7
|
||||||
kotestVersion=5.0.3
|
kotestVersion=5.1.0
|
||||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.2-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
@ -7,3 +7,7 @@ It is separated from core predominantly to allow for potential future integratio
|
|||||||
# Package io.bkbn.kompendium.annotations
|
# Package io.bkbn.kompendium.annotations
|
||||||
|
|
||||||
Contains all annotations used by Kompendium
|
Contains all annotations used by Kompendium
|
||||||
|
|
||||||
|
# Package io.bkbn.kompendium.annotations.constraint
|
||||||
|
|
||||||
|
Annotations that place bespoke constraints on individual fields of your API schemas.
|
||||||
|
@ -1,3 +1,23 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id("io.bkbn.sourdough.library")
|
kotlin("jvm")
|
||||||
|
id("io.bkbn.sourdough.library.jvm")
|
||||||
|
id("io.gitlab.arturbosch.detekt")
|
||||||
|
id("com.adarshr.test-logger")
|
||||||
|
id("org.jetbrains.dokka")
|
||||||
|
id("maven-publish")
|
||||||
|
id("java-library")
|
||||||
|
id("signing")
|
||||||
|
}
|
||||||
|
|
||||||
|
sourdough {
|
||||||
|
libraryName.set("Kompendium Annotations")
|
||||||
|
libraryDescription.set("A set of annotations used by Kompendium to generate OpenAPI Specifications")
|
||||||
|
}
|
||||||
|
|
||||||
|
testing {
|
||||||
|
suites {
|
||||||
|
named("test", JvmTestSuite::class) {
|
||||||
|
useJUnitJupiter()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,5 +5,9 @@ their API authentication with minimal modifications to their existing configurat
|
|||||||
|
|
||||||
# Package io.bkbn.kompendium.auth
|
# Package io.bkbn.kompendium.auth
|
||||||
|
|
||||||
Base package that is responsible for setting up required authentication route handlers along with exposing
|
Base package that is responsible for setting up required authentication route handlers along with exposing wrapper
|
||||||
wrapper methods for each ktor-auth authentication mechanism.
|
methods for each ktor-auth authentication mechanism.
|
||||||
|
|
||||||
|
# Package io.bkbn.kompendium.auth
|
||||||
|
|
||||||
|
Houses the available security configurations. At the moment, `Basic`, `JWT`, `ApiKey`, and `OAuth` are supported
|
||||||
|
@ -1,7 +1,18 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id("io.bkbn.sourdough.library")
|
kotlin("jvm")
|
||||||
|
id("io.bkbn.sourdough.library.jvm")
|
||||||
|
id("io.gitlab.arturbosch.detekt")
|
||||||
|
id("com.adarshr.test-logger")
|
||||||
|
id("org.jetbrains.dokka")
|
||||||
|
id("maven-publish")
|
||||||
|
id("java-library")
|
||||||
|
id("signing")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sourdough {
|
||||||
|
libraryName.set("Kompendium Authentication")
|
||||||
|
libraryDescription.set("Kompendium library to pair with Ktor Auth to provide authorization info to OpenAPI")
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// IMPLEMENTATION
|
// IMPLEMENTATION
|
||||||
@ -16,3 +27,11 @@ dependencies {
|
|||||||
|
|
||||||
testImplementation(testFixtures(projects.kompendiumCore))
|
testImplementation(testFixtures(projects.kompendiumCore))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
testing {
|
||||||
|
suites {
|
||||||
|
named("test", JvmTestSuite::class) {
|
||||||
|
useJUnitJupiter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,9 +1,100 @@
|
|||||||
# Module kompendium-core
|
# Module kompendium-core
|
||||||
|
|
||||||
This is where the magic happens. This module houses all the reflective goodness that powers Kompendium.
|
This is where the magic happens. This module houses all the reflective goodness that powers Kompendium.
|
||||||
|
|
||||||
It is also the only mandatory client-facing module for a basic setup.
|
It is also the only mandatory client-facing module for a basic setup.
|
||||||
|
|
||||||
# Package io.bkbn.kompendium.core
|
# Package io.bkbn.kompendium.core
|
||||||
|
|
||||||
The root package contains several objects that power Kompendium
|
The root package contains several objects that power Kompendium, including the Kompendium Ktor Plugin, route
|
||||||
|
notarization methods, and the reflection engine that analyzes method info type parameters.
|
||||||
|
|
||||||
|
## Plugin
|
||||||
|
|
||||||
|
The Kompendium plugin is an extremely light-weight plugin, with only a couple areas of customization.
|
||||||
|
|
||||||
|
### Serialization
|
||||||
|
|
||||||
|
Kompendium relies on your API to provide a properly-configured `ContentNegotiator` in order to convert the `OpenApiSpec`
|
||||||
|
into JSON. The advantage to this approach is that all of your data classes will be serialized precisely how you define.
|
||||||
|
The downside is that issues could exist in serialization frameworks that have not been tested. At the moment, Jackson,
|
||||||
|
Gson and KotlinX serialization have all been tested. If you run into any serialization issues, particularly with a
|
||||||
|
serializer not listed above, please open an issue on GitHub 🙏
|
||||||
|
|
||||||
|
## Notarization
|
||||||
|
|
||||||
|
Central to Kompendium is the concept of notarization.
|
||||||
|
|
||||||
|
Notarizing a route is the mechanism by which Kompendium analyzes your route types, along with provided metadata, and
|
||||||
|
converts to the expected OpenAPI format.
|
||||||
|
|
||||||
|
Before jumping into notarization, lets first look at a standard Ktor route
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
routing {
|
||||||
|
get {
|
||||||
|
call.respond(HttpStatusCode.OK, BasicResponse(c = UUID.randomUUID().toString()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, let's compare this to the same functionality, but notarized using Kompendium
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
routing {
|
||||||
|
notarizedGet(simpleGetExample) {
|
||||||
|
call.respond(HttpStatusCode.OK, BasicResponse(c = UUID.randomUUID().toString()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Pretty simple huh. But hold on... what is this `simpleGetExample`? How can I know that it is so "simple". Let's take a
|
||||||
|
look
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
val simpleGetExample = GetInfo<Unit, BasicResponse>(
|
||||||
|
summary = "Simple, Documented GET Request",
|
||||||
|
description = "This is to showcase just how easy it is to document your Ktor API!",
|
||||||
|
responseInfo = ResponseInfo(
|
||||||
|
status = HttpStatusCode.OK,
|
||||||
|
description = "This means everything went as expected!",
|
||||||
|
examples = mapOf("demo" to BasicResponse(c = "52c099d7-8642-46cc-b34e-22f39b923cf4"))
|
||||||
|
),
|
||||||
|
tags = setOf("Simple")
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
See, not so bad 😄 `GetInfo<*,*>` is an implementation of `MethodInfo<TParam, TResp>`, a sealed interface designed to
|
||||||
|
encapsulate all the metadata required for documenting an API route. Kompendium leverages this data, along with the
|
||||||
|
provided type parameters `TParam` and `TResp` to construct the full OpenAPI Specification for your route.
|
||||||
|
|
||||||
|
Additionally, just as a backup, each notarization method includes a "post-processing' hook that will allow you to have
|
||||||
|
final say in the generated route info prior to being attached to the spec. This can be accessed via the optional
|
||||||
|
parameter
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
routing {
|
||||||
|
notarizedGet(simpleGetExample, postProcess = { spec -> spec }) {
|
||||||
|
call.respond(HttpStatusCode.OK, BasicResponse(c = UUID.randomUUID().toString()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This should only be used in _extremely_ rare scenarios, but it is nice to know it is there if you need it.
|
||||||
|
|
||||||
|
# Package io.bkbn.kompendium.core.metadata
|
||||||
|
|
||||||
|
Houses all interfaces and types related to describing route metadata.
|
||||||
|
|
||||||
|
# Package io.bkbn.kompendium.core.parser
|
||||||
|
|
||||||
|
Responsible for the parse of method information. Base implementation is an interface to support extensibility as shown
|
||||||
|
in the `kompendium-locations` module.
|
||||||
|
|
||||||
|
# Package io.bkbn.kompendium.core.routes
|
||||||
|
|
||||||
|
Houses any routes provided by the core module. At the moment the only supported route is to enable ReDoc support.
|
||||||
|
|
||||||
|
# Package io.bkbn.kompendium.core.util
|
||||||
|
|
||||||
|
Collection of utility functions used by Kompendium
|
||||||
|
@ -1,21 +1,34 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id("io.bkbn.sourdough.library")
|
kotlin("jvm")
|
||||||
`java-test-fixtures`
|
id("io.bkbn.sourdough.library.jvm")
|
||||||
|
id("io.gitlab.arturbosch.detekt")
|
||||||
|
id("com.adarshr.test-logger")
|
||||||
|
id("org.jetbrains.dokka")
|
||||||
|
id("maven-publish")
|
||||||
|
id("java-library")
|
||||||
|
id("signing")
|
||||||
|
id("java-test-fixtures")
|
||||||
|
}
|
||||||
|
|
||||||
|
sourdough {
|
||||||
|
libraryName.set("Kompendium Core")
|
||||||
|
libraryDescription.set("Core functionality for the Kompendium library")
|
||||||
|
compilerArgs.set(listOf("-opt-in=kotlin.RequiresOptIn"))
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
// VERSIONS
|
||||||
|
val ktorVersion: String by project
|
||||||
|
val kotestVersion: String by project
|
||||||
|
|
||||||
// IMPLEMENTATION
|
// IMPLEMENTATION
|
||||||
|
|
||||||
api(projects.kompendiumOas)
|
api(projects.kompendiumOas)
|
||||||
api(projects.kompendiumAnnotations)
|
api(projects.kompendiumAnnotations)
|
||||||
|
|
||||||
val ktorVersion: String by project
|
|
||||||
val kotestVersion: String by project
|
|
||||||
implementation(group = "io.ktor", name = "ktor-server-core", version = ktorVersion)
|
implementation(group = "io.ktor", name = "ktor-server-core", version = ktorVersion)
|
||||||
implementation(group = "io.ktor", name = "ktor-html-builder", version = ktorVersion)
|
implementation(group = "io.ktor", name = "ktor-html-builder", version = ktorVersion)
|
||||||
|
|
||||||
implementation(group = "com.fasterxml.jackson.module", name = "jackson-module-kotlin", version = "2.13.0")
|
|
||||||
|
|
||||||
// TEST FIXTURES
|
// TEST FIXTURES
|
||||||
|
|
||||||
testFixturesApi(group = "io.kotest", name = "kotest-runner-junit5-jvm", version = kotestVersion)
|
testFixturesApi(group = "io.kotest", name = "kotest-runner-junit5-jvm", version = kotestVersion)
|
||||||
@ -29,5 +42,13 @@ dependencies {
|
|||||||
testFixturesApi(group = "io.ktor", name = "ktor-jackson", version = ktorVersion)
|
testFixturesApi(group = "io.ktor", name = "ktor-jackson", version = ktorVersion)
|
||||||
testFixturesApi(group = "io.ktor", name = "ktor-serialization", version = ktorVersion)
|
testFixturesApi(group = "io.ktor", name = "ktor-serialization", version = ktorVersion)
|
||||||
|
|
||||||
testFixturesApi(group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version = "1.3.1")
|
testFixturesApi(group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version = "1.3.2")
|
||||||
|
}
|
||||||
|
|
||||||
|
testing {
|
||||||
|
suites {
|
||||||
|
named("test", JvmTestSuite::class) {
|
||||||
|
useJUnitJupiter()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
package io.bkbn.kompendium.core
|
package io.bkbn.kompendium.core
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
|
||||||
import com.fasterxml.jackson.databind.SerializationFeature
|
|
||||||
import io.bkbn.kompendium.core.metadata.SchemaMap
|
import io.bkbn.kompendium.core.metadata.SchemaMap
|
||||||
import io.bkbn.kompendium.oas.OpenApiSpec
|
import io.bkbn.kompendium.oas.OpenApiSpec
|
||||||
import io.bkbn.kompendium.oas.schema.TypedSchema
|
import io.bkbn.kompendium.oas.schema.TypedSchema
|
||||||
@ -12,7 +9,7 @@ import io.ktor.application.ApplicationFeature
|
|||||||
import io.ktor.application.call
|
import io.ktor.application.call
|
||||||
import io.ktor.http.HttpStatusCode
|
import io.ktor.http.HttpStatusCode
|
||||||
import io.ktor.request.path
|
import io.ktor.request.path
|
||||||
import io.ktor.response.respondText
|
import io.ktor.response.respond
|
||||||
import io.ktor.util.AttributeKey
|
import io.ktor.util.AttributeKey
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
@ -28,13 +25,6 @@ class Kompendium(val config: Configuration) {
|
|||||||
fun addCustomTypeSchema(clazz: KClass<*>, schema: TypedSchema) {
|
fun addCustomTypeSchema(clazz: KClass<*>, schema: TypedSchema) {
|
||||||
cache = cache.plus(clazz.simpleName!! to schema)
|
cache = cache.plus(clazz.simpleName!! to schema)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Add tests for this!!
|
|
||||||
var om: ObjectMapper = ObjectMapper()
|
|
||||||
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
|
|
||||||
.enable(SerializationFeature.INDENT_OUTPUT)
|
|
||||||
|
|
||||||
fun specToJson(): String = om.writeValueAsString(spec)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object Feature : ApplicationFeature<Application, Configuration, Kompendium> {
|
companion object Feature : ApplicationFeature<Application, Configuration, Kompendium> {
|
||||||
@ -44,8 +34,7 @@ class Kompendium(val config: Configuration) {
|
|||||||
|
|
||||||
pipeline.intercept(ApplicationCallPipeline.Call) {
|
pipeline.intercept(ApplicationCallPipeline.Call) {
|
||||||
if (call.request.path() == configuration.specRoute) {
|
if (call.request.path() == configuration.specRoute) {
|
||||||
call.respondText { configuration.specToJson() }
|
call.respond(HttpStatusCode.OK, configuration.spec)
|
||||||
call.response.status(HttpStatusCode.OK)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,8 +199,13 @@ object Kontent {
|
|||||||
logger.debug("$slug contains $fieldMap")
|
logger.debug("$slug contains $fieldMap")
|
||||||
var schema = ObjectSchema(fieldMap.plus(undeclaredFieldMap))
|
var schema = ObjectSchema(fieldMap.plus(undeclaredFieldMap))
|
||||||
val requiredParams = clazz.primaryConstructor?.parameters?.filterNot { it.isOptional } ?: emptyList()
|
val requiredParams = clazz.primaryConstructor?.parameters?.filterNot { it.isOptional } ?: emptyList()
|
||||||
|
// todo de-dup this logic
|
||||||
if (requiredParams.isNotEmpty()) {
|
if (requiredParams.isNotEmpty()) {
|
||||||
schema = schema.copy(required = requiredParams.map { it.name!! })
|
schema = schema.copy(required = requiredParams.map { param ->
|
||||||
|
clazz.memberProperties.first { it.name == param.name }.findAnnotation<Field>()
|
||||||
|
?.let { field -> field.name.ifBlank { param.name!! } }
|
||||||
|
?: param.name!!
|
||||||
|
})
|
||||||
}
|
}
|
||||||
logger.debug("$slug schema: $schema")
|
logger.debug("$slug schema: $schema")
|
||||||
newCache.plus(slug to schema)
|
newCache.plus(slug to schema)
|
||||||
@ -335,8 +340,13 @@ object Kontent {
|
|||||||
val requiredParams = clazz.primaryConstructor?.parameters?.filterNot { it.isOptional } ?: emptyList()
|
val requiredParams = clazz.primaryConstructor?.parameters?.filterNot { it.isOptional } ?: emptyList()
|
||||||
var schema = this
|
var schema = this
|
||||||
|
|
||||||
|
// todo dedup this
|
||||||
if (requiredParams.isNotEmpty()) {
|
if (requiredParams.isNotEmpty()) {
|
||||||
schema = schema.copy(required = requiredParams.map { it.name!! })
|
schema = schema.copy(required = requiredParams.map { param ->
|
||||||
|
clazz.memberProperties.first { it.name == param.name }.findAnnotation<Field>()
|
||||||
|
?.let { field -> field.name.ifBlank { param.name!! } }
|
||||||
|
?: param.name!!
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prop.returnType.isMarkedNullable) {
|
if (prop.returnType.isMarkedNullable) {
|
||||||
|
@ -2,11 +2,15 @@ package io.bkbn.kompendium.core
|
|||||||
|
|
||||||
import io.bkbn.kompendium.annotations.Param
|
import io.bkbn.kompendium.annotations.Param
|
||||||
import io.bkbn.kompendium.core.KompendiumPreFlight.methodNotarizationPreFlight
|
import io.bkbn.kompendium.core.KompendiumPreFlight.methodNotarizationPreFlight
|
||||||
import io.bkbn.kompendium.core.MethodParser.parseMethodInfo
|
|
||||||
import io.bkbn.kompendium.core.metadata.method.DeleteInfo
|
import io.bkbn.kompendium.core.metadata.method.DeleteInfo
|
||||||
import io.bkbn.kompendium.core.metadata.method.GetInfo
|
import io.bkbn.kompendium.core.metadata.method.GetInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.method.HeadInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.method.OptionsInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.method.PatchInfo
|
||||||
import io.bkbn.kompendium.core.metadata.method.PostInfo
|
import io.bkbn.kompendium.core.metadata.method.PostInfo
|
||||||
import io.bkbn.kompendium.core.metadata.method.PutInfo
|
import io.bkbn.kompendium.core.metadata.method.PutInfo
|
||||||
|
import io.bkbn.kompendium.core.parser.DefaultMethodParser.calculateRoutePath
|
||||||
|
import io.bkbn.kompendium.core.parser.DefaultMethodParser.parseMethodInfo
|
||||||
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
|
||||||
import io.ktor.application.ApplicationCall
|
import io.ktor.application.ApplicationCall
|
||||||
@ -66,7 +70,7 @@ object Notarized {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notarization for an HTTP Delete request
|
* Notarization for an HTTP PUT request
|
||||||
* @param TParam The class containing all parameter fields. Each field must be annotated with @[Param]
|
* @param TParam The class containing all parameter fields. Each field must be annotated with @[Param]
|
||||||
* @param TReq Class detailing the expected API request body
|
* @param TReq Class detailing the expected API request body
|
||||||
* @param TResp Class detailing the expected API response
|
* @param TResp Class detailing the expected API response
|
||||||
@ -87,7 +91,28 @@ object Notarized {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notarization for an HTTP POST request
|
* Notarization for an HTTP PATCH request
|
||||||
|
* @param TParam The class containing all parameter fields. Each field must be annotated with @[Param]
|
||||||
|
* @param TReq Class detailing the expected API request body
|
||||||
|
* @param TResp Class detailing the expected API response
|
||||||
|
* @param info Route metadata
|
||||||
|
* @param postProcess Adds an optional callback hook to perform manual overrides on the generated [PathOperation]
|
||||||
|
*/
|
||||||
|
inline fun <reified TParam : Any, reified TReq : Any, reified TResp : Any> Route.notarizedPatch(
|
||||||
|
info: PatchInfo<TParam, TReq, TResp>,
|
||||||
|
postProcess: (PathOperation) -> PathOperation = { p -> p },
|
||||||
|
noinline body: PipelineInterceptor<Unit, ApplicationCall>,
|
||||||
|
): Route = methodNotarizationPreFlight<TParam, TReq, TResp> { paramType, requestType, responseType ->
|
||||||
|
val feature = this.application.feature(Kompendium)
|
||||||
|
val path = calculateRoutePath()
|
||||||
|
feature.config.spec.paths.getOrPut(path) { Path() }
|
||||||
|
val baseInfo = parseMethodInfo(info, paramType, requestType, responseType, feature)
|
||||||
|
feature.config.spec.paths[path]?.patch = postProcess(baseInfo)
|
||||||
|
return method(HttpMethod.Patch) { handle(body) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notarization for an HTTP DELETE request
|
||||||
* @param TParam The class containing all parameter fields. Each field must be annotated with @[Param]
|
* @param TParam The class containing all parameter fields. Each field must be annotated with @[Param]
|
||||||
* @param TResp Class detailing the expected API response
|
* @param TResp Class detailing the expected API response
|
||||||
* @param info Route metadata
|
* @param info Route metadata
|
||||||
@ -107,8 +132,41 @@ object Notarized {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uses the built-in Ktor route path [Route.toString] but cuts out any meta route such as authentication... anything
|
* Notarization for an HTTP HEAD request
|
||||||
* that matches the RegEx pattern `/\\(.+\\)`
|
* @param TParam The class containing all parameter fields. Each field must be annotated with @[Param]
|
||||||
|
* @param info Route metadata
|
||||||
|
* @param postProcess Adds an optional callback hook to perform manual overrides on the generated [PathOperation]
|
||||||
*/
|
*/
|
||||||
fun Route.calculateRoutePath() = toString().replace(Regex("/\\(.+\\)"), "")
|
inline fun <reified TParam : Any> Route.notarizedHead(
|
||||||
|
info: HeadInfo<TParam>,
|
||||||
|
postProcess: (PathOperation) -> PathOperation = { p -> p },
|
||||||
|
noinline body: PipelineInterceptor<Unit, ApplicationCall>
|
||||||
|
): Route = methodNotarizationPreFlight<TParam, Unit, Unit> { paramType, requestType, responseType ->
|
||||||
|
val feature = this.application.feature(Kompendium)
|
||||||
|
val path = calculateRoutePath()
|
||||||
|
feature.config.spec.paths.getOrPut(path) { Path() }
|
||||||
|
val baseInfo = parseMethodInfo(info, paramType, requestType, responseType, feature)
|
||||||
|
feature.config.spec.paths[path]?.head = postProcess(baseInfo)
|
||||||
|
return method(HttpMethod.Head) { handle(body) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notarization for an HTTP OPTION request
|
||||||
|
* @param TParam The class containing all parameter fields. Each field must be annotated with @[Param]
|
||||||
|
* @param TResp Class detailing the expected API response
|
||||||
|
* @param info Route metadata
|
||||||
|
* @param postProcess Adds an optional callback hook to perform manual overrides on the generated [PathOperation]
|
||||||
|
*/
|
||||||
|
inline fun <reified TParam : Any, reified TResp : Any> Route.notarizedOptions(
|
||||||
|
info: OptionsInfo<TParam, TResp>,
|
||||||
|
postProcess: (PathOperation) -> PathOperation = { p -> p },
|
||||||
|
noinline body: PipelineInterceptor<Unit, ApplicationCall>
|
||||||
|
): Route = methodNotarizationPreFlight<TParam, Unit, TResp> { paramType, requestType, responseType ->
|
||||||
|
val feature = this.application.feature(Kompendium)
|
||||||
|
val path = calculateRoutePath()
|
||||||
|
feature.config.spec.paths.getOrPut(path) { Path() }
|
||||||
|
val baseInfo = parseMethodInfo(info, paramType, requestType, responseType, feature)
|
||||||
|
feature.config.spec.paths[path]?.options = postProcess(baseInfo)
|
||||||
|
return method(HttpMethod.Options) { handle(body) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
package io.bkbn.kompendium.core.metadata
|
||||||
|
|
||||||
|
data class ParameterExample(val parameterName: String, val exampleName: String, val exampleValue: Any)
|
@ -1,6 +1,7 @@
|
|||||||
package io.bkbn.kompendium.core.metadata.method
|
package io.bkbn.kompendium.core.metadata.method
|
||||||
|
|
||||||
import io.bkbn.kompendium.core.metadata.ExceptionInfo
|
import io.bkbn.kompendium.core.metadata.ExceptionInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.ParameterExample
|
||||||
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
||||||
|
|
||||||
data class DeleteInfo<TParam, TResp>(
|
data class DeleteInfo<TParam, TResp>(
|
||||||
@ -11,6 +12,6 @@ data class DeleteInfo<TParam, TResp>(
|
|||||||
override val deprecated: Boolean = false,
|
override val deprecated: Boolean = false,
|
||||||
override val securitySchemes: Set<String> = emptySet(),
|
override val securitySchemes: Set<String> = emptySet(),
|
||||||
override val canThrow: Set<ExceptionInfo<*>> = emptySet(),
|
override val canThrow: Set<ExceptionInfo<*>> = emptySet(),
|
||||||
override val parameterExamples: Map<String, TParam> = emptyMap(),
|
override val parameterExamples: Set<ParameterExample> = emptySet(),
|
||||||
override val operationId: String? = null
|
override val operationId: String? = null
|
||||||
) : MethodInfo<TParam, TResp>
|
) : MethodInfo<TParam, TResp>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package io.bkbn.kompendium.core.metadata.method
|
package io.bkbn.kompendium.core.metadata.method
|
||||||
|
|
||||||
import io.bkbn.kompendium.core.metadata.ExceptionInfo
|
import io.bkbn.kompendium.core.metadata.ExceptionInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.ParameterExample
|
||||||
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
||||||
|
|
||||||
data class GetInfo<TParam, TResp>(
|
data class GetInfo<TParam, TResp>(
|
||||||
@ -11,6 +12,6 @@ data class GetInfo<TParam, TResp>(
|
|||||||
override val deprecated: Boolean = false,
|
override val deprecated: Boolean = false,
|
||||||
override val securitySchemes: Set<String> = emptySet(),
|
override val securitySchemes: Set<String> = emptySet(),
|
||||||
override val canThrow: Set<ExceptionInfo<*>> = emptySet(),
|
override val canThrow: Set<ExceptionInfo<*>> = emptySet(),
|
||||||
override val parameterExamples: Map<String, TParam> = emptyMap(),
|
override val parameterExamples: Set<ParameterExample> = emptySet(),
|
||||||
override val operationId: String? = null
|
override val operationId: String? = null
|
||||||
) : MethodInfo<TParam, TResp>
|
) : MethodInfo<TParam, TResp>
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
package io.bkbn.kompendium.core.metadata.method
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.core.metadata.ExceptionInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.ParameterExample
|
||||||
|
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
||||||
|
|
||||||
|
data class HeadInfo<TParam>(
|
||||||
|
override val responseInfo: ResponseInfo<Unit>,
|
||||||
|
override val summary: String,
|
||||||
|
override val description: String? = null,
|
||||||
|
override val tags: Set<String> = emptySet(),
|
||||||
|
override val deprecated: Boolean = false,
|
||||||
|
override val securitySchemes: Set<String> = emptySet(),
|
||||||
|
override val canThrow: Set<ExceptionInfo<*>> = emptySet(),
|
||||||
|
override val parameterExamples: Set<ParameterExample> = emptySet(),
|
||||||
|
override val operationId: String? = null
|
||||||
|
) : MethodInfo<TParam, Unit>
|
@ -1,6 +1,7 @@
|
|||||||
package io.bkbn.kompendium.core.metadata.method
|
package io.bkbn.kompendium.core.metadata.method
|
||||||
|
|
||||||
import io.bkbn.kompendium.core.metadata.ExceptionInfo
|
import io.bkbn.kompendium.core.metadata.ExceptionInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.ParameterExample
|
||||||
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
||||||
|
|
||||||
sealed interface MethodInfo<TParam, TResp> {
|
sealed interface MethodInfo<TParam, TResp> {
|
||||||
@ -16,9 +17,8 @@ sealed interface MethodInfo<TParam, TResp> {
|
|||||||
val canThrow: Set<ExceptionInfo<*>>
|
val canThrow: Set<ExceptionInfo<*>>
|
||||||
get() = emptySet()
|
get() = emptySet()
|
||||||
val responseInfo: ResponseInfo<TResp>
|
val responseInfo: ResponseInfo<TResp>
|
||||||
// TODO Is this even used anywhere?
|
val parameterExamples: Set<ParameterExample>
|
||||||
val parameterExamples: Map<String, TParam>
|
get() = emptySet()
|
||||||
get() = emptyMap()
|
|
||||||
val operationId: String?
|
val operationId: String?
|
||||||
get() = null
|
get() = null
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
package io.bkbn.kompendium.core.metadata.method
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.core.metadata.ExceptionInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.ParameterExample
|
||||||
|
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
||||||
|
|
||||||
|
data class OptionsInfo<TParam, TResp>(
|
||||||
|
override val responseInfo: ResponseInfo<TResp>,
|
||||||
|
override val summary: String,
|
||||||
|
override val description: String? = null,
|
||||||
|
override val tags: Set<String> = emptySet(),
|
||||||
|
override val deprecated: Boolean = false,
|
||||||
|
override val securitySchemes: Set<String> = emptySet(),
|
||||||
|
override val canThrow: Set<ExceptionInfo<*>> = emptySet(),
|
||||||
|
override val parameterExamples: Set<ParameterExample> = emptySet(),
|
||||||
|
override val operationId: String? = null
|
||||||
|
) : MethodInfo<TParam, TResp>
|
@ -0,0 +1,19 @@
|
|||||||
|
package io.bkbn.kompendium.core.metadata.method
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.core.metadata.ExceptionInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.ParameterExample
|
||||||
|
import io.bkbn.kompendium.core.metadata.RequestInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
||||||
|
|
||||||
|
data class PatchInfo<TParam, TReq, TResp>(
|
||||||
|
val requestInfo: RequestInfo<TReq>?,
|
||||||
|
override val responseInfo: ResponseInfo<TResp>,
|
||||||
|
override val summary: String,
|
||||||
|
override val description: String? = null,
|
||||||
|
override val tags: Set<String> = emptySet(),
|
||||||
|
override val deprecated: Boolean = false,
|
||||||
|
override val securitySchemes: Set<String> = emptySet(),
|
||||||
|
override val canThrow: Set<ExceptionInfo<*>> = emptySet(),
|
||||||
|
override val parameterExamples: Set<ParameterExample> = emptySet(),
|
||||||
|
override val operationId: String? = null
|
||||||
|
) : MethodInfo<TParam, TResp>
|
@ -1,6 +1,7 @@
|
|||||||
package io.bkbn.kompendium.core.metadata.method
|
package io.bkbn.kompendium.core.metadata.method
|
||||||
|
|
||||||
import io.bkbn.kompendium.core.metadata.ExceptionInfo
|
import io.bkbn.kompendium.core.metadata.ExceptionInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.ParameterExample
|
||||||
import io.bkbn.kompendium.core.metadata.RequestInfo
|
import io.bkbn.kompendium.core.metadata.RequestInfo
|
||||||
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
||||||
|
|
||||||
@ -13,6 +14,6 @@ data class PostInfo<TParam, TReq, TResp>(
|
|||||||
override val deprecated: Boolean = false,
|
override val deprecated: Boolean = false,
|
||||||
override val securitySchemes: Set<String> = emptySet(),
|
override val securitySchemes: Set<String> = emptySet(),
|
||||||
override val canThrow: Set<ExceptionInfo<*>> = emptySet(),
|
override val canThrow: Set<ExceptionInfo<*>> = emptySet(),
|
||||||
override val parameterExamples: Map<String, TParam> = emptyMap(),
|
override val parameterExamples: Set<ParameterExample> = emptySet(),
|
||||||
override val operationId: String? = null
|
override val operationId: String? = null
|
||||||
) : MethodInfo<TParam, TResp>
|
) : MethodInfo<TParam, TResp>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package io.bkbn.kompendium.core.metadata.method
|
package io.bkbn.kompendium.core.metadata.method
|
||||||
|
|
||||||
import io.bkbn.kompendium.core.metadata.ExceptionInfo
|
import io.bkbn.kompendium.core.metadata.ExceptionInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.ParameterExample
|
||||||
import io.bkbn.kompendium.core.metadata.RequestInfo
|
import io.bkbn.kompendium.core.metadata.RequestInfo
|
||||||
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
||||||
|
|
||||||
@ -13,6 +14,6 @@ data class PutInfo<TParam, TReq, TResp>(
|
|||||||
override val deprecated: Boolean = false,
|
override val deprecated: Boolean = false,
|
||||||
override val securitySchemes: Set<String> = emptySet(),
|
override val securitySchemes: Set<String> = emptySet(),
|
||||||
override val canThrow: Set<ExceptionInfo<*>> = emptySet(),
|
override val canThrow: Set<ExceptionInfo<*>> = emptySet(),
|
||||||
override val parameterExamples: Map<String, TParam> = emptyMap(),
|
override val parameterExamples: Set<ParameterExample> = emptySet(),
|
||||||
override val operationId: String? = null
|
override val operationId: String? = null
|
||||||
) : MethodInfo<TParam, TResp>
|
) : MethodInfo<TParam, TResp>
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
package io.bkbn.kompendium.core.parser
|
||||||
|
|
||||||
|
object DefaultMethodParser : IMethodParser
|
@ -1,8 +1,10 @@
|
|||||||
package io.bkbn.kompendium.core
|
package io.bkbn.kompendium.core.parser
|
||||||
|
|
||||||
import io.bkbn.kompendium.annotations.Param
|
import io.bkbn.kompendium.annotations.Param
|
||||||
import io.bkbn.kompendium.core.Kontent.generateKontent
|
import io.bkbn.kompendium.core.Kompendium
|
||||||
|
import io.bkbn.kompendium.core.Kontent
|
||||||
import io.bkbn.kompendium.core.metadata.ExceptionInfo
|
import io.bkbn.kompendium.core.metadata.ExceptionInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.ParameterExample
|
||||||
import io.bkbn.kompendium.core.metadata.RequestInfo
|
import io.bkbn.kompendium.core.metadata.RequestInfo
|
||||||
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
||||||
import io.bkbn.kompendium.core.metadata.method.MethodInfo
|
import io.bkbn.kompendium.core.metadata.method.MethodInfo
|
||||||
@ -18,22 +20,20 @@ import io.bkbn.kompendium.oas.payload.Request
|
|||||||
import io.bkbn.kompendium.oas.payload.Response
|
import io.bkbn.kompendium.oas.payload.Response
|
||||||
import io.bkbn.kompendium.oas.schema.AnyOfSchema
|
import io.bkbn.kompendium.oas.schema.AnyOfSchema
|
||||||
import io.bkbn.kompendium.oas.schema.ObjectSchema
|
import io.bkbn.kompendium.oas.schema.ObjectSchema
|
||||||
|
import io.ktor.routing.Route
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
import kotlin.reflect.KParameter
|
import kotlin.reflect.KParameter
|
||||||
import kotlin.reflect.KProperty
|
import kotlin.reflect.KProperty
|
||||||
import kotlin.reflect.KType
|
import kotlin.reflect.KType
|
||||||
import kotlin.reflect.full.createType
|
import kotlin.reflect.full.createType
|
||||||
import kotlin.reflect.full.findAnnotation
|
import kotlin.reflect.full.findAnnotation
|
||||||
|
import kotlin.reflect.full.hasAnnotation
|
||||||
import kotlin.reflect.full.memberProperties
|
import kotlin.reflect.full.memberProperties
|
||||||
import kotlin.reflect.full.primaryConstructor
|
import kotlin.reflect.full.primaryConstructor
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
/**
|
interface IMethodParser {
|
||||||
* The MethodParser is responsible for converting route metadata and types into an OpenAPI compatible data class.
|
|
||||||
*/
|
|
||||||
object MethodParser {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates the OpenAPI Path spec from provided metadata
|
* Generates the OpenAPI Path spec from provided metadata
|
||||||
* @param info implementation of the [MethodInfo] sealed class
|
* @param info implementation of the [MethodInfo] sealed class
|
||||||
@ -54,7 +54,7 @@ object MethodParser {
|
|||||||
operationId = info.operationId,
|
operationId = info.operationId,
|
||||||
tags = info.tags,
|
tags = info.tags,
|
||||||
deprecated = info.deprecated,
|
deprecated = info.deprecated,
|
||||||
parameters = paramType.toParameterSpec(feature),
|
parameters = paramType.toParameterSpec(info, feature),
|
||||||
responses = parseResponse(responseType, info.responseInfo, feature).plus(parseExceptions(info.canThrow, feature)),
|
responses = parseResponse(responseType, info.responseInfo, feature).plus(parseExceptions(info.canThrow, feature)),
|
||||||
requestBody = when (info) {
|
requestBody = when (info) {
|
||||||
is PutInfo<*, *, *> -> requestType.toRequestSpec(info.requestInfo, feature)
|
is PutInfo<*, *, *> -> requestType.toRequestSpec(info.requestInfo, feature)
|
||||||
@ -67,17 +67,17 @@ object MethodParser {
|
|||||||
) else null
|
) else null
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun parseResponse(
|
fun parseResponse(
|
||||||
responseType: KType,
|
responseType: KType,
|
||||||
responseInfo: ResponseInfo<*>?,
|
responseInfo: ResponseInfo<*>?,
|
||||||
feature: Kompendium
|
feature: Kompendium
|
||||||
): Map<Int, Response<*>> = responseType.toResponseSpec(responseInfo, feature)?.let { mapOf(it) }.orEmpty()
|
): Map<Int, Response> = responseType.toResponseSpec(responseInfo, feature)?.let { mapOf(it) }.orEmpty()
|
||||||
|
|
||||||
private fun parseExceptions(
|
fun parseExceptions(
|
||||||
exceptionInfo: Set<ExceptionInfo<*>>,
|
exceptionInfo: Set<ExceptionInfo<*>>,
|
||||||
feature: Kompendium,
|
feature: Kompendium,
|
||||||
): Map<Int, Response<*>> = exceptionInfo.associate { info ->
|
): Map<Int, Response> = exceptionInfo.associate { info ->
|
||||||
feature.config.cache = generateKontent(info.responseType, feature.config.cache)
|
feature.config.cache = Kontent.generateKontent(info.responseType, feature.config.cache)
|
||||||
val response = Response(
|
val response = Response(
|
||||||
description = info.description,
|
description = info.description,
|
||||||
content = feature.resolveContent(info.responseType, info.mediaTypes, info.examples)
|
content = feature.resolveContent(info.responseType, info.mediaTypes, info.examples)
|
||||||
@ -91,13 +91,14 @@ object MethodParser {
|
|||||||
* @param requestInfo request metadata
|
* @param requestInfo request metadata
|
||||||
* @return Will return a generated [Request] if requestInfo is not null
|
* @return Will return a generated [Request] if requestInfo is not null
|
||||||
*/
|
*/
|
||||||
private fun KType.toRequestSpec(requestInfo: RequestInfo<*>?, feature: Kompendium): Request<*>? =
|
fun KType.toRequestSpec(requestInfo: RequestInfo<*>?, feature: Kompendium): Request? =
|
||||||
when (requestInfo) {
|
when (requestInfo) {
|
||||||
null -> null
|
null -> null
|
||||||
else -> {
|
else -> {
|
||||||
Request(
|
Request(
|
||||||
description = requestInfo.description,
|
description = requestInfo.description,
|
||||||
content = feature.resolveContent(this, requestInfo.mediaTypes, requestInfo.examples) ?: mapOf(),
|
content = feature.resolveContent(this, requestInfo.mediaTypes, requestInfo.examples as Map<String, Any>)
|
||||||
|
?: mapOf(),
|
||||||
required = requestInfo.required
|
required = requestInfo.required
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -109,13 +110,13 @@ object MethodParser {
|
|||||||
* @param responseInfo response metadata
|
* @param responseInfo response metadata
|
||||||
* @return Will return a generated [Pair] if responseInfo is not null
|
* @return Will return a generated [Pair] if responseInfo is not null
|
||||||
*/
|
*/
|
||||||
private fun KType.toResponseSpec(responseInfo: ResponseInfo<*>?, feature: Kompendium): Pair<Int, Response<*>>? =
|
fun KType.toResponseSpec(responseInfo: ResponseInfo<*>?, feature: Kompendium): Pair<Int, Response>? =
|
||||||
when (responseInfo) {
|
when (responseInfo) {
|
||||||
null -> null
|
null -> null
|
||||||
else -> {
|
else -> {
|
||||||
val specResponse = Response(
|
val specResponse = Response(
|
||||||
description = responseInfo.description,
|
description = responseInfo.description,
|
||||||
content = feature.resolveContent(this, responseInfo.mediaTypes, responseInfo.examples)
|
content = feature.resolveContent(this, responseInfo.mediaTypes, responseInfo.examples as Map<String, Any>)
|
||||||
)
|
)
|
||||||
Pair(responseInfo.status.value, specResponse)
|
Pair(responseInfo.status.value, specResponse)
|
||||||
}
|
}
|
||||||
@ -128,11 +129,11 @@ object MethodParser {
|
|||||||
* @param examples Mapping of named examples of valid bodies.
|
* @param examples Mapping of named examples of valid bodies.
|
||||||
* @return Named mapping of media types.
|
* @return Named mapping of media types.
|
||||||
*/
|
*/
|
||||||
private fun <F> Kompendium.resolveContent(
|
fun Kompendium.resolveContent(
|
||||||
type: KType,
|
type: KType,
|
||||||
mediaTypes: List<String>,
|
mediaTypes: List<String>,
|
||||||
examples: Map<String, F>
|
examples: Map<String, Any>
|
||||||
): Map<String, MediaType<F>>? {
|
): Map<String, MediaType>? {
|
||||||
val classifier = type.classifier as KClass<*>
|
val classifier = type.classifier as KClass<*>
|
||||||
return if (type != Helpers.UNIT_TYPE && mediaTypes.isNotEmpty()) {
|
return if (type != Helpers.UNIT_TYPE && mediaTypes.isNotEmpty()) {
|
||||||
mediaTypes.associateWith {
|
mediaTypes.associateWith {
|
||||||
@ -161,24 +162,41 @@ object MethodParser {
|
|||||||
* @return list of valid parameter specs as detailed by the [KType] members
|
* @return list of valid parameter specs as detailed by the [KType] members
|
||||||
* @throws [IllegalStateException] if the class could not be parsed properly
|
* @throws [IllegalStateException] if the class could not be parsed properly
|
||||||
*/
|
*/
|
||||||
private fun KType.toParameterSpec(feature: Kompendium): List<Parameter> {
|
fun KType.toParameterSpec(info: MethodInfo<*, *>, feature: Kompendium): List<Parameter> {
|
||||||
val clazz = classifier as KClass<*>
|
val clazz = classifier as KClass<*>
|
||||||
return clazz.memberProperties.filter { prop ->
|
return clazz.memberProperties
|
||||||
prop.findAnnotation<Param>() != null
|
.filter { prop -> prop.hasAnnotation<Param>() }
|
||||||
}.map { prop ->
|
.map { prop -> prop.toParameter(info, this, clazz, feature) }
|
||||||
val wrapperSchema = feature.config.cache[this.getSimpleSlug()]!! as ObjectSchema
|
}
|
||||||
val anny = prop.findAnnotation<Param>()
|
|
||||||
?: error("Field ${prop.name} is not annotated with KompendiumParam")
|
fun KProperty<*>.toParameter(
|
||||||
val schema = wrapperSchema.properties[prop.name]
|
info: MethodInfo<*, *>,
|
||||||
?: error("Could not find component type for $prop")
|
parentType: KType,
|
||||||
val defaultValue = getDefaultParameterValue(clazz, prop)
|
parentClazz: KClass<*>,
|
||||||
Parameter(
|
feature: Kompendium
|
||||||
name = prop.name,
|
): Parameter {
|
||||||
`in` = anny.type.name.lowercase(Locale.getDefault()),
|
val wrapperSchema = feature.config.cache[parentType.getSimpleSlug()]!! as ObjectSchema
|
||||||
schema = schema.addDefault(defaultValue),
|
val anny = this.findAnnotation<Param>()
|
||||||
description = schema.description,
|
?: error("Field $name is not annotated with KompendiumParam")
|
||||||
required = !prop.returnType.isMarkedNullable && defaultValue == null
|
val schema = wrapperSchema.properties[name]
|
||||||
)
|
?: error("Could not find component type for $this")
|
||||||
|
val defaultValue = getDefaultParameterValue(parentClazz, this)
|
||||||
|
return Parameter(
|
||||||
|
name = name,
|
||||||
|
`in` = anny.type.name.lowercase(Locale.getDefault()),
|
||||||
|
schema = schema.addDefault(defaultValue),
|
||||||
|
description = schema.description,
|
||||||
|
required = !returnType.isMarkedNullable && defaultValue == null,
|
||||||
|
examples = info.parameterExamples.mapToSpec(name)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Set<ParameterExample>.mapToSpec(parameterName: String): Map<String, Parameter.Example>? {
|
||||||
|
val filtered = filter { it.parameterName == parameterName }
|
||||||
|
return if (filtered.isEmpty()) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
filtered.associate { it.exampleName to Parameter.Example(it.exampleValue) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,7 +206,7 @@ object MethodParser {
|
|||||||
* @param prop the property in question
|
* @param prop the property in question
|
||||||
* @return The default value if found
|
* @return The default value if found
|
||||||
*/
|
*/
|
||||||
private fun getDefaultParameterValue(clazz: KClass<*>, prop: KProperty<*>): Any? {
|
fun getDefaultParameterValue(clazz: KClass<*>, prop: KProperty<*>): Any? {
|
||||||
val constructor = clazz.primaryConstructor
|
val constructor = clazz.primaryConstructor
|
||||||
val parameterInQuestion = constructor
|
val parameterInQuestion = constructor
|
||||||
?.parameters
|
?.parameters
|
||||||
@ -215,7 +233,7 @@ object MethodParser {
|
|||||||
* @return value of the proper type to match param
|
* @return value of the proper type to match param
|
||||||
* @throws [IllegalStateException] if parameter type is not one of the basic types supported below.
|
* @throws [IllegalStateException] if parameter type is not one of the basic types supported below.
|
||||||
*/
|
*/
|
||||||
private fun defaultValueInjector(param: KParameter): Any = when (param.type.classifier) {
|
fun defaultValueInjector(param: KParameter): Any = when (param.type.classifier) {
|
||||||
String::class -> "test"
|
String::class -> "test"
|
||||||
Boolean::class -> false
|
Boolean::class -> false
|
||||||
Int::class -> 1
|
Int::class -> 1
|
||||||
@ -225,4 +243,10 @@ object MethodParser {
|
|||||||
UUID::class -> UUID.randomUUID()
|
UUID::class -> UUID.randomUUID()
|
||||||
else -> error("Unsupported Type")
|
else -> error("Unsupported Type")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses the built-in Ktor route path [Route.toString] but cuts out any meta route such as authentication... anything
|
||||||
|
* that matches the RegEx pattern `/\\(.+\\)`
|
||||||
|
*/
|
||||||
|
fun Route.calculateRoutePath() = toString().replace(Regex("/\\(.+\\)"), "")
|
||||||
}
|
}
|
@ -8,6 +8,7 @@ import io.bkbn.kompendium.core.util.constrainedDoubleInfo
|
|||||||
import io.bkbn.kompendium.core.util.constrainedIntInfo
|
import io.bkbn.kompendium.core.util.constrainedIntInfo
|
||||||
import io.bkbn.kompendium.core.util.defaultField
|
import io.bkbn.kompendium.core.util.defaultField
|
||||||
import io.bkbn.kompendium.core.util.defaultParameter
|
import io.bkbn.kompendium.core.util.defaultParameter
|
||||||
|
import io.bkbn.kompendium.core.util.exampleParams
|
||||||
import io.bkbn.kompendium.core.util.exclusiveMinMax
|
import io.bkbn.kompendium.core.util.exclusiveMinMax
|
||||||
import io.bkbn.kompendium.core.util.formattedParam
|
import io.bkbn.kompendium.core.util.formattedParam
|
||||||
import io.bkbn.kompendium.core.util.freeFormObject
|
import io.bkbn.kompendium.core.util.freeFormObject
|
||||||
@ -27,6 +28,9 @@ import io.bkbn.kompendium.core.util.notarizedGetWithGenericErrorResponse
|
|||||||
import io.bkbn.kompendium.core.util.notarizedGetWithMultipleThrowables
|
import io.bkbn.kompendium.core.util.notarizedGetWithMultipleThrowables
|
||||||
import io.bkbn.kompendium.core.util.notarizedGetWithNotarizedException
|
import io.bkbn.kompendium.core.util.notarizedGetWithNotarizedException
|
||||||
import io.bkbn.kompendium.core.util.notarizedGetWithPolymorphicErrorResponse
|
import io.bkbn.kompendium.core.util.notarizedGetWithPolymorphicErrorResponse
|
||||||
|
import io.bkbn.kompendium.core.util.notarizedHeadModule
|
||||||
|
import io.bkbn.kompendium.core.util.notarizedOptionsModule
|
||||||
|
import io.bkbn.kompendium.core.util.notarizedPatchModule
|
||||||
import io.bkbn.kompendium.core.util.notarizedPostModule
|
import io.bkbn.kompendium.core.util.notarizedPostModule
|
||||||
import io.bkbn.kompendium.core.util.notarizedPutModule
|
import io.bkbn.kompendium.core.util.notarizedPutModule
|
||||||
import io.bkbn.kompendium.core.util.nullableField
|
import io.bkbn.kompendium.core.util.nullableField
|
||||||
@ -55,56 +59,53 @@ import io.ktor.http.HttpStatusCode
|
|||||||
class KompendiumTest : DescribeSpec({
|
class KompendiumTest : DescribeSpec({
|
||||||
describe("Notarized Open API Metadata Tests") {
|
describe("Notarized Open API Metadata Tests") {
|
||||||
it("Can notarize a get request") {
|
it("Can notarize a get request") {
|
||||||
// act
|
|
||||||
openApiTest("notarized_get.json") { notarizedGetModule() }
|
openApiTest("notarized_get.json") { notarizedGetModule() }
|
||||||
}
|
}
|
||||||
it("Can notarize a post request") {
|
it("Can notarize a post request") {
|
||||||
// act
|
|
||||||
openApiTest("notarized_post.json") { notarizedPostModule() }
|
openApiTest("notarized_post.json") { notarizedPostModule() }
|
||||||
}
|
}
|
||||||
it("Can notarize a put request") {
|
it("Can notarize a put request") {
|
||||||
// act
|
|
||||||
openApiTest("notarized_put.json") { notarizedPutModule() }
|
openApiTest("notarized_put.json") { notarizedPutModule() }
|
||||||
}
|
}
|
||||||
it("Can notarize a delete request") {
|
it("Can notarize a delete request") {
|
||||||
// act
|
|
||||||
openApiTest("notarized_delete.json") { notarizedDeleteModule() }
|
openApiTest("notarized_delete.json") { notarizedDeleteModule() }
|
||||||
}
|
}
|
||||||
|
it("Can notarize a patch request") {
|
||||||
|
openApiTest("notarized_patch.json") { notarizedPatchModule() }
|
||||||
|
}
|
||||||
|
it("Can notarize a head request") {
|
||||||
|
openApiTest("notarized_head.json") { notarizedHeadModule() }
|
||||||
|
}
|
||||||
|
it("Can notarize an options request") {
|
||||||
|
openApiTest("notarized_options.json") { notarizedOptionsModule() }
|
||||||
|
}
|
||||||
it("Can notarize a complex type") {
|
it("Can notarize a complex type") {
|
||||||
// act
|
|
||||||
openApiTest("complex_type.json") { complexType() }
|
openApiTest("complex_type.json") { complexType() }
|
||||||
}
|
}
|
||||||
it("Can notarize primitives") {
|
it("Can notarize primitives") {
|
||||||
// act
|
|
||||||
openApiTest("notarized_primitives.json") { primitives() }
|
openApiTest("notarized_primitives.json") { primitives() }
|
||||||
}
|
}
|
||||||
it("Can notarize a top level list response") {
|
it("Can notarize a top level list response") {
|
||||||
// act
|
|
||||||
openApiTest("response_list.json") { returnsList() }
|
openApiTest("response_list.json") { returnsList() }
|
||||||
}
|
}
|
||||||
it("Can notarize a route with non-required params") {
|
it("Can notarize a route with non-required params") {
|
||||||
// act
|
|
||||||
openApiTest("non_required_params.json") { nonRequiredParamsGet() }
|
openApiTest("non_required_params.json") { nonRequiredParamsGet() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
describe("Notarized Ktor Functionality Tests") {
|
describe("Notarized Ktor Functionality Tests") {
|
||||||
it("Can notarized a get request and return the expected result") {
|
it("Can notarized a get request and return the expected result") {
|
||||||
// act
|
|
||||||
apiFunctionalityTest("hey dude ‼️ congratz on the get request") { notarizedGetModule() }
|
apiFunctionalityTest("hey dude ‼️ congratz on the get request") { notarizedGetModule() }
|
||||||
}
|
}
|
||||||
it("Can notarize a post request and return the expected result") {
|
it("Can notarize a post request and return the expected result") {
|
||||||
// act
|
|
||||||
apiFunctionalityTest(
|
apiFunctionalityTest(
|
||||||
"hey dude ✌️ congratz on the post request",
|
"hey dude ✌️ congratz on the post request",
|
||||||
httpMethod = HttpMethod.Post
|
httpMethod = HttpMethod.Post
|
||||||
) { notarizedPostModule() }
|
) { notarizedPostModule() }
|
||||||
}
|
}
|
||||||
it("Can notarize a put request and return the expected result") {
|
it("Can notarize a put request and return the expected result") {
|
||||||
// act
|
|
||||||
apiFunctionalityTest("hey pal 🌝 whatcha doin' here?", httpMethod = HttpMethod.Put) { notarizedPutModule() }
|
apiFunctionalityTest("hey pal 🌝 whatcha doin' here?", httpMethod = HttpMethod.Put) { notarizedPutModule() }
|
||||||
}
|
}
|
||||||
it("Can notarize a delete request and return the expected result") {
|
it("Can notarize a delete request and return the expected result") {
|
||||||
// act
|
|
||||||
apiFunctionalityTest(
|
apiFunctionalityTest(
|
||||||
null,
|
null,
|
||||||
httpMethod = HttpMethod.Delete,
|
httpMethod = HttpMethod.Delete,
|
||||||
@ -112,59 +113,50 @@ class KompendiumTest : DescribeSpec({
|
|||||||
) { notarizedDeleteModule() }
|
) { notarizedDeleteModule() }
|
||||||
}
|
}
|
||||||
it("Can notarize the root route and return the expected result") {
|
it("Can notarize the root route and return the expected result") {
|
||||||
// act
|
|
||||||
apiFunctionalityTest("☎️🏠🌲", "/") { rootModule() }
|
apiFunctionalityTest("☎️🏠🌲", "/") { rootModule() }
|
||||||
}
|
}
|
||||||
it("Can notarize a trailing slash route and return the expected result") {
|
it("Can notarize a trailing slash route and return the expected result") {
|
||||||
// act
|
|
||||||
apiFunctionalityTest("🙀👾", "/test/") { trailingSlash() }
|
apiFunctionalityTest("🙀👾", "/test/") { trailingSlash() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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") {
|
||||||
// act
|
|
||||||
openApiTest("path_parser.json") { pathParsingTestModule() }
|
openApiTest("path_parser.json") { pathParsingTestModule() }
|
||||||
}
|
}
|
||||||
it("Can notarize the root route") {
|
it("Can notarize the root route") {
|
||||||
// act
|
|
||||||
openApiTest("root_route.json") { rootModule() }
|
openApiTest("root_route.json") { rootModule() }
|
||||||
}
|
}
|
||||||
it("Can notarize a route under the root module without appending trailing slash") {
|
it("Can notarize a route under the root module without appending trailing slash") {
|
||||||
// act
|
|
||||||
openApiTest("nested_under_root.json") { nestedUnderRootModule() }
|
openApiTest("nested_under_root.json") { nestedUnderRootModule() }
|
||||||
}
|
}
|
||||||
it("Can notarize a route with a trailing slash") {
|
it("Can notarize a route with a trailing slash") {
|
||||||
// act
|
|
||||||
openApiTest("trailing_slash.json") { trailingSlash() }
|
openApiTest("trailing_slash.json") { trailingSlash() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
describe("Exceptions") {
|
describe("Exceptions") {
|
||||||
it("Can add an exception status code to a response") {
|
it("Can add an exception status code to a response") {
|
||||||
// act
|
|
||||||
openApiTest("notarized_get_with_exception_response.json") { notarizedGetWithNotarizedException() }
|
openApiTest("notarized_get_with_exception_response.json") { notarizedGetWithNotarizedException() }
|
||||||
}
|
}
|
||||||
it("Can support multiple response codes") {
|
it("Can support multiple response codes") {
|
||||||
// act
|
|
||||||
openApiTest("notarized_get_with_multiple_exception_responses.json") { notarizedGetWithMultipleThrowables() }
|
openApiTest("notarized_get_with_multiple_exception_responses.json") { notarizedGetWithMultipleThrowables() }
|
||||||
}
|
}
|
||||||
it("Can add a polymorphic exception response") {
|
it("Can add a polymorphic exception response") {
|
||||||
// act
|
|
||||||
openApiTest("polymorphic_error_status_codes.json") { notarizedGetWithPolymorphicErrorResponse() }
|
openApiTest("polymorphic_error_status_codes.json") { notarizedGetWithPolymorphicErrorResponse() }
|
||||||
}
|
}
|
||||||
it("Can add a generic exception response") {
|
it("Can add a generic exception response") {
|
||||||
// act
|
|
||||||
openApiTest("generic_exception.json") { notarizedGetWithGenericErrorResponse() }
|
openApiTest("generic_exception.json") { notarizedGetWithGenericErrorResponse() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
describe("Examples") {
|
describe("Examples") {
|
||||||
it("Can generate example response and request bodies") {
|
it("Can generate example response and request bodies") {
|
||||||
// act
|
|
||||||
openApiTest("example_req_and_resp.json") { withExamples() }
|
openApiTest("example_req_and_resp.json") { withExamples() }
|
||||||
}
|
}
|
||||||
|
it("Can describe example parameters") {
|
||||||
|
openApiTest("example_parameters.json") { exampleParams() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
describe("Defaults") {
|
describe("Defaults") {
|
||||||
it("Can generate a default parameter values") {
|
it("Can generate a default parameter values") {
|
||||||
// act
|
|
||||||
openApiTest("query_with_default_parameter.json") { withDefaultParameter() }
|
openApiTest("query_with_default_parameter.json") { withDefaultParameter() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -184,49 +176,38 @@ class KompendiumTest : DescribeSpec({
|
|||||||
}
|
}
|
||||||
describe("Polymorphism and Generics") {
|
describe("Polymorphism and Generics") {
|
||||||
it("can generate a polymorphic response type") {
|
it("can generate a polymorphic response type") {
|
||||||
// act
|
|
||||||
openApiTest("polymorphic_response.json") { polymorphicResponse() }
|
openApiTest("polymorphic_response.json") { polymorphicResponse() }
|
||||||
}
|
}
|
||||||
it("Can generate a collection with polymorphic response type") {
|
it("Can generate a collection with polymorphic response type") {
|
||||||
// act
|
|
||||||
openApiTest("polymorphic_list_response.json") { polymorphicCollectionResponse() }
|
openApiTest("polymorphic_list_response.json") { polymorphicCollectionResponse() }
|
||||||
}
|
}
|
||||||
it("Can generate a map with a polymorphic response type") {
|
it("Can generate a map with a polymorphic response type") {
|
||||||
// act
|
|
||||||
openApiTest("polymorphic_map_response.json") { polymorphicMapResponse() }
|
openApiTest("polymorphic_map_response.json") { polymorphicMapResponse() }
|
||||||
}
|
}
|
||||||
it("Can generate a polymorphic response from a sealed interface") {
|
it("Can generate a polymorphic response from a sealed interface") {
|
||||||
// act
|
|
||||||
openApiTest("sealed_interface_response.json") { polymorphicInterfaceResponse() }
|
openApiTest("sealed_interface_response.json") { polymorphicInterfaceResponse() }
|
||||||
}
|
}
|
||||||
it("Can generate a response type with a generic type") {
|
it("Can generate a response type with a generic type") {
|
||||||
// act
|
|
||||||
openApiTest("generic_response.json") { simpleGenericResponse() }
|
openApiTest("generic_response.json") { simpleGenericResponse() }
|
||||||
}
|
}
|
||||||
it("Can generate a polymorphic response type with generics") {
|
it("Can generate a polymorphic response type with generics") {
|
||||||
// act
|
|
||||||
openApiTest("polymorphic_response_with_generics.json") { genericPolymorphicResponse() }
|
openApiTest("polymorphic_response_with_generics.json") { genericPolymorphicResponse() }
|
||||||
}
|
}
|
||||||
it("Can handle an absolutely psycho inheritance test") {
|
it("Can handle an absolutely psycho inheritance test") {
|
||||||
// act
|
|
||||||
openApiTest("crazy_polymorphic_example.json") { genericPolymorphicResponseMultipleImpls() }
|
openApiTest("crazy_polymorphic_example.json") { genericPolymorphicResponseMultipleImpls() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
describe("Miscellaneous") {
|
describe("Miscellaneous") {
|
||||||
it("Can generate the necessary ReDoc home page") {
|
it("Can generate the necessary ReDoc home page") {
|
||||||
// act
|
|
||||||
apiFunctionalityTest(getFileSnapshot("redoc.html"), "/docs") { returnsList() }
|
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") {
|
||||||
// act
|
|
||||||
openApiTest("notarized_get_with_operation_id.json") { withOperationId() }
|
openApiTest("notarized_get_with_operation_id.json") { withOperationId() }
|
||||||
}
|
}
|
||||||
it("Can add an undeclared field") {
|
it("Can add an undeclared field") {
|
||||||
// act
|
|
||||||
openApiTest("undeclared_field.json") { undeclaredType() }
|
openApiTest("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") {
|
||||||
// act
|
|
||||||
openApiTest("override_parameter_name.json") { headerParameter() }
|
openApiTest("override_parameter_name.json") { headerParameter() }
|
||||||
}
|
}
|
||||||
it("Can override field values via annotation") {
|
it("Can override field values via annotation") {
|
||||||
|
@ -2,6 +2,9 @@ package io.bkbn.kompendium.core.util
|
|||||||
|
|
||||||
import io.bkbn.kompendium.core.Notarized.notarizedDelete
|
import io.bkbn.kompendium.core.Notarized.notarizedDelete
|
||||||
import io.bkbn.kompendium.core.Notarized.notarizedGet
|
import io.bkbn.kompendium.core.Notarized.notarizedGet
|
||||||
|
import io.bkbn.kompendium.core.Notarized.notarizedHead
|
||||||
|
import io.bkbn.kompendium.core.Notarized.notarizedOptions
|
||||||
|
import io.bkbn.kompendium.core.Notarized.notarizedPatch
|
||||||
import io.bkbn.kompendium.core.Notarized.notarizedPost
|
import io.bkbn.kompendium.core.Notarized.notarizedPost
|
||||||
import io.bkbn.kompendium.core.Notarized.notarizedPut
|
import io.bkbn.kompendium.core.Notarized.notarizedPut
|
||||||
import io.bkbn.kompendium.core.fixtures.Bibbity
|
import io.bkbn.kompendium.core.fixtures.Bibbity
|
||||||
@ -105,6 +108,36 @@ fun Application.notarizedDeleteModule() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Application.notarizedPatchModule() {
|
||||||
|
routing {
|
||||||
|
route("/test") {
|
||||||
|
notarizedPatch(TestResponseInfo.testPatchInfo) {
|
||||||
|
call.respondText { "hey dude ✌️ congratz on the patch request" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Application.notarizedHeadModule() {
|
||||||
|
routing {
|
||||||
|
route("/test") {
|
||||||
|
notarizedHead(TestResponseInfo.testHeadInfo) {
|
||||||
|
call.response.status(HttpStatusCode.OK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Application.notarizedOptionsModule() {
|
||||||
|
routing {
|
||||||
|
route("/test") {
|
||||||
|
notarizedOptions(TestResponseInfo.testOptionsInfo) {
|
||||||
|
call.response.status(HttpStatusCode.OK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun Application.notarizedPutModule() {
|
fun Application.notarizedPutModule() {
|
||||||
routing {
|
routing {
|
||||||
route("/test") {
|
route("/test") {
|
||||||
@ -245,12 +278,12 @@ fun Application.withDefaultParameter() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Application.withOperationId(){
|
fun Application.withOperationId() {
|
||||||
routing {
|
routing {
|
||||||
route("/test") {
|
route("/test") {
|
||||||
notarizedGet(
|
notarizedGet(
|
||||||
info = TestResponseInfo.testGetInfo.copy(operationId = "getTest")
|
info = TestResponseInfo.testGetInfo.copy(operationId = "getTest")
|
||||||
){
|
) {
|
||||||
call.respond(HttpStatusCode.OK)
|
call.respond(HttpStatusCode.OK)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -531,3 +564,13 @@ fun Application.minMaxFreeForm() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Application.exampleParams() {
|
||||||
|
routing {
|
||||||
|
route("/test/{a}") {
|
||||||
|
notarizedGet(TestResponseInfo.exampleParams) {
|
||||||
|
call.respondText { "Hi 🌊" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -80,7 +80,7 @@
|
|||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"org",
|
"org",
|
||||||
"amazingField",
|
"amazing_field",
|
||||||
"tables"
|
"tables"
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
|
97
kompendium-core/src/test/resources/example_parameters.json
Normal file
97
kompendium-core/src/test/resources/example_parameters.json
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.0.3",
|
||||||
|
"info": {
|
||||||
|
"title": "Test API",
|
||||||
|
"version": "1.33.7",
|
||||||
|
"description": "An amazing, fully-ish 😉 generated API spec",
|
||||||
|
"termsOfService": "https://example.com",
|
||||||
|
"contact": {
|
||||||
|
"name": "Homer Simpson",
|
||||||
|
"url": "https://gph.is/1NPUDiM",
|
||||||
|
"email": "chunkylover53@aol.com"
|
||||||
|
},
|
||||||
|
"license": {
|
||||||
|
"name": "MIT",
|
||||||
|
"url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"url": "https://myawesomeapi.com",
|
||||||
|
"description": "Production instance of my API"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://staging.myawesomeapi.com",
|
||||||
|
"description": "Where the fun stuff happens"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"/test/{a}": {
|
||||||
|
"get": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "param stuff",
|
||||||
|
"description": "Cool stuff",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "a",
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"deprecated": false,
|
||||||
|
"examples": {
|
||||||
|
"Testerino": {
|
||||||
|
"value": "a"
|
||||||
|
},
|
||||||
|
"Testerina": {
|
||||||
|
"value": "b"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aa",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"format": "int32",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"deprecated": false,
|
||||||
|
"examples": {
|
||||||
|
"Wowza": {
|
||||||
|
"value": 6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A successful endeavor",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"properties": {
|
||||||
|
"c": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"c"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"securitySchemes": {}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
@ -62,7 +62,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"fieldName",
|
"field_name",
|
||||||
"b",
|
"b",
|
||||||
"aaa"
|
"aaa"
|
||||||
],
|
],
|
||||||
|
@ -45,7 +45,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"b"
|
"real_name"
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
}
|
}
|
||||||
|
49
kompendium-core/src/test/resources/notarized_head.json
Normal file
49
kompendium-core/src/test/resources/notarized_head.json
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.0.3",
|
||||||
|
"info": {
|
||||||
|
"title": "Test API",
|
||||||
|
"version": "1.33.7",
|
||||||
|
"description": "An amazing, fully-ish 😉 generated API spec",
|
||||||
|
"termsOfService": "https://example.com",
|
||||||
|
"contact": {
|
||||||
|
"name": "Homer Simpson",
|
||||||
|
"url": "https://gph.is/1NPUDiM",
|
||||||
|
"email": "chunkylover53@aol.com"
|
||||||
|
},
|
||||||
|
"license": {
|
||||||
|
"name": "MIT",
|
||||||
|
"url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"url": "https://myawesomeapi.com",
|
||||||
|
"description": "Production instance of my API"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://staging.myawesomeapi.com",
|
||||||
|
"description": "Where the fun stuff happens"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"/test": {
|
||||||
|
"head": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Test head endpoint",
|
||||||
|
"description": "head test 💀",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "great!"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"securitySchemes": {}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
84
kompendium-core/src/test/resources/notarized_options.json
Normal file
84
kompendium-core/src/test/resources/notarized_options.json
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.0.3",
|
||||||
|
"info": {
|
||||||
|
"title": "Test API",
|
||||||
|
"version": "1.33.7",
|
||||||
|
"description": "An amazing, fully-ish 😉 generated API spec",
|
||||||
|
"termsOfService": "https://example.com",
|
||||||
|
"contact": {
|
||||||
|
"name": "Homer Simpson",
|
||||||
|
"url": "https://gph.is/1NPUDiM",
|
||||||
|
"email": "chunkylover53@aol.com"
|
||||||
|
},
|
||||||
|
"license": {
|
||||||
|
"name": "MIT",
|
||||||
|
"url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"url": "https://myawesomeapi.com",
|
||||||
|
"description": "Production instance of my API"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://staging.myawesomeapi.com",
|
||||||
|
"description": "Where the fun stuff happens"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"/test": {
|
||||||
|
"options": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Test options",
|
||||||
|
"description": "endpoint of options",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "a",
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aa",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"format": "int32",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"deprecated": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "nice",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"properties": {
|
||||||
|
"c": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"c"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"securitySchemes": {}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
64
kompendium-core/src/test/resources/notarized_patch.json
Normal file
64
kompendium-core/src/test/resources/notarized_patch.json
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.0.3",
|
||||||
|
"info": {
|
||||||
|
"title": "Test API",
|
||||||
|
"version": "1.33.7",
|
||||||
|
"description": "An amazing, fully-ish 😉 generated API spec",
|
||||||
|
"termsOfService": "https://example.com",
|
||||||
|
"contact": {
|
||||||
|
"name": "Homer Simpson",
|
||||||
|
"url": "https://gph.is/1NPUDiM",
|
||||||
|
"email": "chunkylover53@aol.com"
|
||||||
|
},
|
||||||
|
"license": {
|
||||||
|
"name": "MIT",
|
||||||
|
"url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"url": "https://myawesomeapi.com",
|
||||||
|
"description": "Production instance of my API"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://staging.myawesomeapi.com",
|
||||||
|
"description": "Where the fun stuff happens"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"/test": {
|
||||||
|
"patch": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Test patch endpoint",
|
||||||
|
"description": "patch your tests here!",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "A Successful Endeavor",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"properties": {
|
||||||
|
"c": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"c"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"securitySchemes": {}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
@ -82,7 +82,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"fieldName",
|
"field_name",
|
||||||
"b",
|
"b",
|
||||||
"aaa"
|
"aaa"
|
||||||
],
|
],
|
||||||
|
@ -82,7 +82,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"fieldName",
|
"field_name",
|
||||||
"b",
|
"b",
|
||||||
"aaa"
|
"aaa"
|
||||||
],
|
],
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
package io.bkbn.kompendium.core.fixtures
|
package io.bkbn.kompendium.core.fixtures
|
||||||
|
|
||||||
import io.bkbn.kompendium.core.metadata.ExceptionInfo
|
import io.bkbn.kompendium.core.metadata.ExceptionInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.ParameterExample
|
||||||
import io.bkbn.kompendium.core.metadata.method.PostInfo
|
import io.bkbn.kompendium.core.metadata.method.PostInfo
|
||||||
import io.bkbn.kompendium.core.metadata.method.PutInfo
|
import io.bkbn.kompendium.core.metadata.method.PutInfo
|
||||||
import io.bkbn.kompendium.core.metadata.RequestInfo
|
import io.bkbn.kompendium.core.metadata.RequestInfo
|
||||||
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
||||||
import io.bkbn.kompendium.core.metadata.method.DeleteInfo
|
import io.bkbn.kompendium.core.metadata.method.DeleteInfo
|
||||||
import io.bkbn.kompendium.core.metadata.method.GetInfo
|
import io.bkbn.kompendium.core.metadata.method.GetInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.method.HeadInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.method.OptionsInfo
|
||||||
|
import io.bkbn.kompendium.core.metadata.method.PatchInfo
|
||||||
import io.ktor.http.HttpStatusCode
|
import io.ktor.http.HttpStatusCode
|
||||||
import kotlin.reflect.typeOf
|
import kotlin.reflect.typeOf
|
||||||
|
|
||||||
@ -15,6 +19,7 @@ object TestResponseInfo {
|
|||||||
private val testGetListResponse =
|
private val testGetListResponse =
|
||||||
ResponseInfo<List<TestResponse>>(HttpStatusCode.OK, "A Successful List-y Endeavor")
|
ResponseInfo<List<TestResponse>>(HttpStatusCode.OK, "A Successful List-y Endeavor")
|
||||||
private val testPostResponse = ResponseInfo<TestCreatedResponse>(HttpStatusCode.Created, "A Successful Endeavor")
|
private val testPostResponse = ResponseInfo<TestCreatedResponse>(HttpStatusCode.Created, "A Successful Endeavor")
|
||||||
|
private val testPatchResponse = ResponseInfo<TestResponse>(HttpStatusCode.Created, "A Successful Endeavor")
|
||||||
private val testPostResponseAgain = ResponseInfo<Boolean>(HttpStatusCode.Created, "A Successful Endeavor")
|
private val testPostResponseAgain = ResponseInfo<Boolean>(HttpStatusCode.Created, "A Successful Endeavor")
|
||||||
private val testDeleteResponse =
|
private val testDeleteResponse =
|
||||||
ResponseInfo<Unit>(HttpStatusCode.NoContent, "A Successful Endeavor", mediaTypes = emptyList())
|
ResponseInfo<Unit>(HttpStatusCode.NoContent, "A Successful Endeavor", mediaTypes = emptyList())
|
||||||
@ -75,6 +80,22 @@ object TestResponseInfo {
|
|||||||
responseInfo = testPostResponse,
|
responseInfo = testPostResponse,
|
||||||
requestInfo = complexRequest
|
requestInfo = complexRequest
|
||||||
)
|
)
|
||||||
|
val testPatchInfo = PatchInfo<Unit, TestRequest, TestResponse>(
|
||||||
|
summary = "Test patch endpoint",
|
||||||
|
description = "patch your tests here!",
|
||||||
|
responseInfo = testPatchResponse,
|
||||||
|
requestInfo = testRequest
|
||||||
|
)
|
||||||
|
val testHeadInfo = HeadInfo<Unit>(
|
||||||
|
summary = "Test head endpoint",
|
||||||
|
description = "head test 💀",
|
||||||
|
responseInfo = ResponseInfo(HttpStatusCode.OK, "great!")
|
||||||
|
)
|
||||||
|
val testOptionsInfo = OptionsInfo<TestParams, TestResponse>(
|
||||||
|
summary = "Test options",
|
||||||
|
description = "endpoint of options",
|
||||||
|
responseInfo = ResponseInfo(HttpStatusCode.OK, "nice")
|
||||||
|
)
|
||||||
val testPutInfoAlso = PutInfo<TestParams, TestRequest, TestCreatedResponse>(
|
val testPutInfoAlso = PutInfo<TestParams, TestRequest, TestCreatedResponse>(
|
||||||
summary = "Test put endpoint",
|
summary = "Test put endpoint",
|
||||||
description = "Put your tests here!",
|
description = "Put your tests here!",
|
||||||
@ -246,5 +267,16 @@ object TestResponseInfo {
|
|||||||
requestInfo = RequestInfo("cool")
|
requestInfo = RequestInfo("cool")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val exampleParams = GetInfo<TestParams, TestResponse>(
|
||||||
|
summary = "param stuff",
|
||||||
|
description = "Cool stuff",
|
||||||
|
responseInfo = simpleOkResponse(),
|
||||||
|
parameterExamples = setOf(
|
||||||
|
ParameterExample(TestParams::a.name, "Testerino", "a"),
|
||||||
|
ParameterExample(TestParams::a.name, "Testerina", "b"),
|
||||||
|
ParameterExample(TestParams::aa.name, "Wowza", 6),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
private fun <T> simpleOkResponse() = ResponseInfo<T>(HttpStatusCode.OK, "A successful endeavor")
|
private fun <T> simpleOkResponse() = ResponseInfo<T>(HttpStatusCode.OK, "A successful endeavor")
|
||||||
}
|
}
|
||||||
|
@ -1 +1,4 @@
|
|||||||
# Module kompendium-locations
|
# Module kompendium-locations
|
||||||
|
|
||||||
|
Adds support for Ktor [Locations](https://ktor.io/docs/locations.html) API. Any notarized location _must_ be provided
|
||||||
|
with a `TParam` annotated with `@Location`. Nested Locations are supported
|
||||||
|
@ -1,5 +1,18 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id("io.bkbn.sourdough.library")
|
kotlin("jvm")
|
||||||
|
id("io.bkbn.sourdough.library.jvm")
|
||||||
|
id("io.gitlab.arturbosch.detekt")
|
||||||
|
id("com.adarshr.test-logger")
|
||||||
|
id("org.jetbrains.dokka")
|
||||||
|
id("maven-publish")
|
||||||
|
id("java-library")
|
||||||
|
id("signing")
|
||||||
|
}
|
||||||
|
|
||||||
|
sourdough {
|
||||||
|
libraryName.set("Kompendium Locations")
|
||||||
|
libraryDescription.set("Supplemental library for Kompendium offering support for Ktor's Location API")
|
||||||
|
compilerArgs.set(listOf("-opt-in=kotlin.RequiresOptIn"))
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@ -14,3 +27,11 @@ dependencies {
|
|||||||
|
|
||||||
testImplementation(testFixtures(projects.kompendiumCore))
|
testImplementation(testFixtures(projects.kompendiumCore))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
testing {
|
||||||
|
suites {
|
||||||
|
named("test", JvmTestSuite::class) {
|
||||||
|
useJUnitJupiter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -0,0 +1,84 @@
|
|||||||
|
package io.bkbn.kompendium.locations
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.annotations.Param
|
||||||
|
import io.bkbn.kompendium.core.Kompendium
|
||||||
|
import io.bkbn.kompendium.core.metadata.method.MethodInfo
|
||||||
|
import io.bkbn.kompendium.core.parser.IMethodParser
|
||||||
|
import io.bkbn.kompendium.oas.path.Path
|
||||||
|
import io.bkbn.kompendium.oas.path.PathOperation
|
||||||
|
import io.bkbn.kompendium.oas.payload.Parameter
|
||||||
|
import io.ktor.application.feature
|
||||||
|
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||||
|
import io.ktor.locations.Location
|
||||||
|
import io.ktor.routing.Route
|
||||||
|
import io.ktor.routing.application
|
||||||
|
import kotlin.reflect.KAnnotatedElement
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
import kotlin.reflect.KClassifier
|
||||||
|
import kotlin.reflect.KType
|
||||||
|
import kotlin.reflect.full.createType
|
||||||
|
import kotlin.reflect.full.findAnnotation
|
||||||
|
import kotlin.reflect.full.hasAnnotation
|
||||||
|
import kotlin.reflect.full.memberProperties
|
||||||
|
|
||||||
|
@OptIn(KtorExperimentalLocationsAPI::class)
|
||||||
|
object LocationMethodParser : IMethodParser {
|
||||||
|
override fun KType.toParameterSpec(info: MethodInfo<*, *>, feature: Kompendium): List<Parameter> {
|
||||||
|
val clazzList = determineLocationParents(classifier!!)
|
||||||
|
return clazzList.associateWith { it.memberProperties }
|
||||||
|
.flatMap { (clazz, memberProperties) -> memberProperties.associateWith { clazz }.toList() }
|
||||||
|
.filter { (prop, _) -> prop.hasAnnotation<Param>() }
|
||||||
|
.map { (prop, clazz) -> prop.toParameter(info, clazz.createType(), clazz, feature) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun determineLocationParents(classifier: KClassifier): List<KClass<*>> {
|
||||||
|
var clazz: KClass<*>? = classifier as KClass<*>
|
||||||
|
val clazzList = mutableListOf<KClass<*>>()
|
||||||
|
while (clazz != null) {
|
||||||
|
clazzList.add(clazz)
|
||||||
|
clazz = getLocationParent(clazz)
|
||||||
|
}
|
||||||
|
return clazzList
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getLocationParent(clazz: KClass<*>): KClass<*>? {
|
||||||
|
val parent = clazz.memberProperties
|
||||||
|
.find { (it.returnType.classifier as KAnnotatedElement).hasAnnotation<Location>() }
|
||||||
|
return parent?.returnType?.classifier as? KClass<*>
|
||||||
|
}
|
||||||
|
|
||||||
|
fun KClass<*>.calculateLocationPath(suffix: String = ""): String {
|
||||||
|
val locationAnnotation = this.findAnnotation<Location>()
|
||||||
|
require(locationAnnotation != null) { "Location annotation must be present to leverage notarized location api" }
|
||||||
|
val parent = this.java.declaringClass?.kotlin
|
||||||
|
val newSuffix = locationAnnotation.path.plus(suffix)
|
||||||
|
return when (parent) {
|
||||||
|
null -> newSuffix
|
||||||
|
else -> parent.calculateLocationPath(newSuffix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified TParam : Any> processBaseInfo(
|
||||||
|
paramType: KType,
|
||||||
|
requestType: KType,
|
||||||
|
responseType: KType,
|
||||||
|
info: MethodInfo<*, *>,
|
||||||
|
route: Route
|
||||||
|
): LocationBaseInfo {
|
||||||
|
val locationAnnotation = TParam::class.findAnnotation<Location>()
|
||||||
|
require(locationAnnotation != null) { "Location annotation must be present to leverage notarized location api" }
|
||||||
|
val path = route.calculateRoutePath()
|
||||||
|
val locationPath = TParam::class.calculateLocationPath()
|
||||||
|
val pathWithLocation = path.plus(locationPath)
|
||||||
|
val feature = route.application.feature(Kompendium)
|
||||||
|
feature.config.spec.paths.getOrPut(pathWithLocation) { Path() }
|
||||||
|
val baseInfo = parseMethodInfo(info, paramType, requestType, responseType, feature)
|
||||||
|
return LocationBaseInfo(baseInfo, feature, pathWithLocation)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class LocationBaseInfo(
|
||||||
|
val op: PathOperation,
|
||||||
|
val feature: Kompendium,
|
||||||
|
val path: String
|
||||||
|
)
|
||||||
|
}
|
@ -1,28 +1,19 @@
|
|||||||
package io.bkbn.kompendium.locations
|
package io.bkbn.kompendium.locations
|
||||||
|
|
||||||
import io.bkbn.kompendium.core.Kompendium
|
|
||||||
import io.bkbn.kompendium.core.KompendiumPreFlight.methodNotarizationPreFlight
|
import io.bkbn.kompendium.core.KompendiumPreFlight.methodNotarizationPreFlight
|
||||||
import io.bkbn.kompendium.core.MethodParser.parseMethodInfo
|
|
||||||
import io.bkbn.kompendium.core.Notarized.calculateRoutePath
|
|
||||||
import io.bkbn.kompendium.core.metadata.method.DeleteInfo
|
import io.bkbn.kompendium.core.metadata.method.DeleteInfo
|
||||||
import io.bkbn.kompendium.core.metadata.method.GetInfo
|
import io.bkbn.kompendium.core.metadata.method.GetInfo
|
||||||
import io.bkbn.kompendium.core.metadata.method.PostInfo
|
import io.bkbn.kompendium.core.metadata.method.PostInfo
|
||||||
import io.bkbn.kompendium.core.metadata.method.PutInfo
|
import io.bkbn.kompendium.core.metadata.method.PutInfo
|
||||||
import io.bkbn.kompendium.oas.path.Path
|
|
||||||
import io.bkbn.kompendium.oas.path.PathOperation
|
import io.bkbn.kompendium.oas.path.PathOperation
|
||||||
import io.ktor.application.ApplicationCall
|
import io.ktor.application.ApplicationCall
|
||||||
import io.ktor.application.feature
|
|
||||||
import io.ktor.http.HttpMethod
|
import io.ktor.http.HttpMethod
|
||||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||||
import io.ktor.locations.Location
|
|
||||||
import io.ktor.locations.handle
|
import io.ktor.locations.handle
|
||||||
import io.ktor.locations.location
|
import io.ktor.locations.location
|
||||||
import io.ktor.routing.Route
|
import io.ktor.routing.Route
|
||||||
import io.ktor.routing.application
|
|
||||||
import io.ktor.routing.method
|
import io.ktor.routing.method
|
||||||
import io.ktor.util.pipeline.PipelineContext
|
import io.ktor.util.pipeline.PipelineContext
|
||||||
import kotlin.reflect.KClass
|
|
||||||
import kotlin.reflect.full.findAnnotation
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This version of notarized routes leverages the Ktor [io.ktor.locations.Locations] plugin to provide type safe access
|
* This version of notarized routes leverages the Ktor [io.ktor.locations.Locations] plugin to provide type safe access
|
||||||
@ -45,15 +36,8 @@ object NotarizedLocation {
|
|||||||
postProcess: (PathOperation) -> PathOperation = { p -> p },
|
postProcess: (PathOperation) -> PathOperation = { p -> p },
|
||||||
noinline body: suspend PipelineContext<Unit, ApplicationCall>.(TParam) -> Unit
|
noinline body: suspend PipelineContext<Unit, ApplicationCall>.(TParam) -> Unit
|
||||||
): Route = methodNotarizationPreFlight<TParam, Unit, TResp>() { paramType, requestType, responseType ->
|
): Route = methodNotarizationPreFlight<TParam, Unit, TResp>() { paramType, requestType, responseType ->
|
||||||
val locationAnnotation = TParam::class.findAnnotation<Location>()
|
val lbi = LocationMethodParser.processBaseInfo<TParam>(paramType, requestType, responseType, info, this)
|
||||||
require(locationAnnotation != null) { "Location annotation must be present to leverage notarized location api" }
|
lbi.feature.config.spec.paths[lbi.path]?.get = postProcess(lbi.op)
|
||||||
val feature = application.feature(Kompendium)
|
|
||||||
val path = calculateRoutePath()
|
|
||||||
val locationPath = TParam::class.calculateLocationPath()
|
|
||||||
val pathWithLocation = path.plus(locationPath)
|
|
||||||
feature.config.spec.paths.getOrPut(pathWithLocation) { Path() }
|
|
||||||
val baseInfo = parseMethodInfo(info, paramType, requestType, responseType, feature)
|
|
||||||
feature.config.spec.paths[pathWithLocation]?.get = postProcess(baseInfo)
|
|
||||||
return location(TParam::class) {
|
return location(TParam::class) {
|
||||||
method(HttpMethod.Get) { handle(body) }
|
method(HttpMethod.Get) { handle(body) }
|
||||||
}
|
}
|
||||||
@ -74,15 +58,8 @@ object NotarizedLocation {
|
|||||||
postProcess: (PathOperation) -> PathOperation = { p -> p },
|
postProcess: (PathOperation) -> PathOperation = { p -> p },
|
||||||
noinline body: suspend PipelineContext<Unit, ApplicationCall>.(TParam) -> Unit
|
noinline body: suspend PipelineContext<Unit, ApplicationCall>.(TParam) -> Unit
|
||||||
): Route = methodNotarizationPreFlight<TParam, TReq, TResp>() { paramType, requestType, responseType ->
|
): Route = methodNotarizationPreFlight<TParam, TReq, TResp>() { paramType, requestType, responseType ->
|
||||||
val locationAnnotation = TParam::class.findAnnotation<Location>()
|
val lbi = LocationMethodParser.processBaseInfo<TParam>(paramType, requestType, responseType, info, this)
|
||||||
require(locationAnnotation != null) { "Location annotation must be present to leverage notarized location api" }
|
lbi.feature.config.spec.paths[lbi.path]?.post = postProcess(lbi.op)
|
||||||
val feature = application.feature(Kompendium)
|
|
||||||
val path = calculateRoutePath()
|
|
||||||
val locationPath = TParam::class.calculateLocationPath()
|
|
||||||
val pathWithLocation = path.plus(locationPath)
|
|
||||||
feature.config.spec.paths.getOrPut(pathWithLocation) { Path() }
|
|
||||||
val baseInfo = parseMethodInfo(info, paramType, requestType, responseType, feature)
|
|
||||||
feature.config.spec.paths[pathWithLocation]?.post = postProcess(baseInfo)
|
|
||||||
return location(TParam::class) {
|
return location(TParam::class) {
|
||||||
method(HttpMethod.Post) { handle(body) }
|
method(HttpMethod.Post) { handle(body) }
|
||||||
}
|
}
|
||||||
@ -103,15 +80,8 @@ object NotarizedLocation {
|
|||||||
postProcess: (PathOperation) -> PathOperation = { p -> p },
|
postProcess: (PathOperation) -> PathOperation = { p -> p },
|
||||||
noinline body: suspend PipelineContext<Unit, ApplicationCall>.(TParam) -> Unit
|
noinline body: suspend PipelineContext<Unit, ApplicationCall>.(TParam) -> Unit
|
||||||
): Route = methodNotarizationPreFlight<TParam, TReq, TResp>() { paramType, requestType, responseType ->
|
): Route = methodNotarizationPreFlight<TParam, TReq, TResp>() { paramType, requestType, responseType ->
|
||||||
val locationAnnotation = TParam::class.findAnnotation<Location>()
|
val lbi = LocationMethodParser.processBaseInfo<TParam>(paramType, requestType, responseType, info, this)
|
||||||
require(locationAnnotation != null) { "Location annotation must be present to leverage notarized location api" }
|
lbi.feature.config.spec.paths[lbi.path]?.put = postProcess(lbi.op)
|
||||||
val feature = application.feature(Kompendium)
|
|
||||||
val path = calculateRoutePath()
|
|
||||||
val locationPath = TParam::class.calculateLocationPath()
|
|
||||||
val pathWithLocation = path.plus(locationPath)
|
|
||||||
feature.config.spec.paths.getOrPut(pathWithLocation) { Path() }
|
|
||||||
val baseInfo = parseMethodInfo(info, paramType, requestType, responseType, feature)
|
|
||||||
feature.config.spec.paths[pathWithLocation]?.put = postProcess(baseInfo)
|
|
||||||
return location(TParam::class) {
|
return location(TParam::class) {
|
||||||
method(HttpMethod.Put) { handle(body) }
|
method(HttpMethod.Put) { handle(body) }
|
||||||
}
|
}
|
||||||
@ -131,28 +101,10 @@ object NotarizedLocation {
|
|||||||
postProcess: (PathOperation) -> PathOperation = { p -> p },
|
postProcess: (PathOperation) -> PathOperation = { p -> p },
|
||||||
noinline body: suspend PipelineContext<Unit, ApplicationCall>.(TParam) -> Unit
|
noinline body: suspend PipelineContext<Unit, ApplicationCall>.(TParam) -> Unit
|
||||||
): Route = methodNotarizationPreFlight<TParam, Unit, TResp> { paramType, requestType, responseType ->
|
): Route = methodNotarizationPreFlight<TParam, Unit, TResp> { paramType, requestType, responseType ->
|
||||||
val locationAnnotation = TParam::class.findAnnotation<Location>()
|
val lbi = LocationMethodParser.processBaseInfo<TParam>(paramType, requestType, responseType, info, this)
|
||||||
require(locationAnnotation != null) { "Location annotation must be present to leverage notarized location api" }
|
lbi.feature.config.spec.paths[lbi.path]?.delete = postProcess(lbi.op)
|
||||||
val feature = application.feature(Kompendium)
|
|
||||||
val path = calculateRoutePath()
|
|
||||||
val locationPath = TParam::class.calculateLocationPath()
|
|
||||||
val pathWithLocation = path.plus(locationPath)
|
|
||||||
feature.config.spec.paths.getOrPut(pathWithLocation) { Path() }
|
|
||||||
val baseInfo = parseMethodInfo(info, paramType, requestType, responseType, feature)
|
|
||||||
feature.config.spec.paths[pathWithLocation]?.delete = postProcess(baseInfo)
|
|
||||||
return location(TParam::class) {
|
return location(TParam::class) {
|
||||||
method(HttpMethod.Delete) { handle(body) }
|
method(HttpMethod.Delete) { handle(body) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun KClass<*>.calculateLocationPath(suffix: String = ""): String {
|
|
||||||
val locationAnnotation = this.findAnnotation<Location>()
|
|
||||||
require(locationAnnotation != null) { "Location annotation must be present to leverage notarized location api" }
|
|
||||||
val parent = this.java.declaringClass?.kotlin
|
|
||||||
val newSuffix = locationAnnotation.path.plus(suffix)
|
|
||||||
return when (parent) {
|
|
||||||
null -> newSuffix
|
|
||||||
else -> parent.calculateLocationPath(newSuffix)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,15 @@
|
|||||||
},
|
},
|
||||||
"required": true,
|
"required": true,
|
||||||
"deprecated": false
|
"deprecated": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"deprecated": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
|
@ -40,6 +40,15 @@
|
|||||||
},
|
},
|
||||||
"required": true,
|
"required": true,
|
||||||
"deprecated": false
|
"deprecated": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"deprecated": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
|
@ -40,6 +40,15 @@
|
|||||||
},
|
},
|
||||||
"required": true,
|
"required": true,
|
||||||
"deprecated": false
|
"deprecated": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"deprecated": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
|
@ -40,6 +40,15 @@
|
|||||||
},
|
},
|
||||||
"required": true,
|
"required": true,
|
||||||
"deprecated": false
|
"deprecated": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"deprecated": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
|
@ -1,3 +1,29 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id("io.bkbn.sourdough.library")
|
kotlin("jvm")
|
||||||
|
kotlin("plugin.serialization")
|
||||||
|
id("io.bkbn.sourdough.library.jvm")
|
||||||
|
id("io.gitlab.arturbosch.detekt")
|
||||||
|
id("com.adarshr.test-logger")
|
||||||
|
id("org.jetbrains.dokka")
|
||||||
|
id("maven-publish")
|
||||||
|
id("java-library")
|
||||||
|
id("signing")
|
||||||
|
}
|
||||||
|
|
||||||
|
sourdough {
|
||||||
|
libraryName.set("Kompendium OpenAPI Spec")
|
||||||
|
libraryDescription.set("Collections of kotlin data classes modeling the OpenAPI specification")
|
||||||
|
compilerArgs.set(listOf("-opt-in=kotlin.RequiresOptIn"))
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(group = "org.jetbrains.kotlinx", "kotlinx-serialization-json", version = "1.3.1")
|
||||||
|
}
|
||||||
|
|
||||||
|
testing {
|
||||||
|
suites {
|
||||||
|
named("test", JvmTestSuite::class) {
|
||||||
|
useJUnitJupiter()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,9 @@ import io.bkbn.kompendium.oas.component.Components
|
|||||||
import io.bkbn.kompendium.oas.info.Info
|
import io.bkbn.kompendium.oas.info.Info
|
||||||
import io.bkbn.kompendium.oas.path.Path
|
import io.bkbn.kompendium.oas.path.Path
|
||||||
import io.bkbn.kompendium.oas.server.Server
|
import io.bkbn.kompendium.oas.server.Server
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class OpenApiSpec(
|
data class OpenApiSpec(
|
||||||
val openapi: String = "3.0.3",
|
val openapi: String = "3.0.3",
|
||||||
val info: Info,
|
val info: Info,
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
package io.bkbn.kompendium.oas.common
|
package io.bkbn.kompendium.oas.common
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.oas.serialization.UriSerializer
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class ExternalDocumentation(
|
data class ExternalDocumentation(
|
||||||
|
@Serializable(with = UriSerializer::class)
|
||||||
val url: URI,
|
val url: URI,
|
||||||
val description: String?
|
val description: String?
|
||||||
)
|
)
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
package io.bkbn.kompendium.oas.common
|
package io.bkbn.kompendium.oas.common
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class Tag(
|
data class Tag(
|
||||||
val name: String,
|
val name: String,
|
||||||
val description: String? = null,
|
val description: String? = null,
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package io.bkbn.kompendium.oas.component
|
package io.bkbn.kompendium.oas.component
|
||||||
|
|
||||||
import io.bkbn.kompendium.oas.security.SecuritySchema
|
import io.bkbn.kompendium.oas.security.SecuritySchema
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class Components(
|
data class Components(
|
||||||
val securitySchemes: MutableMap<String, SecuritySchema> = mutableMapOf()
|
val securitySchemes: MutableMap<String, SecuritySchema> = mutableMapOf()
|
||||||
)
|
)
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
package io.bkbn.kompendium.oas.info
|
package io.bkbn.kompendium.oas.info
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.oas.serialization.UriSerializer
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class Contact(
|
data class Contact(
|
||||||
var name: String,
|
var name: String,
|
||||||
|
@Serializable(with = UriSerializer::class)
|
||||||
var url: URI? = null,
|
var url: URI? = null,
|
||||||
var email: String? = null // TODO Enforce email?
|
var email: String? = null // TODO Enforce email?
|
||||||
)
|
)
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
package io.bkbn.kompendium.oas.info
|
package io.bkbn.kompendium.oas.info
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.oas.serialization.UriSerializer
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class Info(
|
data class Info(
|
||||||
var title: String? = null,
|
var title: String? = null,
|
||||||
var version: String? = null,
|
var version: String? = null,
|
||||||
var description: String? = null,
|
var description: String? = null,
|
||||||
|
@Serializable(with = UriSerializer::class)
|
||||||
var termsOfService: URI? = null,
|
var termsOfService: URI? = null,
|
||||||
var contact: Contact? = null,
|
var contact: Contact? = null,
|
||||||
var license: License? = null
|
var license: License? = null
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
package io.bkbn.kompendium.oas.info
|
package io.bkbn.kompendium.oas.info
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.oas.serialization.UriSerializer
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class License(
|
data class License(
|
||||||
var name: String,
|
var name: String,
|
||||||
|
@Serializable(with = UriSerializer::class)
|
||||||
var url: URI? = null
|
var url: URI? = null
|
||||||
)
|
)
|
||||||
|
@ -2,7 +2,9 @@ package io.bkbn.kompendium.oas.path
|
|||||||
|
|
||||||
import io.bkbn.kompendium.oas.payload.Parameter
|
import io.bkbn.kompendium.oas.payload.Parameter
|
||||||
import io.bkbn.kompendium.oas.server.Server
|
import io.bkbn.kompendium.oas.server.Server
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class Path(
|
data class Path(
|
||||||
var get: PathOperation? = null,
|
var get: PathOperation? = null,
|
||||||
var put: PathOperation? = null,
|
var put: PathOperation? = null,
|
||||||
|
@ -6,7 +6,9 @@ import io.bkbn.kompendium.oas.payload.Payload
|
|||||||
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 io.bkbn.kompendium.oas.server.Server
|
import io.bkbn.kompendium.oas.server.Server
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class PathOperation(
|
data class PathOperation(
|
||||||
var tags: Set<String> = emptySet(),
|
var tags: Set<String> = emptySet(),
|
||||||
var summary: String? = null,
|
var summary: String? = null,
|
||||||
@ -14,9 +16,9 @@ data class PathOperation(
|
|||||||
var externalDocs: ExternalDocumentation? = null,
|
var externalDocs: ExternalDocumentation? = null,
|
||||||
var operationId: String? = null,
|
var operationId: String? = null,
|
||||||
var parameters: List<Parameter>? = null,
|
var parameters: List<Parameter>? = null,
|
||||||
var requestBody: Request<*>? = null,
|
var requestBody: Request? = null,
|
||||||
// TODO How to enforce `default` requirement 🧐
|
// TODO How to enforce `default` requirement 🧐
|
||||||
var responses: Map<Int, Response<*>>? = null,
|
var responses: Map<Int, Response>? = null,
|
||||||
var callbacks: Map<String, Payload>? = null, // todo what is this?
|
var callbacks: Map<String, Payload>? = null, // todo what is this?
|
||||||
var deprecated: Boolean = false,
|
var deprecated: Boolean = false,
|
||||||
var security: List<Map<String, List<String>>>? = null,
|
var security: List<Map<String, List<String>>>? = null,
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
package io.bkbn.kompendium.oas.payload
|
package io.bkbn.kompendium.oas.payload
|
||||||
|
|
||||||
import io.bkbn.kompendium.oas.schema.ComponentSchema
|
import io.bkbn.kompendium.oas.schema.ComponentSchema
|
||||||
|
import kotlinx.serialization.Contextual
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
data class MediaType<T>(
|
@Serializable
|
||||||
|
data class MediaType(
|
||||||
val schema: ComponentSchema,
|
val schema: ComponentSchema,
|
||||||
val examples: Map<String, Example<T>>? = null
|
val examples: Map<String, Example>? = null
|
||||||
) {
|
) {
|
||||||
data class Example<T>(val value: T)
|
@Serializable
|
||||||
|
data class Example(val value: @Contextual Any)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
package io.bkbn.kompendium.oas.payload
|
package io.bkbn.kompendium.oas.payload
|
||||||
|
|
||||||
import io.bkbn.kompendium.oas.schema.ComponentSchema
|
import io.bkbn.kompendium.oas.schema.ComponentSchema
|
||||||
|
import kotlinx.serialization.Contextual
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class Parameter(
|
data class Parameter(
|
||||||
val name: String,
|
val name: String,
|
||||||
val `in`: String, // TODO Enum? "query", "header", "path" or "cookie"
|
val `in`: String, // TODO Enum? "query", "header", "path" or "cookie"
|
||||||
@ -11,5 +14,9 @@ data class Parameter(
|
|||||||
val deprecated: Boolean = false,
|
val deprecated: Boolean = false,
|
||||||
val allowEmptyValue: Boolean? = null,
|
val allowEmptyValue: Boolean? = null,
|
||||||
val style: String? = null,
|
val style: String? = null,
|
||||||
val explode: Boolean? = null
|
val explode: Boolean? = null,
|
||||||
)
|
val examples: Map<String, Example>? = null
|
||||||
|
) {
|
||||||
|
@Serializable
|
||||||
|
data class Example(val value: @Contextual Any)
|
||||||
|
}
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
package io.bkbn.kompendium.oas.payload
|
package io.bkbn.kompendium.oas.payload
|
||||||
|
|
||||||
data class Request<T>(
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Request(
|
||||||
val description: String?,
|
val description: String?,
|
||||||
val content: Map<String, MediaType<T>>,
|
val content: Map<String, MediaType>,
|
||||||
val required: Boolean = false
|
val required: Boolean = false
|
||||||
) : Payload
|
) : Payload
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
package io.bkbn.kompendium.oas.payload
|
package io.bkbn.kompendium.oas.payload
|
||||||
|
|
||||||
data class Response<T>(
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Response(
|
||||||
val description: String? = null,
|
val description: String? = null,
|
||||||
val headers: Map<String, Payload>? = null,
|
val headers: Map<String, Payload>? = null,
|
||||||
val content: Map<String, MediaType<T>>? = null,
|
val content: Map<String, MediaType>? = null,
|
||||||
val links: Map<String, Payload>? = null
|
val links: Map<String, Payload>? = null
|
||||||
) : Payload
|
) : Payload
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
package io.bkbn.kompendium.oas.schema
|
package io.bkbn.kompendium.oas.schema
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class AnyOfSchema(val anyOf: List<ComponentSchema>, override val description: String? = null) : ComponentSchema
|
data class AnyOfSchema(val anyOf: List<ComponentSchema>, override val description: String? = null) : ComponentSchema
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
package io.bkbn.kompendium.oas.schema
|
package io.bkbn.kompendium.oas.schema
|
||||||
|
|
||||||
|
import kotlinx.serialization.Contextual
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class ArraySchema(
|
data class ArraySchema(
|
||||||
val items: ComponentSchema,
|
val items: ComponentSchema,
|
||||||
override val default: Any? = null,
|
override val default: @Contextual Any? = null,
|
||||||
override val description: String? = null,
|
override val description: String? = null,
|
||||||
override val nullable: Boolean? = null,
|
override val nullable: Boolean? = null,
|
||||||
// constraints
|
// constraints
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
package io.bkbn.kompendium.oas.schema
|
package io.bkbn.kompendium.oas.schema
|
||||||
|
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
|
import kotlinx.serialization.json.JsonClassDiscriminator
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
|
@JsonClassDiscriminator("component_type") // todo figure out a way to filter this
|
||||||
sealed interface ComponentSchema {
|
sealed interface ComponentSchema {
|
||||||
val description: String?
|
val description: String?
|
||||||
get() = null
|
get() = null
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
package io.bkbn.kompendium.oas.schema
|
package io.bkbn.kompendium.oas.schema
|
||||||
|
|
||||||
|
import kotlinx.serialization.Contextual
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class DictionarySchema(
|
data class DictionarySchema(
|
||||||
val additionalProperties: ComponentSchema,
|
val additionalProperties: ComponentSchema,
|
||||||
override val default: Any? = null,
|
override val default: @Contextual Any? = null,
|
||||||
override val description: String? = null,
|
override val description: String? = null,
|
||||||
override val nullable: Boolean? = null
|
override val nullable: Boolean? = null
|
||||||
) : TypedSchema {
|
) : TypedSchema {
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
package io.bkbn.kompendium.oas.schema
|
package io.bkbn.kompendium.oas.schema
|
||||||
|
|
||||||
|
import kotlinx.serialization.Contextual
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class EnumSchema(
|
data class EnumSchema(
|
||||||
val `enum`: Set<String>,
|
val `enum`: Set<String>,
|
||||||
override val default: Any? = null,
|
override val default: @Contextual Any? = null,
|
||||||
override val description: String? = null,
|
override val description: String? = null,
|
||||||
override val nullable: Boolean? = null
|
override val nullable: Boolean? = null
|
||||||
) : TypedSchema {
|
) : TypedSchema {
|
||||||
|
@ -1,15 +1,23 @@
|
|||||||
package io.bkbn.kompendium.oas.schema
|
package io.bkbn.kompendium.oas.schema
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.oas.serialization.NumberSerializer
|
||||||
|
import kotlinx.serialization.Contextual
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class FormattedSchema(
|
data class FormattedSchema(
|
||||||
val format: String,
|
val format: String,
|
||||||
override val type: String,
|
override val type: String,
|
||||||
override val default: Any? = null,
|
override val default: @Contextual Any? = null,
|
||||||
override val description: String? = null,
|
override val description: String? = null,
|
||||||
override val nullable: Boolean? = null,
|
override val nullable: Boolean? = null,
|
||||||
// Constraints
|
// Constraints
|
||||||
|
@Serializable(with = NumberSerializer::class)
|
||||||
val minimum: Number? = null,
|
val minimum: Number? = null,
|
||||||
|
@Serializable(with = NumberSerializer::class)
|
||||||
val maximum: Number? = null,
|
val maximum: Number? = null,
|
||||||
val exclusiveMinimum: Boolean? = null,
|
val exclusiveMinimum: Boolean? = null,
|
||||||
val exclusiveMaximum: Boolean? = null,
|
val exclusiveMaximum: Boolean? = null,
|
||||||
|
@Serializable(with = NumberSerializer::class)
|
||||||
val multipleOf: Number? = null,
|
val multipleOf: Number? = null,
|
||||||
) : TypedSchema
|
) : TypedSchema
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
package io.bkbn.kompendium.oas.schema
|
package io.bkbn.kompendium.oas.schema
|
||||||
|
|
||||||
|
import kotlinx.serialization.Contextual
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class FreeFormSchema(
|
data class FreeFormSchema(
|
||||||
override val nullable: Boolean? = null,
|
override val nullable: Boolean? = null,
|
||||||
// constraints
|
// constraints
|
||||||
@ -8,5 +12,5 @@ data class FreeFormSchema(
|
|||||||
) : TypedSchema {
|
) : TypedSchema {
|
||||||
val additionalProperties: Boolean = true
|
val additionalProperties: Boolean = true
|
||||||
override val type: String = "object"
|
override val type: String = "object"
|
||||||
override val default: Any? = null
|
override val default: @Contextual Any? = null
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
package io.bkbn.kompendium.oas.schema
|
package io.bkbn.kompendium.oas.schema
|
||||||
|
|
||||||
|
import kotlinx.serialization.Contextual
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class ObjectSchema(
|
data class ObjectSchema(
|
||||||
val properties: Map<String, ComponentSchema>,
|
val properties: Map<String, ComponentSchema>,
|
||||||
override val default: Any? = null,
|
override val default: @Contextual Any? = null,
|
||||||
override val description: String? = null,
|
override val description: String? = null,
|
||||||
override val nullable: Boolean? = null,
|
override val nullable: Boolean? = null,
|
||||||
// constraints
|
// constraints
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
package io.bkbn.kompendium.oas.schema
|
package io.bkbn.kompendium.oas.schema
|
||||||
|
|
||||||
|
import kotlinx.serialization.Contextual
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class SimpleSchema(
|
data class SimpleSchema(
|
||||||
override val type: String,
|
override val type: String,
|
||||||
override val default: Any? = null,
|
override val default: @Contextual Any? = null,
|
||||||
override val description: String? = null,
|
override val description: String? = null,
|
||||||
override val nullable: Boolean? = null,
|
override val nullable: Boolean? = null,
|
||||||
// Constraints
|
// Constraints
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
package io.bkbn.kompendium.oas.security
|
package io.bkbn.kompendium.oas.security
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
// TODO... is there even an official ktor api auth mechanism??
|
// TODO... is there even an official ktor api auth mechanism??
|
||||||
|
|
||||||
|
@Serializable
|
||||||
@Suppress("UnusedPrivateMember")
|
@Suppress("UnusedPrivateMember")
|
||||||
class ApiKeyAuth(val `in`: ApiKeyLocation, name: String) : SecuritySchema {
|
class ApiKeyAuth(val `in`: ApiKeyLocation, val name: String) : SecuritySchema {
|
||||||
val type: String = "apiKey"
|
val type: String = "apiKey"
|
||||||
|
|
||||||
enum class ApiKeyLocation {
|
enum class ApiKeyLocation {
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
package io.bkbn.kompendium.oas.security
|
package io.bkbn.kompendium.oas.security
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
class BasicAuth : SecuritySchema {
|
class BasicAuth : SecuritySchema {
|
||||||
val type: String = "http"
|
val type: String = "http"
|
||||||
val scheme: String = "basic"
|
val scheme: String = "basic"
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
package io.bkbn.kompendium.oas.security
|
package io.bkbn.kompendium.oas.security
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class BearerAuth(val bearerFormat: String? = null): SecuritySchema {
|
data class BearerAuth(val bearerFormat: String? = null): SecuritySchema {
|
||||||
val type: String = "http"
|
val type: String = "http"
|
||||||
val scheme: String = "bearer"
|
val scheme: String = "bearer"
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
package io.bkbn.kompendium.oas.security
|
package io.bkbn.kompendium.oas.security
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class OAuth(val description: String? = null, val flows: Flows) : SecuritySchema {
|
data class OAuth(val description: String? = null, val flows: Flows) : SecuritySchema {
|
||||||
val type: String = "oauth2"
|
val type: String = "oauth2"
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class Flows(
|
data class Flows(
|
||||||
val implicit: Implicit? = null,
|
val implicit: Implicit? = null,
|
||||||
val authorizationCode: AuthorizationCode? = null,
|
val authorizationCode: AuthorizationCode? = null,
|
||||||
@ -21,12 +25,14 @@ data class OAuth(val description: String? = null, val flows: Flows) : SecuritySc
|
|||||||
get() = emptyMap()
|
get() = emptyMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class Implicit(
|
data class Implicit(
|
||||||
override val authorizationUrl: String,
|
override val authorizationUrl: String,
|
||||||
override val refreshUrl: String? = null,
|
override val refreshUrl: String? = null,
|
||||||
override val scopes: Map<String, String> = emptyMap()
|
override val scopes: Map<String, String> = emptyMap()
|
||||||
) : Flow
|
) : Flow
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class AuthorizationCode(
|
data class AuthorizationCode(
|
||||||
override val authorizationUrl: String,
|
override val authorizationUrl: String,
|
||||||
override val tokenUrl: String? = null,
|
override val tokenUrl: String? = null,
|
||||||
@ -34,12 +40,14 @@ data class OAuth(val description: String? = null, val flows: Flows) : SecuritySc
|
|||||||
override val scopes: Map<String, String> = emptyMap()
|
override val scopes: Map<String, String> = emptyMap()
|
||||||
) : Flow
|
) : Flow
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class Password(
|
data class Password(
|
||||||
override val tokenUrl: String? = null,
|
override val tokenUrl: String? = null,
|
||||||
override val refreshUrl: String? = null,
|
override val refreshUrl: String? = null,
|
||||||
override val scopes: Map<String, String> = emptyMap()
|
override val scopes: Map<String, String> = emptyMap()
|
||||||
) : Flow
|
) : Flow
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class ClientCredential(
|
data class ClientCredential(
|
||||||
override val tokenUrl: String? = null,
|
override val tokenUrl: String? = null,
|
||||||
override val refreshUrl: String? = null,
|
override val refreshUrl: String? = null,
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
package io.bkbn.kompendium.oas.security
|
package io.bkbn.kompendium.oas.security
|
||||||
|
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
|
import kotlinx.serialization.json.JsonClassDiscriminator
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
|
@JsonClassDiscriminator("schema_type") // todo figure out a way to filter this
|
||||||
sealed interface SecuritySchema
|
sealed interface SecuritySchema
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
package io.bkbn.kompendium.oas.serialization
|
||||||
|
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
import kotlinx.serialization.InternalSerializationApi
|
||||||
|
import kotlinx.serialization.KSerializer
|
||||||
|
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||||
|
import kotlinx.serialization.encoding.Decoder
|
||||||
|
import kotlinx.serialization.encoding.Encoder
|
||||||
|
import kotlinx.serialization.serializer
|
||||||
|
|
||||||
|
class AnySerializer<T : Any> : KSerializer<T> {
|
||||||
|
override fun serialize(encoder: Encoder, value: T) {
|
||||||
|
serialize(encoder, value, value::class as KClass<T>)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deserialize(decoder: Decoder): T {
|
||||||
|
error("Abandon all hope ye who enter 💀")
|
||||||
|
}
|
||||||
|
|
||||||
|
override val descriptor: SerialDescriptor
|
||||||
|
get() = TODO("Not yet implemented")
|
||||||
|
|
||||||
|
@OptIn(InternalSerializationApi::class)
|
||||||
|
fun serialize(encoder: Encoder, obj: T, clazz: KClass<T>) {
|
||||||
|
clazz.serializer().serialize(encoder, obj)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
package io.bkbn.kompendium.oas.serialization
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.oas.schema.AnyOfSchema
|
||||||
|
import io.bkbn.kompendium.oas.schema.ArraySchema
|
||||||
|
import io.bkbn.kompendium.oas.schema.ComponentSchema
|
||||||
|
import io.bkbn.kompendium.oas.schema.DictionarySchema
|
||||||
|
import io.bkbn.kompendium.oas.schema.EnumSchema
|
||||||
|
import io.bkbn.kompendium.oas.schema.FormattedSchema
|
||||||
|
import io.bkbn.kompendium.oas.schema.FreeFormSchema
|
||||||
|
import io.bkbn.kompendium.oas.schema.ObjectSchema
|
||||||
|
import io.bkbn.kompendium.oas.schema.SimpleSchema
|
||||||
|
import io.bkbn.kompendium.oas.security.ApiKeyAuth
|
||||||
|
import io.bkbn.kompendium.oas.security.BasicAuth
|
||||||
|
import io.bkbn.kompendium.oas.security.BearerAuth
|
||||||
|
import io.bkbn.kompendium.oas.security.OAuth
|
||||||
|
import io.bkbn.kompendium.oas.security.SecuritySchema
|
||||||
|
import kotlinx.serialization.modules.SerializersModule
|
||||||
|
import kotlinx.serialization.modules.polymorphic
|
||||||
|
|
||||||
|
object KompendiumSerializersModule {
|
||||||
|
|
||||||
|
val module = SerializersModule {
|
||||||
|
polymorphic(ComponentSchema::class) {
|
||||||
|
subclass(SimpleSchema::class, SimpleSchema.serializer())
|
||||||
|
subclass(FormattedSchema::class, FormattedSchema.serializer())
|
||||||
|
subclass(ObjectSchema::class, ObjectSchema.serializer())
|
||||||
|
subclass(AnyOfSchema::class, AnyOfSchema.serializer())
|
||||||
|
subclass(ArraySchema::class, ArraySchema.serializer())
|
||||||
|
subclass(DictionarySchema::class, DictionarySchema.serializer())
|
||||||
|
subclass(EnumSchema::class, EnumSchema.serializer())
|
||||||
|
subclass(FreeFormSchema::class, FreeFormSchema.serializer())
|
||||||
|
}
|
||||||
|
polymorphic(SecuritySchema::class) {
|
||||||
|
subclass(ApiKeyAuth::class, ApiKeyAuth.serializer())
|
||||||
|
subclass(BasicAuth::class, BasicAuth.serializer())
|
||||||
|
subclass(BearerAuth::class, BearerAuth.serializer())
|
||||||
|
subclass(OAuth::class, OAuth.serializer())
|
||||||
|
}
|
||||||
|
contextual(Any::class, AnySerializer())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package io.bkbn.kompendium.oas.serialization
|
||||||
|
|
||||||
|
import kotlinx.serialization.KSerializer
|
||||||
|
import kotlinx.serialization.SerializationException
|
||||||
|
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||||
|
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||||
|
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||||
|
import kotlinx.serialization.encoding.Decoder
|
||||||
|
import kotlinx.serialization.encoding.Encoder
|
||||||
|
|
||||||
|
object NumberSerializer : KSerializer<Number> {
|
||||||
|
override fun deserialize(decoder: Decoder): Number = try {
|
||||||
|
decoder.decodeDouble()
|
||||||
|
} catch (_: SerializationException) {
|
||||||
|
decoder.decodeInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Number", PrimitiveKind.DOUBLE)
|
||||||
|
|
||||||
|
override fun serialize(encoder: Encoder, value: Number) {
|
||||||
|
encoder.encodeString(value.toString())
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package io.bkbn.kompendium.oas.serialization
|
||||||
|
|
||||||
|
import kotlinx.serialization.KSerializer
|
||||||
|
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||||
|
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||||
|
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||||
|
import kotlinx.serialization.encoding.Decoder
|
||||||
|
import kotlinx.serialization.encoding.Encoder
|
||||||
|
import java.net.URI
|
||||||
|
|
||||||
|
object UriSerializer : KSerializer<URI> {
|
||||||
|
override fun deserialize(decoder: Decoder): URI = URI.create(decoder.decodeString())
|
||||||
|
|
||||||
|
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("URI", PrimitiveKind.STRING)
|
||||||
|
|
||||||
|
override fun serialize(encoder: Encoder, value: URI) {
|
||||||
|
encoder.encodeString(value.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,8 +1,12 @@
|
|||||||
package io.bkbn.kompendium.oas.server
|
package io.bkbn.kompendium.oas.server
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.oas.serialization.UriSerializer
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class Server(
|
data class Server(
|
||||||
|
@Serializable(with = UriSerializer::class)
|
||||||
val url: URI,
|
val url: URI,
|
||||||
val description: String? = null,
|
val description: String? = null,
|
||||||
var variables: Map<String, ServerVariable>? = null
|
var variables: Map<String, ServerVariable>? = null
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
package io.bkbn.kompendium.oas.server
|
package io.bkbn.kompendium.oas.server
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class ServerVariable(
|
data class ServerVariable(
|
||||||
val `enum`: Set<String>, // todo enforce not empty
|
val `enum`: Set<String>, // todo enforce not empty
|
||||||
val default: String,
|
val default: String,
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
plugins {
|
plugins {
|
||||||
kotlin("plugin.serialization") version "1.6.0"
|
kotlin("jvm")
|
||||||
application
|
kotlin("plugin.serialization")
|
||||||
|
id("io.bkbn.sourdough.application.jvm")
|
||||||
|
id("application")
|
||||||
|
}
|
||||||
|
|
||||||
|
sourdough {
|
||||||
|
compilerArgs.set(listOf("-opt-in=kotlin.RequiresOptIn"))
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@ -18,10 +24,23 @@ dependencies {
|
|||||||
implementation(group = "io.ktor", name = "ktor-auth", version = ktorVersion)
|
implementation(group = "io.ktor", name = "ktor-auth", version = ktorVersion)
|
||||||
implementation(group = "io.ktor", name = "ktor-auth-jwt", version = ktorVersion)
|
implementation(group = "io.ktor", name = "ktor-auth-jwt", version = ktorVersion)
|
||||||
implementation(group = "io.ktor", name = "ktor-serialization", version = ktorVersion)
|
implementation(group = "io.ktor", name = "ktor-serialization", version = ktorVersion)
|
||||||
|
implementation(group = "io.ktor", name = "ktor-jackson", version = ktorVersion)
|
||||||
|
implementation(group = "io.ktor", name = "ktor-gson", version = ktorVersion)
|
||||||
implementation(group = "io.ktor", name = "ktor-locations", version = ktorVersion)
|
implementation(group = "io.ktor", name = "ktor-locations", version = ktorVersion)
|
||||||
implementation(group = "io.ktor", name = "ktor-webjars", version = ktorVersion)
|
implementation(group = "io.ktor", name = "ktor-webjars", version = ktorVersion)
|
||||||
|
|
||||||
|
// Logging
|
||||||
|
implementation("org.apache.logging.log4j:log4j-api-kotlin:1.1.0")
|
||||||
|
implementation("org.apache.logging.log4j:log4j-api:2.17.1")
|
||||||
|
implementation("org.apache.logging.log4j:log4j-core:2.17.1")
|
||||||
|
implementation("org.slf4j:slf4j-api:1.7.33")
|
||||||
|
implementation("org.slf4j:slf4j-simple:1.7.33")
|
||||||
|
|
||||||
|
|
||||||
implementation(group = "org.jetbrains.kotlinx", "kotlinx-serialization-json", version = "1.3.1")
|
implementation(group = "org.jetbrains.kotlinx", "kotlinx-serialization-json", version = "1.3.1")
|
||||||
|
|
||||||
implementation(group = "joda-time", name = "joda-time", version = "2.10.13")
|
implementation(group = "joda-time", name = "joda-time", version = "2.10.13")
|
||||||
}
|
}
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
@ -7,12 +7,8 @@ import io.bkbn.kompendium.core.Notarized.notarizedGet
|
|||||||
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
||||||
import io.bkbn.kompendium.core.metadata.method.GetInfo
|
import io.bkbn.kompendium.core.metadata.method.GetInfo
|
||||||
import io.bkbn.kompendium.core.routes.redoc
|
import io.bkbn.kompendium.core.routes.redoc
|
||||||
import io.bkbn.kompendium.oas.OpenApiSpec
|
|
||||||
import io.bkbn.kompendium.oas.info.Contact
|
|
||||||
import io.bkbn.kompendium.oas.info.Info
|
|
||||||
import io.bkbn.kompendium.oas.info.License
|
|
||||||
import io.bkbn.kompendium.oas.server.Server
|
|
||||||
import io.bkbn.kompendium.playground.AuthPlaygroundToC.simpleAuthenticatedGet
|
import io.bkbn.kompendium.playground.AuthPlaygroundToC.simpleAuthenticatedGet
|
||||||
|
import io.bkbn.kompendium.playground.util.Util
|
||||||
import io.ktor.application.Application
|
import io.ktor.application.Application
|
||||||
import io.ktor.application.call
|
import io.ktor.application.call
|
||||||
import io.ktor.application.install
|
import io.ktor.application.install
|
||||||
@ -27,7 +23,6 @@ import io.ktor.serialization.json
|
|||||||
import io.ktor.server.engine.embeddedServer
|
import io.ktor.server.engine.embeddedServer
|
||||||
import io.ktor.server.netty.Netty
|
import io.ktor.server.netty.Netty
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import java.net.URI
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Application entrypoint. Run this and head on over to `localhost:8081/docs`
|
* Application entrypoint. Run this and head on over to `localhost:8081/docs`
|
||||||
@ -44,10 +39,10 @@ fun main() {
|
|||||||
// Application Module
|
// Application Module
|
||||||
private fun Application.mainModule() {
|
private fun Application.mainModule() {
|
||||||
install(ContentNegotiation) {
|
install(ContentNegotiation) {
|
||||||
json()
|
json(json = Util.kotlinxConfig)
|
||||||
}
|
}
|
||||||
install(Kompendium) {
|
install(Kompendium) {
|
||||||
spec = AuthMetadata.spec
|
spec = Util.baseSpec
|
||||||
}
|
}
|
||||||
install(Authentication) {
|
install(Authentication) {
|
||||||
// We can leverage the security config name to prevent typos
|
// We can leverage the security config name to prevent typos
|
||||||
@ -73,36 +68,6 @@ private fun Application.mainModule() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object AuthMetadata {
|
|
||||||
val spec = OpenApiSpec(
|
|
||||||
info = Info(
|
|
||||||
title = "Simple API with documented Authentication",
|
|
||||||
version = "1.33.7",
|
|
||||||
description = "Wow isn't this cool?",
|
|
||||||
termsOfService = URI("https://example.com"),
|
|
||||||
contact = Contact(
|
|
||||||
name = "Homer Simpson",
|
|
||||||
email = "chunkylover53@aol.com",
|
|
||||||
url = URI("https://gph.is/1NPUDiM")
|
|
||||||
),
|
|
||||||
license = License(
|
|
||||||
name = "MIT",
|
|
||||||
url = URI("https://github.com/bkbnio/kompendium/blob/main/LICENSE")
|
|
||||||
)
|
|
||||||
),
|
|
||||||
servers = mutableListOf(
|
|
||||||
Server(
|
|
||||||
url = URI("https://myawesomeapi.com"),
|
|
||||||
description = "Production instance of my API"
|
|
||||||
),
|
|
||||||
Server(
|
|
||||||
url = URI("https://staging.myawesomeapi.com"),
|
|
||||||
description = "Where the fun stuff happens"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is where we define the available security configurations for our app
|
// This is where we define the available security configurations for our app
|
||||||
object SecurityConfigurations {
|
object SecurityConfigurations {
|
||||||
val basic = object : BasicAuthConfiguration {
|
val basic = object : BasicAuthConfiguration {
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package io.bkbn.kompendium.playground
|
package io.bkbn.kompendium.playground
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
import io.bkbn.kompendium.annotations.Field
|
import io.bkbn.kompendium.annotations.Field
|
||||||
import io.bkbn.kompendium.annotations.Param
|
import io.bkbn.kompendium.annotations.Param
|
||||||
import io.bkbn.kompendium.annotations.ParamType
|
import io.bkbn.kompendium.annotations.ParamType
|
||||||
@ -13,18 +15,15 @@ import io.bkbn.kompendium.core.metadata.method.DeleteInfo
|
|||||||
import io.bkbn.kompendium.core.metadata.method.GetInfo
|
import io.bkbn.kompendium.core.metadata.method.GetInfo
|
||||||
import io.bkbn.kompendium.core.metadata.method.PostInfo
|
import io.bkbn.kompendium.core.metadata.method.PostInfo
|
||||||
import io.bkbn.kompendium.core.routes.redoc
|
import io.bkbn.kompendium.core.routes.redoc
|
||||||
import io.bkbn.kompendium.oas.OpenApiSpec
|
import io.bkbn.kompendium.oas.serialization.KompendiumSerializersModule
|
||||||
import io.bkbn.kompendium.oas.info.Contact
|
|
||||||
import io.bkbn.kompendium.oas.info.Info
|
|
||||||
import io.bkbn.kompendium.oas.info.License
|
|
||||||
import io.bkbn.kompendium.oas.server.Server
|
|
||||||
import io.bkbn.kompendium.playground.BasicModels.BasicParameters
|
import io.bkbn.kompendium.playground.BasicModels.BasicParameters
|
||||||
import io.bkbn.kompendium.playground.BasicModels.BasicResponse
|
|
||||||
import io.bkbn.kompendium.playground.BasicModels.BasicRequest
|
import io.bkbn.kompendium.playground.BasicModels.BasicRequest
|
||||||
|
import io.bkbn.kompendium.playground.BasicModels.BasicResponse
|
||||||
import io.bkbn.kompendium.playground.BasicPlaygroundToC.simpleDeleteRequest
|
import io.bkbn.kompendium.playground.BasicPlaygroundToC.simpleDeleteRequest
|
||||||
import io.bkbn.kompendium.playground.BasicPlaygroundToC.simpleGetExample
|
import io.bkbn.kompendium.playground.BasicPlaygroundToC.simpleGetExample
|
||||||
import io.bkbn.kompendium.playground.BasicPlaygroundToC.simpleGetExampleWithParameters
|
import io.bkbn.kompendium.playground.BasicPlaygroundToC.simpleGetExampleWithParameters
|
||||||
import io.bkbn.kompendium.playground.BasicPlaygroundToC.simplePostRequest
|
import io.bkbn.kompendium.playground.BasicPlaygroundToC.simplePostRequest
|
||||||
|
import io.bkbn.kompendium.playground.util.Util
|
||||||
import io.ktor.application.Application
|
import io.ktor.application.Application
|
||||||
import io.ktor.application.call
|
import io.ktor.application.call
|
||||||
import io.ktor.application.install
|
import io.ktor.application.install
|
||||||
@ -37,8 +36,9 @@ import io.ktor.routing.routing
|
|||||||
import io.ktor.serialization.json
|
import io.ktor.serialization.json
|
||||||
import io.ktor.server.engine.embeddedServer
|
import io.ktor.server.engine.embeddedServer
|
||||||
import io.ktor.server.netty.Netty
|
import io.ktor.server.netty.Netty
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import java.net.URI
|
import kotlinx.serialization.json.Json
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -57,11 +57,11 @@ fun main() {
|
|||||||
private fun Application.mainModule() {
|
private fun Application.mainModule() {
|
||||||
// Installs Simple JSON Content Negotiation
|
// Installs Simple JSON Content Negotiation
|
||||||
install(ContentNegotiation) {
|
install(ContentNegotiation) {
|
||||||
json()
|
json(json = Util.kotlinxConfig)
|
||||||
}
|
}
|
||||||
// Installs the Kompendium Plugin and sets up baseline server metadata
|
// Installs the Kompendium Plugin and sets up baseline server metadata
|
||||||
install(Kompendium) {
|
install(Kompendium) {
|
||||||
spec = BasicMetadata.spec
|
spec = Util.baseSpec
|
||||||
}
|
}
|
||||||
// Configures the routes for our API
|
// Configures the routes for our API
|
||||||
routing {
|
routing {
|
||||||
@ -161,38 +161,6 @@ object BasicPlaygroundToC {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Contains the root metadata for our server. This is all the stuff that is defined once
|
|
||||||
// and cannot be inferred from the Ktor application
|
|
||||||
object BasicMetadata {
|
|
||||||
val spec = OpenApiSpec(
|
|
||||||
info = Info(
|
|
||||||
title = "Simple Demo API",
|
|
||||||
version = "1.33.7",
|
|
||||||
description = "Wow isn't this cool?",
|
|
||||||
termsOfService = URI("https://example.com"),
|
|
||||||
contact = Contact(
|
|
||||||
name = "Homer Simpson",
|
|
||||||
email = "chunkylover53@aol.com",
|
|
||||||
url = URI("https://gph.is/1NPUDiM")
|
|
||||||
),
|
|
||||||
license = License(
|
|
||||||
name = "MIT",
|
|
||||||
url = URI("https://github.com/bkbnio/kompendium/blob/main/LICENSE")
|
|
||||||
)
|
|
||||||
),
|
|
||||||
servers = mutableListOf(
|
|
||||||
Server(
|
|
||||||
url = URI("https://myawesomeapi.com"),
|
|
||||||
description = "Production instance of my API"
|
|
||||||
),
|
|
||||||
Server(
|
|
||||||
url = URI("https://staging.myawesomeapi.com"),
|
|
||||||
description = "Where the fun stuff happens"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
object BasicModels {
|
object BasicModels {
|
||||||
@Serializable
|
@Serializable
|
||||||
data class BasicResponse(val c: String)
|
data class BasicResponse(val c: String)
|
||||||
@ -207,6 +175,9 @@ object BasicModels {
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class BasicRequest(
|
data class BasicRequest(
|
||||||
|
@JsonProperty("best_field")
|
||||||
|
@SerializedName("best_field")
|
||||||
|
@SerialName("best_field")
|
||||||
@Field(description = "This is a super important field!!", name = "best_field")
|
@Field(description = "This is a super important field!!", name = "best_field")
|
||||||
val d: Boolean
|
val d: Boolean
|
||||||
)
|
)
|
||||||
|
@ -23,16 +23,12 @@ import io.bkbn.kompendium.core.metadata.ResponseInfo
|
|||||||
import io.bkbn.kompendium.core.metadata.method.GetInfo
|
import io.bkbn.kompendium.core.metadata.method.GetInfo
|
||||||
import io.bkbn.kompendium.core.metadata.method.PostInfo
|
import io.bkbn.kompendium.core.metadata.method.PostInfo
|
||||||
import io.bkbn.kompendium.core.routes.redoc
|
import io.bkbn.kompendium.core.routes.redoc
|
||||||
import io.bkbn.kompendium.oas.OpenApiSpec
|
|
||||||
import io.bkbn.kompendium.oas.info.Contact
|
|
||||||
import io.bkbn.kompendium.oas.info.Info
|
|
||||||
import io.bkbn.kompendium.oas.info.License
|
|
||||||
import io.bkbn.kompendium.oas.server.Server
|
|
||||||
import io.bkbn.kompendium.playground.ConstrainedModels.ConstrainedParams
|
import io.bkbn.kompendium.playground.ConstrainedModels.ConstrainedParams
|
||||||
import io.bkbn.kompendium.playground.ConstrainedModels.ConstrainedRequest
|
import io.bkbn.kompendium.playground.ConstrainedModels.ConstrainedRequest
|
||||||
import io.bkbn.kompendium.playground.ConstrainedModels.ConstrainedResponse
|
import io.bkbn.kompendium.playground.ConstrainedModels.ConstrainedResponse
|
||||||
import io.bkbn.kompendium.playground.ConstrainedPlaygroundToC.simpleConstrainedGet
|
import io.bkbn.kompendium.playground.ConstrainedPlaygroundToC.simpleConstrainedGet
|
||||||
import io.bkbn.kompendium.playground.ConstrainedPlaygroundToC.simpleConstrainedPost
|
import io.bkbn.kompendium.playground.ConstrainedPlaygroundToC.simpleConstrainedPost
|
||||||
|
import io.bkbn.kompendium.playground.util.Util
|
||||||
import io.ktor.application.Application
|
import io.ktor.application.Application
|
||||||
import io.ktor.application.call
|
import io.ktor.application.call
|
||||||
import io.ktor.application.install
|
import io.ktor.application.install
|
||||||
@ -45,7 +41,6 @@ import io.ktor.server.engine.embeddedServer
|
|||||||
import io.ktor.server.netty.Netty
|
import io.ktor.server.netty.Netty
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.json.JsonElement
|
import kotlinx.serialization.json.JsonElement
|
||||||
import java.net.URI
|
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
embeddedServer(
|
embeddedServer(
|
||||||
@ -59,11 +54,11 @@ fun main() {
|
|||||||
private fun Application.mainModule() {
|
private fun Application.mainModule() {
|
||||||
// Installs Simple JSON Content Negotiation
|
// Installs Simple JSON Content Negotiation
|
||||||
install(ContentNegotiation) {
|
install(ContentNegotiation) {
|
||||||
json()
|
json(json = Util.kotlinxConfig)
|
||||||
}
|
}
|
||||||
// Installs the Kompendium Plugin and sets up baseline server metadata
|
// Installs the Kompendium Plugin and sets up baseline server metadata
|
||||||
install(Kompendium) {
|
install(Kompendium) {
|
||||||
spec = ConstrainedMetadata.spec
|
spec = Util.baseSpec
|
||||||
}
|
}
|
||||||
// Configures the routes for our API
|
// Configures the routes for our API
|
||||||
routing {
|
routing {
|
||||||
@ -104,36 +99,6 @@ object ConstrainedPlaygroundToC {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
object ConstrainedMetadata {
|
|
||||||
val spec = OpenApiSpec(
|
|
||||||
info = Info(
|
|
||||||
title = "Simple Demo API",
|
|
||||||
version = "1.33.7",
|
|
||||||
description = "Wow isn't this cool?",
|
|
||||||
termsOfService = URI("https://example.com"),
|
|
||||||
contact = Contact(
|
|
||||||
name = "Homer Simpson",
|
|
||||||
email = "chunkylover53@aol.com",
|
|
||||||
url = URI("https://gph.is/1NPUDiM")
|
|
||||||
),
|
|
||||||
license = License(
|
|
||||||
name = "MIT",
|
|
||||||
url = URI("https://github.com/bkbnio/kompendium/blob/main/LICENSE")
|
|
||||||
)
|
|
||||||
),
|
|
||||||
servers = mutableListOf(
|
|
||||||
Server(
|
|
||||||
url = URI("https://myawesomeapi.com"),
|
|
||||||
description = "Production instance of my API"
|
|
||||||
),
|
|
||||||
Server(
|
|
||||||
url = URI("https://staging.myawesomeapi.com"),
|
|
||||||
description = "Where the fun stuff happens"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
object ConstrainedModels {
|
object ConstrainedModels {
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ConstrainedResponse(
|
data class ConstrainedResponse(
|
||||||
|
@ -6,12 +6,8 @@ import io.bkbn.kompendium.core.metadata.ExceptionInfo
|
|||||||
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
||||||
import io.bkbn.kompendium.core.metadata.method.GetInfo
|
import io.bkbn.kompendium.core.metadata.method.GetInfo
|
||||||
import io.bkbn.kompendium.core.routes.redoc
|
import io.bkbn.kompendium.core.routes.redoc
|
||||||
import io.bkbn.kompendium.oas.OpenApiSpec
|
|
||||||
import io.bkbn.kompendium.oas.info.Contact
|
|
||||||
import io.bkbn.kompendium.oas.info.Info
|
|
||||||
import io.bkbn.kompendium.oas.info.License
|
|
||||||
import io.bkbn.kompendium.oas.server.Server
|
|
||||||
import io.bkbn.kompendium.playground.ExceptionPlaygroundToC.simpleGetExample
|
import io.bkbn.kompendium.playground.ExceptionPlaygroundToC.simpleGetExample
|
||||||
|
import io.bkbn.kompendium.playground.util.Util
|
||||||
import io.ktor.application.Application
|
import io.ktor.application.Application
|
||||||
import io.ktor.application.call
|
import io.ktor.application.call
|
||||||
import io.ktor.application.install
|
import io.ktor.application.install
|
||||||
@ -25,7 +21,6 @@ import io.ktor.server.engine.embeddedServer
|
|||||||
import io.ktor.server.netty.Netty
|
import io.ktor.server.netty.Netty
|
||||||
import kotlin.reflect.typeOf
|
import kotlin.reflect.typeOf
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import java.net.URI
|
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
// Application Entrypoint
|
// Application Entrypoint
|
||||||
@ -41,11 +36,11 @@ fun main() {
|
|||||||
private fun Application.mainModule() {
|
private fun Application.mainModule() {
|
||||||
// Installs Simple JSON Content Negotiation
|
// Installs Simple JSON Content Negotiation
|
||||||
install(ContentNegotiation) {
|
install(ContentNegotiation) {
|
||||||
json()
|
json(json = Util.kotlinxConfig)
|
||||||
}
|
}
|
||||||
// Installs the Kompendium Plugin and sets up baseline server metadata
|
// Installs the Kompendium Plugin and sets up baseline server metadata
|
||||||
install(Kompendium) {
|
install(Kompendium) {
|
||||||
spec = ExceptionMetadata.spec
|
spec = Util.baseSpec
|
||||||
}
|
}
|
||||||
install(StatusPages) {
|
install(StatusPages) {
|
||||||
exception<ExceptionModels.BadUserException> {
|
exception<ExceptionModels.BadUserException> {
|
||||||
@ -70,36 +65,6 @@ private fun Application.mainModule() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object ExceptionMetadata {
|
|
||||||
val spec = OpenApiSpec(
|
|
||||||
info = Info(
|
|
||||||
title = "Simple Demo API with notarized exceptions",
|
|
||||||
version = "1.33.7",
|
|
||||||
description = "Wow isn't this cool?",
|
|
||||||
termsOfService = URI("https://example.com"),
|
|
||||||
contact = Contact(
|
|
||||||
name = "Homer Simpson",
|
|
||||||
email = "chunkylover53@aol.com",
|
|
||||||
url = URI("https://gph.is/1NPUDiM")
|
|
||||||
),
|
|
||||||
license = License(
|
|
||||||
name = "MIT",
|
|
||||||
url = URI("https://github.com/bkbnio/kompendium/blob/main/LICENSE")
|
|
||||||
)
|
|
||||||
),
|
|
||||||
servers = mutableListOf(
|
|
||||||
Server(
|
|
||||||
url = URI("https://myawesomeapi.com"),
|
|
||||||
description = "Production instance of my API"
|
|
||||||
),
|
|
||||||
Server(
|
|
||||||
url = URI("https://staging.myawesomeapi.com"),
|
|
||||||
description = "Where the fun stuff happens"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is a table of contents to hold all the metadata for our various API endpoints
|
// This is a table of contents to hold all the metadata for our various API endpoints
|
||||||
object ExceptionPlaygroundToC {
|
object ExceptionPlaygroundToC {
|
||||||
private val simpleException = ExceptionInfo<ExceptionModels.ExceptionResponse>(
|
private val simpleException = ExceptionInfo<ExceptionModels.ExceptionResponse>(
|
||||||
|
@ -5,12 +5,8 @@ import io.bkbn.kompendium.core.Notarized.notarizedGet
|
|||||||
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
||||||
import io.bkbn.kompendium.core.metadata.method.GetInfo
|
import io.bkbn.kompendium.core.metadata.method.GetInfo
|
||||||
import io.bkbn.kompendium.core.routes.redoc
|
import io.bkbn.kompendium.core.routes.redoc
|
||||||
import io.bkbn.kompendium.oas.OpenApiSpec
|
|
||||||
import io.bkbn.kompendium.oas.info.Contact
|
|
||||||
import io.bkbn.kompendium.oas.info.Info
|
|
||||||
import io.bkbn.kompendium.oas.info.License
|
|
||||||
import io.bkbn.kompendium.oas.server.Server
|
|
||||||
import io.bkbn.kompendium.playground.GenericPlaygroundToC.simpleGenericGet
|
import io.bkbn.kompendium.playground.GenericPlaygroundToC.simpleGenericGet
|
||||||
|
import io.bkbn.kompendium.playground.util.Util
|
||||||
import io.ktor.application.Application
|
import io.ktor.application.Application
|
||||||
import io.ktor.application.call
|
import io.ktor.application.call
|
||||||
import io.ktor.application.install
|
import io.ktor.application.install
|
||||||
@ -22,7 +18,6 @@ import io.ktor.serialization.json
|
|||||||
import io.ktor.server.engine.embeddedServer
|
import io.ktor.server.engine.embeddedServer
|
||||||
import io.ktor.server.netty.Netty
|
import io.ktor.server.netty.Netty
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import java.net.URI
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Application entrypoint. Run this and head on over to `localhost:8081/docs`
|
* Application entrypoint. Run this and head on over to `localhost:8081/docs`
|
||||||
@ -40,11 +35,11 @@ fun main() {
|
|||||||
private fun Application.mainModule() {
|
private fun Application.mainModule() {
|
||||||
// Installs Simple JSON Content Negotiation
|
// Installs Simple JSON Content Negotiation
|
||||||
install(ContentNegotiation) {
|
install(ContentNegotiation) {
|
||||||
json()
|
json(json = Util.kotlinxConfig)
|
||||||
}
|
}
|
||||||
// Installs the Kompendium Plugin and sets up baseline server metadata
|
// Installs the Kompendium Plugin and sets up baseline server metadata
|
||||||
install(Kompendium) {
|
install(Kompendium) {
|
||||||
spec = GenericMetadata.spec
|
spec = Util.baseSpec
|
||||||
}
|
}
|
||||||
routing {
|
routing {
|
||||||
redoc(pageTitle = "Simple API Docs")
|
redoc(pageTitle = "Simple API Docs")
|
||||||
@ -70,38 +65,6 @@ object GenericPlaygroundToC {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Contains the root metadata for our server. This is all the stuff that is defined once
|
|
||||||
// and cannot be inferred from the Ktor application
|
|
||||||
object GenericMetadata {
|
|
||||||
val spec = OpenApiSpec(
|
|
||||||
info = Info(
|
|
||||||
title = "Simple Demo API with Generic Data",
|
|
||||||
version = "1.33.7",
|
|
||||||
description = "Wow isn't this cool?",
|
|
||||||
termsOfService = URI("https://example.com"),
|
|
||||||
contact = Contact(
|
|
||||||
name = "Homer Simpson",
|
|
||||||
email = "chunkylover53@aol.com",
|
|
||||||
url = URI("https://gph.is/1NPUDiM")
|
|
||||||
),
|
|
||||||
license = License(
|
|
||||||
name = "MIT",
|
|
||||||
url = URI("https://github.com/bkbnio/kompendium/blob/main/LICENSE")
|
|
||||||
)
|
|
||||||
),
|
|
||||||
servers = mutableListOf(
|
|
||||||
Server(
|
|
||||||
url = URI("https://myawesomeapi.com"),
|
|
||||||
description = "Production instance of my API"
|
|
||||||
),
|
|
||||||
Server(
|
|
||||||
url = URI("https://staging.myawesomeapi.com"),
|
|
||||||
description = "Where the fun stuff happens"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
object GenericModels {
|
object GenericModels {
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Foosy<T, K>(val test: T, val otherThing: List<K>)
|
data class Foosy<T, K>(val test: T, val otherThing: List<K>)
|
||||||
|
@ -0,0 +1,49 @@
|
|||||||
|
package io.bkbn.kompendium.playground
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.core.Kompendium
|
||||||
|
import io.bkbn.kompendium.core.Notarized.notarizedPost
|
||||||
|
import io.bkbn.kompendium.core.routes.redoc
|
||||||
|
import io.bkbn.kompendium.playground.util.Util
|
||||||
|
import io.ktor.application.Application
|
||||||
|
import io.ktor.application.call
|
||||||
|
import io.ktor.application.install
|
||||||
|
import io.ktor.features.ContentNegotiation
|
||||||
|
import io.ktor.gson.gson
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.request.receive
|
||||||
|
import io.ktor.response.respond
|
||||||
|
import io.ktor.routing.route
|
||||||
|
import io.ktor.routing.routing
|
||||||
|
import io.ktor.server.engine.embeddedServer
|
||||||
|
import io.ktor.server.netty.Netty
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
embeddedServer(
|
||||||
|
Netty,
|
||||||
|
port = 8081,
|
||||||
|
module = Application::mainModule
|
||||||
|
).start(wait = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Application.mainModule() {
|
||||||
|
install(ContentNegotiation) {
|
||||||
|
gson {
|
||||||
|
setPrettyPrinting()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
install(Kompendium) {
|
||||||
|
spec = Util.baseSpec
|
||||||
|
}
|
||||||
|
routing {
|
||||||
|
redoc()
|
||||||
|
route("/create") {
|
||||||
|
notarizedPost(BasicPlaygroundToC.simplePostRequest) {
|
||||||
|
val request = call.receive<BasicModels.BasicRequest>()
|
||||||
|
when (request.d) {
|
||||||
|
true -> call.respond(HttpStatusCode.OK, BasicModels.BasicResponse(c = "So it is true!"))
|
||||||
|
false -> call.respond(HttpStatusCode.OK, BasicModels.BasicResponse(c = "Oh, I knew it!"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
package io.bkbn.kompendium.playground
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude
|
||||||
|
import com.fasterxml.jackson.databind.SerializationFeature
|
||||||
|
import io.bkbn.kompendium.core.Kompendium
|
||||||
|
import io.bkbn.kompendium.core.Notarized.notarizedPost
|
||||||
|
import io.bkbn.kompendium.core.routes.redoc
|
||||||
|
import io.bkbn.kompendium.playground.util.Util
|
||||||
|
import io.ktor.application.Application
|
||||||
|
import io.ktor.application.call
|
||||||
|
import io.ktor.application.install
|
||||||
|
import io.ktor.features.ContentNegotiation
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.jackson.jackson
|
||||||
|
import io.ktor.request.receive
|
||||||
|
import io.ktor.response.respond
|
||||||
|
import io.ktor.routing.route
|
||||||
|
import io.ktor.routing.routing
|
||||||
|
import io.ktor.server.engine.embeddedServer
|
||||||
|
import io.ktor.server.netty.Netty
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
embeddedServer(
|
||||||
|
Netty,
|
||||||
|
port = 8081,
|
||||||
|
module = Application::mainModule
|
||||||
|
).start(wait = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Application.mainModule() {
|
||||||
|
install(ContentNegotiation) {
|
||||||
|
jackson {
|
||||||
|
enable(SerializationFeature.INDENT_OUTPUT)
|
||||||
|
setSerializationInclusion(JsonInclude.Include.NON_NULL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
install(Kompendium) {
|
||||||
|
spec = Util.baseSpec
|
||||||
|
}
|
||||||
|
routing {
|
||||||
|
redoc()
|
||||||
|
route("/create") {
|
||||||
|
notarizedPost(BasicPlaygroundToC.simplePostRequest) {
|
||||||
|
val request = call.receive<BasicModels.BasicRequest>()
|
||||||
|
when (request.d) {
|
||||||
|
true -> call.respond(HttpStatusCode.OK, BasicModels.BasicResponse(c = "So it is true!"))
|
||||||
|
false -> call.respond(HttpStatusCode.OK, BasicModels.BasicResponse(c = "Oh, I knew it!"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,14 +7,10 @@ import io.bkbn.kompendium.core.metadata.ResponseInfo
|
|||||||
import io.bkbn.kompendium.core.metadata.method.GetInfo
|
import io.bkbn.kompendium.core.metadata.method.GetInfo
|
||||||
import io.bkbn.kompendium.core.routes.redoc
|
import io.bkbn.kompendium.core.routes.redoc
|
||||||
import io.bkbn.kompendium.locations.NotarizedLocation.notarizedGet
|
import io.bkbn.kompendium.locations.NotarizedLocation.notarizedGet
|
||||||
import io.bkbn.kompendium.oas.OpenApiSpec
|
|
||||||
import io.bkbn.kompendium.oas.info.Contact
|
|
||||||
import io.bkbn.kompendium.oas.info.Info
|
|
||||||
import io.bkbn.kompendium.oas.info.License
|
|
||||||
import io.bkbn.kompendium.oas.server.Server
|
|
||||||
import io.bkbn.kompendium.playground.LocationsToC.ohBoiUCrazy
|
import io.bkbn.kompendium.playground.LocationsToC.ohBoiUCrazy
|
||||||
import io.bkbn.kompendium.playground.LocationsToC.testLocation
|
import io.bkbn.kompendium.playground.LocationsToC.testLocation
|
||||||
import io.bkbn.kompendium.playground.LocationsToC.testNestLocation
|
import io.bkbn.kompendium.playground.LocationsToC.testNestLocation
|
||||||
|
import io.bkbn.kompendium.playground.util.Util
|
||||||
import io.ktor.application.Application
|
import io.ktor.application.Application
|
||||||
import io.ktor.application.call
|
import io.ktor.application.call
|
||||||
import io.ktor.application.install
|
import io.ktor.application.install
|
||||||
@ -28,7 +24,6 @@ import io.ktor.serialization.json
|
|||||||
import io.ktor.server.engine.embeddedServer
|
import io.ktor.server.engine.embeddedServer
|
||||||
import io.ktor.server.netty.Netty
|
import io.ktor.server.netty.Netty
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import java.net.URI
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Application entrypoint. Run this and head on over to `localhost:8081/docs`
|
* Application entrypoint. Run this and head on over to `localhost:8081/docs`
|
||||||
@ -44,10 +39,10 @@ fun main() {
|
|||||||
|
|
||||||
private fun Application.mainModule() {
|
private fun Application.mainModule() {
|
||||||
install(ContentNegotiation) {
|
install(ContentNegotiation) {
|
||||||
json()
|
json(json = Util.kotlinxConfig)
|
||||||
}
|
}
|
||||||
install(Kompendium) {
|
install(Kompendium) {
|
||||||
spec = LocationMetadata.spec
|
spec = Util.baseSpec
|
||||||
}
|
}
|
||||||
install(Locations)
|
install(Locations)
|
||||||
routing {
|
routing {
|
||||||
@ -118,38 +113,6 @@ data class TestLocations(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Contains the root metadata for our server. This is all the stuff that is defined once
|
|
||||||
// and cannot be inferred from the Ktor application
|
|
||||||
object LocationMetadata {
|
|
||||||
val spec = OpenApiSpec(
|
|
||||||
info = Info(
|
|
||||||
title = "Simple Demo Leveraging Ktor Locations",
|
|
||||||
version = "1.33.7",
|
|
||||||
description = "Wow isn't this cool?",
|
|
||||||
termsOfService = URI("https://example.com"),
|
|
||||||
contact = Contact(
|
|
||||||
name = "Homer Simpson",
|
|
||||||
email = "chunkylover53@aol.com",
|
|
||||||
url = URI("https://gph.is/1NPUDiM")
|
|
||||||
),
|
|
||||||
license = License(
|
|
||||||
name = "MIT",
|
|
||||||
url = URI("https://github.com/bkbnio/kompendium/blob/main/LICENSE")
|
|
||||||
)
|
|
||||||
),
|
|
||||||
servers = mutableListOf(
|
|
||||||
Server(
|
|
||||||
url = URI("https://myawesomeapi.com"),
|
|
||||||
description = "Production instance of my API"
|
|
||||||
),
|
|
||||||
Server(
|
|
||||||
url = URI("https://staging.myawesomeapi.com"),
|
|
||||||
description = "Where the fun stuff happens"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
object LocationModels {
|
object LocationModels {
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ExampleResponse(val c: String)
|
data class ExampleResponse(val c: String)
|
||||||
|
@ -5,12 +5,8 @@ import io.bkbn.kompendium.core.Notarized.notarizedGet
|
|||||||
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
||||||
import io.bkbn.kompendium.core.metadata.method.GetInfo
|
import io.bkbn.kompendium.core.metadata.method.GetInfo
|
||||||
import io.bkbn.kompendium.core.routes.redoc
|
import io.bkbn.kompendium.core.routes.redoc
|
||||||
import io.bkbn.kompendium.oas.OpenApiSpec
|
|
||||||
import io.bkbn.kompendium.oas.info.Contact
|
|
||||||
import io.bkbn.kompendium.oas.info.Info
|
|
||||||
import io.bkbn.kompendium.oas.info.License
|
|
||||||
import io.bkbn.kompendium.oas.server.Server
|
|
||||||
import io.bkbn.kompendium.playground.PolymorphicPlaygroundToC.polymorphicExample
|
import io.bkbn.kompendium.playground.PolymorphicPlaygroundToC.polymorphicExample
|
||||||
|
import io.bkbn.kompendium.playground.util.Util
|
||||||
import io.ktor.application.Application
|
import io.ktor.application.Application
|
||||||
import io.ktor.application.call
|
import io.ktor.application.call
|
||||||
import io.ktor.application.install
|
import io.ktor.application.install
|
||||||
@ -22,7 +18,6 @@ import io.ktor.serialization.json
|
|||||||
import io.ktor.server.engine.embeddedServer
|
import io.ktor.server.engine.embeddedServer
|
||||||
import io.ktor.server.netty.Netty
|
import io.ktor.server.netty.Netty
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import java.net.URI
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Application entrypoint. Run this and head on over to `localhost:8081/docs`
|
* Application entrypoint. Run this and head on over to `localhost:8081/docs`
|
||||||
@ -39,11 +34,11 @@ fun main() {
|
|||||||
private fun Application.mainModule() {
|
private fun Application.mainModule() {
|
||||||
// Installs Simple JSON Content Negotiation
|
// Installs Simple JSON Content Negotiation
|
||||||
install(ContentNegotiation) {
|
install(ContentNegotiation) {
|
||||||
json()
|
json(json = Util.kotlinxConfig)
|
||||||
}
|
}
|
||||||
// Installs the Kompendium Plugin and sets up baseline server metadata
|
// Installs the Kompendium Plugin and sets up baseline server metadata
|
||||||
install(Kompendium) {
|
install(Kompendium) {
|
||||||
spec = PolymorphicMetadata.spec
|
spec = Util.baseSpec
|
||||||
}
|
}
|
||||||
// Configures the routes for our API
|
// Configures the routes for our API
|
||||||
routing {
|
routing {
|
||||||
@ -70,36 +65,6 @@ object PolymorphicPlaygroundToC {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
object PolymorphicMetadata {
|
|
||||||
val spec = OpenApiSpec(
|
|
||||||
info = Info(
|
|
||||||
title = "Simple Demo API with Polymorphic Models",
|
|
||||||
version = "1.33.7",
|
|
||||||
description = "Wow isn't this cool?",
|
|
||||||
termsOfService = URI("https://example.com"),
|
|
||||||
contact = Contact(
|
|
||||||
name = "Homer Simpson",
|
|
||||||
email = "chunkylover53@aol.com",
|
|
||||||
url = URI("https://gph.is/1NPUDiM")
|
|
||||||
),
|
|
||||||
license = License(
|
|
||||||
name = "MIT",
|
|
||||||
url = URI("https://github.com/bkbnio/kompendium/blob/main/LICENSE")
|
|
||||||
)
|
|
||||||
),
|
|
||||||
servers = mutableListOf(
|
|
||||||
Server(
|
|
||||||
url = URI("https://myawesomeapi.com"),
|
|
||||||
description = "Production instance of my API"
|
|
||||||
),
|
|
||||||
Server(
|
|
||||||
url = URI("https://staging.myawesomeapi.com"),
|
|
||||||
description = "Where the fun stuff happens"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
object PolymorphicModels {
|
object PolymorphicModels {
|
||||||
sealed interface SlammaJamma
|
sealed interface SlammaJamma
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ package io.bkbn.kompendium.playground
|
|||||||
|
|
||||||
import io.bkbn.kompendium.core.Kompendium
|
import io.bkbn.kompendium.core.Kompendium
|
||||||
import io.bkbn.kompendium.core.Notarized.notarizedGet
|
import io.bkbn.kompendium.core.Notarized.notarizedGet
|
||||||
|
import io.bkbn.kompendium.playground.util.Util
|
||||||
import io.bkbn.kompendium.swagger.swaggerUI
|
import io.bkbn.kompendium.swagger.swaggerUI
|
||||||
import io.ktor.application.Application
|
import io.ktor.application.Application
|
||||||
import io.ktor.application.call
|
import io.ktor.application.call
|
||||||
@ -32,12 +33,12 @@ fun main() {
|
|||||||
private fun Application.mainModule() {
|
private fun Application.mainModule() {
|
||||||
// Installs Simple JSON Content Negotiation
|
// Installs Simple JSON Content Negotiation
|
||||||
install(ContentNegotiation) {
|
install(ContentNegotiation) {
|
||||||
json()
|
json(json = Util.kotlinxConfig)
|
||||||
}
|
}
|
||||||
install(Webjars)
|
install(Webjars)
|
||||||
// Installs the Kompendium Plugin and sets up baseline server metadata
|
// Installs the Kompendium Plugin and sets up baseline server metadata
|
||||||
install(Kompendium) {
|
install(Kompendium) {
|
||||||
spec = BasicMetadata.spec
|
spec = Util.baseSpec
|
||||||
}
|
}
|
||||||
// Configures the routes for our API
|
// Configures the routes for our API
|
||||||
routing {
|
routing {
|
||||||
|
@ -0,0 +1,50 @@
|
|||||||
|
package io.bkbn.kompendium.playground.util
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.oas.OpenApiSpec
|
||||||
|
import io.bkbn.kompendium.oas.info.Contact
|
||||||
|
import io.bkbn.kompendium.oas.info.Info
|
||||||
|
import io.bkbn.kompendium.oas.info.License
|
||||||
|
import io.bkbn.kompendium.oas.serialization.KompendiumSerializersModule
|
||||||
|
import io.bkbn.kompendium.oas.server.Server
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import java.net.URI
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
|
object Util {
|
||||||
|
val kotlinxConfig = Json {
|
||||||
|
classDiscriminator = "class"
|
||||||
|
serializersModule = KompendiumSerializersModule.module
|
||||||
|
prettyPrint = true
|
||||||
|
explicitNulls = false
|
||||||
|
encodeDefaults = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val baseSpec = OpenApiSpec(
|
||||||
|
info = Info(
|
||||||
|
title = "Simple Demo API",
|
||||||
|
version = "1.33.7",
|
||||||
|
description = "Wow isn't this cool?",
|
||||||
|
termsOfService = URI("https://example.com"),
|
||||||
|
contact = Contact(
|
||||||
|
name = "Homer Simpson",
|
||||||
|
email = "chunkylover53@aol.com",
|
||||||
|
url = URI("https://gph.is/1NPUDiM")
|
||||||
|
),
|
||||||
|
license = License(
|
||||||
|
name = "MIT",
|
||||||
|
url = URI("https://github.com/bkbnio/kompendium/blob/main/LICENSE")
|
||||||
|
)
|
||||||
|
),
|
||||||
|
servers = mutableListOf(
|
||||||
|
Server(
|
||||||
|
url = URI("https://myawesomeapi.com"),
|
||||||
|
description = "Production instance of my API"
|
||||||
|
),
|
||||||
|
Server(
|
||||||
|
url = URI("https://staging.myawesomeapi.com"),
|
||||||
|
description = "Where the fun stuff happens"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
@ -1 +1,3 @@
|
|||||||
# Module kompendium-swagger-ui
|
# Module kompendium-swagger-ui
|
||||||
|
|
||||||
|
Contains the code necessary to launch `swagger` as your documentation frontend.
|
||||||
|
@ -1,10 +1,30 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id("io.bkbn.sourdough.library")
|
kotlin("jvm")
|
||||||
|
id("io.bkbn.sourdough.library.jvm")
|
||||||
|
id("io.gitlab.arturbosch.detekt")
|
||||||
|
id("com.adarshr.test-logger")
|
||||||
|
id("org.jetbrains.dokka")
|
||||||
|
id("maven-publish")
|
||||||
|
id("java-library")
|
||||||
|
id("signing")
|
||||||
|
}
|
||||||
|
|
||||||
|
sourdough {
|
||||||
|
libraryName.set("Kompendium Swagger")
|
||||||
|
libraryDescription.set("Offers Swagger as a bundled WebJAR for Ktor")
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
val ktorVersion: String by project
|
val ktorVersion: String by project
|
||||||
implementation(group = "io.ktor", name = "ktor-server-core", version = ktorVersion)
|
implementation(group = "io.ktor", name = "ktor-server-core", version = ktorVersion)
|
||||||
implementation(group = "io.ktor", name = "ktor-webjars", version = ktorVersion)
|
implementation(group = "io.ktor", name = "ktor-webjars", version = ktorVersion)
|
||||||
implementation(group = "org.webjars", name = "swagger-ui", version = "4.1.3")
|
implementation(group = "org.webjars", name = "swagger-ui", version = "4.2.1")
|
||||||
|
}
|
||||||
|
|
||||||
|
testing {
|
||||||
|
suites {
|
||||||
|
named("test", JvmTestSuite::class) {
|
||||||
|
useJUnitJupiter()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
15
renovate.json
Normal file
15
renovate.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"extends": [
|
||||||
|
"config:base"
|
||||||
|
],
|
||||||
|
"packageRules": [
|
||||||
|
{
|
||||||
|
"matchUpdateTypes": ["minor", "patch", "pin", "digest"],
|
||||||
|
"automerge": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matchPackagePatterns": ["ktor"],
|
||||||
|
"groupName": "ktor"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Reference in New Issue
Block a user