Compare commits

..

32 Commits

Author SHA1 Message Date
dd780ad29d feat: add operationId method info (#106) 2021-11-25 15:00:08 +00:00
d2165d23bf update readme 2021-11-13 09:39:35 -05:00
d9d0f129b5 fix: Allow for injectable ObjectMapper to resolve serialization issues for Java 8 date type 2021-10-24 12:25:55 -04:00
c8d56e62a2 fix: code coverage pr check removed 2021-10-17 17:05:36 +00:00
67bd6ad36f Added support for ByteArray type (#88) 2021-10-17 16:43:46 +00:00
1a924058a1 Update README.md 2021-10-04 20:08:25 -04:00
8f81b4d795 Update README.md 2021-10-04 19:58:34 -04:00
9edd3a53ce Update README.md 2021-10-04 19:20:37 -04:00
91a6164663 Codacy integration (#87) 2021-10-04 19:17:25 -04:00
5a7e052ac4 More extensible path calculation (#86) 2021-10-04 18:48:19 -04:00
6ba3617e32 add super hack to support undeclared polymorphic adapter fields (#84) 2021-08-15 01:02:23 +00:00
c32c91829b i forgot to bump the version :(((( 2021-08-12 20:40:09 -05:00
b021935b10 allow custom type overrides (#83) 2021-08-12 21:38:48 -04:00
3d99bf35fd Support polymorphic collections and maps (#82) 2021-08-12 22:32:18 +00:00
c5f8ace5d2 Added support for BigInteger and BigDecimal in responses (#76) 2021-07-25 10:48:14 -04:00
b0149c293e changed jvmTarget version from 11 to 1.8 (#74) 2021-07-22 22:38:39 -04:00
925172cf86 encode oas to json internally (#69) 2021-06-04 20:51:06 +00:00
aa3290243b explicit decoupled serialization (#65) 2021-06-03 09:00:33 -04:00
2e7dad444b Update README.md 2021-05-30 16:02:25 -04:00
6e56bf7425 Update README.md 2021-05-30 16:01:40 -04:00
dfc1593022 Update README.md 2021-05-30 15:57:24 -04:00
5fe98a04e8 Maven Central Here We Come!! (#60) 2021-05-23 13:47:48 -04:00
59c0c3aabf Polymorphic and Generic Support (#59) 2021-05-21 21:35:19 +00:00
c885ff1cfb point well taken, this is a family show (#57) 2021-05-10 13:23:56 +00:00
8c206b7ad6 Update README.md 2021-05-10 08:47:56 -04:00
2bccb1745a i give up, nexus another day i guess (#56) 2021-05-09 16:03:35 +00:00
b66c757c51 fuck u nexy (#55) 2021-05-09 11:54:23 -04:00
1e76a2382d maybe this (#54) 2021-05-09 11:45:09 -04:00
798ff553b5 maybe this (#53) 2021-05-09 11:36:10 -04:00
5b71adc323 maybe (#52) 2021-05-09 11:27:30 -04:00
5a038697bc hopefully will push and release to maven (#51) 2021-05-09 15:22:37 +00:00
5b99564a75 no more lg-backbone (#50) 2021-05-08 10:38:11 -04:00
107 changed files with 2843 additions and 1676 deletions

View File

@ -37,3 +37,8 @@ jobs:
run: ./gradlew assemble
- name: Run Unit Tests
run: ./gradlew test
- name: Cache Coverage Results
uses: actions/cache@v2
with:
path: ./**/build/reports/jacoco
key: ${{ runner.os }}-unit-${{ env.GITHUB_SHA }}

View File

@ -2,6 +2,9 @@ name: Publish to GitHub Packages
on:
push:
branches: [ main ]
env:
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.SONATYPE_SIGNING_KEY }}
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.SONATYPE_SIGNING_PASSWORD }}
jobs:
publish:
runs-on: ubuntu-latest
@ -18,6 +21,26 @@ jobs:
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle.kts') }}
restore-keys: ${{ runner.os }}-gradle
- name: Publish package
run: ./gradlew publish
run: ./gradlew publishAllPublicationsToGithubPackagesRepository
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
code-coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v2
with:
distribution: 'adopt'
java-version: '11'
- name: Cache Gradle packages
uses: actions/cache@v2
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle.kts') }}
restore-keys: ${{ runner.os }}-gradle
- name: Run Unit Tests
run: ./gradlew test
- name: Publish code coverage to Codacy
uses: codacy/codacy-coverage-reporter-action@v1
with:
project-token: ${{ secrets.CODACY_PROJECT_TOKEN }}

View File

@ -4,8 +4,11 @@ on:
types:
- prereleased
- released
env:
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.SONATYPE_SIGNING_KEY }}
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.SONATYPE_SIGNING_PASSWORD }}
jobs:
publish:
publish-to-github:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
@ -19,7 +22,26 @@ jobs:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle.kts') }}
restore-keys: ${{ runner.os }}-gradle
- name: Publish package
run: ./gradlew publish -Prelease=true
- name: Publish packages to Github
run: ./gradlew publishAllPublicationsToGithubPackagesRepository -Prelease=true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
publish-to-nexus:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v2
with:
distribution: 'adopt'
java-version: '11'
- name: Cache Gradle packages
uses: actions/cache@v2
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle.kts') }}
restore-keys: ${{ runner.os }}-gradle
- name: Publish packages to Github
run: ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository -Prelease=true
env:
ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.SONATYPE_USER }}
ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.SONATYPE_PASSWORD }}

View File

@ -1,5 +1,124 @@
# Changelog
## [1.10.0] - November 25th, 2021
### Changed
- Added `operationId` parameter to `MethodInfo`
## [1.9.2] - October 24th, 2021
### Changed
- Jackson ObjectMapper passed by parameter to openapi module
- Added serializable annotation to ExceptionResponse
## [1.9.1] - October 17th, 2021
### Changed
- Code Coverage removed from PR checks due to limitations with GitHub workflows
- Minor linting fixes
- Detekt now builds off of default config
## [1.9.0] - october 15th, 2021
### Added
- ByteArray added to the set of default types
## [1.8.1] - October 4th, 2021
### Added
- Codacy integration
## [1.8.0] - October 4th, 2021
### Changed
- Path calculation revamped to allow for simpler selector injection
- Kotlin version bumped to 1.5.31
- Ktor version bumped to 1.6.4
## [1.7.0] - August 14th, 2021
### Added
- Added ability to inject an emergency `UndeclaredField` in the event of certain polymorphic serializers and such
## [1.6.0] - August 12th, 2021
### Added
- Ability to add custom type schema overrides for edge case types.
## [1.5.1] - August 12th, 2021
### Changed
- Fixed bug where polymorphic types were not being rendered correctly when part of collections and maps
## [1.5.0] - July 25th, 2021
### Changed
- Added support for BigInteger and BigDecimal in response types
## [1.4.0] - July 22nd, 2021
### Changed
- Decreased jvmTarget version from 11 to 1.8
## [1.3.0] - June 4th, 2021
### Changed
- Explicitly encode JSON object by default
## [1.2.3] - June 3rd, 2021
### Added
- Updates showing/explaining serializer agnostic approach
## [1.2.2] - May 23rd, 2021
This is just to get my repo back to normal now that I have confirmed sonatype publish is happening
## [1.2.0] - May 23rd, 2021
### Added
- Finally, successfully pushed to Maven Central!!!
## [1.1.0] - May 19th, 2021
### Added
- Support for sealed classes 🔥
- Support for generic classes ☄️
## [1.0.1] - May 10th, 2021
### Changed
- a word to sweep my rude commit message to Nexus under the rug
## [1.0.0] - May 9th, 2021
### Added
- SonaType integration to publish to MavenCentral
### [1.0.0-rc] - May 8th, 2021
### Changed
- Migrated to io.bkbn group and package name
### [1.0.0-beta] - May 6th, 2021
### Added

144
README.md
View File

@ -1,15 +1,40 @@
# Kompendium
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/a9bfd6c77d22497b907b3221849a3ba9)](https://www.codacy.com/gh/bkbnio/kompendium/dashboard?utm_source=github.com&utm_medium=referral&utm_content=bkbnio/kompendium&utm_campaign=Badge_Grade)
[![Codacy Badge](https://app.codacy.com/project/badge/Coverage/a9bfd6c77d22497b907b3221849a3ba9)](https://www.codacy.com/gh/bkbnio/kompendium/dashboard?utm_source=github.com&utm_medium=referral&utm_content=bkbnio/kompendium&utm_campaign=Badge_Coverage)
[![version](https://img.shields.io/maven-central/v/io.bkbn/kompendium-core?style=flat-square)](https://search.maven.org/search?q=io.bkbn%20kompendium)
## What is Kompendium
### ⚠️ For info on V2 please see [here](#V2)
Kompendium is intended to be a minimally invasive OpenApi Specification generator for [Ktor](https://ktor.io).
Minimally invasive meaning that users will use only Ktor native functions when implementing their API, and will
supplement with Kompendium code in order to generate the appropriate spec.
## How to install
Kompendium uses GitHub packages as its repository. Installing with Gradle is pretty painless. In your `build.gradle.kts`
add the following
Kompendium publishes all releases to Maven Central. As such, using the stable version 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:1.8.1")
implementation("io.bkbn:kompendium-auth:1.8.1")
implementation("io.bkbn:kompendium-swagger-ui:1.8.1")
// Other dependencies...
}
```
The last two dependencies are optional.
If you want to get a little spicy 🤠 every merge of Kompendium is published to the GitHub package registry. Pulling
from GitHub is slightly more involved, but such is the price you pay for bleeding edge fake data generation.
```kotlin
// 1 Setup a helper function to import any Github Repository Package
@ -17,7 +42,7 @@ add the following
fun RepositoryHandler.github(packageUrl: String) = maven {
name = "GithubPackages"
url = uri(packageUrl)
credentials { // TODO Not sure this is necessary for public repositories?
credentials {
username = java.lang.System.getenv("GITHUB_USER")
password = java.lang.System.getenv("GITHUB_TOKEN")
}
@ -25,16 +50,20 @@ fun RepositoryHandler.github(packageUrl: String) = maven {
// 2 Add the repo in question (in this case Kompendium)
repositories {
github("https://maven.pkg.github.com/lg-backbone/kompendium")
github("https://maven.pkg.github.com/bkbnio/kompendium")
}
// 3 Add the package like any normal dependency
dependencies {
implementation("org.leafygreens:kompendium-core:0.1.0-SNAPSHOT")
implementation("io.bkbn:kompendium-core:1.8.1")
}
```
## Local Development
Kompendium should run locally right out of the box, no configuration necessary (assuming you have JDK 1.8+ installed). New features can be built locally and published to your local maven repository with the `./gradlew publishToMavenLocal` command!
## In depth
### Notarized Routes
@ -76,6 +105,79 @@ The intended purpose of `KompendiumField` is to offer field level overrides such
The purpose of `KompendiumParam` is to provide supplemental information needed to properly assign the type of parameter
(cookie, header, query, path) as well as other parameter-level metadata.
### Undeclared Field
There is also a final `UndeclaredField` annotation. This should be used only in an absolutely emergency. This annotation
will allow you to inject a _single_ undeclared field that will be included as part of the schema.
Due to limitations in using repeated annotations, this can only be used once per class
This is a complete hack, and is included for odd scenarios like kotlinx serialization polymorphic adapters that expect a
`type` field in order to perform their analysis.
Use this _only_ when **all** else fails
### Polymorphism
Speaking of polymorphism... out of the box, Kompendium has support for sealed classes and interfaces. At runtime, it will build a mapping of all available sub-classes
and build a spec that takes `anyOf` the implementations. This is currently a weak point of the entire library, and
suggestions on better implementations are welcome 🤠
### Serialization
Under the hood, Kompendium uses Jackson to serialize the final api spec. However, this implementation detail
does not leak to the actual API, meaning that users are free to choose the serialization library of their choice.
Added the possibility to add your own ObjectMapper for Jackson.
Added a default parameter with the following configuration:
```kotlin
ObjectMapper()
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
.enable(SerializationFeature.INDENT_OUTPUT)
```
If you want to change this default configuration and use your own ObjectMapper you only need to pass it as a second argument to the openApi module:
```kotlin
routing {
openApi(oas, objectMapper)
route("/potato/spud") {
notarizedGet(simpleGetInfo) {
call.respond(HttpStatusCode.OK)
}
}
}
```
### Route Handling
> ⚠️ Warning: Custom route handling is almost definitely an indication that either a new selector should be added to kompendium-core or that kompendium is in need of another module to handle a new ktor companion module. If you have encountered a route selector that is not already handled, please consider opening an [issue](https://github.com/bkbnio/kompendium/issues/new)
Kompendium does its best to handle all Ktor routes out of the gate. However, in keeping with the modular approach of
Ktor and Kompendium, this is not always possible.
Should you need to, custom route handlers can be registered via the
`Kompendium.addCustomRouteHandler` function.
The handler signature is as follows
```kotlin
fun <T : RouteSelector> addCustomRouteHandler(
selector: KClass<T>,
handler: PathCalculator.(Route, String) -> String
)
```
This function takes a selector, which _must_ be a KClass of the Ktor `RouteSelector` type. The handler is a function
that extends the Kompendium `PathCalculator`. This is necessary because it gives you access to `PathCalculator.calculate`,
which you are going to want in order to recursively calculate the remainder of the route :)
Its parameters are the `Route` itself, along with the "tail" of the Path (the path that has been calculated thus far).
Working examples `init` blocks of the `PathCalculator` and `KompendiumAuth` object.
## Examples
The full source code can be found in the `kompendium-playground` module. Here is a simple get endpoint example
@ -108,6 +210,7 @@ fun Application.mainModule() {
val simpleGetInfo = GetInfo<Unit, ExampleResponse>(
summary = "Example Parameters",
description = "A test for setting parameter examples",
operationId = "getExamples",
responseInfo = ResponseInfo(
status = 200,
description = "nice",
@ -185,6 +288,25 @@ routing {
}
```
## Custom Type Overrides
Kompendium does its best to analyze types and to generate an OpenAPI format accordingly. However, there are certain
classes that just don't play nice with the standard reflection analysis that Kompendium performs.
Should you encounter a data type that Kompendium cannot comprehend, you will need to
add it explicitly. For example, adding the Joda Time `DateTime` object would be as simple as the following
```kotlin
Kompendium.addCustomTypeSchema(DateTime::class, FormatSchema("date-time", "string"))
```
Since `Kompendium` is an object, this needs to be declared once, ahead of the actual API instantiation. This way, this
type override can be cached ahead of reflection. Kompendium will then match all instances of this type and return the
specified schema.
So how do you know a type can and cannot be inferred? The safe bet is that it can be. So go ahead and give it a shot.
However, in the very odd scenario (almost always having to do with date/time libraries 😤) where it can't, you can rest
safely knowing that you have the option to inject a custom override should you need to.
## Limitations
### Kompendium as a singleton
@ -199,12 +321,16 @@ If this is a blocker, please open a GitHub issue, and we can start to think out
Work on V1 of Kompendium has come to a close. This, however, does not mean it has achieved complete
parity with the OpenAPI feature spec, nor does it have all-of-the nice to have features that a truly next-gen API spec
should have. There are several outstanding features that have been added to the
[V2 Milestone](https://github.com/lg-backbone/kompendium/milestone/2). Among others, this includes
[V2 Milestone](https://github.com/bkbnio/kompendium/milestone/2). Among others, this includes
- Polymorphic support
- AsyncAPI Integration
- Field Validation
- MavenCentral Release
If you have a feature that you would like to see implemented that is not on this list, or discover a 🐞, please open
an issue [here](https://github.com/lg-backbone/kompendium/issues/new)
an issue [here](https://github.com/bkbnio/kompendium/issues/new)
### V2
Due to the large number of breaking changes that will be made in version 2, development is currently being done on the
long-lived `v2` feature branch. If you are working on any feature in the `V2` milestone, please target that branch!
If you are unsure where your changes should be, please open an issue first :)

View File

@ -1,14 +1,20 @@
import com.adarshr.gradle.testlogger.TestLoggerExtension
import com.adarshr.gradle.testlogger.theme.ThemeType
import io.gitlab.arturbosch.detekt.extensions.DetektExtension
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("org.jetbrains.kotlin.jvm") version "1.4.32" apply false
id("io.gitlab.arturbosch.detekt") version "1.16.0-RC2" apply false
id("org.jetbrains.kotlin.jvm") version "1.5.31" apply false
id("io.gitlab.arturbosch.detekt") version "1.18.1" apply false
id("com.adarshr.test-logger") version "3.0.0" apply false
id("io.github.gradle-nexus.publish-plugin") version "1.1.0" apply true
}
allprojects {
group = "org.leafygreens"
group = "io.bkbn"
version = run {
val baseVersion =
project.findProperty("project.version") ?: error("project.version must be set in gradle.properties")
project.findProperty("project.version") ?: error("project.version needs to be set in gradle.properties")
when ((project.findProperty("release") as? String)?.toBoolean()) {
true -> baseVersion
else -> "$baseVersion-SNAPSHOT"
@ -24,15 +30,32 @@ allprojects {
apply(plugin = "io.gitlab.arturbosch.detekt")
apply(plugin = "com.adarshr.test-logger")
apply(plugin = "idea")
apply(plugin = "jacoco")
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
tasks.withType<KotlinCompile>().configureEach {
kotlinOptions {
jvmTarget = "11"
jvmTarget = "1.8"
}
}
configure<com.adarshr.gradle.testlogger.TestLoggerExtension> {
setTheme("standard")
tasks.withType<Test>() {
finalizedBy(tasks.withType(JacocoReport::class))
}
tasks.withType<JacocoReport>() {
reports {
html.required.set(true)
xml.required.set(true)
}
}
configure<JacocoPluginExtension> {
toolVersion = "0.8.7"
}
@Suppress("MagicNumber")
configure<TestLoggerExtension> {
theme = ThemeType.MOCHA
setLogLevel("lifecycle")
showExceptions = true
showStackTraces = true
@ -50,13 +73,23 @@ allprojects {
showFailedStandardStreams = true
}
configure<io.gitlab.arturbosch.detekt.extensions.DetektExtension> {
toolVersion = "1.16.0-RC2"
configure<DetektExtension> {
toolVersion = "1.18.1"
config = files("${rootProject.projectDir}/detekt.yml")
buildUponDefaultConfig = true
}
configure<JavaPluginExtension> {
withSourcesJar()
withJavadocJar()
}
}
nexusPublishing {
repositories {
sonatype {
nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/"))
snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/"))
}
}
}

View File

@ -1,710 +1,20 @@
build:
maxIssues: 0
excludeCorrectable: false
weights:
# complexity: 2
# LongParameterList: 1
# style: 1
# comments: 1
config:
validation: true
warningsAsErrors: false
# when writing own rules with new properties, exclude the property path e.g.: 'my_rule_set,.*>.*>[my_property]'
excludes: ''
processors:
active: true
exclude:
- 'DetektProgressListener'
console-reports:
active: true
exclude:
- 'ProjectStatisticsReport'
- 'ComplexityReport'
- 'NotificationReport'
- 'FileBasedFindingsReport'
output-reports:
active: true
comments:
active: true
excludes: ['**/test/**', '**/testIntegration/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
AbsentOrWrongFileLicense:
active: false
licenseTemplateFile: 'license.template'
CommentOverPrivateFunction:
active: false
CommentOverPrivateProperty:
active: false
EndOfSentenceFormat:
active: false
endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)'
UndocumentedPublicClass:
active: false
searchInNestedClass: true
searchInInnerClass: true
searchInInnerObject: true
searchInInnerInterface: true
UndocumentedPublicFunction:
active: false
UndocumentedPublicProperty:
active: false
complexity:
active: true
ComplexCondition:
active: true
threshold: 4
ComplexInterface:
TooManyFunctions:
active: false
threshold: 10
includeStaticDeclarations: false
includePrivateDeclarations: false
ComplexMethod:
active: true
threshold: 25
ignoreSingleWhenExpression: false
ignoreSimpleWhenEntries: false
ignoreNestingFunctions: false
nestingFunctions: [run, let, apply, with, also, use, forEach, isNotNull, ifNull]
LabeledExpression:
active: false
ignoredLabels: []
LargeClass:
active: true
threshold: 600
LongMethod:
active: true
threshold: 80
LongParameterList:
active: true
functionThreshold: 10
constructorThreshold: 10
ignoreDefaultParameters: false
ignoreDataClasses: true
ignoreAnnotated: []
MethodOverloading:
active: false
threshold: 6
NamedArguments:
active: false
threshold: 3
NestedBlockDepth:
active: true
threshold: 6
ReplaceSafeCallChainWithRun:
active: false
StringLiteralDuplication:
active: false
excludes: ['**/test/**', '**/testIntegration/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
threshold: 3
ignoreAnnotation: true
excludeStringsWithLessThan5Characters: true
ignoreStringsRegex: '$^'
TooManyFunctions:
active: false
excludes: ['**/test/**', '**/testIntegration/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
thresholdInFiles: 11
thresholdInClasses: 11
thresholdInInterfaces: 11
thresholdInObjects: 11
thresholdInEnums: 11
ignoreDeprecated: false
ignorePrivate: false
ignoreOverridden: false
coroutines:
active: true
GlobalCoroutineUsage:
active: false
RedundantSuspendModifier:
active: false
SleepInsteadOfDelay:
active: false
SuspendFunWithFlowReturnType:
active: false
empty-blocks:
active: true
EmptyCatchBlock:
active: true
allowedExceptionNameRegex: '_|(ignore|expected).*'
EmptyClassBlock:
active: true
EmptyDefaultConstructor:
active: true
EmptyDoWhileBlock:
active: true
EmptyElseBlock:
active: true
EmptyFinallyBlock:
active: true
EmptyForBlock:
active: true
EmptyFunctionBlock:
active: true
ignoreOverridden: false
EmptyIfBlock:
active: true
EmptyInitBlock:
active: true
EmptyKtFile:
active: true
EmptySecondaryConstructor:
active: true
EmptyTryBlock:
active: true
EmptyWhenBlock:
active: true
EmptyWhileBlock:
active: true
exceptions:
active: true
ExceptionRaisedInUnexpectedLocation:
active: true
methodNames: [toString, hashCode, equals, finalize]
InstanceOfCheckForException:
active: false
excludes: ['**/test/**', '**/testIntegration/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
NotImplementedDeclaration:
active: false
ObjectExtendsThrowable:
active: false
PrintStackTrace:
active: true
RethrowCaughtException:
active: true
ReturnFromFinally:
active: true
ignoreLabeled: false
SwallowedException:
active: true
ignoredExceptionTypes:
- InterruptedException
- NumberFormatException
- ParseException
- MalformedURLException
allowedExceptionNameRegex: '_|(ignore|expected).*'
ThrowingExceptionFromFinally:
active: true
ThrowingExceptionInMain:
active: false
ThrowingExceptionsWithoutMessageOrCause:
active: true
excludes: ['**/test/**', '**/testIntegration/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
exceptions:
- IllegalArgumentException
- IllegalStateException
- IOException
ThrowingNewInstanceOfSameException:
active: true
TooGenericExceptionCaught:
active: true
excludes: ['**/test/**', '**/testIntegration/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
exceptionNames:
- ArrayIndexOutOfBoundsException
- Error
- Exception
- IllegalMonitorStateException
- NullPointerException
- IndexOutOfBoundsException
- RuntimeException
- Throwable
allowedExceptionNameRegex: '_|(ignore|expected).*'
TooGenericExceptionThrown:
active: true
exceptionNames:
- Error
- Exception
- Throwable
- RuntimeException
ComplexMethod:
threshold: 20
formatting:
active: true
android: false
autoCorrect: true
AnnotationOnSeparateLine:
active: false
autoCorrect: true
AnnotationSpacing:
active: false
autoCorrect: true
ArgumentListWrapping:
active: false
autoCorrect: true
ChainWrapping:
active: true
autoCorrect: true
CommentSpacing:
active: true
autoCorrect: true
EnumEntryNameCase:
active: false
autoCorrect: true
Filename:
active: true
FinalNewline:
active: true
autoCorrect: true
insertFinalNewLine: true
ImportOrdering:
active: false
autoCorrect: true
layout: 'idea'
Indentation:
active: false
autoCorrect: true
indentSize: 4
continuationIndentSize: 4
MaximumLineLength:
active: true
maxLineLength: 120
ModifierOrdering:
active: true
autoCorrect: true
MultiLineIfElse:
active: true
autoCorrect: true
NoBlankLineBeforeRbrace:
active: true
autoCorrect: true
NoConsecutiveBlankLines:
active: true
autoCorrect: true
NoEmptyClassBody:
active: true
autoCorrect: true
NoEmptyFirstLineInMethodBlock:
active: false
autoCorrect: true
NoLineBreakAfterElse:
active: true
autoCorrect: true
NoLineBreakBeforeAssignment:
active: true
autoCorrect: true
NoMultipleSpaces:
active: true
autoCorrect: true
NoSemicolons:
active: true
autoCorrect: true
NoTrailingSpaces:
active: true
autoCorrect: true
NoUnitReturn:
active: true
autoCorrect: true
NoUnusedImports:
active: true
autoCorrect: true
NoWildcardImports:
active: true
PackageName:
active: true
autoCorrect: true
ParameterListWrapping:
active: false
autoCorrect: true
indentSize: 4
SpacingAroundAngleBrackets:
active: false
autoCorrect: true
SpacingAroundColon:
active: true
autoCorrect: true
SpacingAroundComma:
active: true
autoCorrect: true
SpacingAroundCurly:
active: true
autoCorrect: true
SpacingAroundDot:
active: true
autoCorrect: true
SpacingAroundDoubleColon:
active: false
autoCorrect: true
SpacingAroundKeyword:
active: true
autoCorrect: true
SpacingAroundOperators:
active: true
autoCorrect: true
SpacingAroundParens:
active: true
autoCorrect: true
SpacingAroundRangeOperator:
active: true
autoCorrect: true
SpacingAroundUnaryOperator:
active: false
autoCorrect: true
SpacingBetweenDeclarationsWithAnnotations:
active: false
autoCorrect: true
SpacingBetweenDeclarationsWithComments:
active: false
autoCorrect: true
StringTemplate:
active: true
autoCorrect: true
naming:
active: true
ClassNaming:
active: true
excludes: ['**/test/**', '**/testIntegration/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
classPattern: '[A-Z][a-zA-Z0-9]*'
ConstructorParameterNaming:
active: false
excludes: ['**/test/**', '**/testIntegration/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
parameterPattern: '[a-z][A-Za-z0-9]*'
privateParameterPattern: '[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
ignoreOverridden: true
EnumNaming:
active: true
excludes: ['**/test/**', '**/testIntegration/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
enumEntryPattern: '[A-Z][_a-zA-Z0-9]*'
ForbiddenClassName:
active: false
excludes: ['**/test/**', '**/testIntegration/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
forbiddenName: []
FunctionMaxLength:
active: false
excludes: ['**/test/**', '**/testIntegration/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
maximumFunctionNameLength: 30
FunctionMinLength:
active: false
excludes: ['**/test/**', '**/testIntegration/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
minimumFunctionNameLength: 3
FunctionNaming:
active: true
excludes: ['**/test/**', '**/testIntegration/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
functionPattern: '([a-z][a-zA-Z0-9]*)|(`.*`)'
excludeClassPattern: '$^'
ignoreOverridden: true
ignoreAnnotated: ['Composable']
FunctionParameterNaming:
active: true
excludes: ['**/test/**', '**/testIntegration/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
parameterPattern: '[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
ignoreOverridden: true
InvalidPackageDeclaration:
active: false
excludes: ['*.kts']
rootPackage: ''
MatchingDeclarationName:
active: true
mustBeFirst: true
MemberNameEqualsClassName:
active: true
ignoreOverridden: true
NonBooleanPropertyPrefixedWithIs:
active: false
excludes: ['**/test/**', '**/testIntegration/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
ObjectPropertyNaming:
active: true
excludes: ['**/test/**', '**/testIntegration/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
constantPattern: '[A-Za-z][_A-Za-z0-9]*'
propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*'
PackageNaming:
active: true
excludes: ['**/test/**', '**/testIntegration/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*'
TopLevelPropertyNaming:
active: true
excludes: ['**/test/**', '**/testIntegration/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
constantPattern: '[A-Z][_A-Z0-9]*'
propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*'
VariableMaxLength:
active: false
excludes: ['**/test/**', '**/testIntegration/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
maximumVariableNameLength: 64
VariableMinLength:
active: false
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
minimumVariableNameLength: 1
VariableNaming:
active: true
excludes: ['**/test/**', '**/testIntegration/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
variablePattern: '[a-z][A-Za-z0-9]*'
privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
ignoreOverridden: true
performance:
active: true
ArrayPrimitive:
active: true
ForEachOnRange:
active: true
excludes: ['**/test/**', '**/testIntegration/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
SpreadOperator:
active: true
excludes: ['**/test/**', '**/testIntegration/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
UnnecessaryTemporaryInstantiation:
active: true
potential-bugs:
active: true
Deprecation:
active: false
DontDowncastCollectionTypes:
active: false
DuplicateCaseInWhenExpression:
active: true
EqualsAlwaysReturnsTrueOrFalse:
active: true
EqualsWithHashCodeExist:
active: true
ExitOutsideMain:
active: false
ExplicitGarbageCollectionCall:
active: true
HasPlatformType:
active: false
IgnoredReturnValue:
active: false
restrictToAnnotatedMethods: true
returnValueAnnotations: ['*.CheckReturnValue', '*.CheckResult']
ImplicitDefaultLocale:
active: true
ImplicitUnitReturnType:
active: false
allowExplicitReturnType: true
InvalidRange:
active: true
IteratorHasNextCallsNextMethod:
active: true
IteratorNotThrowingNoSuchElementException:
active: true
LateinitUsage:
active: false
excludes: ['**/test/**', '**/testIntegration/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
excludeAnnotatedProperties: []
ignoreOnClassesPattern: ''
MapGetWithNotNullAssertionOperator:
active: false
MissingWhenCase:
active: true
allowElseExpression: true
NullableToStringCall:
active: false
RedundantElseInWhen:
active: true
UnconditionalJumpStatementInLoop:
active: false
UnnecessaryNotNullOperator:
active: true
UnnecessarySafeCall:
active: true
UnreachableCatchBlock:
active: false
UnreachableCode:
active: true
UnsafeCallOnNullableType:
active: true
UnsafeCast:
active: true
UselessPostfixExpression:
active: false
WrongEqualsTypeParameter:
active: true
style:
active: true
ClassOrdering:
active: false
CollapsibleIfStatements:
active: false
DataClassContainsFunctions:
active: false
conversionFunctionPrefix: 'to'
DataClassShouldBeImmutable:
active: false
DestructuringDeclarationWithTooManyEntries:
active: false
maxDestructuringEntries: 3
EqualsNullCall:
active: true
EqualsOnSignatureLine:
active: false
ExplicitCollectionElementAccessMethod:
active: false
ExplicitItLambdaParameter:
active: false
ExpressionBodySyntax:
active: false
includeLineWrapping: false
ForbiddenComment:
active: true
values: ['TODO:', 'FIXME:', 'STOPSHIP:']
allowedPatterns: ''
ForbiddenImport:
active: false
imports: []
forbiddenPatterns: ''
ForbiddenMethodCall:
active: false
methods: ['kotlin.io.println', 'kotlin.io.print']
ForbiddenPublicDataClass:
active: true
excludes: ['**']
ignorePackages: ['*.internal', '*.internal.*']
ForbiddenVoid:
active: false
ignoreOverridden: false
ignoreUsageInGenerics: false
FunctionOnlyReturningConstant:
active: true
ignoreOverridableFunction: true
ignoreActualFunction: true
excludedFunctions: 'describeContents'
excludeAnnotatedFunction: ['dagger.Provides']
LibraryCodeMustSpecifyReturnType:
active: true
excludes: ['**']
LibraryEntitiesShouldNotBePublic:
active: true
excludes: ['**']
LoopWithTooManyJumpStatements:
active: true
maxJumpCount: 1
MagicNumber:
active: true
excludes: ['**/test/**', '**/testIntegration/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
ignoreNumbers: ['-1', '0', '1', '2']
ignoreHashCodeFunction: true
ignorePropertyDeclaration: false
ignoreLocalVariableDeclaration: false
ignoreConstantDeclaration: true
ignoreCompanionObjectPropertyDeclaration: true
ignoreAnnotation: false
ignoreNamedArgument: true
ignoreEnums: false
ignoreRanges: false
ignoreExtensionFunctions: true
MandatoryBracesIfStatements:
active: false
MandatoryBracesLoops:
active: false
MaxLineLength:
excludes: ['**/test/**/*', '**/testIntegration/**/*']
active: true
maxLineLength: 120
excludePackageStatements: true
excludeImportStatements: true
excludeCommentStatements: false
MayBeConst:
active: true
ModifierOrder:
active: true
MultilineLambdaItParameter:
naming:
ConstructorParameterNaming:
active: false
NestedClassesVisibility:
active: true
NewLineAtEndOfFile:
active: true
NoTabs:
active: false
OptionalAbstractKeyword:
active: true
OptionalUnit:
active: false
OptionalWhenBraces:
active: false
PreferToOverPairSyntax:
active: false
ProtectedMemberInFinalClass:
active: true
RedundantExplicitType:
active: false
RedundantHigherOrderMapUsage:
active: false
RedundantVisibilityModifierRule:
active: false
ReturnCount:
active: true
max: 2
excludedFunctions: 'equals'
excludeLabeled: false
excludeReturnFromLambda: true
excludeGuardClauses: false
SafeCast:
active: true
SerialVersionUIDInSerializableClass:
active: true
SpacingBetweenPackageAndImports:
active: false
ThrowsCount:
active: true
max: 2
TrailingWhitespace:
active: false
UnderscoresInNumericLiterals:
active: false
acceptableDecimalLength: 5
UnnecessaryAbstractClass:
active: true
excludeAnnotatedClasses: ['dagger.Module']
UnnecessaryAnnotationUseSiteTarget:
active: false
UnnecessaryApply:
active: true
UnnecessaryFilter:
active: false
UnnecessaryInheritance:
active: true
UnnecessaryLet:
active: false
UnnecessaryParentheses:
active: false
UntilInsteadOfRangeTo:
active: false
UnusedImports:
active: false
UnusedPrivateClass:
active: true
UnusedPrivateMember:
active: true
allowedNames: '(_|ignored|expected|serialVersionUID)'
UseArrayLiteralsInAnnotations:
active: false
UseCheckNotNull:
active: false
UseCheckOrError:
active: false
UseDataClass:
active: false
excludeAnnotatedClasses: []
allowVars: false
UseEmptyCounterpart:
active: false
UseIfEmptyOrIfBlank:
active: false
UseIfInsteadOfWhen:
active: false
UseIsNullOrEmpty:
active: false
UseRequire:
active: false
UseRequireNotNull:
active: false
UselessCallOnNotNull:
active: true
UtilityClassWithPublicConstructor:
active: true
VarCouldBeVal:
active: true
WildcardImport:
active: true
excludes: ['**/test/**', '**/testIntegration/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
excludeImports: ['java.util.*', 'kotlinx.android.synthetic.*']

View File

@ -1,5 +1,5 @@
# Kompendium
project.version=1.0.0-beta
project.version=1.10.0
# Kotlin
kotlin.code.style=official
# Gradle

View File

@ -1,6 +1,8 @@
[versions]
kotlin = "1.4.32"
ktor = "1.5.3"
ktor = "1.6.4"
kotlinx-serialization = "1.2.1"
jackson-kotlin = "2.12.0"
slf4j = "1.7.30"
logback = "1.2.3"
swagger-ui = "3.47.1"
@ -10,11 +12,17 @@ swagger-ui = "3.47.1"
ktor-server-core = { group = "io.ktor", name = "ktor-server-core", version.ref = "ktor" }
ktor-server-netty = { group = "io.ktor", name = "ktor-server-netty", version.ref = "ktor" }
ktor-jackson = { group = "io.ktor", name = "ktor-jackson", version.ref = "ktor" }
ktor-serialization = { group = "io.ktor", name = "ktor-serialization", version.ref = "ktor" }
ktor-html-builder = { group = "io.ktor", name = "ktor-html-builder", version.ref = "ktor" }
ktor-auth-lib = { group = "io.ktor", name = "ktor-auth", version.ref = "ktor" }
ktor-auth-jwt = { group = "io.ktor", name = "ktor-auth-jwt", version.ref = "ktor" }
ktor-webjars = { group = "io.ktor", name = "ktor-webjars", version.ref = "ktor" }
# Serialization
jackson-module-kotlin = { group = "com.fasterxml.jackson.module", name = "jackson-module-kotlin", version.ref = "jackson-kotlin" }
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
# Logging
slf4j = { group = "org.slf4j", name = "slf4j-api", version.ref = "slf4j" }
logback-classic = { group = "ch.qos.logback", name = "logback-classic", version.ref = "logback" }
@ -23,7 +31,10 @@ logback-core = { group = "ch.qos.logback", name = "logback-core", version.ref =
# webjars
webjars-swagger-ui = { group = "org.webjars", name = "swagger-ui", version.ref = "swagger-ui" }
# Testing
ktor-server-test-host = { group = "io.ktor", name = "ktor-server-test-host", version.ref = "ktor" }
[bundles]
ktor = [ "ktor-server-core", "ktor-server-netty", "ktor-jackson", "ktor-html-builder" ]
ktor = ["ktor-server-core", "ktor-server-netty", "ktor-html-builder"]
ktorAuth = ["ktor-auth-lib", "ktor-auth-jwt"]
logging = ["slf4j", "logback-classic", "logback-core"]

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@ -1,6 +1,7 @@
plugins {
`java-library`
`maven-publish`
signing
}
dependencies {
@ -10,21 +11,23 @@ dependencies {
implementation(libs.bundles.ktorAuth)
implementation(projects.kompendiumCore)
testImplementation(libs.ktor.jackson)
testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
testImplementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.12.0")
testImplementation("io.ktor:ktor-server-test-host:1.5.3")
testImplementation(libs.jackson.module.kotlin)
testImplementation(libs.ktor.server.test.host)
}
java {
withSourcesJar()
withJavadocJar()
}
publishing {
repositories {
maven {
name = "GithubPackages"
url = uri("https://maven.pkg.github.com/lg-backbone/kompendium")
url = uri("https://maven.pkg.github.com/bkbnio/kompendium")
credentials {
username = System.getenv("GITHUB_ACTOR")
password = System.getenv("GITHUB_TOKEN")
@ -35,6 +38,41 @@ publishing {
create<MavenPublication>("kompendium") {
from(components["kotlin"])
artifact(tasks.sourcesJar)
artifact(tasks.javadocJar)
groupId = project.group.toString()
artifactId = project.name.toLowerCase()
version = project.version.toString()
pom {
name.set("Kompendium")
description.set("A minimally invasive OpenAPI spec generator for Ktor")
url.set("https://github.com/bkbnio/Kompendium")
licenses {
license {
name.set("MIT License")
url.set("https://mit-license.org/")
}
}
developers {
developer {
id.set("bkbnio")
name.set("Ryan Brink")
email.set("admin@bkbn.io")
}
}
scm {
connection.set("scm:git:git://github.com/bkbnio/Kompendium.git")
developerConnection.set("scm:git:ssh://github.com/bkbnio/Kompendium.git")
url.set("https://github.com/bkbnio/Kompendium.git")
}
}
}
}
}
signing {
val signingKey: String? by project
val signingPassword: String? by project
useInMemoryPgpKeys(signingKey, signingPassword)
sign(publishing.publications)
}

View File

@ -1,17 +1,20 @@
package org.leafygreens.kompendium.auth
package io.bkbn.kompendium.auth
import io.ktor.auth.Authentication
import io.ktor.auth.basic
import io.ktor.auth.BasicAuthenticationProvider
import io.ktor.auth.jwt.jwt
import io.ktor.auth.jwt.JWTAuthenticationProvider
import org.leafygreens.kompendium.Kompendium
import org.leafygreens.kompendium.models.oas.OpenApiSpecSchemaSecurity
import io.bkbn.kompendium.Kompendium
import io.bkbn.kompendium.models.oas.OpenApiSpecSchemaSecurity
import io.ktor.auth.AuthenticationRouteSelector
object KompendiumAuth {
init {
Kompendium.pathCalculator = AuthPathCalculator()
Kompendium.addCustomRouteHandler(AuthenticationRouteSelector::class) { route, tail ->
calculate(route.parent, tail)
}
}
fun Authentication.Configuration.notarizedBasic(

View File

@ -1,19 +0,0 @@
package org.leafygreens.kompendium.auth
import io.ktor.auth.AuthenticationRouteSelector
import io.ktor.routing.Route
import org.leafygreens.kompendium.path.CorePathCalculator
import org.slf4j.LoggerFactory
class AuthPathCalculator : CorePathCalculator() {
private val logger = LoggerFactory.getLogger(javaClass)
override fun handleCustomSelectors(route: Route, tail: String): String = when (route.selector) {
is AuthenticationRouteSelector -> {
logger.debug("Found authentication route selector ${route.selector}")
super.calculate(route.parent, tail)
}
else -> super.handleCustomSelectors(route, tail)
}
}

View File

@ -1,4 +1,4 @@
package org.leafygreens.kompendium.auth
package io.bkbn.kompendium.auth
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.databind.SerializationFeature
@ -21,17 +21,17 @@ import io.ktor.server.testing.withTestApplication
import kotlin.test.AfterTest
import kotlin.test.assertEquals
import org.junit.Test
import org.leafygreens.kompendium.Kompendium
import org.leafygreens.kompendium.Notarized.notarizedGet
import org.leafygreens.kompendium.auth.KompendiumAuth.notarizedBasic
import org.leafygreens.kompendium.auth.KompendiumAuth.notarizedJwt
import org.leafygreens.kompendium.auth.util.TestData
import org.leafygreens.kompendium.auth.util.TestParams
import org.leafygreens.kompendium.auth.util.TestResponse
import org.leafygreens.kompendium.models.meta.MethodInfo
import org.leafygreens.kompendium.models.meta.ResponseInfo
import org.leafygreens.kompendium.routes.openApi
import org.leafygreens.kompendium.routes.redoc
import io.bkbn.kompendium.Kompendium
import io.bkbn.kompendium.Notarized.notarizedGet
import io.bkbn.kompendium.auth.KompendiumAuth.notarizedBasic
import io.bkbn.kompendium.auth.KompendiumAuth.notarizedJwt
import io.bkbn.kompendium.auth.util.TestData
import io.bkbn.kompendium.auth.util.TestParams
import io.bkbn.kompendium.auth.util.TestResponse
import io.bkbn.kompendium.models.meta.MethodInfo
import io.bkbn.kompendium.models.meta.ResponseInfo
import io.bkbn.kompendium.routes.openApi
import io.bkbn.kompendium.routes.redoc
internal class KompendiumAuthTest {

View File

@ -1,4 +1,4 @@
package org.leafygreens.kompendium.auth.util
package io.bkbn.kompendium.auth.util
import java.io.File

View File

@ -1,8 +1,8 @@
package org.leafygreens.kompendium.auth.util
package io.bkbn.kompendium.auth.util
import org.leafygreens.kompendium.annotations.KompendiumField
import org.leafygreens.kompendium.annotations.KompendiumParam
import org.leafygreens.kompendium.annotations.ParamType
import io.bkbn.kompendium.annotations.KompendiumField
import io.bkbn.kompendium.annotations.KompendiumParam
import io.bkbn.kompendium.annotations.ParamType
data class TestParams(
@KompendiumParam(ParamType.PATH) val a: String,

View File

@ -20,8 +20,8 @@
"name" : "aa",
"in" : "query",
"schema" : {
"format" : "int32",
"type" : "integer"
"type" : "integer",
"format" : "int32"
},
"required" : true,
"deprecated" : false
@ -51,16 +51,16 @@
"type" : "string"
},
"TestResponse" : {
"type" : "object",
"properties" : {
"c" : {
"$ref" : "#/components/schemas/String"
}
},
"type" : "object"
}
},
"Int" : {
"format" : "int32",
"type" : "integer"
"type" : "integer",
"format" : "int32"
}
},
"securitySchemes" : {

View File

@ -20,8 +20,8 @@
"name" : "aa",
"in" : "query",
"schema" : {
"format" : "int32",
"type" : "integer"
"type" : "integer",
"format" : "int32"
},
"required" : true,
"deprecated" : false
@ -51,16 +51,16 @@
"type" : "string"
},
"TestResponse" : {
"type" : "object",
"properties" : {
"c" : {
"$ref" : "#/components/schemas/String"
}
},
"type" : "object"
}
},
"Int" : {
"format" : "int32",
"type" : "integer"
"type" : "integer",
"format" : "int32"
}
},
"securitySchemes" : {

View File

@ -20,8 +20,8 @@
"name" : "aa",
"in" : "query",
"schema" : {
"format" : "int32",
"type" : "integer"
"type" : "integer",
"format" : "int32"
},
"required" : true,
"deprecated" : false
@ -51,16 +51,16 @@
"type" : "string"
},
"TestResponse" : {
"type" : "object",
"properties" : {
"c" : {
"$ref" : "#/components/schemas/String"
}
},
"type" : "object"
}
},
"Int" : {
"format" : "int32",
"type" : "integer"
"type" : "integer",
"format" : "int32"
}
},
"securitySchemes" : {

View File

@ -20,8 +20,8 @@
"name" : "aa",
"in" : "query",
"schema" : {
"format" : "int32",
"type" : "integer"
"type" : "integer",
"format" : "int32"
},
"required" : true,
"deprecated" : false
@ -51,16 +51,16 @@
"type" : "string"
},
"TestResponse" : {
"type" : "object",
"properties" : {
"c" : {
"$ref" : "#/components/schemas/String"
}
},
"type" : "object"
}
},
"Int" : {
"format" : "int32",
"type" : "integer"
"type" : "integer",
"format" : "int32"
}
},
"securitySchemes" : {

View File

@ -20,8 +20,8 @@
"name" : "aa",
"in" : "query",
"schema" : {
"format" : "int32",
"type" : "integer"
"type" : "integer",
"format" : "int32"
},
"required" : true,
"deprecated" : false
@ -52,16 +52,16 @@
"type" : "string"
},
"TestResponse" : {
"type" : "object",
"properties" : {
"c" : {
"$ref" : "#/components/schemas/String"
}
},
"type" : "object"
}
},
"Int" : {
"format" : "int32",
"type" : "integer"
"type" : "integer",
"format" : "int32"
}
},
"securitySchemes" : {

View File

@ -1,27 +1,32 @@
plugins {
`java-library`
`maven-publish`
signing
}
dependencies {
implementation(platform("org.jetbrains.kotlin:kotlin-bom"))
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation(libs.jackson.module.kotlin)
implementation(libs.bundles.ktor)
testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
testImplementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.12.0")
testImplementation("io.ktor:ktor-server-test-host:1.5.3")
testImplementation(libs.ktor.serialization)
testImplementation(libs.kotlinx.serialization.json)
testImplementation(libs.ktor.jackson)
testImplementation(libs.ktor.server.test.host)
}
java {
withSourcesJar()
withJavadocJar()
}
publishing {
repositories {
maven {
name = "GithubPackages"
url = uri("https://maven.pkg.github.com/lg-backbone/kompendium")
url = uri("https://maven.pkg.github.com/bkbnio/kompendium")
credentials {
username = System.getenv("GITHUB_ACTOR")
password = System.getenv("GITHUB_TOKEN")
@ -32,6 +37,41 @@ publishing {
create<MavenPublication>("kompendium") {
from(components["kotlin"])
artifact(tasks.sourcesJar)
artifact(tasks.javadocJar)
groupId = project.group.toString()
artifactId = project.name.toLowerCase()
version = project.version.toString()
pom {
name.set("Kompendium")
description.set("A minimally invasive OpenAPI spec generator for Ktor")
url.set("https://github.com/bkbnio/Kompendium")
licenses {
license {
name.set("MIT License")
url.set("https://mit-license.org/")
}
}
developers {
developer {
id.set("bkbnio")
name.set("Ryan Brink")
email.set("admin@bkbn.io")
}
}
scm {
connection.set("scm:git:git://github.com/bkbnio/Kompendium.git")
developerConnection.set("scm:git:ssh://github.com/bkbnio/Kompendium.git")
url.set("https://github.com/bkbnio/Kompendium.git")
}
}
}
}
}
signing {
val signingKey: String? by project
val signingPassword: String? by project
useInMemoryPgpKeys(signingKey, signingPassword)
sign(publishing.publications)
}

View File

@ -0,0 +1,49 @@
package io.bkbn.kompendium
import io.bkbn.kompendium.models.meta.ErrorMap
import io.bkbn.kompendium.models.meta.SchemaMap
import io.bkbn.kompendium.models.oas.OpenApiSpec
import io.bkbn.kompendium.models.oas.OpenApiSpecInfo
import io.bkbn.kompendium.models.oas.TypedSchema
import io.bkbn.kompendium.path.IPathCalculator
import io.bkbn.kompendium.path.PathCalculator
import io.ktor.routing.Route
import io.ktor.routing.RouteSelector
import kotlin.reflect.KClass
/**
* Maintains all state for the Kompendium library
*/
object Kompendium {
var errorMap: ErrorMap = emptyMap()
var cache: SchemaMap = emptyMap()
var openApiSpec = OpenApiSpec(
info = OpenApiSpecInfo(),
servers = mutableListOf(),
paths = mutableMapOf()
)
fun calculatePath(route: Route) = PathCalculator.calculate(route)
fun resetSchema() {
openApiSpec = OpenApiSpec(
info = OpenApiSpecInfo(),
servers = mutableListOf(),
paths = mutableMapOf()
)
cache = emptyMap()
}
fun addCustomTypeSchema(clazz: KClass<*>, schema: TypedSchema) {
cache = cache.plus(clazz.simpleName!! to schema)
}
fun <T : RouteSelector> addCustomRouteHandler(
selector: KClass<T>,
handler: IPathCalculator.(Route, String) -> String
) {
PathCalculator.addCustomRouteHandler(selector, handler)
}
}

View File

@ -1,4 +1,4 @@
package org.leafygreens.kompendium
package io.bkbn.kompendium
import io.ktor.routing.Route
import kotlin.reflect.KType
@ -21,13 +21,11 @@ object KompendiumPreFlight {
inline fun <reified TParam : Any, reified TReq : Any, reified TResp : Any> methodNotarizationPreFlight(
block: (KType, KType, KType) -> Route
): Route {
Kompendium.cache = Kontent.generateKontent<TResp>(Kompendium.cache)
Kompendium.cache = Kontent.generateKontent<TReq>(Kompendium.cache)
Kompendium.cache = Kontent.generateParameterKontent<TParam>(Kompendium.cache)
Kompendium.openApiSpec.components.schemas.putAll(Kompendium.cache)
val requestType = typeOf<TReq>()
val responseType = typeOf<TResp>()
val paramType = typeOf<TParam>()
addToCache(paramType, requestType, responseType)
Kompendium.openApiSpec.components.schemas.putAll(Kompendium.cache)
return block.invoke(paramType, requestType, responseType)
}
@ -41,10 +39,16 @@ object KompendiumPreFlight {
inline fun <reified TErr : Throwable, reified TResp : Any> errorNotarizationPreFlight(
block: (KType, KType) -> Unit
) {
Kompendium.cache = Kontent.generateKontent<TResp>(Kompendium.cache)
Kompendium.openApiSpec.components.schemas.putAll(Kompendium.cache)
val errorType = typeOf<TErr>()
val responseType = typeOf<TResp>()
addToCache(typeOf<Unit>(), typeOf<Unit>(), responseType)
Kompendium.openApiSpec.components.schemas.putAll(Kompendium.cache)
return block.invoke(errorType, responseType)
}
fun addToCache(paramType: KType, requestType: KType, responseType: KType) {
Kompendium.cache = Kontent.generateKontent(requestType, Kompendium.cache)
Kompendium.cache = Kontent.generateKontent(responseType, Kompendium.cache)
Kompendium.cache = Kontent.generateParameterKontent(paramType, Kompendium.cache)
}
}

View File

@ -0,0 +1,290 @@
package io.bkbn.kompendium
import io.bkbn.kompendium.annotations.UndeclaredField
import io.bkbn.kompendium.models.meta.SchemaMap
import io.bkbn.kompendium.models.oas.AnyOfReferencedSchema
import io.bkbn.kompendium.models.oas.ArraySchema
import io.bkbn.kompendium.models.oas.DictionarySchema
import io.bkbn.kompendium.models.oas.EnumSchema
import io.bkbn.kompendium.models.oas.FormatSchema
import io.bkbn.kompendium.models.oas.ObjectSchema
import io.bkbn.kompendium.models.oas.ReferencedSchema
import io.bkbn.kompendium.models.oas.SimpleSchema
import io.bkbn.kompendium.util.Helpers.COMPONENT_SLUG
import io.bkbn.kompendium.util.Helpers.genericNameAdapter
import io.bkbn.kompendium.util.Helpers.getReferenceSlug
import io.bkbn.kompendium.util.Helpers.getSimpleSlug
import io.bkbn.kompendium.util.Helpers.logged
import org.slf4j.LoggerFactory
import java.math.BigDecimal
import java.math.BigInteger
import java.util.UUID
import kotlin.reflect.KClass
import kotlin.reflect.KType
import kotlin.reflect.full.createType
import kotlin.reflect.full.isSubclassOf
import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.javaField
import kotlin.reflect.typeOf
/**
* Responsible for generating the schema map that is used to power all object references across the API Spec.
*/
object Kontent {
private val logger = LoggerFactory.getLogger(javaClass)
/**
* Analyzes a type [T] for its top-level and any nested schemas, and adds them to a [SchemaMap], if provided
* @param T type to analyze
* @param cache Existing schema map to append to
* @return an updated schema map containing all type information for [T]
*/
@OptIn(ExperimentalStdlibApi::class)
inline fun <reified T> generateKontent(
cache: SchemaMap = emptyMap()
): SchemaMap {
val kontentType = typeOf<T>()
return generateKTypeKontent(kontentType, cache)
}
/**
* Analyzes a [KType] for its top-level and any nested schemas, and adds them to a [SchemaMap], if provided
* @param type [KType] to analyze
* @param cache Existing schema map to append to
* @return an updated schema map containing all type information for [KType] type
*/
fun generateKontent(
type: KType,
cache: SchemaMap = emptyMap()
): SchemaMap {
var newCache = cache
gatherSubTypes(type).forEach {
newCache = generateKTypeKontent(it, newCache)
}
return newCache
}
private fun gatherSubTypes(type: KType): List<KType> {
val classifier = type.classifier as KClass<*>
return if (classifier.isSealed) {
classifier.sealedSubclasses.map {
it.createType(type.arguments)
}
} else {
listOf(type)
}
}
/**
* Analyze a type [T], but filters out the top-level type
* @param T type to analyze
* @param cache Existing schema map to append to
* @return an updated schema map containing all type information for [T]
*/
@OptIn(ExperimentalStdlibApi::class)
inline fun <reified T> generateParameterKontent(
cache: SchemaMap = emptyMap()
): SchemaMap {
val kontentType = typeOf<T>()
return generateKTypeKontent(kontentType, cache)
.filterNot { (slug, _) -> slug == (kontentType.classifier as KClass<*>).simpleName }
}
/**
* Analyze a type but filters out the top-level type
* @param type to analyze
* @param cache Existing schema map to append to
* @return an updated schema map containing all type information for [T]
*/
fun generateParameterKontent(
type: KType,
cache: SchemaMap = emptyMap()
): SchemaMap {
return generateKTypeKontent(type, cache)
.filterNot { (slug, _) -> slug == (type.classifier as KClass<*>).simpleName }
}
/**
* Recursively fills schema map depending on [KType] classifier
* @param type [KType] to parse
* @param cache Existing schema map to append to
*/
fun generateKTypeKontent(
type: KType,
cache: SchemaMap = emptyMap()
): SchemaMap = logged(object {}.javaClass.enclosingMethod.name, mapOf("cache" to cache)) {
logger.debug("Parsing Kontent of $type")
when (val clazz = type.classifier as KClass<*>) {
Unit::class -> cache
Int::class -> cache.plus(clazz.simpleName!! to FormatSchema("int32", "integer"))
Long::class -> cache.plus(clazz.simpleName!! to FormatSchema("int64", "integer"))
Double::class -> cache.plus(clazz.simpleName!! to FormatSchema("double", "number"))
Float::class -> cache.plus(clazz.simpleName!! to FormatSchema("float", "number"))
String::class -> cache.plus(clazz.simpleName!! to SimpleSchema("string"))
Boolean::class -> cache.plus(clazz.simpleName!! to SimpleSchema("boolean"))
UUID::class -> cache.plus(clazz.simpleName!! to FormatSchema("uuid", "string"))
BigDecimal::class -> cache.plus(clazz.simpleName!! to FormatSchema("double", "number"))
BigInteger::class -> cache.plus(clazz.simpleName!! to FormatSchema("int64", "integer"))
ByteArray::class -> cache.plus(clazz.simpleName!! to FormatSchema("byte", "string"))
else -> when {
clazz.isSubclassOf(Collection::class) -> handleCollectionType(type, clazz, cache)
clazz.isSubclassOf(Enum::class) -> handleEnumType(clazz, cache)
clazz.isSubclassOf(Map::class) -> handleMapType(type, clazz, cache)
else -> handleComplexType(type, clazz, cache)
}
}
}
/**
* In the event of an object type, this method will parse out individual fields to recursively aggregate object map.
* @param clazz Class of the object to analyze
* @param cache Existing schema map to append to
*/
// TODO Fix as part of this issue https://github.com/bkbnio/kompendium/issues/80
@Suppress("LongMethod", "ComplexMethod")
private fun handleComplexType(type: KType, clazz: KClass<*>, cache: SchemaMap): SchemaMap {
// This needs to be simple because it will be stored under it's appropriate reference component implicitly
val slug = type.getSimpleSlug()
// Only analyze if component has not already been stored in the cache
return when (cache.containsKey(slug)) {
true -> {
logger.debug("Cache already contains $slug, returning cache untouched")
cache
}
false -> {
logger.debug("$slug was not found in cache, generating now")
var newCache = cache
// Grabs any type parameters as a zip with the corresponding type argument
val typeMap = clazz.typeParameters.zip(type.arguments).toMap()
// associates each member with a Pair of prop name to property schema
val fieldMap = clazz.memberProperties.associate { prop ->
logger.debug("Analyzing $prop in class $clazz")
// Grab the field of the current property
val field = prop.javaField?.type?.kotlin ?: error("Unable to parse field type from $prop")
logger.debug("Detected field $field")
// Yoinks any generic types from the type map should the field be a generic
val yoinkBaseType = if (typeMap.containsKey(prop.returnType.classifier)) {
logger.debug("Generic type detected")
typeMap[prop.returnType.classifier]?.type!!
} else {
prop.returnType
}
// converts the base type to a class
val yoinkedClassifier = yoinkBaseType.classifier as KClass<*>
// in the event of a sealed class, grab all sealed subclasses and create a type from the base args
val yoinkedTypes = if (yoinkedClassifier.isSealed) {
yoinkedClassifier.sealedSubclasses.map { it.createType(yoinkBaseType.arguments) }
} else {
listOf(yoinkBaseType)
}
// if the most up-to-date cache does not contain the content for this field, generate it and add to cache
if (!newCache.containsKey(field.simpleName)) {
logger.debug("Cache was missing ${field.simpleName}, adding now")
yoinkedTypes.forEach {
newCache = generateKTypeKontent(it, newCache)
}
}
// TODO This in particular is worthy of a refactor... just not very well written
// builds the appropriate property schema based on the property return type
val propSchema = if (typeMap.containsKey(prop.returnType.classifier)) {
if (yoinkedClassifier.isSealed) {
val refs = yoinkedClassifier.sealedSubclasses
.map { it.createType(yoinkBaseType.arguments) }
.map { ReferencedSchema(it.getReferenceSlug()) }
AnyOfReferencedSchema(refs)
} else {
ReferencedSchema(typeMap[prop.returnType.classifier]?.type!!.getReferenceSlug())
}
} else {
if (yoinkedClassifier.isSealed) {
val refs = yoinkedClassifier.sealedSubclasses
.map { it.createType(yoinkBaseType.arguments) }
.map { ReferencedSchema(it.getReferenceSlug()) }
AnyOfReferencedSchema(refs)
} else {
ReferencedSchema(field.getReferenceSlug(prop))
}
}
Pair(prop.name, propSchema)
}
logger.debug("Looking for undeclared fields")
val undeclaredFieldMap = clazz.annotations.filterIsInstance<UndeclaredField>().associate {
val undeclaredType = it.clazz.createType()
newCache = generateKontent(undeclaredType, newCache)
it.field to ReferencedSchema(undeclaredType.getReferenceSlug())
}
logger.debug("$slug contains $fieldMap")
val schema = ObjectSchema(fieldMap.plus(undeclaredFieldMap))
logger.debug("$slug schema: $schema")
newCache.plus(slug to schema)
}
}
}
/**
* Handler for when an [Enum] is encountered
* @param clazz Class of the object to analyze
* @param cache Existing schema map to append to
*/
private fun handleEnumType(clazz: KClass<*>, cache: SchemaMap): SchemaMap {
val options = clazz.java.enumConstants.map { it.toString() }.toSet()
return cache.plus(clazz.simpleName!! to EnumSchema(options))
}
/**
* Handler for when a [Map] is encountered
* @param type Map type information
* @param clazz Map class information
* @param cache Existing schema map to append to
*/
private fun handleMapType(type: KType, clazz: KClass<*>, cache: SchemaMap): SchemaMap {
logger.debug("Map detected for $type, generating schema and appending to cache")
val (keyType, valType) = type.arguments.map { it.type }
logger.debug("Obtained map types -> key: $keyType and value: $valType")
if (keyType?.classifier != String::class) {
error("Invalid Map $type: OpenAPI dictionaries must have keys of type String")
}
val valClass = valType?.classifier as KClass<*>
val valClassName = valClass.simpleName
val referenceName = genericNameAdapter(type, clazz)
val valueReference = when (valClass.isSealed) {
true -> {
val subTypes = gatherSubTypes(valType)
AnyOfReferencedSchema(subTypes.map {
ReferencedSchema(("$COMPONENT_SLUG/${it.getSimpleSlug()}"))
})
}
false -> ReferencedSchema("$COMPONENT_SLUG/$valClassName")
}
val schema = DictionarySchema(additionalProperties = valueReference)
val updatedCache = generateKontent(valType, cache)
return updatedCache.plus(referenceName to schema)
}
/**
* Handler for when a [Collection] is encountered
* @param type Collection type information
* @param clazz Collection class information
* @param cache Existing schema map to append to
*/
private fun handleCollectionType(type: KType, clazz: KClass<*>, cache: SchemaMap): SchemaMap {
logger.debug("Collection detected for $type, generating schema and appending to cache")
val collectionType = type.arguments.first().type!!
val collectionClass = collectionType.classifier as KClass<*>
logger.debug("Obtained collection class: $collectionClass")
val referenceName = genericNameAdapter(type, clazz)
val valueReference = when (collectionClass.isSealed) {
true -> {
val subTypes = gatherSubTypes(collectionType)
AnyOfReferencedSchema(subTypes.map {
ReferencedSchema(("$COMPONENT_SLUG/${it.getSimpleSlug()}"))
})
}
false -> ReferencedSchema("$COMPONENT_SLUG/${collectionClass.simpleName}")
}
val schema = ArraySchema(items = valueReference)
val updatedCache = generateKontent(collectionType, cache)
return updatedCache.plus(referenceName to schema)
}
}

View File

@ -1,5 +1,22 @@
package org.leafygreens.kompendium
package io.bkbn.kompendium
import io.bkbn.kompendium.annotations.KompendiumParam
import io.bkbn.kompendium.models.meta.MethodInfo
import io.bkbn.kompendium.models.meta.RequestInfo
import io.bkbn.kompendium.models.meta.ResponseInfo
import io.bkbn.kompendium.models.oas.ExampleWrapper
import io.bkbn.kompendium.models.oas.OpenApiAnyOf
import io.bkbn.kompendium.models.oas.OpenApiSpecMediaType
import io.bkbn.kompendium.models.oas.OpenApiSpecParameter
import io.bkbn.kompendium.models.oas.OpenApiSpecPathItemOperation
import io.bkbn.kompendium.models.oas.OpenApiSpecReferencable
import io.bkbn.kompendium.models.oas.OpenApiSpecReferenceObject
import io.bkbn.kompendium.models.oas.OpenApiSpecRequest
import io.bkbn.kompendium.models.oas.OpenApiSpecResponse
import io.bkbn.kompendium.util.Helpers
import io.bkbn.kompendium.util.Helpers.getReferenceSlug
import io.bkbn.kompendium.util.Helpers.getSimpleSlug
import java.util.Locale
import java.util.UUID
import kotlin.reflect.KClass
import kotlin.reflect.KParameter
@ -10,21 +27,6 @@ import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.memberProperties
import kotlin.reflect.full.primaryConstructor
import kotlin.reflect.jvm.javaField
import org.leafygreens.kompendium.annotations.KompendiumParam
import org.leafygreens.kompendium.models.meta.MethodInfo
import org.leafygreens.kompendium.models.meta.RequestInfo
import org.leafygreens.kompendium.models.meta.ResponseInfo
import org.leafygreens.kompendium.models.oas.ExampleWrapper
import org.leafygreens.kompendium.models.oas.OpenApiSpecMediaType
import org.leafygreens.kompendium.models.oas.OpenApiSpecParameter
import org.leafygreens.kompendium.models.oas.OpenApiSpecPathItemOperation
import org.leafygreens.kompendium.models.oas.OpenApiSpecReferencable
import org.leafygreens.kompendium.models.oas.OpenApiSpecReferenceObject
import org.leafygreens.kompendium.models.oas.OpenApiSpecRequest
import org.leafygreens.kompendium.models.oas.OpenApiSpecResponse
import org.leafygreens.kompendium.util.Helpers
import org.leafygreens.kompendium.util.Helpers.getReferenceSlug
import org.leafygreens.kompendium.util.Helpers.getSimpleSlug
/**
* The MethodParser is responsible for converting route metadata and types into an OpenAPI compatible data class.
@ -47,6 +49,7 @@ object MethodParser {
) = OpenApiSpecPathItemOperation(
summary = info.summary,
description = info.description,
operationId = info.operationId,
tags = info.tags,
deprecated = info.deprecated,
parameters = paramType.toParameterSpec(),
@ -106,7 +109,7 @@ object MethodParser {
else -> {
OpenApiSpecRequest(
description = requestInfo.description,
content = resolveContent(requestInfo.mediaTypes, requestInfo.examples) ?: mapOf()
content = resolveContent(this, requestInfo.mediaTypes, requestInfo.examples) ?: mapOf()
)
}
}
@ -123,7 +126,7 @@ object MethodParser {
else -> {
val specResponse = OpenApiSpecResponse(
description = responseInfo.description,
content = resolveContent(responseInfo.mediaTypes, responseInfo.examples)
content = resolveContent(this, responseInfo.mediaTypes, responseInfo.examples)
)
Pair(responseInfo.status.value, specResponse)
}
@ -131,20 +134,31 @@ object MethodParser {
/**
* Generates MediaTypes along with any examples provided
* @receiver [KType] Type of the object
* @param type [KType] Type of the object
* @param mediaTypes list of acceptable http media types
* @param examples Mapping of named examples of valid bodies.
* @return Named mapping of media types.
*/
private fun <F> KType.resolveContent(
private fun <F> resolveContent(
type: KType,
mediaTypes: List<String>,
examples: Map<String, F>
): Map<String, OpenApiSpecMediaType<F>>? {
return if (this != Helpers.UNIT_TYPE && mediaTypes.isNotEmpty()) {
val classifier = type.classifier as KClass<*>
return if (type != Helpers.UNIT_TYPE && mediaTypes.isNotEmpty()) {
mediaTypes.associateWith {
val ref = getReferenceSlug()
val schema = if (classifier.isSealed) {
val refs = classifier.sealedSubclasses
.map { it.createType(type.arguments) }
.map { it.getReferenceSlug() }
.map { OpenApiSpecReferenceObject(it) }
OpenApiAnyOf(refs)
} else {
val ref = type.getReferenceSlug()
OpenApiSpecReferenceObject(ref)
}
OpenApiSpecMediaType(
schema = OpenApiSpecReferenceObject(ref),
schema = schema,
examples = examples.mapValues { (_, v) -> ExampleWrapper(v) }.ifEmpty { null }
)
}
@ -153,7 +167,7 @@ object MethodParser {
/**
* Parses a type for all parameter information. All fields in the receiver
* must be annotated with [org.leafygreens.kompendium.annotations.KompendiumParam].
* must be annotated with [io.bkbn.kompendium.annotations.KompendiumParam].
* @receiver type
* @return list of valid parameter specs as detailed by the [KType] members
* @throws [IllegalStateException] if the class could not be parsed properly
@ -170,7 +184,7 @@ object MethodParser {
val defaultValue = getDefaultParameterValue(clazz, prop)
OpenApiSpecParameter(
name = prop.name,
`in` = anny.type.name.toLowerCase(),
`in` = anny.type.name.lowercase(Locale.getDefault()),
schema = schema.addDefault(defaultValue),
description = anny.description.ifBlank { null },
required = !prop.returnType.isMarkedNullable

View File

@ -1,4 +1,4 @@
package org.leafygreens.kompendium
package io.bkbn.kompendium
import io.ktor.application.ApplicationCall
import io.ktor.features.StatusPages
@ -7,15 +7,15 @@ import io.ktor.routing.Route
import io.ktor.routing.method
import io.ktor.util.pipeline.PipelineContext
import io.ktor.util.pipeline.PipelineInterceptor
import org.leafygreens.kompendium.KompendiumPreFlight.errorNotarizationPreFlight
import org.leafygreens.kompendium.MethodParser.parseErrorInfo
import org.leafygreens.kompendium.MethodParser.parseMethodInfo
import org.leafygreens.kompendium.models.meta.MethodInfo.GetInfo
import org.leafygreens.kompendium.models.meta.MethodInfo.PostInfo
import org.leafygreens.kompendium.models.meta.MethodInfo.PutInfo
import org.leafygreens.kompendium.models.meta.MethodInfo.DeleteInfo
import org.leafygreens.kompendium.models.meta.ResponseInfo
import org.leafygreens.kompendium.models.oas.OpenApiSpecPathItem
import io.bkbn.kompendium.KompendiumPreFlight.errorNotarizationPreFlight
import io.bkbn.kompendium.MethodParser.parseErrorInfo
import io.bkbn.kompendium.MethodParser.parseMethodInfo
import io.bkbn.kompendium.models.meta.MethodInfo.GetInfo
import io.bkbn.kompendium.models.meta.MethodInfo.PostInfo
import io.bkbn.kompendium.models.meta.MethodInfo.PutInfo
import io.bkbn.kompendium.models.meta.MethodInfo.DeleteInfo
import io.bkbn.kompendium.models.meta.ResponseInfo
import io.bkbn.kompendium.models.oas.OpenApiSpecPathItem
/**
* Notarization methods are the primary way that a Ktor API using Kompendium differentiates
@ -27,7 +27,7 @@ object Notarized {
/**
* Notarization for an HTTP GET request
* @param TParam The class containing all parameter fields.
* Each field must be annotated with @[org.leafygreens.kompendium.annotations.KompendiumField]
* Each field must be annotated with @[io.bkbn.kompendium.annotations.KompendiumField]
* @param TResp Class detailing the expected API response
* @param info Route metadata
*/
@ -36,7 +36,7 @@ object Notarized {
noinline body: PipelineInterceptor<Unit, ApplicationCall>
): Route =
KompendiumPreFlight.methodNotarizationPreFlight<TParam, Unit, TResp>() { paramType, requestType, responseType ->
val path = Kompendium.pathCalculator.calculate(this)
val path = Kompendium.calculatePath(this)
Kompendium.openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() }
Kompendium.openApiSpec.paths[path]?.get = parseMethodInfo(info, paramType, requestType, responseType)
return method(HttpMethod.Get) { handle(body) }
@ -45,7 +45,7 @@ object Notarized {
/**
* Notarization for an HTTP POST request
* @param TParam The class containing all parameter fields.
* Each field must be annotated with @[org.leafygreens.kompendium.annotations.KompendiumField]
* Each field must be annotated with @[io.bkbn.kompendium.annotations.KompendiumField]
* @param TReq Class detailing the expected API request body
* @param TResp Class detailing the expected API response
* @param info Route metadata
@ -55,7 +55,7 @@ object Notarized {
noinline body: PipelineInterceptor<Unit, ApplicationCall>
): Route =
KompendiumPreFlight.methodNotarizationPreFlight<TParam, TReq, TResp>() { paramType, requestType, responseType ->
val path = Kompendium.pathCalculator.calculate(this)
val path = Kompendium.calculatePath(this)
Kompendium.openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() }
Kompendium.openApiSpec.paths[path]?.post = parseMethodInfo(info, paramType, requestType, responseType)
return method(HttpMethod.Post) { handle(body) }
@ -64,7 +64,7 @@ object Notarized {
/**
* Notarization for an HTTP Delete request
* @param TParam The class containing all parameter fields.
* Each field must be annotated with @[org.leafygreens.kompendium.annotations.KompendiumField]
* Each field must be annotated with @[io.bkbn.kompendium.annotations.KompendiumField]
* @param TReq Class detailing the expected API request body
* @param TResp Class detailing the expected API response
* @param info Route metadata
@ -74,7 +74,7 @@ object Notarized {
noinline body: PipelineInterceptor<Unit, ApplicationCall>,
): Route =
KompendiumPreFlight.methodNotarizationPreFlight<TParam, TReq, TResp>() { paramType, requestType, responseType ->
val path = Kompendium.pathCalculator.calculate(this)
val path = Kompendium.calculatePath(this)
Kompendium.openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() }
Kompendium.openApiSpec.paths[path]?.put =
parseMethodInfo(info, paramType, requestType, responseType)
@ -84,7 +84,7 @@ object Notarized {
/**
* Notarization for an HTTP POST request
* @param TParam The class containing all parameter fields.
* Each field must be annotated with @[org.leafygreens.kompendium.annotations.KompendiumField]
* Each field must be annotated with @[io.bkbn.kompendium.annotations.KompendiumField]
* @param TResp Class detailing the expected API response
* @param info Route metadata
*/
@ -93,7 +93,7 @@ object Notarized {
noinline body: PipelineInterceptor<Unit, ApplicationCall>
): Route =
KompendiumPreFlight.methodNotarizationPreFlight<TParam, Unit, TResp> { paramType, requestType, responseType ->
val path = Kompendium.pathCalculator.calculate(this)
val path = Kompendium.calculatePath(this)
Kompendium.openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() }
Kompendium.openApiSpec.paths[path]?.delete = parseMethodInfo(info, paramType, requestType, responseType)
return method(HttpMethod.Delete) { handle(body) }
@ -112,5 +112,4 @@ object Notarized {
info.parseErrorInfo(errorType, responseType)
exception(handler)
}
}

View File

@ -1,4 +1,4 @@
package org.leafygreens.kompendium.annotations
package io.bkbn.kompendium.annotations
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.PROPERTY)

View File

@ -1,4 +1,4 @@
package org.leafygreens.kompendium.annotations
package io.bkbn.kompendium.annotations
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.PROPERTY)

View File

@ -0,0 +1,8 @@
package io.bkbn.kompendium.annotations
import kotlin.reflect.KClass
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
@Repeatable
annotation class UndeclaredField(val field: String, val clazz: KClass<*>)

View File

@ -0,0 +1,6 @@
package io.bkbn.kompendium.models.meta
import kotlin.reflect.KType
import io.bkbn.kompendium.models.oas.OpenApiSpecResponse
typealias ErrorMap = Map<KType, Pair<Int, OpenApiSpecResponse<*>>?>

View File

@ -1,4 +1,4 @@
package org.leafygreens.kompendium.models.meta
package io.bkbn.kompendium.models.meta
import kotlin.reflect.KClass
@ -11,6 +11,7 @@ sealed class MethodInfo<TParam, TResp>(
open val canThrow: Set<KClass<*>> = emptySet(),
open val responseInfo: ResponseInfo<TResp>? = null,
open val parameterExamples: Map<String, TParam> = emptyMap(),
open val operationId: String? = null
) {
data class GetInfo<TParam, TResp>(
@ -21,7 +22,8 @@ sealed class MethodInfo<TParam, TResp>(
override val deprecated: Boolean = false,
override val securitySchemes: Set<String> = emptySet(),
override val canThrow: Set<KClass<*>> = emptySet(),
override val parameterExamples: Map<String, TParam> = emptyMap()
override val parameterExamples: Map<String, TParam> = emptyMap(),
override val operationId: String? = null
) : MethodInfo<TParam, TResp>(
summary = summary,
description = description,
@ -30,7 +32,8 @@ sealed class MethodInfo<TParam, TResp>(
securitySchemes = securitySchemes,
canThrow = canThrow,
responseInfo = responseInfo,
parameterExamples = parameterExamples
parameterExamples = parameterExamples,
operationId = operationId
)
data class PostInfo<TParam, TReq, TResp>(
@ -42,7 +45,8 @@ sealed class MethodInfo<TParam, TResp>(
override val deprecated: Boolean = false,
override val securitySchemes: Set<String> = emptySet(),
override val canThrow: Set<KClass<*>> = emptySet(),
override val parameterExamples: Map<String, TParam> = emptyMap()
override val parameterExamples: Map<String, TParam> = emptyMap(),
override val operationId: String? = null
) : MethodInfo<TParam, TResp>(
summary = summary,
description = description,
@ -51,7 +55,8 @@ sealed class MethodInfo<TParam, TResp>(
securitySchemes = securitySchemes,
canThrow = canThrow,
responseInfo = responseInfo,
parameterExamples = parameterExamples
parameterExamples = parameterExamples,
operationId = operationId
)
data class PutInfo<TParam, TReq, TResp>(
@ -63,7 +68,8 @@ sealed class MethodInfo<TParam, TResp>(
override val deprecated: Boolean = false,
override val securitySchemes: Set<String> = emptySet(),
override val canThrow: Set<KClass<*>> = emptySet(),
override val parameterExamples: Map<String, TParam> = emptyMap()
override val parameterExamples: Map<String, TParam> = emptyMap(),
override val operationId: String? = null
) : MethodInfo<TParam, TResp>(
summary = summary,
description = description,
@ -71,7 +77,8 @@ sealed class MethodInfo<TParam, TResp>(
deprecated = deprecated,
securitySchemes = securitySchemes,
canThrow = canThrow,
parameterExamples = parameterExamples
parameterExamples = parameterExamples,
operationId = operationId
)
data class DeleteInfo<TParam, TResp>(
@ -82,7 +89,8 @@ sealed class MethodInfo<TParam, TResp>(
override val deprecated: Boolean = false,
override val securitySchemes: Set<String> = emptySet(),
override val canThrow: Set<KClass<*>> = emptySet(),
override val parameterExamples: Map<String, TParam> = emptyMap()
override val parameterExamples: Map<String, TParam> = emptyMap(),
override val operationId: String? = null
) : MethodInfo<TParam, TResp>(
summary = summary,
description = description,
@ -90,6 +98,7 @@ sealed class MethodInfo<TParam, TResp>(
deprecated = deprecated,
securitySchemes = securitySchemes,
canThrow = canThrow,
parameterExamples = parameterExamples
parameterExamples = parameterExamples,
operationId = operationId
)
}

View File

@ -1,4 +1,4 @@
package org.leafygreens.kompendium.models.meta
package io.bkbn.kompendium.models.meta
data class RequestInfo<TReq>(
val description: String,

View File

@ -1,4 +1,4 @@
package org.leafygreens.kompendium.models.meta
package io.bkbn.kompendium.models.meta
import io.ktor.http.HttpStatusCode

View File

@ -0,0 +1,5 @@
package io.bkbn.kompendium.models.meta
import io.bkbn.kompendium.models.oas.OpenApiSpecComponentSchema
typealias SchemaMap = Map<String, OpenApiSpecComponentSchema>

View File

@ -1,4 +1,4 @@
package org.leafygreens.kompendium.models.oas
package io.bkbn.kompendium.models.oas
data class OpenApiSpec(
val openapi: String = "3.0.3",

View File

@ -1,8 +1,8 @@
package org.leafygreens.kompendium.models.oas
package io.bkbn.kompendium.models.oas
sealed class OpenApiSpecComponentSchema(open val default: Any? = null) {
fun addDefault(default: Any?): OpenApiSpecComponentSchema = when (this) {
is AnyOfReferencedSchema -> error("Cannot add default to anyOf reference")
is ReferencedSchema -> this.copy(default = default)
is ObjectSchema -> this.copy(default = default)
is DictionarySchema -> this.copy(default = default)
@ -11,12 +11,12 @@ sealed class OpenApiSpecComponentSchema(open val default: Any? = null) {
is FormatSchema -> this.copy(default = default)
is ArraySchema -> this.copy(default = default)
}
}
sealed class TypedSchema(open val type: String, override val default: Any? = null) : OpenApiSpecComponentSchema(default)
data class ReferencedSchema(val `$ref`: String, override val default: Any? = null) : OpenApiSpecComponentSchema(default)
data class AnyOfReferencedSchema(val anyOf: List<ReferencedSchema>) : OpenApiSpecComponentSchema()
data class ObjectSchema(
val properties: Map<String, OpenApiSpecComponentSchema>,

View File

@ -1,4 +1,4 @@
package org.leafygreens.kompendium.models.oas
package io.bkbn.kompendium.models.oas
// TODO I *think* the only thing I need here is the security https://swagger.io/specification/#components-object
data class OpenApiSpecComponents(

View File

@ -1,4 +1,4 @@
package org.leafygreens.kompendium.models.oas
package io.bkbn.kompendium.models.oas
import java.net.URI

View File

@ -1,4 +1,4 @@
package org.leafygreens.kompendium.models.oas
package io.bkbn.kompendium.models.oas
import java.net.URI

View File

@ -1,4 +1,4 @@
package org.leafygreens.kompendium.models.oas
package io.bkbn.kompendium.models.oas
import java.net.URI

View File

@ -1,4 +1,4 @@
package org.leafygreens.kompendium.models.oas
package io.bkbn.kompendium.models.oas
data class OpenApiSpecLink(
val operationRef: String?, // todo mutually exclusive with operationId

View File

@ -1,7 +1,7 @@
package org.leafygreens.kompendium.models.oas
package io.bkbn.kompendium.models.oas
data class OpenApiSpecMediaType<T>(
val schema: OpenApiSpecReferenceObject,
val schema: OpenApiSpecReferencable,
val examples: Map<String, ExampleWrapper<T>>? = null
)

View File

@ -1,4 +1,4 @@
package org.leafygreens.kompendium.models.oas
package io.bkbn.kompendium.models.oas
import java.net.URI

View File

@ -1,4 +1,4 @@
package org.leafygreens.kompendium.models.oas
package io.bkbn.kompendium.models.oas
data class OpenApiSpecOAuthFlows(
val implicit: OpenApiSpecOAuthFlow?,

View File

@ -1,4 +1,4 @@
package org.leafygreens.kompendium.models.oas
package io.bkbn.kompendium.models.oas
data class OpenApiSpecPathItem(
var get: OpenApiSpecPathItemOperation? = null,

View File

@ -1,4 +1,4 @@
package org.leafygreens.kompendium.models.oas
package io.bkbn.kompendium.models.oas
data class OpenApiSpecPathItemOperation(
var tags: Set<String> = emptySet(),

View File

@ -1,15 +1,16 @@
package org.leafygreens.kompendium.models.oas
package io.bkbn.kompendium.models.oas
sealed class OpenApiSpecReferencable
sealed interface OpenApiSpecReferencable
class OpenApiSpecReferenceObject(val `$ref`: String) : OpenApiSpecReferencable()
data class OpenApiAnyOf(val anyOf: List<OpenApiSpecReferenceObject>) : OpenApiSpecReferencable
data class OpenApiSpecReferenceObject(val `$ref`: String) : OpenApiSpecReferencable
data class OpenApiSpecResponse<T>(
val description: String? = null,
val headers: Map<String, OpenApiSpecReferencable>? = null,
val content: Map<String, OpenApiSpecMediaType<T>>? = null,
val links: Map<String, OpenApiSpecReferencable>? = null
) : OpenApiSpecReferencable()
) : OpenApiSpecReferencable
data class OpenApiSpecParameter(
val name: String,
@ -21,10 +22,10 @@ data class OpenApiSpecParameter(
val allowEmptyValue: Boolean? = null,
val style: String? = null,
val explode: Boolean? = null
) : OpenApiSpecReferencable()
) : OpenApiSpecReferencable
data class OpenApiSpecRequest<T>(
val description: String?,
val content: Map<String, OpenApiSpecMediaType<T>>,
val required: Boolean = false
) : OpenApiSpecReferencable()
) : OpenApiSpecReferencable

View File

@ -1,4 +1,4 @@
package org.leafygreens.kompendium.models.oas
package io.bkbn.kompendium.models.oas
data class OpenApiSpecSchemaSecurity(
val type: String? = null, // TODO Enum? "apiKey", "http", "oauth2", "openIdConnect"

View File

@ -1,4 +1,4 @@
package org.leafygreens.kompendium.models.oas
package io.bkbn.kompendium.models.oas
import java.net.URI

View File

@ -1,4 +1,4 @@
package org.leafygreens.kompendium.models.oas
package io.bkbn.kompendium.models.oas
data class OpenApiSpecServerVariable(
val `enum`: Set<String>, // todo enforce not empty

View File

@ -1,4 +1,4 @@
package org.leafygreens.kompendium.models.oas
package io.bkbn.kompendium.models.oas
data class OpenApiSpecTag(
val name: String,

View File

@ -0,0 +1,13 @@
package io.bkbn.kompendium.path
import io.ktor.routing.Route
import io.ktor.routing.RouteSelector
import kotlin.reflect.KClass
interface IPathCalculator {
fun calculate(route: Route?, tail: String = ""): String
fun <T : RouteSelector> addCustomRouteHandler(
selector: KClass<T>,
handler: IPathCalculator.(Route, String) -> String
)
}

View File

@ -0,0 +1,54 @@
package io.bkbn.kompendium.path
import io.ktor.routing.PathSegmentConstantRouteSelector
import io.ktor.routing.PathSegmentParameterRouteSelector
import io.ktor.routing.RootRouteSelector
import io.ktor.routing.Route
import io.ktor.routing.RouteSelector
import io.ktor.routing.TrailingSlashRouteSelector
import io.ktor.util.InternalAPI
import kotlin.reflect.KClass
/**
* Responsible for calculating a url path from a provided [Route]
*/
@OptIn(InternalAPI::class)
internal object PathCalculator: IPathCalculator {
private val pathHandler: RouteHandlerMap = mutableMapOf()
init {
addCustomRouteHandler(RootRouteSelector::class) { _, tail -> tail }
addCustomRouteHandler(PathSegmentParameterRouteSelector::class) { route, tail ->
val newTail = "/${route.selector}$tail"
calculate(route.parent, newTail)
}
addCustomRouteHandler(PathSegmentConstantRouteSelector::class) { route, tail ->
val newTail = "/${route.selector}$tail"
calculate(route.parent, newTail)
}
addCustomRouteHandler(TrailingSlashRouteSelector::class) { route, tail ->
val newTail = tail.ifBlank { "/" }
calculate(route.parent, newTail)
}
}
@OptIn(InternalAPI::class)
override fun calculate(
route: Route?,
tail: String
): String = when (route) {
null -> tail
else -> when (pathHandler.containsKey(route.selector::class)) {
true -> pathHandler[route.selector::class]!!.invoke(this, route, tail)
else -> error("No handler has been registered for ${route.selector}")
}
}
override fun <T : RouteSelector> addCustomRouteHandler(
selector: KClass<T>,
handler: IPathCalculator.(Route, String) -> String
) {
pathHandler[selector] = handler
}
}

View File

@ -0,0 +1,7 @@
package io.bkbn.kompendium.path
import io.ktor.routing.Route
import io.ktor.routing.RouteSelector
import kotlin.reflect.KClass
typealias RouteHandlerMap = MutableMap<KClass<out RouteSelector>, IPathCalculator.(Route, String) -> String>

View File

@ -0,0 +1,31 @@
package io.bkbn.kompendium.routes
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.SerializationFeature
import io.bkbn.kompendium.models.oas.OpenApiSpec
import io.ktor.application.call
import io.ktor.response.respondText
import io.ktor.routing.Routing
import io.ktor.routing.get
import io.ktor.routing.route
/**
* Provides an out-of-the-box route to return the generated [OpenApiSpec]
* @param oas spec that is returned
* @param om provider for Jackson
*/
fun Routing.openApi(
oas: OpenApiSpec,
om: ObjectMapper = objectMapper
) {
route("/openapi.json") {
get {
call.respondText { om.writeValueAsString(oas) }
}
}
}
private val objectMapper = ObjectMapper()
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
.enable(SerializationFeature.INDENT_OUTPUT)

View File

@ -1,4 +1,4 @@
package org.leafygreens.kompendium.routes
package io.bkbn.kompendium.routes
import io.ktor.application.call
import io.ktor.html.respondHtml
@ -13,7 +13,7 @@ import kotlinx.html.script
import kotlinx.html.style
import kotlinx.html.title
import kotlinx.html.unsafe
import org.leafygreens.kompendium.models.oas.OpenApiSpec
import io.bkbn.kompendium.models.oas.OpenApiSpec
/**
* Provides an out-of-the-box route to view docs using ReDoc

View File

@ -1,12 +1,12 @@
package org.leafygreens.kompendium.util
package io.bkbn.kompendium.util
import io.bkbn.kompendium.util.Helpers.getReferenceSlug
import java.lang.reflect.ParameterizedType
import kotlin.reflect.KClass
import kotlin.reflect.KProperty
import kotlin.reflect.KType
import kotlin.reflect.full.createType
import kotlin.reflect.jvm.javaField
import org.leafygreens.kompendium.util.Helpers.getReferenceSlug
import org.slf4j.LoggerFactory
object Helpers {
@ -17,27 +17,6 @@ object Helpers {
val UNIT_TYPE by lazy { Unit::class.createType() }
/**
* Simple extension function that will take a [Pair] and place it (if absent) into a [MutableMap].
*
* @receiver [MutableMap]
* @param pair to add to map
*/
fun <K, V> MutableMap<K, V>.putPairIfAbsent(pair: Pair<K, V>) = putIfAbsent(pair.first, pair.second)
/**
* Simple extension function that will convert a list with two items into a [Pair]
* @receiver [List]
* @return [Pair]
* @throws [IllegalArgumentException] when the list size is not exactly two
*/
fun <T> List<T>.toPair(): Pair<T, T> {
if (this.size != 2) {
throw IllegalArgumentException("List is not of length 2!")
}
return Pair(this[0], this[1])
}
/**
* Higher order function that takes a map of names to objects and will log their state ahead of function invocation
* along with the result of the function invocation
@ -54,6 +33,11 @@ object Helpers {
else -> simpleName ?: error("Could not determine simple name for $this")
}
fun KType.getSimpleSlug(): String = when {
this.arguments.isNotEmpty() -> genericNameAdapter(this, classifier as KClass<*>)
else -> (classifier as KClass<*>).simpleName ?: error("Could not determine simple name for $this")
}
fun KType.getReferenceSlug(): String = when {
arguments.isNotEmpty() -> "$COMPONENT_SLUG/${genericNameAdapter(this, classifier as KClass<*>)}"
else -> "$COMPONENT_SLUG/${(classifier as KClass<*>).simpleName}"

View File

@ -1,34 +0,0 @@
package org.leafygreens.kompendium
import org.leafygreens.kompendium.models.meta.ErrorMap
import org.leafygreens.kompendium.models.meta.SchemaMap
import org.leafygreens.kompendium.models.oas.OpenApiSpec
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfo
import org.leafygreens.kompendium.path.CorePathCalculator
import org.leafygreens.kompendium.path.PathCalculator
/**
* Maintains all state for the Kompendium library
*/
object Kompendium {
var errorMap: ErrorMap = emptyMap()
var cache: SchemaMap = emptyMap()
var openApiSpec = OpenApiSpec(
info = OpenApiSpecInfo(),
servers = mutableListOf(),
paths = mutableMapOf()
)
var pathCalculator: PathCalculator = CorePathCalculator()
fun resetSchema() {
openApiSpec = OpenApiSpec(
info = OpenApiSpecInfo(),
servers = mutableListOf(),
paths = mutableMapOf()
)
cache = emptyMap()
}
}

View File

@ -1,168 +0,0 @@
package org.leafygreens.kompendium
import java.util.UUID
import kotlin.reflect.KClass
import kotlin.reflect.KType
import kotlin.reflect.full.isSubclassOf
import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.javaField
import kotlin.reflect.typeOf
import org.leafygreens.kompendium.models.meta.SchemaMap
import org.leafygreens.kompendium.models.oas.ArraySchema
import org.leafygreens.kompendium.models.oas.DictionarySchema
import org.leafygreens.kompendium.models.oas.EnumSchema
import org.leafygreens.kompendium.models.oas.FormatSchema
import org.leafygreens.kompendium.models.oas.ObjectSchema
import org.leafygreens.kompendium.models.oas.ReferencedSchema
import org.leafygreens.kompendium.models.oas.SimpleSchema
import org.leafygreens.kompendium.util.Helpers.COMPONENT_SLUG
import org.leafygreens.kompendium.util.Helpers.genericNameAdapter
import org.leafygreens.kompendium.util.Helpers.getReferenceSlug
import org.leafygreens.kompendium.util.Helpers.logged
import org.slf4j.LoggerFactory
/**
* Responsible for generating the schema map that is used to power all object references across the API Spec.
*/
object Kontent {
private val logger = LoggerFactory.getLogger(javaClass)
/**
* Analyzes a type [T] for its top-level and any nested schemas, and adds them to a [SchemaMap], if provided
* @param T type to analyze
* @param cache Existing schema map to append to
* @return an updated schema map containing all type information for [T]
*/
@OptIn(ExperimentalStdlibApi::class)
inline fun <reified T> generateKontent(
cache: SchemaMap = emptyMap()
): SchemaMap {
val kontentType = typeOf<T>()
return generateKTypeKontent(kontentType, cache)
}
/**
* Analyze a type [T], but filters out the top-level type
* @param T type to analyze
* @param cache Existing schema map to append to
* @return an updated schema map containing all type information for [T]
*/
@OptIn(ExperimentalStdlibApi::class)
inline fun <reified T> generateParameterKontent(
cache: SchemaMap = emptyMap()
): SchemaMap {
val kontentType = typeOf<T>()
return generateKTypeKontent(kontentType, cache)
.filterNot { (slug, _) -> slug == (kontentType.classifier as KClass<*>).simpleName }
}
/**
* Recursively fills schema map depending on [KType] classifier
* @param type [KType] to parse
* @param cache Existing schema map to append to
*/
fun generateKTypeKontent(
type: KType,
cache: SchemaMap = emptyMap()
): SchemaMap = logged(object {}.javaClass.enclosingMethod.name, mapOf("cache" to cache)) {
logger.debug("Parsing Kontent of $type")
when (val clazz = type.classifier as KClass<*>) {
Unit::class -> cache
Int::class -> cache.plus(clazz.simpleName!! to FormatSchema("int32", "integer"))
Long::class -> cache.plus(clazz.simpleName!! to FormatSchema("int64", "integer"))
Double::class -> cache.plus(clazz.simpleName!! to FormatSchema("double", "number"))
Float::class -> cache.plus(clazz.simpleName!! to FormatSchema("float", "number"))
String::class -> cache.plus(clazz.simpleName!! to SimpleSchema("string"))
Boolean::class -> cache.plus(clazz.simpleName!! to SimpleSchema("boolean"))
UUID::class -> cache.plus(clazz.simpleName!! to FormatSchema("uuid", "string"))
else -> when {
clazz.isSubclassOf(Collection::class) -> handleCollectionType(type, clazz, cache)
clazz.isSubclassOf(Enum::class) -> handleEnumType(clazz, cache)
clazz.isSubclassOf(Map::class) -> handleMapType(type, clazz, cache)
else -> handleComplexType(clazz, cache)
}
}
}
/**
* In the event of an object type, this method will parse out individual fields to recursively aggregate object map.
* @param clazz Class of the object to analyze
* @param cache Existing schema map to append to
*/
private fun handleComplexType(clazz: KClass<*>, cache: SchemaMap): SchemaMap =
when (cache.containsKey(clazz.simpleName)) {
true -> {
logger.debug("Cache already contains ${clazz.simpleName}, returning cache untouched")
cache
}
false -> {
logger.debug("${clazz.simpleName} was not found in cache, generating now")
var newCache = cache
val fieldMap = clazz.memberProperties.associate { prop ->
logger.debug("Analyzing $prop in class $clazz")
val field = prop.javaField?.type?.kotlin ?: error("Unable to parse field type from $prop")
logger.debug("Detected field $field")
if (!newCache.containsKey(field.simpleName)) {
logger.debug("Cache was missing ${field.simpleName}, adding now")
newCache = generateKTypeKontent(prop.returnType, newCache)
}
val propSchema = ReferencedSchema(field.getReferenceSlug(prop))
Pair(prop.name, propSchema)
}
logger.debug("${clazz.simpleName} contains $fieldMap")
val schema = ObjectSchema(fieldMap)
logger.debug("${clazz.simpleName} schema: $schema")
newCache.plus(clazz.simpleName!! to schema)
}
}
/**
* Handler for when an [Enum] is encountered
* @param clazz Class of the object to analyze
* @param cache Existing schema map to append to
*/
private fun handleEnumType(clazz: KClass<*>, cache: SchemaMap): SchemaMap {
val options = clazz.java.enumConstants.map { it.toString() }.toSet()
return cache.plus(clazz.simpleName!! to EnumSchema(options))
}
/**
* Handler for when a [Map] is encountered
* @param type Map type information
* @param clazz Map class information
* @param cache Existing schema map to append to
*/
private fun handleMapType(type: KType, clazz: KClass<*>, cache: SchemaMap): SchemaMap {
logger.debug("Map detected for $type, generating schema and appending to cache")
val (keyType, valType) = type.arguments.map { it.type }
logger.debug("Obtained map types -> key: $keyType and value: $valType")
if (keyType?.classifier != String::class) {
error("Invalid Map $type: OpenAPI dictionaries must have keys of type String")
}
val valClassName = (valType?.classifier as KClass<*>).simpleName
val referenceName = genericNameAdapter(type, clazz)
val valueReference = ReferencedSchema("$COMPONENT_SLUG/$valClassName")
val schema = DictionarySchema(additionalProperties = valueReference)
val updatedCache = generateKTypeKontent(valType, cache)
return updatedCache.plus(referenceName to schema)
}
/**
* Handler for when a [Collection] is encountered
* @param type Collection type information
* @param clazz Collection class information
* @param cache Existing schema map to append to
*/
private fun handleCollectionType(type: KType, clazz: KClass<*>, cache: SchemaMap): SchemaMap {
logger.debug("Collection detected for $type, generating schema and appending to cache")
val collectionType = type.arguments.first().type!!
val collectionClass = collectionType.classifier as KClass<*>
logger.debug("Obtained collection class: $collectionClass")
val referenceName = genericNameAdapter(type, clazz)
val valueReference = ReferencedSchema("${COMPONENT_SLUG}/${collectionClass.simpleName}")
val schema = ArraySchema(items = valueReference)
val updatedCache = generateKTypeKontent(collectionType, cache)
return updatedCache.plus(referenceName to schema)
}
}

View File

@ -1,6 +0,0 @@
package org.leafygreens.kompendium.models.meta
import kotlin.reflect.KType
import org.leafygreens.kompendium.models.oas.OpenApiSpecResponse
typealias ErrorMap = Map<KType, Pair<Int, OpenApiSpecResponse<*>>?>

View File

@ -1,5 +0,0 @@
package org.leafygreens.kompendium.models.meta
import org.leafygreens.kompendium.models.oas.OpenApiSpecComponentSchema
typealias SchemaMap = Map<String, OpenApiSpecComponentSchema>

View File

@ -1,51 +0,0 @@
package org.leafygreens.kompendium.path
import io.ktor.routing.PathSegmentConstantRouteSelector
import io.ktor.routing.PathSegmentParameterRouteSelector
import io.ktor.routing.RootRouteSelector
import io.ktor.routing.Route
import io.ktor.util.InternalAPI
import org.slf4j.LoggerFactory
/**
* Default [PathCalculator] meant to be overridden as necessary
*/
open class CorePathCalculator : PathCalculator {
private val logger = LoggerFactory.getLogger(javaClass)
@OptIn(InternalAPI::class)
override fun calculate(
route: Route?,
tail: String
): String = when (route) {
null -> tail
else -> when (route.selector) {
is RootRouteSelector -> {
logger.debug("Root route detected, returning path: $tail")
tail
}
is PathSegmentParameterRouteSelector -> {
logger.debug("Found segment parameter ${route.selector}, continuing to parent")
val newTail = "/${route.selector}$tail"
calculate(route.parent, newTail)
}
is PathSegmentConstantRouteSelector -> {
logger.debug("Found segment constant ${route.selector}, continuing to parent")
val newTail = "/${route.selector}$tail"
calculate(route.parent, newTail)
}
else -> when (route.selector.javaClass.simpleName) {
"TrailingSlashRouteSelector" -> {
logger.debug("Found trailing slash route selector")
val newTail = tail.ifBlank { "/" }
calculate(route.parent, newTail)
}
else -> handleCustomSelectors(route, tail)
}
}
}
override fun handleCustomSelectors(route: Route, tail: String): String = error("Unknown selector ${route.selector}")
}

View File

@ -1,20 +0,0 @@
package org.leafygreens.kompendium.path
import io.ktor.routing.Route
/**
* Extensible interface for calculating Ktor paths
*/
interface PathCalculator {
/**
* Core route calculation method
*/
fun calculate(route: Route?, tail: String = ""): String
/**
* Used to handle any custom selectors that may be missed by the base route calculation
*/
fun handleCustomSelectors(route: Route, tail: String): String
}

View File

@ -1,20 +0,0 @@
package org.leafygreens.kompendium.routes
import io.ktor.application.call
import io.ktor.response.respond
import io.ktor.routing.Routing
import io.ktor.routing.get
import io.ktor.routing.route
import org.leafygreens.kompendium.models.oas.OpenApiSpec
/**
* Provides an out-of-the-box route to return the generated [OpenApiSpec]
* @param oas spec that is returned
*/
fun Routing.openApi(oas: OpenApiSpec) {
route("/openapi.json") {
get {
call.respond(oas)
}
}
}

View File

@ -1,4 +1,4 @@
package org.leafygreens.kompendium
package io.bkbn.kompendium
import io.ktor.application.Application
import io.ktor.http.HttpMethod
@ -10,33 +10,43 @@ import java.net.URI
import kotlin.test.AfterTest
import kotlin.test.Test
import kotlin.test.assertEquals
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfo
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfoContact
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfoLicense
import org.leafygreens.kompendium.models.oas.OpenApiSpecServer
import org.leafygreens.kompendium.routes.openApi
import org.leafygreens.kompendium.routes.redoc
import org.leafygreens.kompendium.util.TestHelpers.getFileSnapshot
import org.leafygreens.kompendium.util.complexType
import org.leafygreens.kompendium.util.configModule
import org.leafygreens.kompendium.util.emptyGet
import org.leafygreens.kompendium.util.nestedUnderRootModule
import org.leafygreens.kompendium.util.nonRequiredParamsGet
import org.leafygreens.kompendium.util.notarizedDeleteModule
import org.leafygreens.kompendium.util.notarizedGetModule
import org.leafygreens.kompendium.util.notarizedGetWithMultipleThrowables
import org.leafygreens.kompendium.util.notarizedGetWithNotarizedException
import org.leafygreens.kompendium.util.notarizedPostModule
import org.leafygreens.kompendium.util.notarizedPutModule
import org.leafygreens.kompendium.util.pathParsingTestModule
import org.leafygreens.kompendium.util.primitives
import org.leafygreens.kompendium.util.returnsList
import org.leafygreens.kompendium.util.rootModule
import org.leafygreens.kompendium.util.statusPageModule
import org.leafygreens.kompendium.util.statusPageMultiExceptions
import org.leafygreens.kompendium.util.trailingSlash
import org.leafygreens.kompendium.util.withDefaultParameter
import org.leafygreens.kompendium.util.withExamples
import io.bkbn.kompendium.models.oas.OpenApiSpecInfo
import io.bkbn.kompendium.models.oas.OpenApiSpecInfoContact
import io.bkbn.kompendium.models.oas.OpenApiSpecInfoLicense
import io.bkbn.kompendium.models.oas.OpenApiSpecServer
import io.bkbn.kompendium.routes.openApi
import io.bkbn.kompendium.routes.redoc
import io.bkbn.kompendium.util.TestHelpers.getFileSnapshot
import io.bkbn.kompendium.util.complexType
import io.bkbn.kompendium.util.jacksonConfigModule
import io.bkbn.kompendium.util.emptyGet
import io.bkbn.kompendium.util.genericPolymorphicResponse
import io.bkbn.kompendium.util.genericPolymorphicResponseMultipleImpls
import io.bkbn.kompendium.util.kotlinxConfigModule
import io.bkbn.kompendium.util.nestedUnderRootModule
import io.bkbn.kompendium.util.nonRequiredParamsGet
import io.bkbn.kompendium.util.notarizedDeleteModule
import io.bkbn.kompendium.util.notarizedGetModule
import io.bkbn.kompendium.util.notarizedGetWithMultipleThrowables
import io.bkbn.kompendium.util.notarizedGetWithNotarizedException
import io.bkbn.kompendium.util.notarizedPostModule
import io.bkbn.kompendium.util.notarizedPutModule
import io.bkbn.kompendium.util.pathParsingTestModule
import io.bkbn.kompendium.util.polymorphicCollectionResponse
import io.bkbn.kompendium.util.polymorphicInterfaceResponse
import io.bkbn.kompendium.util.polymorphicMapResponse
import io.bkbn.kompendium.util.polymorphicResponse
import io.bkbn.kompendium.util.primitives
import io.bkbn.kompendium.util.returnsList
import io.bkbn.kompendium.util.rootModule
import io.bkbn.kompendium.util.simpleGenericResponse
import io.bkbn.kompendium.util.statusPageModule
import io.bkbn.kompendium.util.statusPageMultiExceptions
import io.bkbn.kompendium.util.trailingSlash
import io.bkbn.kompendium.util.undeclaredType
import io.bkbn.kompendium.util.withDefaultParameter
import io.bkbn.kompendium.util.withExamples
import io.bkbn.kompendium.util.withOperationId
internal class KompendiumTest {
@ -53,7 +63,7 @@ internal class KompendiumTest {
@Test
fun `Notarized Get records all expected information`() {
withTestApplication({
configModule()
kotlinxConfigModule()
docs()
notarizedGetModule()
}) {
@ -69,7 +79,7 @@ internal class KompendiumTest {
@Test
fun `Notarized Get does not interrupt the pipeline`() {
withTestApplication({
configModule()
jacksonConfigModule()
docs()
notarizedGetModule()
}) {
@ -85,7 +95,7 @@ internal class KompendiumTest {
@Test
fun `Notarized Post records all expected information`() {
withTestApplication({
configModule()
jacksonConfigModule()
docs()
notarizedPostModule()
}) {
@ -101,7 +111,7 @@ internal class KompendiumTest {
@Test
fun `Notarized post does not interrupt the pipeline`() {
withTestApplication({
configModule()
kotlinxConfigModule()
docs()
notarizedPostModule()
}) {
@ -117,7 +127,7 @@ internal class KompendiumTest {
@Test
fun `Notarized Put records all expected information`() {
withTestApplication({
configModule()
jacksonConfigModule()
docs()
notarizedPutModule()
}) {
@ -130,11 +140,10 @@ internal class KompendiumTest {
}
}
@Test
fun `Notarized put does not interrupt the pipeline`() {
withTestApplication({
configModule()
jacksonConfigModule()
docs()
notarizedPutModule()
}) {
@ -150,7 +159,7 @@ internal class KompendiumTest {
@Test
fun `Notarized delete records all expected information`() {
withTestApplication({
configModule()
jacksonConfigModule()
docs()
notarizedDeleteModule()
}) {
@ -166,7 +175,7 @@ internal class KompendiumTest {
@Test
fun `Notarized delete does not interrupt the pipeline`() {
withTestApplication({
configModule()
jacksonConfigModule()
docs()
notarizedDeleteModule()
}) {
@ -181,7 +190,7 @@ internal class KompendiumTest {
@Test
fun `Path parser stores the expected path`() {
withTestApplication({
configModule()
jacksonConfigModule()
docs()
pathParsingTestModule()
}) {
@ -197,7 +206,7 @@ internal class KompendiumTest {
@Test
fun `Can notarize the root route`() {
withTestApplication({
configModule()
jacksonConfigModule()
docs()
rootModule()
}) {
@ -213,7 +222,7 @@ internal class KompendiumTest {
@Test
fun `Can call the root route`() {
withTestApplication({
configModule()
kotlinxConfigModule()
docs()
rootModule()
}) {
@ -229,7 +238,7 @@ internal class KompendiumTest {
@Test
fun `Nested under root module does not append trailing slash`() {
withTestApplication({
configModule()
jacksonConfigModule()
docs()
nestedUnderRootModule()
}) {
@ -245,7 +254,7 @@ internal class KompendiumTest {
@Test
fun `Can notarize a trailing slash route`() {
withTestApplication({
configModule()
jacksonConfigModule()
docs()
trailingSlash()
}) {
@ -261,7 +270,7 @@ internal class KompendiumTest {
@Test
fun `Can call a trailing slash route`() {
withTestApplication({
configModule()
jacksonConfigModule()
docs()
trailingSlash()
}) {
@ -277,7 +286,7 @@ internal class KompendiumTest {
@Test
fun `Can notarize a complex type`() {
withTestApplication({
configModule()
jacksonConfigModule()
docs()
complexType()
}) {
@ -293,7 +302,7 @@ internal class KompendiumTest {
@Test
fun `Can notarize primitives`() {
withTestApplication({
configModule()
jacksonConfigModule()
docs()
primitives()
}) {
@ -309,7 +318,7 @@ internal class KompendiumTest {
@Test
fun `Can notarize a top level list response`() {
withTestApplication({
configModule()
jacksonConfigModule()
docs()
returnsList()
}) {
@ -325,7 +334,7 @@ internal class KompendiumTest {
@Test
fun `Can notarize route with no request params and no response body`() {
withTestApplication({
configModule()
kotlinxConfigModule()
docs()
emptyGet()
}) {
@ -341,7 +350,7 @@ internal class KompendiumTest {
@Test
fun `Can notarize route with non-required params`() {
withTestApplication({
configModule()
jacksonConfigModule()
docs()
nonRequiredParamsGet()
}) {
@ -354,10 +363,26 @@ internal class KompendiumTest {
}
}
@Test
fun `Can add operationId`() {
withTestApplication({
jacksonConfigModule()
docs()
withOperationId()
}) {
// do
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
// expect
val expected = getFileSnapshot("notarized_get_with_operation_id.json").trim()
assertEquals(expected, json, "The received json spec should match the expected content")
}
}
@Test
fun `Generates the expected redoc`() {
withTestApplication({
configModule()
jacksonConfigModule()
docs()
returnsList()
}) {
@ -374,7 +399,7 @@ internal class KompendiumTest {
fun `Generates additional responses when passed a throwable`() {
withTestApplication({
statusPageModule()
configModule()
jacksonConfigModule()
docs()
notarizedGetWithNotarizedException()
}) {
@ -391,7 +416,7 @@ internal class KompendiumTest {
fun `Generates additional responses when passed multiple throwables`() {
withTestApplication({
statusPageMultiExceptions()
configModule()
jacksonConfigModule()
docs()
notarizedGetWithMultipleThrowables()
}) {
@ -407,7 +432,7 @@ internal class KompendiumTest {
@Test
fun `Can generate example response and request bodies`() {
withTestApplication({
configModule()
kotlinxConfigModule()
docs()
withExamples()
}) {
@ -423,7 +448,7 @@ internal class KompendiumTest {
@Test
fun `Can generate a default parameter value`() {
withTestApplication({
configModule()
jacksonConfigModule()
docs()
withDefaultParameter()
}) {
@ -436,6 +461,133 @@ internal class KompendiumTest {
}
}
@Test
fun `Can generate a polymorphic response type`() {
withTestApplication({
jacksonConfigModule()
docs()
polymorphicResponse()
}) {
// do
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
// expect
val expected = getFileSnapshot("polymorphic_response.json").trim()
assertEquals(expected, json, "The received json spec should match the expected content")
}
}
@Test
fun `Can generate a collection with polymorphic response type`() {
withTestApplication({
jacksonConfigModule()
docs()
polymorphicCollectionResponse()
}) {
// do
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
// expect
val expected = getFileSnapshot("polymorphic_list_response.json").trim()
assertEquals(expected, json, "The received json spec should match the expected content")
}
}
@Test
fun `Can generate a map with a polymorphic response type`() {
withTestApplication({
jacksonConfigModule()
docs()
polymorphicMapResponse()
}) {
// do
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
// expect
val expected = getFileSnapshot("polymorphic_map_response.json").trim()
assertEquals(expected, json, "The received json spec should match the expected content")
}
}
@Test
fun `Can generate a polymorphic response from a sealed interface`() {
withTestApplication({
jacksonConfigModule()
docs()
polymorphicInterfaceResponse()
}) {
// do
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
// expect
val expected = getFileSnapshot("sealed_interface_response.json").trim()
assertEquals(expected, json, "The received json spec should match the expected content")
}
}
@Test
fun `Can generate a response type with a generic type`() {
withTestApplication({
jacksonConfigModule()
docs()
simpleGenericResponse()
}) {
// do
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
// expect
val expected = getFileSnapshot("generic_response.json").trim()
assertEquals(expected, json, "The received json spec should match the expected content")
}
}
@Test
fun `Can generate a polymorphic response type with generics`() {
withTestApplication({
jacksonConfigModule()
docs()
genericPolymorphicResponse()
}) {
// do
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
// expect
val expected = getFileSnapshot("polymorphic_response_with_generics.json").trim()
assertEquals(expected, json, "The received json spec should match the expected content")
}
}
@Test
fun `Absolute Psycho Inheritance Test`() {
withTestApplication({
kotlinxConfigModule()
docs()
genericPolymorphicResponseMultipleImpls()
}) {
// do
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
// expect
val expected = getFileSnapshot("crazy_polymorphic_example.json").trim()
assertEquals(expected, json, "The received json spec should match the expected content")
}
}
@Test
fun `Can add an undeclared field`() {
withTestApplication({
kotlinxConfigModule()
docs()
undeclaredType()
}) {
// do
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
// expect
val expected = getFileSnapshot("undeclared_field.json").trim()
assertEquals(expected, json, "The received json spec should match the expected content")
}
}
private val oas = Kompendium.openApiSpec.copy(
info = OpenApiSpecInfo(
@ -450,7 +602,7 @@ internal class KompendiumTest {
),
license = OpenApiSpecInfoLicense(
name = "MIT",
url = URI("https://github.com/lg-backbone/kompendium/blob/main/LICENSE")
url = URI("https://github.com/bkbnio/kompendium/blob/main/LICENSE")
)
),
servers = mutableListOf(
@ -471,5 +623,4 @@ internal class KompendiumTest {
redoc(oas)
}
}
}

View File

@ -1,4 +1,4 @@
package org.leafygreens.kompendium
package io.bkbn.kompendium
import java.util.UUID
import kotlin.test.Test
@ -7,21 +7,13 @@ import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
import org.leafygreens.kompendium.Kontent.generateKontent
import org.leafygreens.kompendium.Kontent.generateParameterKontent
import org.leafygreens.kompendium.models.oas.DictionarySchema
import org.leafygreens.kompendium.models.oas.FormatSchema
import org.leafygreens.kompendium.models.oas.ObjectSchema
import org.leafygreens.kompendium.models.oas.ReferencedSchema
import org.leafygreens.kompendium.util.ComplexRequest
import org.leafygreens.kompendium.util.TestInvalidMap
import org.leafygreens.kompendium.util.TestNestedModel
import org.leafygreens.kompendium.util.TestSimpleModel
import org.leafygreens.kompendium.util.TestSimpleWithEnumList
import org.leafygreens.kompendium.util.TestSimpleWithEnums
import org.leafygreens.kompendium.util.TestSimpleWithList
import org.leafygreens.kompendium.util.TestSimpleWithMap
import org.leafygreens.kompendium.util.TestWithUUID
import io.bkbn.kompendium.Kontent.generateKontent
import io.bkbn.kompendium.Kontent.generateParameterKontent
import io.bkbn.kompendium.models.oas.DictionarySchema
import io.bkbn.kompendium.models.oas.FormatSchema
import io.bkbn.kompendium.models.oas.ObjectSchema
import io.bkbn.kompendium.models.oas.ReferencedSchema
import io.bkbn.kompendium.util.*
@ExperimentalStdlibApi
internal class KontentTest {
@ -45,6 +37,29 @@ internal class KontentTest {
assertEquals(FormatSchema("int64", "integer"), result["Long"])
}
@Test
fun `Object with BigDecimal and BigInteger types`() {
// do
val result = generateKontent<TestBigNumberModel>()
// expect
assertEquals(3, result.count())
assertTrue { result.containsKey(TestBigNumberModel::class.simpleName) }
assertEquals(FormatSchema("double", "number"), result["BigDecimal"])
assertEquals(FormatSchema("int64", "integer"), result["BigInteger"])
}
@Test
fun `Object with ByteArray type`() {
// do
val result = generateKontent<TestByteArrayModel>()
// expect
assertEquals(2, result.count())
assertTrue { result.containsKey(TestByteArrayModel::class.simpleName) }
assertEquals(FormatSchema("byte", "string"), result["ByteArray"])
}
@Test
fun `Objects reference their base types in the cache`() {
// do

View File

@ -1,4 +1,4 @@
package org.leafygreens.kompendium.util
package io.bkbn.kompendium.util
import java.io.File

View File

@ -1,15 +1,19 @@
package org.leafygreens.kompendium.util
package io.bkbn.kompendium.util
import java.util.UUID
import kotlin.reflect.KParameter
import kotlin.reflect.full.primaryConstructor
import org.leafygreens.kompendium.MethodParser
import org.leafygreens.kompendium.annotations.KompendiumField
import org.leafygreens.kompendium.annotations.KompendiumParam
import org.leafygreens.kompendium.annotations.ParamType
import io.bkbn.kompendium.annotations.KompendiumField
import io.bkbn.kompendium.annotations.KompendiumParam
import io.bkbn.kompendium.annotations.ParamType
import io.bkbn.kompendium.annotations.UndeclaredField
import java.math.BigDecimal
import java.math.BigInteger
data class TestSimpleModel(val a: String, val b: Int)
data class TestBigNumberModel(val a: BigDecimal, val b: BigInteger)
data class TestByteArrayModel(val a: ByteArray)
data class TestNestedModel(val inner: TestSimpleModel)
data class TestSimpleWithEnums(val a: String, val b: SimpleEnum)
@ -40,6 +44,8 @@ data class TestRequest(
data class TestResponse(val c: String)
data class TestGeneric<T>(val messy: String, val potato: T)
data class TestCreatedResponse(val id: Int, val c: String)
data class ComplexRequest(
@ -75,3 +81,27 @@ data class OptionalParams(
@KompendiumParam(ParamType.QUERY) val required: String,
@KompendiumParam(ParamType.QUERY) val notRequired: String?
)
sealed class FlibbityGibbit
data class SimpleGibbit(val a: String) : FlibbityGibbit()
data class ComplexGibbit(val b: String, val c: Int) : FlibbityGibbit()
sealed interface SlammaJamma
data class OneJamma(val a: Int) : SlammaJamma
data class AnothaJamma(val b: Float) : SlammaJamma
//data class InsaneJamma(val c: SlammaJamma) : SlammaJamma // 👀
sealed interface Flibbity<T>
data class Gibbity<T>(val a: T) : Flibbity<T>
data class Bibbity<T>(val b: String, val f: T) : Flibbity<T>
enum class Hehe {
HAHA,
HOHO
}
@UndeclaredField("nowYouDont", Hehe::class)
data class Mysterious(val nowYouSeeMe: String)

View File

@ -1,7 +1,15 @@
package org.leafygreens.kompendium.util
package io.bkbn.kompendium.util
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.databind.SerializationFeature
import io.bkbn.kompendium.Notarized.notarizedDelete
import io.bkbn.kompendium.Notarized.notarizedException
import io.bkbn.kompendium.Notarized.notarizedGet
import io.bkbn.kompendium.Notarized.notarizedPost
import io.bkbn.kompendium.Notarized.notarizedPut
import io.bkbn.kompendium.models.meta.MethodInfo
import io.bkbn.kompendium.models.meta.RequestInfo
import io.bkbn.kompendium.models.meta.ResponseInfo
import io.ktor.application.Application
import io.ktor.application.call
import io.ktor.application.install
@ -13,16 +21,9 @@ import io.ktor.response.respond
import io.ktor.response.respondText
import io.ktor.routing.route
import io.ktor.routing.routing
import org.leafygreens.kompendium.Notarized.notarizedDelete
import org.leafygreens.kompendium.Notarized.notarizedException
import org.leafygreens.kompendium.Notarized.notarizedGet
import org.leafygreens.kompendium.Notarized.notarizedPost
import org.leafygreens.kompendium.Notarized.notarizedPut
import org.leafygreens.kompendium.models.meta.MethodInfo
import org.leafygreens.kompendium.models.meta.RequestInfo
import org.leafygreens.kompendium.models.meta.ResponseInfo
import io.ktor.serialization.json
fun Application.configModule() {
fun Application.jacksonConfigModule() {
install(ContentNegotiation) {
jackson {
enable(SerializationFeature.INDENT_OUTPUT)
@ -31,9 +32,20 @@ fun Application.configModule() {
}
}
fun Application.kotlinxConfigModule() {
install(ContentNegotiation) {
json()
}
}
fun Application.statusPageModule() {
install(StatusPages) {
notarizedException<Exception, ExceptionResponse>(info = ResponseInfo(HttpStatusCode.BadRequest, "Bad Things Happened")) {
notarizedException<Exception, ExceptionResponse>(
info = ResponseInfo(
HttpStatusCode.BadRequest,
"Bad Things Happened"
)
) {
call.respond(HttpStatusCode.BadRequest, ExceptionResponse("Why you do dis?"))
}
}
@ -41,10 +53,17 @@ fun Application.statusPageModule() {
fun Application.statusPageMultiExceptions() {
install(StatusPages) {
notarizedException<AccessDeniedException, Unit>(info = ResponseInfo(HttpStatusCode.Forbidden, "New API who dis?")) {
notarizedException<AccessDeniedException, Unit>(
info = ResponseInfo(HttpStatusCode.Forbidden, "New API who dis?")
) {
call.respond(HttpStatusCode.Forbidden)
}
notarizedException<Exception, ExceptionResponse>(info = ResponseInfo(HttpStatusCode.BadRequest, "Bad Things Happened")) {
notarizedException<Exception, ExceptionResponse>(
info = ResponseInfo(
HttpStatusCode.BadRequest,
"Bad Things Happened"
)
) {
call.respond(HttpStatusCode.BadRequest, ExceptionResponse("Why you do dis?"))
}
}
@ -246,6 +265,18 @@ fun Application.withDefaultParameter() {
}
}
fun Application.withOperationId(){
routing {
route("/test") {
notarizedGet(
info = TestResponseInfo.testGetInfo.copy(operationId = "getTest")
){
call.respond(HttpStatusCode.OK)
}
}
}
}
fun Application.nonRequiredParamsGet() {
routing {
route("/test/optional") {
@ -255,3 +286,88 @@ fun Application.nonRequiredParamsGet() {
}
}
}
fun Application.polymorphicResponse() {
routing {
route("/test/polymorphic") {
notarizedGet(TestResponseInfo.polymorphicResponse) {
call.respond(HttpStatusCode.OK, SimpleGibbit("hey"))
}
}
}
}
fun Application.polymorphicCollectionResponse() {
routing {
route("/test/polymorphiclist") {
notarizedGet(TestResponseInfo.polymorphicListResponse) {
call.respond(HttpStatusCode.OK, listOf(SimpleGibbit("hi")))
}
}
}
}
fun Application.polymorphicMapResponse() {
routing {
route("/test/polymorphicmap") {
notarizedGet(TestResponseInfo.polymorphicMapResponse) {
call.respond(HttpStatusCode.OK, listOf(SimpleGibbit("hi")))
}
}
}
}
fun Application.polymorphicInterfaceResponse() {
routing {
route("/test/polymorphicmap") {
notarizedGet(TestResponseInfo.polymorphicInterfaceResponse) {
call.respond(HttpStatusCode.OK, listOf(SimpleGibbit("hi")))
}
}
}
}
fun Application.genericPolymorphicResponse() {
routing {
route("/test/polymorphic") {
notarizedGet(TestResponseInfo.genericPolymorphicResponse) {
call.respond(HttpStatusCode.OK, Gibbity("hey"))
}
}
}
}
fun Application.genericPolymorphicResponseMultipleImpls() {
routing {
route("/test/polymorphic") {
notarizedGet(TestResponseInfo.genericPolymorphicResponse) {
call.respond(HttpStatusCode.OK, Gibbity("hey"))
}
}
route("/test/also/poly") {
notarizedGet(TestResponseInfo.anotherGenericPolymorphicResponse) {
call.respond(HttpStatusCode.OK, Bibbity("test", ComplexGibbit("nice", 1)))
}
}
}
}
fun Application.undeclaredType() {
routing {
route("/test/polymorphic") {
notarizedGet(TestResponseInfo.undeclaredResponseType) {
call.respond(HttpStatusCode.OK, Mysterious("hi"))
}
}
}
}
fun Application.simpleGenericResponse() {
routing {
route("/test/polymorphic") {
notarizedGet(TestResponseInfo.genericResponse) {
call.respond(HttpStatusCode.OK, Gibbity("hey"))
}
}
}
}

View File

@ -0,0 +1,118 @@
package io.bkbn.kompendium.util
import io.bkbn.kompendium.models.meta.MethodInfo.DeleteInfo
import io.bkbn.kompendium.models.meta.MethodInfo.GetInfo
import io.bkbn.kompendium.models.meta.MethodInfo.PostInfo
import io.bkbn.kompendium.models.meta.MethodInfo.PutInfo
import io.bkbn.kompendium.models.meta.RequestInfo
import io.bkbn.kompendium.models.meta.ResponseInfo
import io.ktor.http.HttpStatusCode
object TestResponseInfo {
private val testGetResponse = ResponseInfo<TestResponse>(HttpStatusCode.OK, "A Successful Endeavor")
private val testGetListResponse =
ResponseInfo<List<TestResponse>>(HttpStatusCode.OK, "A Successful List-y Endeavor")
private val testPostResponse = ResponseInfo<TestCreatedResponse>(HttpStatusCode.Created, "A Successful Endeavor")
private val testPostResponseAgain = ResponseInfo<Boolean>(HttpStatusCode.Created, "A Successful Endeavor")
private val testDeleteResponse =
ResponseInfo<Unit>(HttpStatusCode.NoContent, "A Successful Endeavor", mediaTypes = emptyList())
private val testRequest = RequestInfo<TestRequest>("A Test request")
private val testRequestAgain = RequestInfo<Int>("A Test request")
private val complexRequest = RequestInfo<ComplexRequest>("A Complex request")
val testGetInfo = GetInfo<TestParams, TestResponse>(
summary = "Another get test",
description = "testing more",
responseInfo = testGetResponse
)
val testGetInfoAgain = GetInfo<TestParams, List<TestResponse>>(
summary = "Another get test",
description = "testing more",
responseInfo = testGetListResponse
)
val testGetWithException = testGetInfo.copy(
canThrow = setOf(Exception::class)
)
val testGetWithMultipleExceptions = testGetInfo.copy(
canThrow = setOf(AccessDeniedException::class, Exception::class)
)
val testPostInfo = PostInfo<TestParams, TestRequest, TestCreatedResponse>(
summary = "Test post endpoint",
description = "Post your tests here!",
responseInfo = testPostResponse,
requestInfo = testRequest
)
val testPutInfo = PutInfo<Unit, ComplexRequest, TestCreatedResponse>(
summary = "Test put endpoint",
description = "Put your tests here!",
responseInfo = testPostResponse,
requestInfo = complexRequest
)
val testPutInfoAlso = PutInfo<TestParams, TestRequest, TestCreatedResponse>(
summary = "Test put endpoint",
description = "Put your tests here!",
responseInfo = testPostResponse,
requestInfo = testRequest
)
val testPutInfoAgain = PutInfo<Unit, Int, Boolean>(
summary = "Test put endpoint",
description = "Put your tests here!",
responseInfo = testPostResponseAgain,
requestInfo = testRequestAgain
)
val testDeleteInfo = DeleteInfo<TestParams, Unit>(
summary = "Test delete endpoint",
description = "testing my deletes",
responseInfo = testDeleteResponse
)
val emptyTestGetInfo =
GetInfo<OptionalParams, Unit>(
summary = "No request params and response body",
description = "testing more"
)
val trulyEmptyTestGetInfo = GetInfo<Unit, Unit>(
summary = "No request params and response body",
description = "testing more"
)
val polymorphicResponse = GetInfo<Unit, FlibbityGibbit>(
summary = "All the gibbits",
description = "Polymorphic response",
responseInfo = simpleOkResponse()
)
val polymorphicListResponse = GetInfo<Unit, List<FlibbityGibbit>>(
summary = "Oh so many gibbits",
description = "Polymorphic list response",
responseInfo = simpleOkResponse()
)
val polymorphicMapResponse = GetInfo<Unit, Map<String, FlibbityGibbit>>(
summary = "By gawd that's a lot of gibbits",
description = "Polymorphic list response",
responseInfo = simpleOkResponse()
)
val polymorphicInterfaceResponse = GetInfo<Unit, SlammaJamma>(
summary = "Come on and slam",
description = "and welcome to the jam",
responseInfo = simpleOkResponse()
)
val genericPolymorphicResponse = GetInfo<Unit, Flibbity<TestNested>>(
summary = "More flibbity",
description = "Polymorphic with generics",
responseInfo = simpleOkResponse()
)
val anotherGenericPolymorphicResponse = GetInfo<Unit, Flibbity<FlibbityGibbit>>(
summary = "The Most Flibbity",
description = "Polymorphic with generics but like... crazier",
responseInfo = simpleOkResponse()
)
val undeclaredResponseType = GetInfo<Unit, Mysterious>(
summary = "spooky class",
description = "break this glass in scenario of emergency",
responseInfo = simpleOkResponse()
)
val genericResponse = GetInfo<Unit, TestGeneric<Int>>(
summary = "Single Generic",
description = "Simple generic data class",
responseInfo = simpleOkResponse()
)
private fun <T> simpleOkResponse() = ResponseInfo<T>(HttpStatusCode.OK, "A successful endeavor")
}

View File

@ -1,71 +0,0 @@
package org.leafygreens.kompendium.util
import io.ktor.http.HttpStatusCode
import org.leafygreens.kompendium.models.meta.MethodInfo
import org.leafygreens.kompendium.models.meta.RequestInfo
import org.leafygreens.kompendium.models.meta.ResponseInfo
object TestResponseInfo {
private val testGetResponse = ResponseInfo<TestResponse>(HttpStatusCode.OK, "A Successful Endeavor")
private val testGetListResponse =
ResponseInfo<List<TestResponse>>(HttpStatusCode.OK, "A Successful List-y Endeavor")
private val testPostResponse = ResponseInfo<TestCreatedResponse>(HttpStatusCode.Created, "A Successful Endeavor")
private val testPostResponseAgain = ResponseInfo<Boolean>(HttpStatusCode.Created, "A Successful Endeavor")
private val testDeleteResponse =
ResponseInfo<Unit>(HttpStatusCode.NoContent, "A Successful Endeavor", mediaTypes = emptyList())
private val testRequest = RequestInfo<TestRequest>("A Test request")
private val testRequestAgain = RequestInfo<Int>("A Test request")
private val complexRequest = RequestInfo<ComplexRequest>("A Complex request")
val testGetInfo = MethodInfo.GetInfo<TestParams, TestResponse>(
summary = "Another get test",
description = "testing more",
responseInfo = testGetResponse
)
val testGetInfoAgain = MethodInfo.GetInfo<TestParams, List<TestResponse>>(
summary = "Another get test",
description = "testing more",
responseInfo = testGetListResponse
)
val testGetWithException = testGetInfo.copy(
canThrow = setOf(Exception::class)
)
val testGetWithMultipleExceptions = testGetInfo.copy(
canThrow = setOf(AccessDeniedException::class, Exception::class)
)
val testPostInfo = MethodInfo.PostInfo<TestParams, TestRequest, TestCreatedResponse>(
summary = "Test post endpoint",
description = "Post your tests here!",
responseInfo = testPostResponse,
requestInfo = testRequest
)
val testPutInfo = MethodInfo.PutInfo<Unit, ComplexRequest, TestCreatedResponse>(
summary = "Test put endpoint",
description = "Put your tests here!",
responseInfo = testPostResponse,
requestInfo = complexRequest
)
val testPutInfoAlso = MethodInfo.PutInfo<TestParams, TestRequest, TestCreatedResponse>(
summary = "Test put endpoint",
description = "Put your tests here!",
responseInfo = testPostResponse,
requestInfo = testRequest
)
val testPutInfoAgain = MethodInfo.PutInfo<Unit, Int, Boolean>(
summary = "Test put endpoint",
description = "Put your tests here!",
responseInfo = testPostResponseAgain,
requestInfo = testRequestAgain
)
val testDeleteInfo = MethodInfo.DeleteInfo<TestParams, Unit>(
summary = "Test delete endpoint",
description = "testing my deletes",
responseInfo = testDeleteResponse
)
val emptyTestGetInfo =
MethodInfo.GetInfo<OptionalParams, Unit>(
summary = "No request params and response body",
description = "testing more"
)
val trulyEmptyTestGetInfo =
MethodInfo.GetInfo<Unit, Unit>(summary = "No request params and response body", description = "testing more")
}

View File

@ -3,7 +3,7 @@
"info" : {
"title" : "Test API",
"version" : "1.33.7",
"description" : "An amazing, fully-ish \uD83D\uDE09 generated API spec",
"description" : "An amazing, fully-ish 😉 generated API spec",
"termsOfService" : "https://example.com",
"contact" : {
"name" : "Homer Simpson",
@ -12,7 +12,7 @@
},
"license" : {
"name" : "MIT",
"url" : "https://github.com/lg-backbone/kompendium/blob/main/LICENSE"
"url" : "https://github.com/bkbnio/kompendium/blob/main/LICENSE"
}
},
"servers" : [ {
@ -61,40 +61,26 @@
"String" : {
"type" : "string"
},
"Int" : {
"format" : "int32",
"type" : "integer"
},
"TestCreatedResponse" : {
"properties" : {
"c" : {
"$ref" : "#/components/schemas/String"
},
"id" : {
"$ref" : "#/components/schemas/Int"
}
},
"type" : "object"
},
"SimpleEnum" : {
"enum" : [ "ONE", "TWO" ],
"type" : "string"
"type" : "string",
"enum" : [ "ONE", "TWO" ]
},
"CrazyItem" : {
"type" : "object",
"properties" : {
"enumeration" : {
"$ref" : "#/components/schemas/SimpleEnum"
}
},
"type" : "object"
}
},
"Map-String-CrazyItem" : {
"type" : "object",
"additionalProperties" : {
"$ref" : "#/components/schemas/CrazyItem"
},
"type" : "object"
}
},
"NestedComplexItem" : {
"type" : "object",
"properties" : {
"alias" : {
"$ref" : "#/components/schemas/Map-String-CrazyItem"
@ -102,16 +88,16 @@
"name" : {
"$ref" : "#/components/schemas/String"
}
},
"type" : "object"
}
},
"List-NestedComplexItem" : {
"type" : "array",
"items" : {
"$ref" : "#/components/schemas/NestedComplexItem"
},
"type" : "array"
}
},
"ComplexRequest" : {
"type" : "object",
"properties" : {
"amazingField" : {
"$ref" : "#/components/schemas/String"
@ -122,8 +108,22 @@
"tables" : {
"$ref" : "#/components/schemas/List-NestedComplexItem"
}
}
},
"type" : "object"
"Int" : {
"type" : "integer",
"format" : "int32"
},
"TestCreatedResponse" : {
"type" : "object",
"properties" : {
"c" : {
"$ref" : "#/components/schemas/String"
},
"id" : {
"$ref" : "#/components/schemas/Int"
}
}
}
},
"securitySchemes" : { }

View File

@ -0,0 +1,164 @@
{
"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/polymorphic" : {
"get" : {
"tags" : [ ],
"summary" : "More flibbity",
"description" : "Polymorphic with generics",
"parameters" : [ ],
"responses" : {
"200" : {
"description" : "A successful endeavor",
"content" : {
"application/json" : {
"schema" : {
"anyOf" : [ {
"$ref" : "#/components/schemas/Gibbity-TestNested"
}, {
"$ref" : "#/components/schemas/Bibbity-TestNested"
} ]
}
}
}
}
},
"deprecated" : false
}
},
"/test/also/poly" : {
"get" : {
"tags" : [ ],
"summary" : "The Most Flibbity",
"description" : "Polymorphic with generics but like... crazier",
"parameters" : [ ],
"responses" : {
"200" : {
"description" : "A successful endeavor",
"content" : {
"application/json" : {
"schema" : {
"anyOf" : [ {
"$ref" : "#/components/schemas/Gibbity-FlibbityGibbit"
}, {
"$ref" : "#/components/schemas/Bibbity-FlibbityGibbit"
} ]
}
}
}
}
},
"deprecated" : false
}
}
},
"components" : {
"schemas" : {
"String" : {
"type" : "string"
},
"TestNested" : {
"type" : "object",
"properties" : {
"nesty" : {
"$ref" : "#/components/schemas/String"
}
}
},
"Gibbity-TestNested" : {
"type" : "object",
"properties" : {
"a" : {
"$ref" : "#/components/schemas/TestNested"
}
}
},
"Bibbity-TestNested" : {
"type" : "object",
"properties" : {
"b" : {
"$ref" : "#/components/schemas/String"
},
"f" : {
"$ref" : "#/components/schemas/TestNested"
}
}
},
"SimpleGibbit" : {
"type" : "object",
"properties" : {
"a" : {
"$ref" : "#/components/schemas/String"
}
}
},
"Int" : {
"type" : "integer",
"format" : "int32"
},
"ComplexGibbit" : {
"type" : "object",
"properties" : {
"b" : {
"$ref" : "#/components/schemas/String"
},
"c" : {
"$ref" : "#/components/schemas/Int"
}
}
},
"Gibbity-FlibbityGibbit" : {
"type" : "object",
"properties" : {
"a" : {
"anyOf" : [ {
"$ref" : "#/components/schemas/SimpleGibbit"
}, {
"$ref" : "#/components/schemas/ComplexGibbit"
} ]
}
}
},
"Bibbity-FlibbityGibbit" : {
"type" : "object",
"properties" : {
"b" : {
"$ref" : "#/components/schemas/String"
},
"f" : {
"anyOf" : [ {
"$ref" : "#/components/schemas/SimpleGibbit"
}, {
"$ref" : "#/components/schemas/ComplexGibbit"
} ]
}
}
}
},
"securitySchemes" : { }
},
"security" : [ ],
"tags" : [ ]
}

View File

@ -3,7 +3,7 @@
"info" : {
"title" : "Test API",
"version" : "1.33.7",
"description" : "An amazing, fully-ish \uD83D\uDE09 generated API spec",
"description" : "An amazing, fully-ish 😉 generated API spec",
"termsOfService" : "https://example.com",
"contact" : {
"name" : "Homer Simpson",
@ -12,7 +12,7 @@
},
"license" : {
"name" : "MIT",
"url" : "https://github.com/lg-backbone/kompendium/blob/main/LICENSE"
"url" : "https://github.com/bkbnio/kompendium/blob/main/LICENSE"
}
},
"servers" : [ {
@ -85,40 +85,33 @@
},
"components" : {
"schemas" : {
"Long" : {
"type" : "integer",
"format" : "int64"
},
"List-Long" : {
"type" : "array",
"items" : {
"$ref" : "#/components/schemas/Long"
}
},
"Double" : {
"type" : "number",
"format" : "double"
},
"String" : {
"type" : "string"
},
"TestResponse" : {
"properties" : {
"c" : {
"$ref" : "#/components/schemas/String"
}
},
"type" : "object"
},
"Long" : {
"format" : "int64",
"type" : "integer"
},
"List-Long" : {
"items" : {
"$ref" : "#/components/schemas/Long"
},
"type" : "array"
},
"Double" : {
"format" : "double",
"type" : "number"
},
"TestNested" : {
"type" : "object",
"properties" : {
"nesty" : {
"$ref" : "#/components/schemas/String"
}
},
"type" : "object"
}
},
"TestRequest" : {
"type" : "object",
"properties" : {
"aaa" : {
"$ref" : "#/components/schemas/List-Long"
@ -129,8 +122,15 @@
"fieldName" : {
"$ref" : "#/components/schemas/TestNested"
}
}
},
"type" : "object"
"TestResponse" : {
"type" : "object",
"properties" : {
"c" : {
"$ref" : "#/components/schemas/String"
}
}
}
},
"securitySchemes" : { }

View File

@ -0,0 +1,73 @@
{
"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/polymorphic" : {
"get" : {
"tags" : [ ],
"summary" : "Single Generic",
"description" : "Simple generic data class",
"parameters" : [ ],
"responses" : {
"200" : {
"description" : "A successful endeavor",
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/TestGeneric-Int"
}
}
}
}
},
"deprecated" : false
}
}
},
"components" : {
"schemas" : {
"String" : {
"type" : "string"
},
"Int" : {
"type" : "integer",
"format" : "int32"
},
"TestGeneric-Int" : {
"type" : "object",
"properties" : {
"messy" : {
"$ref" : "#/components/schemas/String"
},
"potato" : {
"$ref" : "#/components/schemas/Int"
}
}
}
},
"securitySchemes" : { }
},
"security" : [ ],
"tags" : [ ]
}

View File

@ -3,7 +3,7 @@
"info" : {
"title" : "Test API",
"version" : "1.33.7",
"description" : "An amazing, fully-ish \uD83D\uDE09 generated API spec",
"description" : "An amazing, fully-ish 😉 generated API spec",
"termsOfService" : "https://example.com",
"contact" : {
"name" : "Homer Simpson",
@ -12,7 +12,7 @@
},
"license" : {
"name" : "MIT",
"url" : "https://github.com/lg-backbone/kompendium/blob/main/LICENSE"
"url" : "https://github.com/bkbnio/kompendium/blob/main/LICENSE"
}
},
"servers" : [ {
@ -40,8 +40,8 @@
"name" : "aa",
"in" : "query",
"schema" : {
"format" : "int32",
"type" : "integer"
"type" : "integer",
"format" : "int32"
},
"required" : true,
"deprecated" : false
@ -68,16 +68,16 @@
"type" : "string"
},
"TestResponse" : {
"type" : "object",
"properties" : {
"c" : {
"$ref" : "#/components/schemas/String"
}
},
"type" : "object"
}
},
"Int" : {
"format" : "int32",
"type" : "integer"
"type" : "integer",
"format" : "int32"
}
},
"securitySchemes" : { }

View File

@ -3,7 +3,7 @@
"info" : {
"title" : "Test API",
"version" : "1.33.7",
"description" : "An amazing, fully-ish \uD83D\uDE09 generated API spec",
"description" : "An amazing, fully-ish 😉 generated API spec",
"termsOfService" : "https://example.com",
"contact" : {
"name" : "Homer Simpson",
@ -12,7 +12,7 @@
},
"license" : {
"name" : "MIT",
"url" : "https://github.com/lg-backbone/kompendium/blob/main/LICENSE"
"url" : "https://github.com/bkbnio/kompendium/blob/main/LICENSE"
}
},
"servers" : [ {

View File

@ -3,7 +3,7 @@
"info" : {
"title" : "Test API",
"version" : "1.33.7",
"description" : "An amazing, fully-ish \uD83D\uDE09 generated API spec",
"description" : "An amazing, fully-ish 😉 generated API spec",
"termsOfService" : "https://example.com",
"contact" : {
"name" : "Homer Simpson",
@ -12,7 +12,7 @@
},
"license" : {
"name" : "MIT",
"url" : "https://github.com/lg-backbone/kompendium/blob/main/LICENSE"
"url" : "https://github.com/bkbnio/kompendium/blob/main/LICENSE"
}
},
"servers" : [ {

View File

@ -3,7 +3,7 @@
"info" : {
"title" : "Test API",
"version" : "1.33.7",
"description" : "An amazing, fully-ish \uD83D\uDE09 generated API spec",
"description" : "An amazing, fully-ish 😉 generated API spec",
"termsOfService" : "https://example.com",
"contact" : {
"name" : "Homer Simpson",
@ -12,7 +12,7 @@
},
"license" : {
"name" : "MIT",
"url" : "https://github.com/lg-backbone/kompendium/blob/main/LICENSE"
"url" : "https://github.com/bkbnio/kompendium/blob/main/LICENSE"
}
},
"servers" : [ {
@ -40,8 +40,8 @@
"name" : "aa",
"in" : "query",
"schema" : {
"format" : "int32",
"type" : "integer"
"type" : "integer",
"format" : "int32"
},
"required" : true,
"deprecated" : false
@ -61,8 +61,8 @@
"type" : "string"
},
"Int" : {
"format" : "int32",
"type" : "integer"
"type" : "integer",
"format" : "int32"
}
},
"securitySchemes" : { }

View File

@ -3,7 +3,7 @@
"info" : {
"title" : "Test API",
"version" : "1.33.7",
"description" : "An amazing, fully-ish \uD83D\uDE09 generated API spec",
"description" : "An amazing, fully-ish 😉 generated API spec",
"termsOfService" : "https://example.com",
"contact" : {
"name" : "Homer Simpson",
@ -12,7 +12,7 @@
},
"license" : {
"name" : "MIT",
"url" : "https://github.com/lg-backbone/kompendium/blob/main/LICENSE"
"url" : "https://github.com/bkbnio/kompendium/blob/main/LICENSE"
}
},
"servers" : [ {
@ -40,8 +40,8 @@
"name" : "aa",
"in" : "query",
"schema" : {
"format" : "int32",
"type" : "integer"
"type" : "integer",
"format" : "int32"
},
"required" : true,
"deprecated" : false
@ -68,16 +68,16 @@
"type" : "string"
},
"TestResponse" : {
"type" : "object",
"properties" : {
"c" : {
"$ref" : "#/components/schemas/String"
}
},
"type" : "object"
}
},
"Int" : {
"format" : "int32",
"type" : "integer"
"type" : "integer",
"format" : "int32"
}
},
"securitySchemes" : { }

View File

@ -3,7 +3,7 @@
"info" : {
"title" : "Test API",
"version" : "1.33.7",
"description" : "An amazing, fully-ish \uD83D\uDE09 generated API spec",
"description" : "An amazing, fully-ish 😉 generated API spec",
"termsOfService" : "https://example.com",
"contact" : {
"name" : "Homer Simpson",
@ -12,7 +12,7 @@
},
"license" : {
"name" : "MIT",
"url" : "https://github.com/lg-backbone/kompendium/blob/main/LICENSE"
"url" : "https://github.com/bkbnio/kompendium/blob/main/LICENSE"
}
},
"servers" : [ {
@ -40,8 +40,8 @@
"name" : "aa",
"in" : "query",
"schema" : {
"format" : "int32",
"type" : "integer"
"type" : "integer",
"format" : "int32"
},
"required" : true,
"deprecated" : false
@ -78,24 +78,24 @@
"type" : "string"
},
"ExceptionResponse" : {
"type" : "object",
"properties" : {
"message" : {
"$ref" : "#/components/schemas/String"
}
},
"type" : "object"
}
},
"TestResponse" : {
"type" : "object",
"properties" : {
"c" : {
"$ref" : "#/components/schemas/String"
}
},
"type" : "object"
}
},
"Int" : {
"format" : "int32",
"type" : "integer"
"type" : "integer",
"format" : "int32"
}
},
"securitySchemes" : { }

View File

@ -3,7 +3,7 @@
"info" : {
"title" : "Test API",
"version" : "1.33.7",
"description" : "An amazing, fully-ish \uD83D\uDE09 generated API spec",
"description" : "An amazing, fully-ish 😉 generated API spec",
"termsOfService" : "https://example.com",
"contact" : {
"name" : "Homer Simpson",
@ -12,7 +12,7 @@
},
"license" : {
"name" : "MIT",
"url" : "https://github.com/lg-backbone/kompendium/blob/main/LICENSE"
"url" : "https://github.com/bkbnio/kompendium/blob/main/LICENSE"
}
},
"servers" : [ {
@ -40,8 +40,8 @@
"name" : "aa",
"in" : "query",
"schema" : {
"format" : "int32",
"type" : "integer"
"type" : "integer",
"format" : "int32"
},
"required" : true,
"deprecated" : false
@ -81,24 +81,24 @@
"type" : "string"
},
"ExceptionResponse" : {
"type" : "object",
"properties" : {
"message" : {
"$ref" : "#/components/schemas/String"
}
},
"type" : "object"
}
},
"TestResponse" : {
"type" : "object",
"properties" : {
"c" : {
"$ref" : "#/components/schemas/String"
}
},
"type" : "object"
}
},
"Int" : {
"format" : "int32",
"type" : "integer"
"type" : "integer",
"format" : "int32"
}
},
"securitySchemes" : { }

View File

@ -0,0 +1,88 @@
{
"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" : {
"get" : {
"tags" : [ ],
"summary" : "Another get test",
"description" : "testing more",
"operationId" : "getTest",
"parameters" : [ {
"name" : "a",
"in" : "path",
"schema" : {
"type" : "string"
},
"required" : true,
"deprecated" : false
}, {
"name" : "aa",
"in" : "query",
"schema" : {
"type" : "integer",
"format" : "int32"
},
"required" : true,
"deprecated" : false
} ],
"responses" : {
"200" : {
"description" : "A Successful Endeavor",
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/TestResponse"
}
}
}
}
},
"deprecated" : false
}
}
},
"components" : {
"schemas" : {
"String" : {
"type" : "string"
},
"TestResponse" : {
"type" : "object",
"properties" : {
"c" : {
"$ref" : "#/components/schemas/String"
}
}
},
"Int" : {
"type" : "integer",
"format" : "int32"
}
},
"securitySchemes" : { }
},
"security" : [ ],
"tags" : [ ]
}

View File

@ -3,7 +3,7 @@
"info" : {
"title" : "Test API",
"version" : "1.33.7",
"description" : "An amazing, fully-ish \uD83D\uDE09 generated API spec",
"description" : "An amazing, fully-ish 😉 generated API spec",
"termsOfService" : "https://example.com",
"contact" : {
"name" : "Homer Simpson",
@ -12,7 +12,7 @@
},
"license" : {
"name" : "MIT",
"url" : "https://github.com/lg-backbone/kompendium/blob/main/LICENSE"
"url" : "https://github.com/bkbnio/kompendium/blob/main/LICENSE"
}
},
"servers" : [ {
@ -40,8 +40,8 @@
"name" : "aa",
"in" : "query",
"schema" : {
"format" : "int32",
"type" : "integer"
"type" : "integer",
"format" : "int32"
},
"required" : true,
"deprecated" : false
@ -75,47 +75,33 @@
},
"components" : {
"schemas" : {
"Long" : {
"type" : "integer",
"format" : "int64"
},
"List-Long" : {
"type" : "array",
"items" : {
"$ref" : "#/components/schemas/Long"
}
},
"Double" : {
"type" : "number",
"format" : "double"
},
"String" : {
"type" : "string"
},
"Int" : {
"format" : "int32",
"type" : "integer"
},
"TestCreatedResponse" : {
"properties" : {
"c" : {
"$ref" : "#/components/schemas/String"
},
"id" : {
"$ref" : "#/components/schemas/Int"
}
},
"type" : "object"
},
"Long" : {
"format" : "int64",
"type" : "integer"
},
"List-Long" : {
"items" : {
"$ref" : "#/components/schemas/Long"
},
"type" : "array"
},
"Double" : {
"format" : "double",
"type" : "number"
},
"TestNested" : {
"type" : "object",
"properties" : {
"nesty" : {
"$ref" : "#/components/schemas/String"
}
},
"type" : "object"
}
},
"TestRequest" : {
"type" : "object",
"properties" : {
"aaa" : {
"$ref" : "#/components/schemas/List-Long"
@ -126,8 +112,22 @@
"fieldName" : {
"$ref" : "#/components/schemas/TestNested"
}
}
},
"type" : "object"
"Int" : {
"type" : "integer",
"format" : "int32"
},
"TestCreatedResponse" : {
"type" : "object",
"properties" : {
"c" : {
"$ref" : "#/components/schemas/String"
},
"id" : {
"$ref" : "#/components/schemas/Int"
}
}
}
},
"securitySchemes" : { }

View File

@ -3,7 +3,7 @@
"info" : {
"title" : "Test API",
"version" : "1.33.7",
"description" : "An amazing, fully-ish \uD83D\uDE09 generated API spec",
"description" : "An amazing, fully-ish 😉 generated API spec",
"termsOfService" : "https://example.com",
"contact" : {
"name" : "Homer Simpson",
@ -12,7 +12,7 @@
},
"license" : {
"name" : "MIT",
"url" : "https://github.com/lg-backbone/kompendium/blob/main/LICENSE"
"url" : "https://github.com/bkbnio/kompendium/blob/main/LICENSE"
}
},
"servers" : [ {
@ -58,12 +58,12 @@
},
"components" : {
"schemas" : {
"Int" : {
"type" : "integer",
"format" : "int32"
},
"Boolean" : {
"type" : "boolean"
},
"Int" : {
"format" : "int32",
"type" : "integer"
}
},
"securitySchemes" : { }

View File

@ -3,7 +3,7 @@
"info" : {
"title" : "Test API",
"version" : "1.33.7",
"description" : "An amazing, fully-ish \uD83D\uDE09 generated API spec",
"description" : "An amazing, fully-ish 😉 generated API spec",
"termsOfService" : "https://example.com",
"contact" : {
"name" : "Homer Simpson",
@ -12,7 +12,7 @@
},
"license" : {
"name" : "MIT",
"url" : "https://github.com/lg-backbone/kompendium/blob/main/LICENSE"
"url" : "https://github.com/bkbnio/kompendium/blob/main/LICENSE"
}
},
"servers" : [ {
@ -40,8 +40,8 @@
"name" : "aa",
"in" : "query",
"schema" : {
"format" : "int32",
"type" : "integer"
"type" : "integer",
"format" : "int32"
},
"required" : true,
"deprecated" : false
@ -75,47 +75,33 @@
},
"components" : {
"schemas" : {
"Long" : {
"type" : "integer",
"format" : "int64"
},
"List-Long" : {
"type" : "array",
"items" : {
"$ref" : "#/components/schemas/Long"
}
},
"Double" : {
"type" : "number",
"format" : "double"
},
"String" : {
"type" : "string"
},
"Int" : {
"format" : "int32",
"type" : "integer"
},
"TestCreatedResponse" : {
"properties" : {
"c" : {
"$ref" : "#/components/schemas/String"
},
"id" : {
"$ref" : "#/components/schemas/Int"
}
},
"type" : "object"
},
"Long" : {
"format" : "int64",
"type" : "integer"
},
"List-Long" : {
"items" : {
"$ref" : "#/components/schemas/Long"
},
"type" : "array"
},
"Double" : {
"format" : "double",
"type" : "number"
},
"TestNested" : {
"type" : "object",
"properties" : {
"nesty" : {
"$ref" : "#/components/schemas/String"
}
},
"type" : "object"
}
},
"TestRequest" : {
"type" : "object",
"properties" : {
"aaa" : {
"$ref" : "#/components/schemas/List-Long"
@ -126,8 +112,22 @@
"fieldName" : {
"$ref" : "#/components/schemas/TestNested"
}
}
},
"type" : "object"
"Int" : {
"type" : "integer",
"format" : "int32"
},
"TestCreatedResponse" : {
"type" : "object",
"properties" : {
"c" : {
"$ref" : "#/components/schemas/String"
},
"id" : {
"$ref" : "#/components/schemas/Int"
}
}
}
},
"securitySchemes" : { }

View File

@ -3,7 +3,7 @@
"info" : {
"title" : "Test API",
"version" : "1.33.7",
"description" : "An amazing, fully-ish \uD83D\uDE09 generated API spec",
"description" : "An amazing, fully-ish 😉 generated API spec",
"termsOfService" : "https://example.com",
"contact" : {
"name" : "Homer Simpson",
@ -12,7 +12,7 @@
},
"license" : {
"name" : "MIT",
"url" : "https://github.com/lg-backbone/kompendium/blob/main/LICENSE"
"url" : "https://github.com/bkbnio/kompendium/blob/main/LICENSE"
}
},
"servers" : [ {
@ -40,8 +40,8 @@
"name" : "aa",
"in" : "query",
"schema" : {
"format" : "int32",
"type" : "integer"
"type" : "integer",
"format" : "int32"
},
"required" : true,
"deprecated" : false
@ -68,16 +68,16 @@
"type" : "string"
},
"TestResponse" : {
"type" : "object",
"properties" : {
"c" : {
"$ref" : "#/components/schemas/String"
}
},
"type" : "object"
}
},
"Int" : {
"format" : "int32",
"type" : "integer"
"type" : "integer",
"format" : "int32"
}
},
"securitySchemes" : { }

View File

@ -0,0 +1,91 @@
{
"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/polymorphiclist" : {
"get" : {
"tags" : [ ],
"summary" : "Oh so many gibbits",
"description" : "Polymorphic list response",
"parameters" : [ ],
"responses" : {
"200" : {
"description" : "A successful endeavor",
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/List-FlibbityGibbit"
}
}
}
}
},
"deprecated" : false
}
}
},
"components" : {
"schemas" : {
"String" : {
"type" : "string"
},
"SimpleGibbit" : {
"type" : "object",
"properties" : {
"a" : {
"$ref" : "#/components/schemas/String"
}
}
},
"Int" : {
"type" : "integer",
"format" : "int32"
},
"ComplexGibbit" : {
"type" : "object",
"properties" : {
"b" : {
"$ref" : "#/components/schemas/String"
},
"c" : {
"$ref" : "#/components/schemas/Int"
}
}
},
"List-FlibbityGibbit" : {
"type" : "array",
"items" : {
"anyOf" : [ {
"$ref" : "#/components/schemas/SimpleGibbit"
}, {
"$ref" : "#/components/schemas/ComplexGibbit"
} ]
}
}
},
"securitySchemes" : { }
},
"security" : [ ],
"tags" : [ ]
}

View File

@ -0,0 +1,91 @@
{
"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/polymorphicmap" : {
"get" : {
"tags" : [ ],
"summary" : "By gawd that's a lot of gibbits",
"description" : "Polymorphic list response",
"parameters" : [ ],
"responses" : {
"200" : {
"description" : "A successful endeavor",
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/Map-String-FlibbityGibbit"
}
}
}
}
},
"deprecated" : false
}
}
},
"components" : {
"schemas" : {
"String" : {
"type" : "string"
},
"SimpleGibbit" : {
"type" : "object",
"properties" : {
"a" : {
"$ref" : "#/components/schemas/String"
}
}
},
"Int" : {
"type" : "integer",
"format" : "int32"
},
"ComplexGibbit" : {
"type" : "object",
"properties" : {
"b" : {
"$ref" : "#/components/schemas/String"
},
"c" : {
"$ref" : "#/components/schemas/Int"
}
}
},
"Map-String-FlibbityGibbit" : {
"type" : "object",
"additionalProperties" : {
"anyOf" : [ {
"$ref" : "#/components/schemas/SimpleGibbit"
}, {
"$ref" : "#/components/schemas/ComplexGibbit"
} ]
}
}
},
"securitySchemes" : { }
},
"security" : [ ],
"tags" : [ ]
}

View File

@ -0,0 +1,85 @@
{
"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/polymorphic" : {
"get" : {
"tags" : [ ],
"summary" : "All the gibbits",
"description" : "Polymorphic response",
"parameters" : [ ],
"responses" : {
"200" : {
"description" : "A successful endeavor",
"content" : {
"application/json" : {
"schema" : {
"anyOf" : [ {
"$ref" : "#/components/schemas/SimpleGibbit"
}, {
"$ref" : "#/components/schemas/ComplexGibbit"
} ]
}
}
}
}
},
"deprecated" : false
}
}
},
"components" : {
"schemas" : {
"String" : {
"type" : "string"
},
"SimpleGibbit" : {
"type" : "object",
"properties" : {
"a" : {
"$ref" : "#/components/schemas/String"
}
}
},
"Int" : {
"type" : "integer",
"format" : "int32"
},
"ComplexGibbit" : {
"type" : "object",
"properties" : {
"b" : {
"$ref" : "#/components/schemas/String"
},
"c" : {
"$ref" : "#/components/schemas/Int"
}
}
}
},
"securitySchemes" : { }
},
"security" : [ ],
"tags" : [ ]
}

View File

@ -0,0 +1,89 @@
{
"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/polymorphic" : {
"get" : {
"tags" : [ ],
"summary" : "More flibbity",
"description" : "Polymorphic with generics",
"parameters" : [ ],
"responses" : {
"200" : {
"description" : "A successful endeavor",
"content" : {
"application/json" : {
"schema" : {
"anyOf" : [ {
"$ref" : "#/components/schemas/Gibbity-TestNested"
}, {
"$ref" : "#/components/schemas/Bibbity-TestNested"
} ]
}
}
}
}
},
"deprecated" : false
}
}
},
"components" : {
"schemas" : {
"String" : {
"type" : "string"
},
"TestNested" : {
"type" : "object",
"properties" : {
"nesty" : {
"$ref" : "#/components/schemas/String"
}
}
},
"Gibbity-TestNested" : {
"type" : "object",
"properties" : {
"a" : {
"$ref" : "#/components/schemas/TestNested"
}
}
},
"Bibbity-TestNested" : {
"type" : "object",
"properties" : {
"b" : {
"$ref" : "#/components/schemas/String"
},
"f" : {
"$ref" : "#/components/schemas/TestNested"
}
}
}
},
"securitySchemes" : { }
},
"security" : [ ],
"tags" : [ ]
}

View File

@ -3,7 +3,7 @@
"info" : {
"title" : "Test API",
"version" : "1.33.7",
"description" : "An amazing, fully-ish \uD83D\uDE09 generated API spec",
"description" : "An amazing, fully-ish 😉 generated API spec",
"termsOfService" : "https://example.com",
"contact" : {
"name" : "Homer Simpson",
@ -12,7 +12,7 @@
},
"license" : {
"name" : "MIT",
"url" : "https://github.com/lg-backbone/kompendium/blob/main/LICENSE"
"url" : "https://github.com/bkbnio/kompendium/blob/main/LICENSE"
}
},
"servers" : [ {
@ -32,9 +32,9 @@
"name" : "a",
"in" : "query",
"schema" : {
"format" : "int32",
"default" : 100,
"type" : "integer",
"default" : 100
"format" : "int32"
},
"required" : true,
"deprecated" : false
@ -65,16 +65,16 @@
"type" : "string"
},
"TestResponse" : {
"type" : "object",
"properties" : {
"c" : {
"$ref" : "#/components/schemas/String"
}
},
"type" : "object"
}
},
"Int" : {
"format" : "int32",
"type" : "integer"
"type" : "integer",
"format" : "int32"
},
"Boolean" : {
"type" : "boolean"

View File

@ -3,7 +3,7 @@
"info" : {
"title" : "Test API",
"version" : "1.33.7",
"description" : "An amazing, fully-ish \uD83D\uDE09 generated API spec",
"description" : "An amazing, fully-ish 😉 generated API spec",
"termsOfService" : "https://example.com",
"contact" : {
"name" : "Homer Simpson",
@ -12,7 +12,7 @@
},
"license" : {
"name" : "MIT",
"url" : "https://github.com/lg-backbone/kompendium/blob/main/LICENSE"
"url" : "https://github.com/bkbnio/kompendium/blob/main/LICENSE"
}
},
"servers" : [ {
@ -40,8 +40,8 @@
"name" : "aa",
"in" : "query",
"schema" : {
"format" : "int32",
"type" : "integer"
"type" : "integer",
"format" : "int32"
},
"required" : true,
"deprecated" : false
@ -68,22 +68,22 @@
"type" : "string"
},
"TestResponse" : {
"type" : "object",
"properties" : {
"c" : {
"$ref" : "#/components/schemas/String"
}
},
"type" : "object"
}
},
"List-TestResponse" : {
"type" : "array",
"items" : {
"$ref" : "#/components/schemas/TestResponse"
},
"type" : "array"
}
},
"Int" : {
"format" : "int32",
"type" : "integer"
"type" : "integer",
"format" : "int32"
}
},
"securitySchemes" : { }

View File

@ -3,7 +3,7 @@
"info" : {
"title" : "Test API",
"version" : "1.33.7",
"description" : "An amazing, fully-ish \uD83D\uDE09 generated API spec",
"description" : "An amazing, fully-ish 😉 generated API spec",
"termsOfService" : "https://example.com",
"contact" : {
"name" : "Homer Simpson",
@ -12,7 +12,7 @@
},
"license" : {
"name" : "MIT",
"url" : "https://github.com/lg-backbone/kompendium/blob/main/LICENSE"
"url" : "https://github.com/bkbnio/kompendium/blob/main/LICENSE"
}
},
"servers" : [ {
@ -40,8 +40,8 @@
"name" : "aa",
"in" : "query",
"schema" : {
"format" : "int32",
"type" : "integer"
"type" : "integer",
"format" : "int32"
},
"required" : true,
"deprecated" : false
@ -68,16 +68,16 @@
"type" : "string"
},
"TestResponse" : {
"type" : "object",
"properties" : {
"c" : {
"$ref" : "#/components/schemas/String"
}
},
"type" : "object"
}
},
"Int" : {
"format" : "int32",
"type" : "integer"
"type" : "integer",
"format" : "int32"
}
},
"securitySchemes" : { }

View File

@ -0,0 +1,83 @@
{
"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/polymorphicmap" : {
"get" : {
"tags" : [ ],
"summary" : "Come on and slam",
"description" : "and welcome to the jam",
"parameters" : [ ],
"responses" : {
"200" : {
"description" : "A successful endeavor",
"content" : {
"application/json" : {
"schema" : {
"anyOf" : [ {
"$ref" : "#/components/schemas/OneJamma"
}, {
"$ref" : "#/components/schemas/AnothaJamma"
} ]
}
}
}
}
},
"deprecated" : false
}
}
},
"components" : {
"schemas" : {
"Int" : {
"type" : "integer",
"format" : "int32"
},
"OneJamma" : {
"type" : "object",
"properties" : {
"a" : {
"$ref" : "#/components/schemas/Int"
}
}
},
"Float" : {
"type" : "number",
"format" : "float"
},
"AnothaJamma" : {
"type" : "object",
"properties" : {
"b" : {
"$ref" : "#/components/schemas/Float"
}
}
}
},
"securitySchemes" : { }
},
"security" : [ ],
"tags" : [ ]
}

View File

@ -3,7 +3,7 @@
"info" : {
"title" : "Test API",
"version" : "1.33.7",
"description" : "An amazing, fully-ish \uD83D\uDE09 generated API spec",
"description" : "An amazing, fully-ish 😉 generated API spec",
"termsOfService" : "https://example.com",
"contact" : {
"name" : "Homer Simpson",
@ -12,7 +12,7 @@
},
"license" : {
"name" : "MIT",
"url" : "https://github.com/lg-backbone/kompendium/blob/main/LICENSE"
"url" : "https://github.com/bkbnio/kompendium/blob/main/LICENSE"
}
},
"servers" : [ {
@ -40,8 +40,8 @@
"name" : "aa",
"in" : "query",
"schema" : {
"format" : "int32",
"type" : "integer"
"type" : "integer",
"format" : "int32"
},
"required" : true,
"deprecated" : false
@ -68,16 +68,16 @@
"type" : "string"
},
"TestResponse" : {
"type" : "object",
"properties" : {
"c" : {
"$ref" : "#/components/schemas/String"
}
},
"type" : "object"
}
},
"Int" : {
"format" : "int32",
"type" : "integer"
"type" : "integer",
"format" : "int32"
}
},
"securitySchemes" : { }

View File

@ -0,0 +1,73 @@
{
"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/polymorphic" : {
"get" : {
"tags" : [ ],
"summary" : "spooky class",
"description" : "break this glass in scenario of emergency",
"parameters" : [ ],
"responses" : {
"200" : {
"description" : "A successful endeavor",
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/Mysterious"
}
}
}
}
},
"deprecated" : false
}
}
},
"components" : {
"schemas" : {
"String" : {
"type" : "string"
},
"Hehe" : {
"type" : "string",
"enum" : [ "HAHA", "HOHO" ]
},
"Mysterious" : {
"type" : "object",
"properties" : {
"nowYouSeeMe" : {
"$ref" : "#/components/schemas/String"
},
"nowYouDont" : {
"$ref" : "#/components/schemas/Hehe"
}
}
}
},
"securitySchemes" : { }
},
"security" : [ ],
"tags" : [ ]
}

View File

@ -1,4 +1,5 @@
plugins {
kotlin("plugin.serialization") version "1.5.0"
application
}
@ -11,15 +12,20 @@ dependencies {
implementation(projects.kompendiumSwaggerUi)
implementation(libs.bundles.ktor)
implementation(libs.ktor.jackson)
implementation(libs.kotlinx.serialization.json)
implementation(libs.ktor.serialization)
implementation(libs.bundles.ktorAuth)
implementation(libs.bundles.logging)
implementation("joda-time:joda-time:2.10.10")
testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
}
application {
@Suppress("DEPRECATION")
mainClassName = "org.leafygreens.kompendium.playground.MainKt"
mainClassName = "io.bkbn.kompendium.playground.MainKt"
applicationDefaultJvmArgs = listOf("-Dio.ktor.development=true") // TODO I don't think this is working 😢
}

Some files were not shown because too many files have changed in this diff Show More