Skip to content

Commit 1b1752a

Browse files
authored
Merge pull request #66 from Javuto2/master
Some documentation and renamed template types
2 parents cb716fd + 8100b1e commit 1b1752a

File tree

12 files changed

+349
-164
lines changed

12 files changed

+349
-164
lines changed

src/main/kotlin/com/papsign/ktor/openapigen/APITag.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,20 @@ import com.papsign.ktor.openapigen.model.info.ExternalDocumentationModel
44
import com.papsign.ktor.openapigen.model.info.TagModel
55

66

7+
/**
8+
* This interface is used to define tags to classify endpoints.
9+
* It needs to be implemented using an enum so that the processor properly detects equality.
10+
*
11+
* This is assigned to a service using [com.papsign.ktor.openapigen.route.tag].
12+
*
13+
* Implementation example:
14+
*
15+
* enum class Tags(override val description: String) : APITag {
16+
* EXAMPLE("Wow this is a tag?!")
17+
* }
18+
*
19+
* @see [com.papsign.ktor.openapigen.route.tag]
20+
*/
721
interface APITag {
822
val name: String
923
val description: String

src/main/kotlin/com/papsign/ktor/openapigen/interop/StatusPages.kt

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,61 @@ import io.ktor.features.StatusPages
99
import io.ktor.http.HttpStatusCode
1010
import io.ktor.response.respond
1111

12-
12+
/**
13+
* Wraps [StatusPages.Configuration] to enable OpenAPI configuration for exception handling.
14+
* ```
15+
* val api = install(OpenAPIGen) { ... }
16+
* ...
17+
* // StatusPage interop, can also define exceptions per-route
18+
* install(StatusPages) {
19+
* withAPI(api) {
20+
* exception<JsonMappingException, Error>(HttpStatusCode.BadRequest) {
21+
* it.printStackTrace()
22+
* Error("mapping.json", it.localizedMessage)
23+
* }
24+
* exception<ProperException, Error>(HttpStatusCode.BadRequest) {
25+
* it.printStackTrace()
26+
* Error(it.id, it.localizedMessage)
27+
* }
28+
* }
29+
* }
30+
* ```
31+
*
32+
* @param api the installed instance of [OpenAPIGen] in the [io.ktor.application.Application]
33+
* @param cfg the block that loads the configuration, see [OpenAPIGenStatusPagesInterop]
34+
*/
1335
inline fun StatusPages.Configuration.withAPI(api: OpenAPIGen, crossinline cfg: OpenAPIGenStatusPagesInterop.() -> Unit = {}) {
1436
OpenAPIGenStatusPagesInterop(api, this).cfg()
1537
}
1638

39+
/**
40+
* Wrapper for Status pages that handles exceptions and generates documentation in OpenAPI.
41+
* This is useful for default error pages.
42+
*/
1743
class OpenAPIGenStatusPagesInterop(val api: OpenAPIGen, val statusCfg: StatusPages.Configuration) {
1844

19-
inline fun <reified EX : Throwable> exception(status: HttpStatusCode) {
20-
val ex = apiException<EX>(status)
45+
/**
46+
* Registers a handler for exception type [TThrowable] and returns a [status] page
47+
*/
48+
inline fun <reified TThrowable : Throwable> exception(status: HttpStatusCode) {
49+
val ex = apiException<TThrowable>(status)
2150
api.globalModuleProvider.registerModule(ThrowsInfo(listOf(ex)))
22-
statusCfg.exception<EX> {
51+
statusCfg.exception<TThrowable> {
2352
call.respond(status)
2453
}
2554
}
2655

27-
inline fun <reified EX : Throwable, reified B> exception(status: HttpStatusCode, example: B? = null, noinline gen: (EX) -> B) {
56+
/**
57+
* Registers a handler for exception type [TThrowable] and returns a [status] page that includes a response body
58+
* of type [TResponse].
59+
*
60+
* @param example An example of the response body
61+
* @param gen handler for [TThrowable] that should return an instance of [TResponse]
62+
*/
63+
inline fun <reified TThrowable : Throwable, reified TResponse> exception(status: HttpStatusCode, example: TResponse? = null, noinline gen: (TThrowable) -> TResponse) {
2864
val ex = apiException(status, example, gen)
2965
api.globalModuleProvider.registerModule(ThrowsInfo(listOf(ex)))
30-
statusCfg.exception<EX> { t ->
66+
statusCfg.exception<TThrowable> { t ->
3167
val ret = gen(t)
3268
if (ret != null) {
3369
call.respond(status, ret)

src/main/kotlin/com/papsign/ktor/openapigen/model/security/FlowsModel.kt

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,19 @@ import com.papsign.ktor.openapigen.model.Described
66
import java.util.*
77
import kotlin.reflect.KProperty
88

9-
class FlowsModel<T> : MutableMap<String, FlowsModel.FlowModel<T>> by HashMap<String, FlowModel<T>>()
10-
where T : Enum<T>, T : Described {
9+
class FlowsModel<TScope> : MutableMap<String, FlowsModel.FlowModel<TScope>> by HashMap<String, FlowModel<TScope>>()
10+
where TScope : Enum<TScope>, TScope : Described {
1111

12-
private var implicit: FlowModel<T>? by this
13-
private var password: FlowModel<T>? by this
14-
private var clientCredentials: FlowModel<T>? by this
15-
private var authorizationCode: FlowModel<T>? by this
12+
private var implicit: FlowModel<TScope>? by this
13+
private var password: FlowModel<TScope>? by this
14+
private var clientCredentials: FlowModel<TScope>? by this
15+
private var authorizationCode: FlowModel<TScope>? by this
1616

1717
fun implicit(
18-
scopes: Iterable<T>,
18+
scopes: Iterable<TScope>,
1919
authorizationUrl: String,
2020
refreshUrl: String? = null
21-
): FlowsModel<T> {
21+
): FlowsModel<TScope> {
2222
implicit =
2323
FlowModel.implicit(
2424
scopes,
@@ -29,8 +29,8 @@ class FlowsModel<T> : MutableMap<String, FlowsModel.FlowModel<T>> by HashMap<Str
2929
}
3030

3131
fun password(
32-
scopes: Iterable<T>, tokenUrl: String, refreshUrl: String? = null
33-
): FlowsModel<T> {
32+
scopes: Iterable<TScope>, tokenUrl: String, refreshUrl: String? = null
33+
): FlowsModel<TScope> {
3434
password =
3535
FlowModel.password(
3636
scopes,
@@ -41,10 +41,10 @@ class FlowsModel<T> : MutableMap<String, FlowsModel.FlowModel<T>> by HashMap<Str
4141
}
4242

4343
fun clientCredentials(
44-
scopes: Iterable<T>,
44+
scopes: Iterable<TScope>,
4545
tokenUrl: String,
4646
refreshUrl: String? = null
47-
): FlowsModel<T> {
47+
): FlowsModel<TScope> {
4848
clientCredentials =
4949
FlowModel.clientCredentials(
5050
scopes,
@@ -55,11 +55,11 @@ class FlowsModel<T> : MutableMap<String, FlowsModel.FlowModel<T>> by HashMap<Str
5555
}
5656

5757
fun authorizationCode(
58-
scopes: Iterable<T>,
58+
scopes: Iterable<TScope>,
5959
authorizationUrl: String,
6060
tokenUrl: String,
6161
refreshUrl: String? = null
62-
): FlowsModel<T> {
62+
): FlowsModel<TScope> {
6363
authorizationCode =
6464
FlowModel.authorizationCode(
6565
scopes,
@@ -70,14 +70,14 @@ class FlowsModel<T> : MutableMap<String, FlowsModel.FlowModel<T>> by HashMap<Str
7070
return this
7171
}
7272

73-
private operator fun setValue(any: Any, property: KProperty<*>, any1: FlowModel<T>?) {
73+
private operator fun setValue(any: Any, property: KProperty<*>, any1: FlowModel<TScope>?) {
7474
if (any1 == null)
7575
this.remove(property.name)
7676
else
7777
this[property.name] = any1
7878
}
7979

80-
private operator fun getValue(any: Any, property: KProperty<*>): FlowModel<T>? {
80+
private operator fun getValue(any: Any, property: KProperty<*>): FlowModel<TScope>? {
8181
return this[property.name]
8282
}
8383

src/main/kotlin/com/papsign/ktor/openapigen/model/security/SecuritySchemeModel.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ package com.papsign.ktor.openapigen.model.security
33
import com.papsign.ktor.openapigen.model.DataModel
44
import com.papsign.ktor.openapigen.model.Described
55

6-
data class SecuritySchemeModel<T> constructor(
6+
data class SecuritySchemeModel<TScope> constructor(
77
val type: SecuritySchemeType,
88
val name: String,
99
val `in`: APIKeyLocation? = null,
1010
val scheme: HttpSecurityScheme? = null,
1111
val bearerFormat: String? = null,
12-
val flows: FlowsModel<T>? = null,
12+
val flows: FlowsModel<TScope>? = null,
1313
val openIdConnectUrl: String? = null
14-
): DataModel where T : Enum<T>, T : Described
14+
): DataModel where TScope : Enum<TScope>, TScope : Described

src/main/kotlin/com/papsign/ktor/openapigen/modules/providers/AuthProvider.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ import com.papsign.ktor.openapigen.route.path.normal.NormalOpenAPIRoute
1010
import io.ktor.application.ApplicationCall
1111
import io.ktor.util.pipeline.PipelineContext
1212

13-
interface AuthProvider<A>: OpenAPIModule, DependentModule {
14-
suspend fun getAuth(pipeline: PipelineContext<Unit, ApplicationCall>): A
15-
fun apply(route: NormalOpenAPIRoute): OpenAPIAuthenticatedRoute<A>
13+
interface AuthProvider<TAuth>: OpenAPIModule, DependentModule {
14+
suspend fun getAuth(pipeline: PipelineContext<Unit, ApplicationCall>): TAuth
15+
fun apply(route: NormalOpenAPIRoute): OpenAPIAuthenticatedRoute<TAuth>
1616
val security: Iterable<Iterable<Security<*>>>
1717
override val handlers: Collection<OpenAPIModule>
1818
get() = listOf(AuthHandler)
1919

20-
data class Security<T>(val scheme: SecuritySchemeModel<T>, val requirements: List<T>) where T: Enum<T>, T: Described
20+
data class Security<TScope>(val scheme: SecuritySchemeModel<TScope>, val requirements: List<TScope>) where TScope: Enum<TScope>, TScope: Described
2121
}

src/main/kotlin/com/papsign/ktor/openapigen/route/Functions.kt

Lines changed: 64 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -23,57 +23,101 @@ fun <T : OpenAPIRoute<T>> T.route(path: String): T {
2323
}
2424
}
2525

26+
/**
27+
* Creates a new route matching the specified [path]
28+
*/
2629
@ContextDsl
27-
inline fun <T : OpenAPIRoute<T>> T.route(path: String, crossinline fn: T.() -> Unit) {
30+
inline fun <TRoute : OpenAPIRoute<TRoute>> TRoute.route(path: String, crossinline fn: TRoute.() -> Unit) {
2831
route(path).fn()
2932
}
3033

31-
fun <T : OpenAPIRoute<T>> T.method(method: HttpMethod): T {
34+
fun <TRoute : OpenAPIRoute<TRoute>> TRoute.method(method: HttpMethod): TRoute {
3235
return child(ktorRoute.createChild(HttpMethodRouteSelector(method))).apply {
3336
provider.registerModule(HttpMethodProviderModule(method))
3437
}
3538
}
3639

40+
/**
41+
* Creates a new route matching the specified [method]
42+
*/
3743
@ContextDsl
38-
inline fun <T : OpenAPIRoute<T>> T.method(method: HttpMethod, crossinline fn: T.() -> Unit) {
44+
inline fun <TRoute : OpenAPIRoute<TRoute>> TRoute.method(method: HttpMethod, crossinline fn: TRoute.() -> Unit) {
3945
method(method).fn()
4046
}
4147

42-
fun <T : OpenAPIRoute<T>> T.provider(vararg content: ContentTypeProvider): T {
48+
fun <TRoute : OpenAPIRoute<TRoute>> TRoute.provider(vararg content: ContentTypeProvider): TRoute {
4349
return child().apply {
4450
content.forEach {
4551
provider.registerModule(it)
4652
}
4753
}
4854
}
4955

56+
/**
57+
* Creates a new route matching the specified [content]
58+
*/
5059
@ContextDsl
51-
inline fun <T : OpenAPIRoute<T>> T.provider(vararg content: ContentTypeProvider, crossinline fn: T.() -> Unit) {
60+
inline fun <TRoute : OpenAPIRoute<TRoute>> TRoute.provider(vararg content: ContentTypeProvider, crossinline fn: TRoute.() -> Unit) {
5261
provider(*content).fn()
5362
}
5463

55-
56-
fun <T : OpenAPIRoute<T>> T.tag(tag: APITag): T {
64+
/**
65+
* Applies a tag to all children of this route.
66+
* Parameter [tag] should be an enum that inherits from [APITag], check [APITag] description for
67+
* an explanation.
68+
*
69+
* @param tag the tag to apply
70+
* @return the same route that received the call to chain multiple calls
71+
*/
72+
fun <TRoute : OpenAPIRoute<TRoute>> TRoute.tag(tag: APITag): TRoute {
5773
return child().apply {
5874
provider.registerModule(TagModule(listOf(tag)))
5975
}
6076
}
6177

6278

79+
/**
80+
* This method assigns an OpenAPI [tag] too all child routes defined inside [fn].
81+
* Parameter [tag] should be an enum that inherits from [APITag], check [APITag] description for an
82+
* explanation.
83+
*
84+
* Usage example:
85+
*
86+
* // Defined tags
87+
* enum class Tags(override val description: String) : APITag {
88+
* EXAMPLE("Wow this is a tag?!")
89+
* }
90+
*
91+
* ...
92+
* apiRouting {
93+
* route("examples") {
94+
* tag(Tags.EXAMPLE) { // <-- Applies the tag here
95+
* route("getTextData").get<StringParam, StringResponse> { params ->
96+
* respond(StringResponse(params.a))
97+
* }
98+
* // Multiple routes can be specified here
99+
* }
100+
* }
101+
* }
102+
* ...
103+
*
104+
* @param tag the tag to apply
105+
* @param fn the block where the sub routes are defined
106+
*/
63107
@ContextDsl
64-
inline fun <T : OpenAPIRoute<T>> T.tag(tag: APITag, crossinline fn: T.() -> Unit) {
108+
inline fun <TRoute : OpenAPIRoute<TRoute>> TRoute.tag(tag: APITag, crossinline fn: TRoute.() -> Unit) {
65109
tag(tag).fn()
66110
}
67111

68-
inline fun <reified P : Any, reified R : Any, reified B : Any, T : OpenAPIRoute<T>> T.preHandle(
69-
exampleResponse: R? = null,
70-
exampleRequest: B? = null,
71-
noinline handle: T.() -> Unit
112+
inline fun <reified TParams : Any, reified TResponse : Any, reified TRequest : Any, TRoute : OpenAPIRoute<TRoute>> TRoute.preHandle(
113+
exampleResponse: TResponse? = null,
114+
exampleRequest: TRequest? = null,
115+
noinline handle: TRoute.() -> Unit
72116
) {
73-
preHandle<P, R, B, T>(
74-
typeOf<P>(),
75-
typeOf<R>(),
76-
typeOf<B>(),
117+
preHandle<TParams, TResponse, TRequest, TRoute>(
118+
typeOf<TParams>(),
119+
typeOf<TResponse>(),
120+
typeOf<TRequest>(),
77121
exampleResponse,
78122
exampleRequest,
79123
handle
@@ -82,13 +126,13 @@ inline fun <reified P : Any, reified R : Any, reified B : Any, T : OpenAPIRoute<
82126

83127
// hide this function from public api as it can be "misused" easily but make it accessible to inlined functions from this package
84128
@PublishedApi
85-
internal fun <P : Any, R : Any, B : Any, T : OpenAPIRoute<T>> T.preHandle(
129+
internal fun <TParams : Any, TResponse : Any, TRequest : Any, TRoute : OpenAPIRoute<TRoute>> TRoute.preHandle(
86130
pType: KType,
87131
rType: KType,
88132
bType: KType,
89-
exampleResponse: R? = null,
90-
exampleRequest: B? = null,
91-
handle: T.() -> Unit
133+
exampleResponse: TResponse? = null,
134+
exampleRequest: TRequest? = null,
135+
handle: TRoute.() -> Unit
92136
) {
93137
val path = pType.jvmErasure.findAnnotation<Path>()
94138
val new = if (path != null) child(ktorRoute.createRouteFromPath(path.path)) else child()

src/main/kotlin/com/papsign/ktor/openapigen/route/Info.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import com.papsign.ktor.openapigen.modules.ModuleProvider
66
import com.papsign.ktor.openapigen.modules.RouteOpenAPIModule
77
import com.papsign.ktor.openapigen.modules.openapi.OperationModule
88

9+
/**
10+
* Adds a summary and description for the endpoint being configured
11+
*/
912
fun info(summary: String? = null, description: String? = null) = EndpointInfo(summary, description)
1013

1114
data class EndpointInfo(

src/main/kotlin/com/papsign/ktor/openapigen/route/RouteConfig.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ import io.ktor.routing.Routing
88
import io.ktor.routing.routing
99
import io.ktor.util.pipeline.ContextDsl
1010

11+
/**
12+
* Wrapper for [io.ktor.routing.routing] to create the endpoints while configuring OpenAPI
13+
* documentation at the same time.
14+
*/
1115
@ContextDsl
1216
fun Application.apiRouting(config: NormalOpenAPIRoute.() -> Unit) {
1317
routing {
@@ -18,6 +22,12 @@ fun Application.apiRouting(config: NormalOpenAPIRoute.() -> Unit) {
1822
}
1923
}
2024

25+
/**
26+
* Wrapper for [io.ktor.routing.routing] to create the endpoints while configuring OpenAPI
27+
* documentation at the same time.
28+
*
29+
* @param config
30+
*/
2131
@ContextDsl
2232
fun Routing.apiRouting(config: NormalOpenAPIRoute.() -> Unit) {
2333
NormalOpenAPIRoute(

0 commit comments

Comments
 (0)