Skip to content

Commit 2d2c4b9

Browse files
committed
mvp: improved support for typesafe-routing
1 parent 66b804f commit 2d2c4b9

File tree

13 files changed

+309
-52
lines changed

13 files changed

+309
-52
lines changed

examples/build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ plugins {
99
}
1010

1111
repositories {
12+
mavenLocal() // todo: remove after releasing schema-kenerator 1.7.0
1213
mavenCentral()
1314
}
1415

@@ -17,13 +18,16 @@ dependencies {
1718
implementation(project(":ktor-swagger-ui"))
1819
implementation(project(":ktor-redoc"))
1920

21+
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.0")
22+
2023
val versionKtor: String by project
2124
implementation("io.ktor:ktor-server-netty-jvm:$versionKtor")
2225
implementation("io.ktor:ktor-server-content-negotiation:$versionKtor")
2326
implementation("io.ktor:ktor-serialization-jackson:$versionKtor")
2427
implementation("io.ktor:ktor-server-auth:$versionKtor")
2528
implementation("io.ktor:ktor-server-call-logging:$versionKtor")
2629
implementation("io.ktor:ktor-server-test-host:$versionKtor")
30+
implementation("io.ktor:ktor-server-resources:$versionKtor")
2731

2832
val versionSchemaKenerator: String by project
2933
implementation("io.github.smiley4:schema-kenerator-core:$versionSchemaKenerator")

examples/src/main/kotlin/io/github/smiley4/ktoropenapi/examples/Petstore.kt

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,21 @@ fun main() {
2727
*/
2828
private fun Application.myModule() {
2929

30+
data class Pet(
31+
val id: Long,
32+
val name: String,
33+
val tag: String
34+
)
35+
36+
data class NewPet(
37+
val name: String,
38+
val tag: String
39+
)
40+
41+
data class ErrorModel(
42+
val message: String
43+
)
44+
3045
install(OpenApi) {
3146
info {
3247
title = "Swagger Petstore"
@@ -247,19 +262,4 @@ private fun Application.myModule() {
247262

248263
}
249264

250-
}
251-
252-
private data class Pet(
253-
val id: Long,
254-
val name: String,
255-
val tag: String
256-
)
257-
258-
private data class NewPet(
259-
val name: String,
260-
val tag: String
261-
)
262-
263-
private data class ErrorModel(
264-
val message: String
265-
)
265+
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package io.github.smiley4.ktoropenapi.examples
2+
3+
import io.github.smiley4.ktoropenapi.OpenApi
4+
import io.github.smiley4.ktoropenapi.openApi
5+
import io.github.smiley4.ktoropenapi.resources.*
6+
import io.github.smiley4.ktorredoc.redoc
7+
import io.github.smiley4.ktorswaggerui.swaggerUI
8+
import io.github.smiley4.schemakenerator.serialization.processKotlinxSerialization
9+
import io.github.smiley4.schemakenerator.swagger.compileReferencingRoot
10+
import io.github.smiley4.schemakenerator.swagger.data.TitleType
11+
import io.github.smiley4.schemakenerator.swagger.generateSwaggerSchema
12+
import io.github.smiley4.schemakenerator.swagger.withTitle
13+
import io.ktor.http.HttpStatusCode
14+
import io.ktor.resources.Resource
15+
import io.ktor.server.application.Application
16+
import io.ktor.server.application.install
17+
import io.ktor.server.engine.embeddedServer
18+
import io.ktor.server.netty.Netty
19+
import io.ktor.server.resources.Resources
20+
import io.ktor.server.response.respond
21+
import io.ktor.server.routing.route
22+
import io.ktor.server.routing.routing
23+
import kotlinx.serialization.Serializable
24+
25+
fun main() {
26+
embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true)
27+
}
28+
29+
private fun Application.myModule() {
30+
31+
install(Resources)
32+
33+
install(OpenApi) {
34+
info {
35+
title = "Example API"
36+
description = "An example api to showcase basic swagger-ui functionality."
37+
}
38+
externalDocs {
39+
url = "https://github.com/SMILEY4/ktor-swagger-ui/wiki"
40+
description = "Sample external documentation"
41+
}
42+
server {
43+
url = "http://localhost:8080"
44+
description = "Development Server"
45+
}
46+
server {
47+
url = "https://www.example.com"
48+
description = "Production Server"
49+
}
50+
schemas {
51+
generator = { type ->
52+
type
53+
.processKotlinxSerialization()
54+
.generateSwaggerSchema()
55+
.withTitle(TitleType.SIMPLE)
56+
.compileReferencingRoot()
57+
}
58+
}
59+
}
60+
61+
routing {
62+
63+
route("api.json") {
64+
openApi()
65+
}
66+
route("swagger") {
67+
swaggerUI("/api.json")
68+
}
69+
route("redoc") {
70+
redoc("/api.json")
71+
}
72+
73+
get<PetsRoute.All>({
74+
description = "custom description"
75+
}) { request ->
76+
println("..${request.tags}, ${request.limit}")
77+
call.respond(HttpStatusCode.NotImplemented, Unit)
78+
}
79+
80+
get<PetsRoute.Id> { request ->
81+
println("..${request.id}")
82+
call.respond(HttpStatusCode.NotImplemented, Unit)
83+
}
84+
85+
delete<PetsRoute.Id.Delete> { request ->
86+
println("..${request.parent.id}")
87+
call.respond(HttpStatusCode.NotImplemented, Unit)
88+
}
89+
90+
post<PetsRoute.New> { request ->
91+
println("..${request.pet}")
92+
call.respond(HttpStatusCode.NotImplemented, Unit)
93+
}
94+
95+
}
96+
97+
}
98+
99+
@Resource("/pets")
100+
class PetsRoute {
101+
102+
@Resource("/")
103+
class All(val parent: PetsRoute = PetsRoute(), val tags: List<String> = emptyList(), val limit: Int = 100)
104+
105+
@Resource("{id}")
106+
class Id(val parent: PetsRoute = PetsRoute(), val id: Long) {
107+
108+
@Resource("/")
109+
class Delete(val parent: Id)
110+
111+
}
112+
113+
@Resource("/")
114+
class New(val parent: PetsRoute = PetsRoute(), val pet: NewPet)
115+
116+
117+
}
118+
119+
@Serializable
120+
data class Pet(
121+
val id: Long,
122+
val name: String,
123+
val tag: String
124+
)
125+
126+
@Serializable
127+
data class NewPet(
128+
val name: String,
129+
val tag: String
130+
)
131+
132+
@Serializable
133+
data class ErrorModel(
134+
val message: String
135+
)

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ projectDeveloperUrl=https://github.com/SMILEY4
1616
versionKtor=3.0.0
1717
versionSwaggerUI=5.17.11
1818
versionSwaggerParser=2.1.24
19-
versionSchemaKenerator=1.6.3
19+
versionSchemaKenerator=1.7.0
2020
versionKotlinLogging=7.0.0
2121
versionKotest=5.8.0
2222
versionKotlinTest=2.0.21

ktor-openapi/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ plugins {
1919

2020
repositories {
2121
mavenCentral()
22+
mavenLocal() // todo: remove after releasing schema-kenerator 1.7.0
2223
}
2324

2425
dependencies {
@@ -43,6 +44,7 @@ dependencies {
4344
val versionSchemaKenerator: String by project
4445
implementation("io.github.smiley4:schema-kenerator-core:$versionSchemaKenerator")
4546
implementation("io.github.smiley4:schema-kenerator-reflection:$versionSchemaKenerator")
47+
implementation("io.github.smiley4:schema-kenerator-serialization:$versionSchemaKenerator")
4648
implementation("io.github.smiley4:schema-kenerator-swagger:$versionSchemaKenerator")
4749

4850
val versionKotlinLogging: String by project

ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/OpenApiPlugin.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import io.github.smiley4.ktoropenapi.config.OpenApiPluginConfig
99
import io.ktor.http.ContentType
1010
import io.ktor.http.HttpStatusCode
1111
import io.ktor.server.application.Application
12+
import io.ktor.server.application.ApplicationPlugin
1213
import io.ktor.server.application.ApplicationStarted
1314
import io.ktor.server.application.createApplicationPlugin
1415
import io.ktor.server.application.hooks.MonitoringEvent
@@ -20,7 +21,7 @@ import io.ktor.server.routing.get
2021

2122
private val logger = KotlinLogging.logger {}
2223

23-
val OpenApi = createApplicationPlugin(name = "OpenApi", createConfiguration = ::OpenApiPluginConfig) {
24+
val OpenApi: ApplicationPlugin<OpenApiPluginConfig> = createApplicationPlugin(name = "OpenApi", createConfiguration = ::OpenApiPluginConfig) {
2425
OpenApiPlugin.config = pluginConfig.build(OpenApiPluginData.DEFAULT, getRootPath(application))
2526
on(MonitoringEvent(ApplicationStarted)) { application ->
2627
try {

ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/builder/route/RouteCollector.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package io.github.smiley4.ktoropenapi.builder.route
22

3-
import io.github.smiley4.ktoropenapi.data.OpenApiPluginData
4-
import io.github.smiley4.ktoropenapi.config.RouteConfig
53
import io.github.smiley4.ktoropenapi.DocumentedRouteSelector
4+
import io.github.smiley4.ktoropenapi.config.RouteConfig
5+
import io.github.smiley4.ktoropenapi.data.OpenApiPluginData
66
import io.ktor.http.HttpMethod
77
import io.ktor.server.auth.AuthenticationRouteSelector
88
import io.ktor.server.routing.ConstantParameterRouteSelector
@@ -49,6 +49,13 @@ internal class RouteCollector {
4949
.toList()
5050
}
5151

52+
private fun unroll(route: RoutingNode): List<Pair<RoutingNode, RouteSelector>> {
53+
return if (route.parent != null) {
54+
unroll(route.parent!!) + listOf(route to route.selector)
55+
} else {
56+
emptyList()
57+
}
58+
}
5259

5360
private fun getDocumentation(route: RoutingNode, base: RouteConfig): RouteConfig {
5461
var documentation = base

ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/builder/schema/SchemaContextImpl.kt

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,19 @@ import io.github.smiley4.ktoropenapi.config.ArrayTypeDescriptor
66
import io.github.smiley4.ktoropenapi.config.EmptyTypeDescriptor
77
import io.github.smiley4.ktoropenapi.config.KTypeDescriptor
88
import io.github.smiley4.ktoropenapi.config.RefTypeDescriptor
9+
import io.github.smiley4.ktoropenapi.config.SerialTypeDescriptor
910
import io.github.smiley4.ktoropenapi.config.SwaggerTypeDescriptor
1011
import io.github.smiley4.ktoropenapi.config.TypeDescriptor
11-
import io.github.smiley4.ktoropenapi.data.*
12+
import io.github.smiley4.ktoropenapi.data.MultipartBodyData
13+
import io.github.smiley4.ktoropenapi.data.SchemaConfigData
14+
import io.github.smiley4.ktoropenapi.data.SimpleBodyData
15+
import io.github.smiley4.schemakenerator.core.data.KTypeInput
1216
import io.github.smiley4.schemakenerator.core.data.WildcardTypeData
17+
import io.github.smiley4.schemakenerator.serialization.SerialDescriptorInput
1318
import io.github.smiley4.schemakenerator.swagger.data.CompiledSwaggerSchema
1419
import io.github.smiley4.schemakenerator.swagger.steps.SwaggerSchemaUtils
1520
import io.swagger.v3.oas.models.media.Schema
21+
import kotlinx.serialization.descriptors.SerialDescriptor
1622
import kotlin.reflect.KType
1723

1824
internal class SchemaContextImpl(private val schemaConfig: SchemaConfigData) : SchemaContext {
@@ -56,6 +62,10 @@ internal class SchemaContextImpl(private val schemaConfig: SchemaConfigData) : S
5662
generateSchema(typeDescriptor.type)
5763
}
5864
}
65+
is SerialTypeDescriptor -> {
66+
// todo: support schemaConfig.overwrite
67+
generateSchema(typeDescriptor.descriptor)
68+
}
5969
is SwaggerTypeDescriptor -> {
6070
CompiledSwaggerSchema(
6171
typeData = WildcardTypeData(),
@@ -107,7 +117,11 @@ internal class SchemaContextImpl(private val schemaConfig: SchemaConfigData) : S
107117
}
108118

109119
private fun generateSchema(type: KType): CompiledSwaggerSchema {
110-
return schemaConfig.generator(type)
120+
return schemaConfig.generator(KTypeInput(type))
121+
}
122+
123+
private fun generateSchema(descriptor: SerialDescriptor): CompiledSwaggerSchema {
124+
return schemaConfig.generator(SerialDescriptorInput(descriptor))
111125
}
112126

113127
private fun collectTypeDescriptor(routes: Collection<RouteMeta>): List<TypeDescriptor> {

ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/config/SchemaConfig.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.github.smiley4.ktoropenapi.config
22

33
import io.github.smiley4.ktoropenapi.data.*
4+
import io.github.smiley4.schemakenerator.core.data.InputType
45
import io.github.smiley4.schemakenerator.swagger.data.CompiledSwaggerSchema
56
import io.swagger.v3.oas.models.media.Schema
67
import kotlin.reflect.KType
@@ -15,7 +16,7 @@ class SchemaConfig {
1516
/**
1617
* The json-schema generator for all schemas. See https://github.com/SMILEY4/schema-kenerator/wiki for more information.
1718
*/
18-
var generator: (type: KType) -> CompiledSwaggerSchema = SchemaConfigData.DEFAULT.generator
19+
var generator: (type: InputType) -> CompiledSwaggerSchema = SchemaConfigData.DEFAULT.generator
1920

2021
private val schemas = mutableMapOf<String, TypeDescriptor>()
2122

ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/config/TypeDescriptor.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.github.smiley4.ktoropenapi.config
22

33
import io.swagger.v3.oas.models.media.Schema
4+
import kotlinx.serialization.descriptors.SerialDescriptor
45
import kotlin.reflect.KType
56
import kotlin.reflect.typeOf
67

@@ -21,6 +22,11 @@ class SwaggerTypeDescriptor(val schema: Schema<*>) : TypeDescriptor
2122
*/
2223
class KTypeDescriptor(val type: KType) : TypeDescriptor
2324

25+
/**
26+
* Describes a type from a kotlinx-serialization [SerialDescriptor]
27+
*/
28+
class SerialTypeDescriptor(val descriptor: SerialDescriptor) : TypeDescriptor
29+
2430
/**
2531
* Describes an array of types.
2632
*/

0 commit comments

Comments
 (0)