V1 beta release time 🥳 (#48)
This commit is contained in:
25
.github/workflows/release.yml
vendored
Normal file
25
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
name: Publish to GitHub Packages
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types:
|
||||||
|
- prereleased
|
||||||
|
- released
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
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 package
|
||||||
|
run: ./gradlew publish -Prelease=true
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
12
CHANGELOG.md
12
CHANGELOG.md
@ -1,5 +1,17 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
### [1.0.0-beta] - May 6th, 2021
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Release action to package a release JAR 🍻
|
||||||
|
- EXTREME DOCUMENTATION 📜
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Cleanup to test files
|
||||||
|
- Removes KompendiumHttpCodes in favor of Ktor HttpStatusCode
|
||||||
|
|
||||||
### [0.9.0] - May 5th, 2021
|
### [0.9.0] - May 5th, 2021
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
24
README.md
24
README.md
@ -37,16 +37,6 @@ dependencies {
|
|||||||
|
|
||||||
## In depth
|
## In depth
|
||||||
|
|
||||||
### Warning 🚨
|
|
||||||
Kompendium is still under active development ⚠️ There are a number of yet-to-be-implemented features, including
|
|
||||||
|
|
||||||
- Sealed Class / Polymorphic Support 😬
|
|
||||||
- Validation / Enforcement (❓👀❓)
|
|
||||||
|
|
||||||
If you have a feature that is not listed here, please open an issue!
|
|
||||||
|
|
||||||
In addition, if you find any 🐞😱 please open an issue as well!
|
|
||||||
|
|
||||||
### Notarized Routes
|
### Notarized Routes
|
||||||
|
|
||||||
Kompendium introduces the concept of `notarized` HTTP methods. That is, for all your `GET`, `POST`, `PUT`, and `DELETE`
|
Kompendium introduces the concept of `notarized` HTTP methods. That is, for all your `GET`, `POST`, `PUT`, and `DELETE`
|
||||||
@ -204,3 +194,17 @@ it offers a seriously clean UX where the implementer doesn't need to worry about
|
|||||||
drawback, however, is that you are limited to a single API per classpath.
|
drawback, however, is that you are limited to a single API per classpath.
|
||||||
|
|
||||||
If this is a blocker, please open a GitHub issue, and we can start to think out solutions!
|
If this is a blocker, please open a GitHub issue, and we can start to think out solutions!
|
||||||
|
|
||||||
|
## Future Work
|
||||||
|
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
|
||||||
|
|
||||||
|
- 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)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Kompendium
|
# Kompendium
|
||||||
project.version=0.9.0
|
project.version=1.0.0-beta
|
||||||
# Kotlin
|
# Kotlin
|
||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
# Gradle
|
# Gradle
|
||||||
|
@ -11,6 +11,7 @@ import io.ktor.auth.authenticate
|
|||||||
import io.ktor.features.ContentNegotiation
|
import io.ktor.features.ContentNegotiation
|
||||||
import io.ktor.http.ContentType
|
import io.ktor.http.ContentType
|
||||||
import io.ktor.http.HttpMethod
|
import io.ktor.http.HttpMethod
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
import io.ktor.jackson.jackson
|
import io.ktor.jackson.jackson
|
||||||
import io.ktor.response.respondText
|
import io.ktor.response.respondText
|
||||||
import io.ktor.routing.route
|
import io.ktor.routing.route
|
||||||
@ -31,7 +32,6 @@ import org.leafygreens.kompendium.models.meta.MethodInfo
|
|||||||
import org.leafygreens.kompendium.models.meta.ResponseInfo
|
import org.leafygreens.kompendium.models.meta.ResponseInfo
|
||||||
import org.leafygreens.kompendium.routes.openApi
|
import org.leafygreens.kompendium.routes.openApi
|
||||||
import org.leafygreens.kompendium.routes.redoc
|
import org.leafygreens.kompendium.routes.redoc
|
||||||
import org.leafygreens.kompendium.util.KompendiumHttpCodes
|
|
||||||
|
|
||||||
internal class KompendiumAuthTest {
|
internal class KompendiumAuthTest {
|
||||||
|
|
||||||
@ -189,7 +189,7 @@ internal class KompendiumAuthTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
val testGetResponse = ResponseInfo<TestResponse>(KompendiumHttpCodes.OK, "A Successful Endeavor")
|
val testGetResponse = ResponseInfo<TestResponse>(HttpStatusCode.OK, "A Successful Endeavor")
|
||||||
fun testGetInfo(vararg security: String) =
|
fun testGetInfo(vararg security: String) =
|
||||||
MethodInfo.GetInfo<TestParams, TestResponse>(
|
MethodInfo.GetInfo<TestParams, TestResponse>(
|
||||||
summary = "Another get test",
|
summary = "Another get test",
|
||||||
|
@ -7,6 +7,9 @@ import org.leafygreens.kompendium.models.oas.OpenApiSpecInfo
|
|||||||
import org.leafygreens.kompendium.path.CorePathCalculator
|
import org.leafygreens.kompendium.path.CorePathCalculator
|
||||||
import org.leafygreens.kompendium.path.PathCalculator
|
import org.leafygreens.kompendium.path.PathCalculator
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maintains all state for the Kompendium library
|
||||||
|
*/
|
||||||
object Kompendium {
|
object Kompendium {
|
||||||
|
|
||||||
var errorMap: ErrorMap = emptyMap()
|
var errorMap: ErrorMap = emptyMap()
|
||||||
|
@ -4,8 +4,19 @@ import io.ktor.routing.Route
|
|||||||
import kotlin.reflect.KType
|
import kotlin.reflect.KType
|
||||||
import kotlin.reflect.typeOf
|
import kotlin.reflect.typeOf
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Functions are considered preflight when they are used to intercept a method ahead of running.
|
||||||
|
*/
|
||||||
object KompendiumPreFlight {
|
object KompendiumPreFlight {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs all content analysis on the types provided to a notarized route and adds it to the top level spec
|
||||||
|
* @param TParam
|
||||||
|
* @param TReq
|
||||||
|
* @param TResp
|
||||||
|
* @param block The function to execute, provided type information of the parameters above
|
||||||
|
* @return [Route]
|
||||||
|
*/
|
||||||
@OptIn(ExperimentalStdlibApi::class)
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
inline fun <reified TParam : Any, reified TReq : Any, reified TResp : Any> methodNotarizationPreFlight(
|
inline fun <reified TParam : Any, reified TReq : Any, reified TResp : Any> methodNotarizationPreFlight(
|
||||||
block: (KType, KType, KType) -> Route
|
block: (KType, KType, KType) -> Route
|
||||||
@ -20,6 +31,12 @@ object KompendiumPreFlight {
|
|||||||
return block.invoke(paramType, requestType, responseType)
|
return block.invoke(paramType, requestType, responseType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs all content analysis on the types provided to a notarized error and adds them to the top level spec.
|
||||||
|
* @param TErr
|
||||||
|
* @param TResp
|
||||||
|
* @param block The function to execute, provided type information of the parameters above
|
||||||
|
*/
|
||||||
@OptIn(ExperimentalStdlibApi::class)
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
inline fun <reified TErr: Throwable, reified TResp : Any> errorNotarizationPreFlight(
|
inline fun <reified TErr: Throwable, reified TResp : Any> errorNotarizationPreFlight(
|
||||||
block: (KType, KType) -> Unit
|
block: (KType, KType) -> Unit
|
||||||
|
@ -21,10 +21,19 @@ import org.leafygreens.kompendium.util.Helpers.getReferenceSlug
|
|||||||
import org.leafygreens.kompendium.util.Helpers.logged
|
import org.leafygreens.kompendium.util.Helpers.logged
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Responsible for generating the schema map that is used to power all object references across the API Spec.
|
||||||
|
*/
|
||||||
object Kontent {
|
object Kontent {
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(javaClass)
|
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)
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
inline fun <reified T> generateKontent(
|
inline fun <reified T> generateKontent(
|
||||||
cache: SchemaMap = emptyMap()
|
cache: SchemaMap = emptyMap()
|
||||||
@ -33,6 +42,12 @@ object Kontent {
|
|||||||
return generateKTypeKontent(kontentType, cache)
|
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)
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
inline fun <reified T> generateParameterKontent(
|
inline fun <reified T> generateParameterKontent(
|
||||||
cache: SchemaMap = emptyMap()
|
cache: SchemaMap = emptyMap()
|
||||||
@ -42,6 +57,11 @@ object Kontent {
|
|||||||
.filterNot { (slug, _) -> slug == (kontentType.classifier as KClass<*>).simpleName }
|
.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(
|
fun generateKTypeKontent(
|
||||||
type: KType,
|
type: KType,
|
||||||
cache: SchemaMap = emptyMap()
|
cache: SchemaMap = emptyMap()
|
||||||
@ -65,6 +85,11 @@ object Kontent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 =
|
private fun handleComplexType(clazz: KClass<*>, cache: SchemaMap): SchemaMap =
|
||||||
when (cache.containsKey(clazz.simpleName)) {
|
when (cache.containsKey(clazz.simpleName)) {
|
||||||
true -> {
|
true -> {
|
||||||
@ -92,11 +117,22 @@ object Kontent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
private fun handleEnumType(clazz: KClass<*>, cache: SchemaMap): SchemaMap {
|
||||||
val options = clazz.java.enumConstants.map { it.toString() }.toSet()
|
val options = clazz.java.enumConstants.map { it.toString() }.toSet()
|
||||||
return cache.plus(clazz.simpleName!! to EnumSchema(options))
|
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 {
|
private fun handleMapType(type: KType, clazz: KClass<*>, cache: SchemaMap): SchemaMap {
|
||||||
logger.debug("Map detected for $type, generating schema and appending to cache")
|
logger.debug("Map detected for $type, generating schema and appending to cache")
|
||||||
val (keyType, valType) = type.arguments.map { it.type }
|
val (keyType, valType) = type.arguments.map { it.type }
|
||||||
@ -112,6 +148,12 @@ object Kontent {
|
|||||||
return updatedCache.plus(referenceName to schema)
|
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 {
|
private fun handleCollectionType(type: KType, clazz: KClass<*>, cache: SchemaMap): SchemaMap {
|
||||||
logger.debug("Collection detected for $type, generating schema and appending to cache")
|
logger.debug("Collection detected for $type, generating schema and appending to cache")
|
||||||
val collectionType = type.arguments.first().type!!
|
val collectionType = type.arguments.first().type!!
|
||||||
|
@ -26,7 +26,19 @@ import org.leafygreens.kompendium.util.Helpers
|
|||||||
import org.leafygreens.kompendium.util.Helpers.getReferenceSlug
|
import org.leafygreens.kompendium.util.Helpers.getReferenceSlug
|
||||||
import org.leafygreens.kompendium.util.Helpers.getSimpleSlug
|
import org.leafygreens.kompendium.util.Helpers.getSimpleSlug
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The MethodParser is responsible for converting route metadata and types into an OpenAPI compatible data class.
|
||||||
|
*/
|
||||||
object MethodParser {
|
object MethodParser {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the OpenAPI Path spec from provided metadata
|
||||||
|
* @param info implementation of the [MethodInfo] sealed class
|
||||||
|
* @param paramType Type of `TParam`
|
||||||
|
* @param requestType Type of `TReq` if required
|
||||||
|
* @param responseType Type of `TResp`
|
||||||
|
* @return object representing the OpenAPI Path spec.
|
||||||
|
*/
|
||||||
fun parseMethodInfo(
|
fun parseMethodInfo(
|
||||||
info: MethodInfo<*, *>,
|
info: MethodInfo<*, *>,
|
||||||
paramType: KType,
|
paramType: KType,
|
||||||
@ -61,19 +73,34 @@ object MethodParser {
|
|||||||
) else null
|
) else null
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun parseThrowables(throwables: Set<KClass<*>>): Map<Int, OpenApiSpecReferencable> = throwables.mapNotNull {
|
/**
|
||||||
Kompendium.errorMap[it.createType()]
|
* Adds the error to the [Kompendium.errorMap] for reference in notarized routes.
|
||||||
}.toMap()
|
* @param errorType [KType] of the throwable being handled
|
||||||
|
* @param responseType [KType] the type of the response sent in event of error
|
||||||
fun <TResp> ResponseInfo<TResp>.parseErrorInfo(
|
*/
|
||||||
|
fun ResponseInfo<*>.parseErrorInfo(
|
||||||
errorType: KType,
|
errorType: KType,
|
||||||
responseType: KType
|
responseType: KType
|
||||||
) {
|
) {
|
||||||
Kompendium.errorMap = Kompendium.errorMap.plus(errorType to responseType.toResponseSpec(this))
|
Kompendium.errorMap = Kompendium.errorMap.plus(errorType to responseType.toResponseSpec(this))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO These two lookin' real similar 👀 Combine?
|
/**
|
||||||
private fun <TReq> KType.toRequestSpec(requestInfo: RequestInfo<TReq>?): OpenApiSpecRequest<TReq>? =
|
* Parses possible errors thrown by a route
|
||||||
|
* @param throwables Set of classes that can be thrown
|
||||||
|
* @return Mapping of status codes to their corresponding error spec
|
||||||
|
*/
|
||||||
|
private fun parseThrowables(throwables: Set<KClass<*>>): Map<Int, OpenApiSpecReferencable> = throwables.mapNotNull {
|
||||||
|
Kompendium.errorMap[it.createType()]
|
||||||
|
}.toMap()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a [KType] to an [OpenApiSpecRequest]
|
||||||
|
* @receiver [KType] to convert
|
||||||
|
* @param requestInfo request metadata
|
||||||
|
* @return Will return a generated [OpenApiSpecRequest] if requestInfo is not null
|
||||||
|
*/
|
||||||
|
private fun KType.toRequestSpec(requestInfo: RequestInfo<*>?): OpenApiSpecRequest<*>? =
|
||||||
when (requestInfo) {
|
when (requestInfo) {
|
||||||
null -> null
|
null -> null
|
||||||
else -> {
|
else -> {
|
||||||
@ -84,18 +111,31 @@ object MethodParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <TResp> KType.toResponseSpec(responseInfo: ResponseInfo<TResp>?): Pair<Int, OpenApiSpecResponse<TResp>>? =
|
/**
|
||||||
|
* Converts a [KType] to a pairing of http status code to [OpenApiSpecRequest]
|
||||||
|
* @receiver [KType] to convert
|
||||||
|
* @param responseInfo response metadata
|
||||||
|
* @return Will return a generated [Pair] if responseInfo is not null
|
||||||
|
*/
|
||||||
|
private fun KType.toResponseSpec(responseInfo: ResponseInfo<*>?): Pair<Int, OpenApiSpecResponse<*>>? =
|
||||||
when (responseInfo) {
|
when (responseInfo) {
|
||||||
null -> null // TODO again probably revisit this
|
null -> null
|
||||||
else -> {
|
else -> {
|
||||||
val specResponse = OpenApiSpecResponse(
|
val specResponse = OpenApiSpecResponse(
|
||||||
description = responseInfo.description,
|
description = responseInfo.description,
|
||||||
content = resolveContent(responseInfo.mediaTypes, responseInfo.examples)
|
content = resolveContent(responseInfo.mediaTypes, responseInfo.examples)
|
||||||
)
|
)
|
||||||
Pair(responseInfo.status, specResponse)
|
Pair(responseInfo.status.value, specResponse)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates MediaTypes along with any examples provided
|
||||||
|
* @receiver [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> KType.resolveContent(
|
||||||
mediaTypes: List<String>,
|
mediaTypes: List<String>,
|
||||||
examples: Map<String, F>
|
examples: Map<String, F>
|
||||||
@ -111,6 +151,13 @@ object MethodParser {
|
|||||||
} else null
|
} else null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a type for all parameter information. All fields in the receiver
|
||||||
|
* must be annotated with [org.leafygreens.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
|
||||||
|
*/
|
||||||
private fun KType.toParameterSpec(): List<OpenApiSpecParameter> {
|
private fun KType.toParameterSpec(): List<OpenApiSpecParameter> {
|
||||||
val clazz = classifier as KClass<*>
|
val clazz = classifier as KClass<*>
|
||||||
return clazz.memberProperties.map { prop ->
|
return clazz.memberProperties.map { prop ->
|
||||||
@ -131,6 +178,12 @@ object MethodParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Absolutely disgusting reflection to determine if a default value is available for a given property.
|
||||||
|
* @param clazz to which the property belongs
|
||||||
|
* @param prop the property in question
|
||||||
|
* @return The default value if found
|
||||||
|
*/
|
||||||
private fun getDefaultParameterValue(clazz: KClass<*>, prop: KProperty<*>): Any? {
|
private fun getDefaultParameterValue(clazz: KClass<*>, prop: KProperty<*>): Any? {
|
||||||
val constructor = clazz.primaryConstructor
|
val constructor = clazz.primaryConstructor
|
||||||
val parameterInQuestion = constructor
|
val parameterInQuestion = constructor
|
||||||
@ -152,6 +205,12 @@ object MethodParser {
|
|||||||
return getterFunction.invoke(instance)
|
return getterFunction.invoke(instance)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows the reflection invoker to populate a parameter map with values in order to sus out any default parameters.
|
||||||
|
* @param param Parameter to provide value for
|
||||||
|
* @return value of the proper type to match param
|
||||||
|
* @throws [IllegalStateException] if parameter type is not one of the basic types supported below.
|
||||||
|
*/
|
||||||
private fun defaultValueInjector(param: KParameter): Any = when (param.type.classifier) {
|
private fun defaultValueInjector(param: KParameter): Any = when (param.type.classifier) {
|
||||||
String::class -> "test"
|
String::class -> "test"
|
||||||
Boolean::class -> false
|
Boolean::class -> false
|
||||||
@ -162,5 +221,4 @@ object MethodParser {
|
|||||||
UUID::class -> UUID.randomUUID()
|
UUID::class -> UUID.randomUUID()
|
||||||
else -> error("Unsupported Type")
|
else -> error("Unsupported Type")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -17,9 +17,20 @@ import org.leafygreens.kompendium.models.meta.MethodInfo.DeleteInfo
|
|||||||
import org.leafygreens.kompendium.models.meta.ResponseInfo
|
import org.leafygreens.kompendium.models.meta.ResponseInfo
|
||||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecPathItem
|
import org.leafygreens.kompendium.models.oas.OpenApiSpecPathItem
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notarization methods are the primary way that a Ktor API using Kompendium differentiates
|
||||||
|
* from a default Ktor application. On instantiation, a notarized route, provided with the proper metadata,
|
||||||
|
* will reflectively analyze all pertinent data to build a corresponding OpenAPI entry.
|
||||||
|
*/
|
||||||
object Notarized {
|
object Notarized {
|
||||||
|
|
||||||
@OptIn(ExperimentalStdlibApi::class)
|
/**
|
||||||
|
* 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]
|
||||||
|
* @param TResp Class detailing the expected API response
|
||||||
|
* @param info Route metadata
|
||||||
|
*/
|
||||||
inline fun <reified TParam : Any, reified TResp : Any> Route.notarizedGet(
|
inline fun <reified TParam : Any, reified TResp : Any> Route.notarizedGet(
|
||||||
info: GetInfo<TParam, TResp>,
|
info: GetInfo<TParam, TResp>,
|
||||||
noinline body: PipelineInterceptor<Unit, ApplicationCall>
|
noinline body: PipelineInterceptor<Unit, ApplicationCall>
|
||||||
@ -31,6 +42,14 @@ object Notarized {
|
|||||||
return method(HttpMethod.Get) { handle(body) }
|
return method(HttpMethod.Get) { handle(body) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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]
|
||||||
|
* @param TReq Class detailing the expected API request body
|
||||||
|
* @param TResp Class detailing the expected API response
|
||||||
|
* @param info Route metadata
|
||||||
|
*/
|
||||||
inline fun <reified TParam : Any, reified TReq : Any, reified TResp : Any> Route.notarizedPost(
|
inline fun <reified TParam : Any, reified TReq : Any, reified TResp : Any> Route.notarizedPost(
|
||||||
info: PostInfo<TParam, TReq, TResp>,
|
info: PostInfo<TParam, TReq, TResp>,
|
||||||
noinline body: PipelineInterceptor<Unit, ApplicationCall>
|
noinline body: PipelineInterceptor<Unit, ApplicationCall>
|
||||||
@ -42,6 +61,14 @@ object Notarized {
|
|||||||
return method(HttpMethod.Post) { handle(body) }
|
return method(HttpMethod.Post) { handle(body) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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]
|
||||||
|
* @param TReq Class detailing the expected API request body
|
||||||
|
* @param TResp Class detailing the expected API response
|
||||||
|
* @param info Route metadata
|
||||||
|
*/
|
||||||
inline fun <reified TParam : Any, reified TReq : Any, reified TResp : Any> Route.notarizedPut(
|
inline fun <reified TParam : Any, reified TReq : Any, reified TResp : Any> Route.notarizedPut(
|
||||||
info: PutInfo<TParam, TReq, TResp>,
|
info: PutInfo<TParam, TReq, TResp>,
|
||||||
noinline body: PipelineInterceptor<Unit, ApplicationCall>,
|
noinline body: PipelineInterceptor<Unit, ApplicationCall>,
|
||||||
@ -54,6 +81,13 @@ object Notarized {
|
|||||||
return method(HttpMethod.Put) { handle(body) }
|
return method(HttpMethod.Put) { handle(body) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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]
|
||||||
|
* @param TResp Class detailing the expected API response
|
||||||
|
* @param info Route metadata
|
||||||
|
*/
|
||||||
inline fun <reified TParam : Any, reified TResp : Any> Route.notarizedDelete(
|
inline fun <reified TParam : Any, reified TResp : Any> Route.notarizedDelete(
|
||||||
info: DeleteInfo<TParam, TResp>,
|
info: DeleteInfo<TParam, TResp>,
|
||||||
noinline body: PipelineInterceptor<Unit, ApplicationCall>
|
noinline body: PipelineInterceptor<Unit, ApplicationCall>
|
||||||
@ -65,6 +99,12 @@ object Notarized {
|
|||||||
return method(HttpMethod.Delete) { handle(body) }
|
return method(HttpMethod.Delete) { handle(body) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notarization for a handled exception response
|
||||||
|
* @param TErr The [Throwable] that is being handled
|
||||||
|
* @param TResp Class detailing the expected API response when handled
|
||||||
|
* @param info Response metadata
|
||||||
|
*/
|
||||||
inline fun <reified TErr : Throwable, reified TResp : Any> StatusPages.Configuration.notarizedException(
|
inline fun <reified TErr : Throwable, reified TResp : Any> StatusPages.Configuration.notarizedException(
|
||||||
info: ResponseInfo<TResp>,
|
info: ResponseInfo<TResp>,
|
||||||
noinline handler: suspend PipelineContext<Unit, ApplicationCall>.(TErr) -> Unit
|
noinline handler: suspend PipelineContext<Unit, ApplicationCall>.(TErr) -> Unit
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package org.leafygreens.kompendium.models.meta
|
package org.leafygreens.kompendium.models.meta
|
||||||
|
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
|
||||||
data class ResponseInfo<TResp>(
|
data class ResponseInfo<TResp>(
|
||||||
val status: Int,
|
val status: HttpStatusCode,
|
||||||
val description: String,
|
val description: String,
|
||||||
val mediaTypes: List<String> = listOf("application/json"),
|
val mediaTypes: List<String> = listOf("application/json"),
|
||||||
val examples: Map<String, TResp> = emptyMap()
|
val examples: Map<String, TResp> = emptyMap()
|
||||||
|
@ -7,6 +7,9 @@ import io.ktor.routing.Route
|
|||||||
import io.ktor.util.InternalAPI
|
import io.ktor.util.InternalAPI
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default [PathCalculator] meant to be overridden as necessary
|
||||||
|
*/
|
||||||
open class CorePathCalculator : PathCalculator {
|
open class CorePathCalculator : PathCalculator {
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(javaClass)
|
private val logger = LoggerFactory.getLogger(javaClass)
|
||||||
|
@ -2,10 +2,19 @@ package org.leafygreens.kompendium.path
|
|||||||
|
|
||||||
import io.ktor.routing.Route
|
import io.ktor.routing.Route
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extensible interface for calculating Ktor paths
|
||||||
|
*/
|
||||||
interface PathCalculator {
|
interface PathCalculator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Core route calculation method
|
||||||
|
*/
|
||||||
fun calculate(route: Route?, tail: String = ""): String
|
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
|
fun handleCustomSelectors(route: Route, tail: String): String
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,10 @@ import io.ktor.routing.get
|
|||||||
import io.ktor.routing.route
|
import io.ktor.routing.route
|
||||||
import org.leafygreens.kompendium.models.oas.OpenApiSpec
|
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) {
|
fun Routing.openApi(oas: OpenApiSpec) {
|
||||||
route("/openapi.json") {
|
route("/openapi.json") {
|
||||||
get {
|
get {
|
||||||
|
@ -15,7 +15,12 @@ import kotlinx.html.title
|
|||||||
import kotlinx.html.unsafe
|
import kotlinx.html.unsafe
|
||||||
import org.leafygreens.kompendium.models.oas.OpenApiSpec
|
import org.leafygreens.kompendium.models.oas.OpenApiSpec
|
||||||
|
|
||||||
fun Routing.redoc(oas: OpenApiSpec) {
|
/**
|
||||||
|
* Provides an out-of-the-box route to view docs using ReDoc
|
||||||
|
* @param oas spec to reference
|
||||||
|
* @param specUrl url to point ReDoc to the OpenAPI json document
|
||||||
|
*/
|
||||||
|
fun Routing.redoc(oas: OpenApiSpec, specUrl: String = "/openapi.json") {
|
||||||
route("/docs") {
|
route("/docs") {
|
||||||
get {
|
get {
|
||||||
call.respondHtml {
|
call.respondHtml {
|
||||||
@ -41,8 +46,7 @@ fun Routing.redoc(oas: OpenApiSpec) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
// TODO needs to mirror openApi route
|
unsafe { +"<redoc spec-url='${specUrl}'></redoc>" }
|
||||||
unsafe { +"<redoc spec-url='/openapi.json'></redoc>" }
|
|
||||||
script {
|
script {
|
||||||
src = "https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"
|
src = "https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"
|
||||||
}
|
}
|
||||||
|
@ -1,81 +0,0 @@
|
|||||||
package org.leafygreens.kompendium.util
|
|
||||||
|
|
||||||
// Take from https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
|
|
||||||
object KompendiumHttpCodes {
|
|
||||||
|
|
||||||
// Informational responses
|
|
||||||
const val CONTINUE = 100
|
|
||||||
const val SWITCHING_PROTOCOL = 101
|
|
||||||
const val PROCESSING = 102
|
|
||||||
const val EARLY_HINTS = 103
|
|
||||||
|
|
||||||
// Successful responses
|
|
||||||
const val OK = 200
|
|
||||||
const val CREATED = 201
|
|
||||||
const val ACCEPTED = 202
|
|
||||||
const val NON_AUTHORITATIVE_INFORMATION = 203
|
|
||||||
const val NO_CONTENT = 204
|
|
||||||
const val RESET_CONTENT = 205
|
|
||||||
const val PARTIAL_CONTENT = 206
|
|
||||||
const val MULTI_STATUS = 207
|
|
||||||
const val ALREADY_REPORTED = 208
|
|
||||||
const val IM_USED = 226
|
|
||||||
|
|
||||||
// Redirection messages
|
|
||||||
const val MULTIPLE_CHOICE = 300
|
|
||||||
const val MOVED_PERMANENTLY = 301
|
|
||||||
const val FOUND = 302
|
|
||||||
const val SEE_OTHER = 303
|
|
||||||
const val NOT_MODIFIED = 304
|
|
||||||
@Deprecated("Deprecated due to security concerns regarding in-band configuration of a proxy")
|
|
||||||
const val USE_PROXY = 305
|
|
||||||
@Deprecated("This response code is no longer used; it is just reserved.")
|
|
||||||
const val UNUSED = 306
|
|
||||||
const val TEMPORARY_REDIRECT = 307
|
|
||||||
const val PERMANENT_REDIRECT = 308
|
|
||||||
|
|
||||||
// Client Response Errors
|
|
||||||
const val BAD_REQUEST = 400
|
|
||||||
const val UNAUTHORIZED = 401
|
|
||||||
const val PAYMENT_REQUIRED = 402
|
|
||||||
const val FORBIDDEN = 403
|
|
||||||
const val NOT_FOUND = 404
|
|
||||||
const val METHOD_NOT_ALLOWED = 405
|
|
||||||
const val NOT_ACCEPTABLE = 406
|
|
||||||
const val PROXY_AUTHENTICATION_REQUIRED = 407
|
|
||||||
const val REQUEST_TIMEOUT = 408
|
|
||||||
const val CONFLICT = 409
|
|
||||||
const val GONE = 410
|
|
||||||
const val LENGTH_REQUIRED = 411
|
|
||||||
const val PRECONDITION_FAILED = 412
|
|
||||||
const val PAYLOAD_TOO_LARGE = 413
|
|
||||||
const val URI_TOO_LONG = 414
|
|
||||||
const val UNSUPPORTED_MEDIA_TYPE = 415
|
|
||||||
const val RANGE_NOT_SATISFIABLE = 416
|
|
||||||
const val EXPECTATION_FAILED = 417
|
|
||||||
const val IM_A_TEAPOT = 418
|
|
||||||
const val MISDIRECTED_REQUEST = 421
|
|
||||||
const val UNPROCESSABLE_ENTITY = 422
|
|
||||||
const val LOCKED = 423
|
|
||||||
const val FAILED_DEPENDENCY = 424
|
|
||||||
const val TOO_EARLY = 425
|
|
||||||
const val UPGRADE_REQUIRED = 426
|
|
||||||
const val PRECONDITION_REQUIRED = 428
|
|
||||||
const val TOO_MANY_REQUESTS = 429
|
|
||||||
const val REQUEST_HEADER_FIELDS_TOO_LARGE = 431
|
|
||||||
const val UNAVAILABLE_FOR_LEGAL_REASONS = 451
|
|
||||||
|
|
||||||
// Server Error Responses
|
|
||||||
const val INTERNAL_SERVER_ERROR = 500
|
|
||||||
const val NOT_IMPLEMENTED = 501
|
|
||||||
const val BAD_GATEWAY = 502
|
|
||||||
const val SERVICE_UNAVAILABLE = 503
|
|
||||||
const val GATEWAY_TIMEOUT = 504
|
|
||||||
const val HTTP_VERSION_NOT_SUPPORTED = 505
|
|
||||||
const val VARIANT_ALSO_NEGOTIATES = 506
|
|
||||||
const val INSUFFICIENT_STORAGE = 507
|
|
||||||
const val LOOP_DETECTED = 508
|
|
||||||
const val NOT_EXTENDED = 510
|
|
||||||
const val NETWORK_AUTHENTICATION_REQUIRED = 511
|
|
||||||
|
|
||||||
}
|
|
@ -1,18 +1,8 @@
|
|||||||
package org.leafygreens.kompendium
|
package org.leafygreens.kompendium
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude
|
|
||||||
import com.fasterxml.jackson.databind.SerializationFeature
|
|
||||||
import io.ktor.application.Application
|
import io.ktor.application.Application
|
||||||
import io.ktor.application.call
|
|
||||||
import io.ktor.application.install
|
|
||||||
import io.ktor.features.ContentNegotiation
|
|
||||||
import io.ktor.features.StatusPages
|
|
||||||
import io.ktor.http.HttpMethod
|
import io.ktor.http.HttpMethod
|
||||||
import io.ktor.http.HttpStatusCode
|
import io.ktor.http.HttpStatusCode
|
||||||
import io.ktor.jackson.jackson
|
|
||||||
import io.ktor.response.respond
|
|
||||||
import io.ktor.response.respondText
|
|
||||||
import io.ktor.routing.route
|
|
||||||
import io.ktor.routing.routing
|
import io.ktor.routing.routing
|
||||||
import io.ktor.server.testing.handleRequest
|
import io.ktor.server.testing.handleRequest
|
||||||
import io.ktor.server.testing.withTestApplication
|
import io.ktor.server.testing.withTestApplication
|
||||||
@ -20,35 +10,33 @@ import java.net.URI
|
|||||||
import kotlin.test.AfterTest
|
import kotlin.test.AfterTest
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
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.annotations.KompendiumParam
|
|
||||||
import org.leafygreens.kompendium.annotations.ParamType
|
|
||||||
import org.leafygreens.kompendium.models.meta.MethodInfo.DeleteInfo
|
|
||||||
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.RequestInfo
|
|
||||||
import org.leafygreens.kompendium.models.meta.ResponseInfo
|
|
||||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfo
|
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfo
|
||||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfoContact
|
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfoContact
|
||||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfoLicense
|
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfoLicense
|
||||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecServer
|
import org.leafygreens.kompendium.models.oas.OpenApiSpecServer
|
||||||
import org.leafygreens.kompendium.routes.openApi
|
import org.leafygreens.kompendium.routes.openApi
|
||||||
import org.leafygreens.kompendium.routes.redoc
|
import org.leafygreens.kompendium.routes.redoc
|
||||||
import org.leafygreens.kompendium.util.ComplexRequest
|
|
||||||
import org.leafygreens.kompendium.util.DefaultParameter
|
|
||||||
import org.leafygreens.kompendium.util.ExceptionResponse
|
|
||||||
import org.leafygreens.kompendium.util.KompendiumHttpCodes
|
|
||||||
import org.leafygreens.kompendium.util.TestCreatedResponse
|
|
||||||
import org.leafygreens.kompendium.util.TestHelpers.getFileSnapshot
|
import org.leafygreens.kompendium.util.TestHelpers.getFileSnapshot
|
||||||
import org.leafygreens.kompendium.util.TestNested
|
import org.leafygreens.kompendium.util.complexType
|
||||||
import org.leafygreens.kompendium.util.TestParams
|
import org.leafygreens.kompendium.util.configModule
|
||||||
import org.leafygreens.kompendium.util.TestRequest
|
import org.leafygreens.kompendium.util.emptyGet
|
||||||
import org.leafygreens.kompendium.util.TestResponse
|
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
|
||||||
|
|
||||||
internal class KompendiumTest {
|
internal class KompendiumTest {
|
||||||
|
|
||||||
@ -448,305 +436,6 @@ internal class KompendiumTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private companion object {
|
|
||||||
val testGetResponse = ResponseInfo<TestResponse>(KompendiumHttpCodes.OK, "A Successful Endeavor")
|
|
||||||
val testGetListResponse = ResponseInfo<List<TestResponse>>(KompendiumHttpCodes.OK, "A Successful List-y Endeavor")
|
|
||||||
val testPostResponse = ResponseInfo<TestCreatedResponse>(KompendiumHttpCodes.CREATED, "A Successful Endeavor")
|
|
||||||
val testPostResponseAgain = ResponseInfo<Boolean>(KompendiumHttpCodes.CREATED, "A Successful Endeavor")
|
|
||||||
val testDeleteResponse =
|
|
||||||
ResponseInfo<Unit>(KompendiumHttpCodes.NO_CONTENT, "A Successful Endeavor", mediaTypes = emptyList())
|
|
||||||
val testRequest = RequestInfo<TestRequest>("A Test request")
|
|
||||||
val testRequestAgain = RequestInfo<Int>("A Test request")
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Application.configModule() {
|
|
||||||
install(ContentNegotiation) {
|
|
||||||
jackson {
|
|
||||||
enable(SerializationFeature.INDENT_OUTPUT)
|
|
||||||
setSerializationInclusion(JsonInclude.Include.NON_NULL)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Application.statusPageModule() {
|
|
||||||
install(StatusPages) {
|
|
||||||
notarizedException<Exception, ExceptionResponse>(info = ResponseInfo(400, "Bad Things Happened")) {
|
|
||||||
call.respond(HttpStatusCode.BadRequest, ExceptionResponse("Why you do dis?"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Application.statusPageMultiExceptions() {
|
|
||||||
install(StatusPages) {
|
|
||||||
notarizedException<AccessDeniedException, Unit>(info = ResponseInfo(403, "New API who dis?")) {
|
|
||||||
call.respond(HttpStatusCode.Forbidden)
|
|
||||||
}
|
|
||||||
notarizedException<Exception, ExceptionResponse>(info = ResponseInfo(400, "Bad Things Happened")) {
|
|
||||||
call.respond(HttpStatusCode.BadRequest, ExceptionResponse("Why you do dis?"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Application.notarizedGetWithNotarizedException() {
|
|
||||||
routing {
|
|
||||||
route("/test") {
|
|
||||||
notarizedGet(testGetWithException) {
|
|
||||||
error("something terrible has happened!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Application.notarizedGetWithMultipleThrowables() {
|
|
||||||
routing {
|
|
||||||
route("/test") {
|
|
||||||
notarizedGet(testGetWithMultipleExceptions) {
|
|
||||||
error("something terrible has happened!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Application.notarizedGetModule() {
|
|
||||||
routing {
|
|
||||||
route("/test") {
|
|
||||||
notarizedGet(testGetInfo) {
|
|
||||||
call.respondText { "hey dude ‼️ congratz on the get request" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Application.notarizedPostModule() {
|
|
||||||
routing {
|
|
||||||
route("/test") {
|
|
||||||
notarizedPost(testPostInfo) {
|
|
||||||
call.respondText { "hey dude ✌️ congratz on the post request" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Application.notarizedDeleteModule() {
|
|
||||||
routing {
|
|
||||||
route("/test") {
|
|
||||||
notarizedDelete(testDeleteInfo) {
|
|
||||||
call.respond(HttpStatusCode.NoContent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Application.notarizedPutModule() {
|
|
||||||
routing {
|
|
||||||
route("/test") {
|
|
||||||
notarizedPut(testPutInfoAlso) {
|
|
||||||
call.respondText { "hey pal 🌝 whatcha doin' here?" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Application.pathParsingTestModule() {
|
|
||||||
routing {
|
|
||||||
route("/this") {
|
|
||||||
route("/is") {
|
|
||||||
route("/a") {
|
|
||||||
route("/complex") {
|
|
||||||
route("path") {
|
|
||||||
route("with/an/{id}") {
|
|
||||||
notarizedGet(testGetInfo) {
|
|
||||||
call.respondText { "Aww you followed this whole route 🥺" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Application.rootModule() {
|
|
||||||
routing {
|
|
||||||
route("/") {
|
|
||||||
notarizedGet(testGetInfo) {
|
|
||||||
call.respondText { "☎️🏠🌲" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Application.nestedUnderRootModule() {
|
|
||||||
routing {
|
|
||||||
route("/") {
|
|
||||||
route("/testerino") {
|
|
||||||
notarizedGet(testGetInfo) {
|
|
||||||
call.respondText { "🤔🔥" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Application.trailingSlash() {
|
|
||||||
routing {
|
|
||||||
route("/test") {
|
|
||||||
route("/") {
|
|
||||||
notarizedGet(testGetInfo) {
|
|
||||||
call.respondText { "🙀👾" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Application.returnsList() {
|
|
||||||
routing {
|
|
||||||
route("/test") {
|
|
||||||
notarizedGet(testGetInfoAgain) {
|
|
||||||
call.respondText { "hey dude ur doing amazing work!" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Application.complexType() {
|
|
||||||
routing {
|
|
||||||
route("/test") {
|
|
||||||
notarizedPut(testPutInfo) {
|
|
||||||
call.respondText { "heya" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Application.primitives() {
|
|
||||||
routing {
|
|
||||||
route("/test") {
|
|
||||||
notarizedPut(testPutInfoAgain) {
|
|
||||||
call.respondText { "heya" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Application.emptyGet() {
|
|
||||||
routing {
|
|
||||||
route("/test/empty") {
|
|
||||||
notarizedGet(trulyEmptyTestGetInfo) {
|
|
||||||
call.respond(HttpStatusCode.OK)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Application.withExamples() {
|
|
||||||
routing {
|
|
||||||
route("/test/examples") {
|
|
||||||
notarizedPost(
|
|
||||||
info = PostInfo<Unit, TestRequest, TestResponse>(
|
|
||||||
summary = "Example Parameters",
|
|
||||||
description = "A test for setting parameter examples",
|
|
||||||
requestInfo = RequestInfo(
|
|
||||||
description = "Test",
|
|
||||||
examples = mapOf(
|
|
||||||
"one" to TestRequest(fieldName = TestNested(nesty = "hey"), b = 4.0, aaa = emptyList()),
|
|
||||||
"two" to TestRequest(fieldName = TestNested(nesty = "hello"), b = 3.8, aaa = listOf(31324234))
|
|
||||||
)
|
|
||||||
),
|
|
||||||
responseInfo = ResponseInfo(
|
|
||||||
status = 201,
|
|
||||||
description = "nice",
|
|
||||||
examples = mapOf("test" to TestResponse(c = "spud"))
|
|
||||||
),
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
call.respond(HttpStatusCode.OK)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Application.withDefaultParameter() {
|
|
||||||
routing {
|
|
||||||
route("/test") {
|
|
||||||
notarizedGet(
|
|
||||||
info = GetInfo<DefaultParameter, TestResponse>(
|
|
||||||
summary = "Testing Default Params",
|
|
||||||
description = "Should have a default parameter value"
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
call.respond(TestResponse("hey"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class OptionalParams(
|
|
||||||
@KompendiumParam(ParamType.QUERY) val required: String,
|
|
||||||
@KompendiumParam(ParamType.QUERY) val notRequired: String?
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun Application.nonRequiredParamsGet() {
|
|
||||||
routing {
|
|
||||||
route("/test/optional") {
|
|
||||||
notarizedGet(emptyTestGetInfo) {
|
|
||||||
call.respond(HttpStatusCode.OK)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val oas = Kompendium.openApiSpec.copy(
|
private val oas = Kompendium.openApiSpec.copy(
|
||||||
info = OpenApiSpecInfo(
|
info = OpenApiSpecInfo(
|
||||||
|
@ -42,18 +42,12 @@ data class TestResponse(val c: String)
|
|||||||
|
|
||||||
data class TestCreatedResponse(val id: Int, val c: String)
|
data class TestCreatedResponse(val id: Int, val c: String)
|
||||||
|
|
||||||
object TestDeleteResponse
|
|
||||||
|
|
||||||
data class ComplexRequest(
|
data class ComplexRequest(
|
||||||
val org: String,
|
val org: String,
|
||||||
@KompendiumField("amazing_field")
|
@KompendiumField("amazing_field")
|
||||||
val amazingField: String,
|
val amazingField: String,
|
||||||
val tables: List<NestedComplexItem>
|
val tables: List<NestedComplexItem>
|
||||||
) {
|
)
|
||||||
fun testThing() {
|
|
||||||
println("hey mom 👋")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class NestedComplexItem(
|
data class NestedComplexItem(
|
||||||
val name: String,
|
val name: String,
|
||||||
@ -75,10 +69,9 @@ data class DefaultParameter(
|
|||||||
@KompendiumParam(ParamType.PATH) val c: Boolean
|
@KompendiumParam(ParamType.PATH) val c: Boolean
|
||||||
)
|
)
|
||||||
|
|
||||||
sealed class TestSealedClass(open val a: String)
|
|
||||||
|
|
||||||
data class SimpleTSC(val b: Int) : TestSealedClass("hey")
|
|
||||||
open class MediumTSC(override val a: String, val b: Int) : TestSealedClass(a)
|
|
||||||
data class WildTSC(val c: Boolean, val d: String, val e: Int) : MediumTSC(d, e)
|
|
||||||
|
|
||||||
data class ExceptionResponse(val message: String)
|
data class ExceptionResponse(val message: String)
|
||||||
|
|
||||||
|
data class OptionalParams(
|
||||||
|
@KompendiumParam(ParamType.QUERY) val required: String,
|
||||||
|
@KompendiumParam(ParamType.QUERY) val notRequired: String?
|
||||||
|
)
|
||||||
|
@ -0,0 +1,257 @@
|
|||||||
|
package org.leafygreens.kompendium.util
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude
|
||||||
|
import com.fasterxml.jackson.databind.SerializationFeature
|
||||||
|
import io.ktor.application.Application
|
||||||
|
import io.ktor.application.call
|
||||||
|
import io.ktor.application.install
|
||||||
|
import io.ktor.features.ContentNegotiation
|
||||||
|
import io.ktor.features.StatusPages
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.jackson.jackson
|
||||||
|
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
|
||||||
|
|
||||||
|
fun Application.configModule() {
|
||||||
|
install(ContentNegotiation) {
|
||||||
|
jackson {
|
||||||
|
enable(SerializationFeature.INDENT_OUTPUT)
|
||||||
|
setSerializationInclusion(JsonInclude.Include.NON_NULL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Application.statusPageModule() {
|
||||||
|
install(StatusPages) {
|
||||||
|
notarizedException<Exception, ExceptionResponse>(info = ResponseInfo(HttpStatusCode.BadRequest, "Bad Things Happened")) {
|
||||||
|
call.respond(HttpStatusCode.BadRequest, ExceptionResponse("Why you do dis?"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Application.statusPageMultiExceptions() {
|
||||||
|
install(StatusPages) {
|
||||||
|
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")) {
|
||||||
|
call.respond(HttpStatusCode.BadRequest, ExceptionResponse("Why you do dis?"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Application.notarizedGetWithNotarizedException() {
|
||||||
|
routing {
|
||||||
|
route("/test") {
|
||||||
|
notarizedGet(TestResponseInfo.testGetWithException) {
|
||||||
|
error("something terrible has happened!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Application.notarizedGetWithMultipleThrowables() {
|
||||||
|
routing {
|
||||||
|
route("/test") {
|
||||||
|
notarizedGet(TestResponseInfo.testGetWithMultipleExceptions) {
|
||||||
|
error("something terrible has happened!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Application.notarizedGetModule() {
|
||||||
|
routing {
|
||||||
|
route("/test") {
|
||||||
|
notarizedGet(TestResponseInfo.testGetInfo) {
|
||||||
|
call.respondText { "hey dude ‼️ congratz on the get request" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Application.notarizedPostModule() {
|
||||||
|
routing {
|
||||||
|
route("/test") {
|
||||||
|
notarizedPost(TestResponseInfo.testPostInfo) {
|
||||||
|
call.respondText { "hey dude ✌️ congratz on the post request" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Application.notarizedDeleteModule() {
|
||||||
|
routing {
|
||||||
|
route("/test") {
|
||||||
|
notarizedDelete(TestResponseInfo.testDeleteInfo) {
|
||||||
|
call.respond(HttpStatusCode.NoContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Application.notarizedPutModule() {
|
||||||
|
routing {
|
||||||
|
route("/test") {
|
||||||
|
notarizedPut(TestResponseInfo.testPutInfoAlso) {
|
||||||
|
call.respondText { "hey pal 🌝 whatcha doin' here?" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Application.pathParsingTestModule() {
|
||||||
|
routing {
|
||||||
|
route("/this") {
|
||||||
|
route("/is") {
|
||||||
|
route("/a") {
|
||||||
|
route("/complex") {
|
||||||
|
route("path") {
|
||||||
|
route("with/an/{id}") {
|
||||||
|
notarizedGet(TestResponseInfo.testGetInfo) {
|
||||||
|
call.respondText { "Aww you followed this whole route 🥺" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Application.rootModule() {
|
||||||
|
routing {
|
||||||
|
route("/") {
|
||||||
|
notarizedGet(TestResponseInfo.testGetInfo) {
|
||||||
|
call.respondText { "☎️🏠🌲" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Application.nestedUnderRootModule() {
|
||||||
|
routing {
|
||||||
|
route("/") {
|
||||||
|
route("/testerino") {
|
||||||
|
notarizedGet(TestResponseInfo.testGetInfo) {
|
||||||
|
call.respondText { "🤔🔥" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Application.trailingSlash() {
|
||||||
|
routing {
|
||||||
|
route("/test") {
|
||||||
|
route("/") {
|
||||||
|
notarizedGet(TestResponseInfo.testGetInfo) {
|
||||||
|
call.respondText { "🙀👾" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Application.returnsList() {
|
||||||
|
routing {
|
||||||
|
route("/test") {
|
||||||
|
notarizedGet(TestResponseInfo.testGetInfoAgain) {
|
||||||
|
call.respondText { "hey dude ur doing amazing work!" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Application.complexType() {
|
||||||
|
routing {
|
||||||
|
route("/test") {
|
||||||
|
notarizedPut(TestResponseInfo.testPutInfo) {
|
||||||
|
call.respondText { "heya" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Application.primitives() {
|
||||||
|
routing {
|
||||||
|
route("/test") {
|
||||||
|
notarizedPut(TestResponseInfo.testPutInfoAgain) {
|
||||||
|
call.respondText { "heya" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Application.emptyGet() {
|
||||||
|
routing {
|
||||||
|
route("/test/empty") {
|
||||||
|
notarizedGet(TestResponseInfo.trulyEmptyTestGetInfo) {
|
||||||
|
call.respond(HttpStatusCode.OK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Application.withExamples() {
|
||||||
|
routing {
|
||||||
|
route("/test/examples") {
|
||||||
|
notarizedPost(
|
||||||
|
info = MethodInfo.PostInfo<Unit, TestRequest, TestResponse>(
|
||||||
|
summary = "Example Parameters",
|
||||||
|
description = "A test for setting parameter examples",
|
||||||
|
requestInfo = RequestInfo(
|
||||||
|
description = "Test",
|
||||||
|
examples = mapOf(
|
||||||
|
"one" to TestRequest(fieldName = TestNested(nesty = "hey"), b = 4.0, aaa = emptyList()),
|
||||||
|
"two" to TestRequest(fieldName = TestNested(nesty = "hello"), b = 3.8, aaa = listOf(31324234))
|
||||||
|
)
|
||||||
|
),
|
||||||
|
responseInfo = ResponseInfo(
|
||||||
|
status = HttpStatusCode.Created,
|
||||||
|
description = "nice",
|
||||||
|
examples = mapOf("test" to TestResponse(c = "spud"))
|
||||||
|
),
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
call.respond(HttpStatusCode.OK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Application.withDefaultParameter() {
|
||||||
|
routing {
|
||||||
|
route("/test") {
|
||||||
|
notarizedGet(
|
||||||
|
info = MethodInfo.GetInfo<DefaultParameter, TestResponse>(
|
||||||
|
summary = "Testing Default Params",
|
||||||
|
description = "Should have a default parameter value"
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
call.respond(TestResponse("hey"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Application.nonRequiredParamsGet() {
|
||||||
|
routing {
|
||||||
|
route("/test/optional") {
|
||||||
|
notarizedGet(TestResponseInfo.emptyTestGetInfo) {
|
||||||
|
call.respond(HttpStatusCode.OK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,71 @@
|
|||||||
|
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")
|
||||||
|
}
|
@ -38,7 +38,6 @@ import org.leafygreens.kompendium.playground.PlaygroundToC.testSinglePutInfo
|
|||||||
import org.leafygreens.kompendium.routes.openApi
|
import org.leafygreens.kompendium.routes.openApi
|
||||||
import org.leafygreens.kompendium.routes.redoc
|
import org.leafygreens.kompendium.routes.redoc
|
||||||
import org.leafygreens.kompendium.swagger.swaggerUI
|
import org.leafygreens.kompendium.swagger.swaggerUI
|
||||||
import org.leafygreens.kompendium.util.KompendiumHttpCodes
|
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
embeddedServer(
|
embeddedServer(
|
||||||
@ -74,7 +73,7 @@ fun Application.configModule() {
|
|||||||
install(StatusPages) {
|
install(StatusPages) {
|
||||||
notarizedException<Exception, ExceptionResponse>(
|
notarizedException<Exception, ExceptionResponse>(
|
||||||
info = ResponseInfo(
|
info = ResponseInfo(
|
||||||
KompendiumHttpCodes.BAD_REQUEST,
|
HttpStatusCode.BadRequest,
|
||||||
"Bad Things Happened",
|
"Bad Things Happened",
|
||||||
examples = mapOf("example" to ExceptionResponse("hey bad things happened sorry"))
|
examples = mapOf("example" to ExceptionResponse("hey bad things happened sorry"))
|
||||||
)
|
)
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
package org.leafygreens.kompendium.playground
|
package org.leafygreens.kompendium.playground
|
||||||
|
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
import org.leafygreens.kompendium.models.meta.MethodInfo
|
import org.leafygreens.kompendium.models.meta.MethodInfo
|
||||||
import org.leafygreens.kompendium.models.meta.RequestInfo
|
import org.leafygreens.kompendium.models.meta.RequestInfo
|
||||||
import org.leafygreens.kompendium.models.meta.ResponseInfo
|
import org.leafygreens.kompendium.models.meta.ResponseInfo
|
||||||
import org.leafygreens.kompendium.util.KompendiumHttpCodes
|
|
||||||
|
|
||||||
object PlaygroundToC {
|
object PlaygroundToC {
|
||||||
val testGetWithExamples = MethodInfo.GetInfo<Unit, ExampleResponse>(
|
val testGetWithExamples = MethodInfo.GetInfo<Unit, ExampleResponse>(
|
||||||
summary = "Example Parameters",
|
summary = "Example Parameters",
|
||||||
description = "A test for setting parameter examples",
|
description = "A test for setting parameter examples",
|
||||||
responseInfo = ResponseInfo(
|
responseInfo = ResponseInfo(
|
||||||
status = 200,
|
status = HttpStatusCode.OK,
|
||||||
description = "nice",
|
description = "nice",
|
||||||
examples = mapOf("test" to ExampleResponse(c = "spud"))
|
examples = mapOf("test" to ExampleResponse(c = "spud"))
|
||||||
),
|
),
|
||||||
@ -27,7 +27,7 @@ object PlaygroundToC {
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
responseInfo = ResponseInfo(
|
responseInfo = ResponseInfo(
|
||||||
status = KompendiumHttpCodes.CREATED,
|
status = HttpStatusCode.Created,
|
||||||
description = "Congratz you hit da endpoint",
|
description = "Congratz you hit da endpoint",
|
||||||
examples = mapOf(
|
examples = mapOf(
|
||||||
"Expect This" to ExampleResponse(c = "Hi"),
|
"Expect This" to ExampleResponse(c = "Hi"),
|
||||||
@ -42,7 +42,7 @@ object PlaygroundToC {
|
|||||||
description = "Test for the getting",
|
description = "Test for the getting",
|
||||||
tags = setOf("test", "sample", "get"),
|
tags = setOf("test", "sample", "get"),
|
||||||
responseInfo = ResponseInfo(
|
responseInfo = ResponseInfo(
|
||||||
status = KompendiumHttpCodes.OK,
|
status = HttpStatusCode.OK,
|
||||||
description = "Returns sample info"
|
description = "Returns sample info"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -51,7 +51,7 @@ object PlaygroundToC {
|
|||||||
description = "testing more",
|
description = "testing more",
|
||||||
tags = setOf("anotherTest", "sample"),
|
tags = setOf("anotherTest", "sample"),
|
||||||
responseInfo = ResponseInfo(
|
responseInfo = ResponseInfo(
|
||||||
status = KompendiumHttpCodes.OK,
|
status = HttpStatusCode.OK,
|
||||||
description = "Returns a different sample"
|
description = "Returns a different sample"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -66,7 +66,7 @@ object PlaygroundToC {
|
|||||||
description = "Simple request body"
|
description = "Simple request body"
|
||||||
),
|
),
|
||||||
responseInfo = ResponseInfo(
|
responseInfo = ResponseInfo(
|
||||||
status = KompendiumHttpCodes.CREATED,
|
status = HttpStatusCode.Created,
|
||||||
description = "Worlds most complex response"
|
description = "Worlds most complex response"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -77,7 +77,7 @@ object PlaygroundToC {
|
|||||||
description = "Info needed to perform this put request"
|
description = "Info needed to perform this put request"
|
||||||
),
|
),
|
||||||
responseInfo = ResponseInfo(
|
responseInfo = ResponseInfo(
|
||||||
status = KompendiumHttpCodes.CREATED,
|
status = HttpStatusCode.Created,
|
||||||
description = "What we give you when u do the puts"
|
description = "What we give you when u do the puts"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -85,7 +85,7 @@ object PlaygroundToC {
|
|||||||
summary = "Test delete endpoint",
|
summary = "Test delete endpoint",
|
||||||
description = "testing my deletes",
|
description = "testing my deletes",
|
||||||
responseInfo = ResponseInfo(
|
responseInfo = ResponseInfo(
|
||||||
status = KompendiumHttpCodes.NO_CONTENT,
|
status = HttpStatusCode.NoContent,
|
||||||
description = "Signifies that your item was deleted successfully",
|
description = "Signifies that your item was deleted successfully",
|
||||||
mediaTypes = emptyList()
|
mediaTypes = emptyList()
|
||||||
)
|
)
|
||||||
@ -95,7 +95,7 @@ object PlaygroundToC {
|
|||||||
description = "testing more",
|
description = "testing more",
|
||||||
tags = setOf("anotherTest", "sample"),
|
tags = setOf("anotherTest", "sample"),
|
||||||
responseInfo = ResponseInfo(
|
responseInfo = ResponseInfo(
|
||||||
status = KompendiumHttpCodes.OK,
|
status = HttpStatusCode.OK,
|
||||||
description = "Returns a different sample"
|
description = "Returns a different sample"
|
||||||
),
|
),
|
||||||
securitySchemes = setOf("basic")
|
securitySchemes = setOf("basic")
|
||||||
|
Reference in New Issue
Block a user