Skip to content

Commit 24ddb33

Browse files
committed
add ktor2 library option to kotlin server generator
1 parent b218e23 commit 24ddb33

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+2099
-2
lines changed

.github/workflows/samples-kotlin-server-jdk17.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ jobs:
4141
- samples/server/petstore/kotlin-server/javalin
4242
- samples/server/petstore/kotlin-server/javalin-6
4343
- samples/server/petstore/kotlin-server/ktor
44+
- samples/server/petstore/kotlin-server/ktor2
4445
# comment out due to gradle build failure
4546
# - samples/server/petstore/kotlin-spring-default/
4647
steps:

.github/workflows/samples-kotlin-server-jdk21.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ jobs:
2323
sample:
2424
- samples/server/petstore/kotlin-server/javalin-6
2525
- samples/server/petstore/kotlin-server/ktor
26+
- samples/server/petstore/kotlin-server/ktor2
2627
- samples/server/petstore/kotlin-server-required-and-nullable-properties
2728
steps:
2829
- uses: actions/checkout@v4

.github/workflows/samples-kotlin-server.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ jobs:
3838
- samples/server/petstore/kotlin-springboot-source-swagger2
3939
- samples/server/petstore/kotlin-springboot-springfox
4040
- samples/server/petstore/kotlin-server/ktor
41+
- samples/server/petstore/kotlin-server/ktor2
4142
- samples/server/petstore/kotlin-server/jaxrs-spec
4243
- samples/server/petstore/kotlin-server/jaxrs-spec-mutiny
4344
- samples/server/petstore/kotlin-server-modelMutable

bin/configs/kotlin-server-ktor2.yaml

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
generatorName: kotlin-server
2+
outputDir: samples/server/petstore/kotlin-server/ktor2
3+
library: ktor2
4+
inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore.yaml
5+
templateDir: modules/openapi-generator/src/main/resources/kotlin-server
6+
additionalProperties:
7+
hideGenerationTimestamp: "true"
8+
serializableModel: "true"

docs/generators/kotlin-server.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
3232
|featureResources|Generates routes in a typed way, for both: constructing URLs and reading the parameters.| |true|
3333
|groupId|Generated artifact package's organization (i.e. maven groupId).| |org.openapitools|
3434
|interfaceOnly|Whether to generate only API interface stubs without the server files. This option is currently supported only when using jaxrs-spec library.| |false|
35-
|library|library template (sub-template)|<dl><dt>**ktor**</dt><dd>ktor framework</dd><dt>**jaxrs-spec**</dt><dd>JAX-RS spec only</dd><dt>**javalin5**</dt><dd>Javalin 5</dd><dt>**javalin6**</dt><dd>Javalin 6</dd></dl>|ktor|
35+
|library|library template (sub-template)|<dl><dt>**ktor**</dt><dd>ktor framework</dd><dt>**ktor2**</dt><dd>ktor (2.x) framework</dd><dt>**jaxrs-spec**</dt><dd>JAX-RS spec only</dd><dt>**javalin5**</dt><dd>Javalin 5</dd><dt>**javalin6**</dt><dd>Javalin 6</dd></dl>|ktor|
3636
|modelMutable|Create mutable models| |false|
3737
|omitGradleWrapper|Whether to omit Gradle wrapper for creating a sub project.| |false|
3838
|packageName|Generated artifact package name.| |org.openapitools.server|

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinServerCodegen.java

+13-1
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,16 @@ public class KotlinServerCodegen extends AbstractKotlinCodegen implements BeanVa
7373
Constants.METRICS,
7474
Constants.OMIT_GRADLE_WRAPPER
7575
))
76+
.put(Constants.KTOR2, Arrays.asList(
77+
Constants.AUTOMATIC_HEAD_REQUESTS,
78+
Constants.CONDITIONAL_HEADERS,
79+
Constants.HSTS,
80+
Constants.CORS,
81+
Constants.COMPRESSION,
82+
Constants.RESOURCES,
83+
Constants.METRICS,
84+
Constants.OMIT_GRADLE_WRAPPER
85+
))
7686
.put(Constants.JAXRS_SPEC, Arrays.asList(
7787
USE_BEANVALIDATION,
7888
Constants.USE_COROUTINES,
@@ -127,6 +137,7 @@ public KotlinServerCodegen() {
127137
modelPackage = packageName + ".models";
128138

129139
supportedLibraries.put(Constants.KTOR, "ktor framework");
140+
supportedLibraries.put(Constants.KTOR2, "ktor (2.x) framework");
130141
supportedLibraries.put(Constants.JAXRS_SPEC, "JAX-RS spec only");
131142
supportedLibraries.put(Constants.JAVALIN5, "Javalin 5");
132143
supportedLibraries.put(Constants.JAVALIN6, "Javalin 6");
@@ -323,6 +334,7 @@ public void setUseBeanValidation(boolean useBeanValidation) {
323334

324335
public static class Constants {
325336
public final static String KTOR = "ktor";
337+
public final static String KTOR2 = "ktor2";
326338
public final static String JAXRS_SPEC = "jaxrs-spec";
327339

328340
public final static String JAVALIN5 = "javalin5";
@@ -419,6 +431,6 @@ private boolean isJavalin() {
419431
}
420432

421433
private boolean isKtor() {
422-
return Constants.KTOR.equals(library);
434+
return Constants.KTOR.equals(library) || Constants.KTOR2.equals(library);
423435
}
424436
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package org.openapitools.server.infrastructure
2+
3+
import io.ktor.http.auth.*
4+
import io.ktor.server.application.*
5+
import io.ktor.server.auth.*
6+
import io.ktor.server.request.*
7+
import io.ktor.server.response.*
8+
9+
enum class ApiKeyLocation(val location: String) {
10+
QUERY("query"),
11+
HEADER("header")
12+
}
13+
14+
data class ApiKeyCredential(val value: String) : Credential
15+
data class ApiPrincipal(val apiKeyCredential: ApiKeyCredential?) : Principal
16+
17+
/**
18+
* Represents an Api Key authentication provider
19+
*/
20+
class ApiKeyAuthenticationProvider(configuration: Configuration) : AuthenticationProvider(configuration) {
21+
22+
private val authenticationFunction = configuration.authenticationFunction
23+
24+
private val apiKeyName: String = configuration.apiKeyName
25+
26+
private val apiKeyLocation: ApiKeyLocation = configuration.apiKeyLocation
27+
28+
override suspend fun onAuthenticate(context: AuthenticationContext) {
29+
val call = context.call
30+
val credentials = call.request.apiKeyAuthenticationCredentials(apiKeyName, apiKeyLocation)
31+
val principal = credentials?.let { authenticationFunction.invoke(call, it) }
32+
33+
val cause = when {
34+
credentials == null -> AuthenticationFailedCause.NoCredentials
35+
principal == null -> AuthenticationFailedCause.InvalidCredentials
36+
else -> null
37+
}
38+
39+
if (cause != null) {
40+
context.challenge(apiKeyName, cause) { challenge, call ->
41+
call.respond(
42+
UnauthorizedResponse(
43+
HttpAuthHeader.Parameterized(
44+
"API_KEY",
45+
mapOf("key" to apiKeyName),
46+
HeaderValueEncoding.QUOTED_ALWAYS
47+
)
48+
)
49+
)
50+
challenge.complete()
51+
}
52+
}
53+
54+
if (principal != null) {
55+
context.principal(principal)
56+
}
57+
}
58+
59+
class Configuration internal constructor(name: String?) : Config(name) {
60+
61+
internal var authenticationFunction: suspend ApplicationCall.(ApiKeyCredential) -> Principal? = {
62+
throw NotImplementedError(
63+
"Api Key auth validate function is not specified. Use apiKeyAuth { validate { ... } } to fix."
64+
)
65+
}
66+
67+
var apiKeyName: String = ""
68+
69+
var apiKeyLocation: ApiKeyLocation = ApiKeyLocation.QUERY
70+
71+
/**
72+
* Sets a validation function that will check given [ApiKeyCredential] instance and return [Principal],
73+
* or null if credential does not correspond to an authenticated principal
74+
*/
75+
fun validate(body: suspend ApplicationCall.(ApiKeyCredential) -> Principal?) {
76+
authenticationFunction = body
77+
}
78+
}
79+
}
80+
81+
fun AuthenticationConfig.apiKeyAuth(
82+
name: String? = null,
83+
configure: ApiKeyAuthenticationProvider.Configuration.() -> Unit
84+
) {
85+
val configuration = ApiKeyAuthenticationProvider.Configuration(name).apply(configure)
86+
val provider = ApiKeyAuthenticationProvider(configuration)
87+
register(provider)
88+
}
89+
90+
fun ApplicationRequest.apiKeyAuthenticationCredentials(
91+
apiKeyName: String,
92+
apiKeyLocation: ApiKeyLocation
93+
): ApiKeyCredential? {
94+
val value: String? = when (apiKeyLocation) {
95+
ApiKeyLocation.QUERY -> this.queryParameters[apiKeyName]
96+
ApiKeyLocation.HEADER -> this.headers[apiKeyName]
97+
}
98+
return when (value) {
99+
null -> null
100+
else -> ApiKeyCredential(value)
101+
}
102+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package {{packageName}}
2+
3+
import io.ktor.server.application.*
4+
import io.ktor.serialization.gson.*
5+
import io.ktor.http.*
6+
{{#featureResources}}
7+
import io.ktor.server.resources.*
8+
{{/featureResources}}
9+
{{#featureCORS}}
10+
import io.ktor.server.plugins.cors.routing.*
11+
{{/featureCORS}}
12+
{{#featureAutoHead}}
13+
import io.ktor.server.plugins.autohead.*
14+
{{/featureAutoHead}}
15+
{{#featureConditionalHeaders}}
16+
import io.ktor.server.plugins.conditionalheaders.*
17+
{{/featureConditionalHeaders}}
18+
{{#featureCompression}}
19+
import io.ktor.server.plugins.compression.*
20+
{{/featureCompression}}
21+
import io.ktor.server.plugins.contentnegotiation.*
22+
import io.ktor.server.plugins.defaultheaders.*
23+
{{#featureHSTS}}
24+
import io.ktor.server.plugins.hsts.*
25+
{{/featureHSTS}}
26+
{{#featureMetrics}}
27+
import com.codahale.metrics.Slf4jReporter
28+
import io.ktor.server.metrics.dropwizard.*
29+
import java.util.concurrent.TimeUnit
30+
{{/featureMetrics}}
31+
import io.ktor.server.routing.*
32+
{{#hasAuthMethods}}
33+
import com.typesafe.config.ConfigFactory
34+
import io.ktor.client.HttpClient
35+
import io.ktor.client.engine.apache.Apache
36+
import io.ktor.server.config.HoconApplicationConfig
37+
import io.ktor.server.auth.*
38+
import org.openapitools.server.infrastructure.*
39+
{{/hasAuthMethods}}
40+
{{#generateApis}}{{#apiInfo}}{{#apis}}import {{apiPackage}}.{{classname}}
41+
{{/apis}}{{/apiInfo}}{{/generateApis}}
42+
43+
{{#hasAuthMethods}}
44+
internal val settings = HoconApplicationConfig(ConfigFactory.defaultApplication(HTTP::class.java.classLoader))
45+
46+
object HTTP {
47+
val client = HttpClient(Apache)
48+
}
49+
{{/hasAuthMethods}}
50+
51+
fun Application.main() {
52+
install(DefaultHeaders)
53+
{{#featureMetrics}}
54+
install(DropwizardMetrics) {
55+
val reporter = Slf4jReporter.forRegistry(registry)
56+
.outputTo([email protected])
57+
.convertRatesTo(TimeUnit.SECONDS)
58+
.convertDurationsTo(TimeUnit.MILLISECONDS)
59+
.build()
60+
reporter.start(10, TimeUnit.SECONDS)
61+
}
62+
{{/featureMetrics}}
63+
{{#generateApis}}
64+
install(ContentNegotiation) {
65+
register(ContentType.Application.Json, GsonConverter())
66+
}
67+
{{#featureAutoHead}}
68+
install(AutoHeadResponse) // see https://ktor.io/docs/autoheadresponse.html
69+
{{/featureAutoHead}}
70+
{{#featureConditionalHeaders}}
71+
install(ConditionalHeaders) // see https://ktor.io/docs/conditional-headers.html
72+
{{/featureConditionalHeaders}}
73+
{{#featureCompression}}
74+
install(Compression, ApplicationCompressionConfiguration()) // see https://ktor.io/docs/compression.html
75+
{{/featureCompression}}
76+
{{#featureCORS}}
77+
install(CORS, ApplicationCORSConfiguration()) // see https://ktor.io/docs/cors.html
78+
{{/featureCORS}}
79+
{{#featureHSTS}}
80+
install(HSTS, ApplicationHstsConfiguration()) // see https://ktor.io/docs/hsts.html
81+
{{/featureHSTS}}
82+
{{#featureResources}}
83+
install(Resources)
84+
{{/featureResources}}
85+
{{#hasAuthMethods}}
86+
install(Authentication) {
87+
{{#authMethods}}
88+
{{#isBasicBasic}}
89+
basic("{{{name}}}") {
90+
validate { credentials ->
91+
// TODO: "Apply your basic authentication functionality."
92+
// Accessible in-method via call.principal<UserIdPrincipal>()
93+
if (credentials.name == "Swagger" && "Codegen" == credentials.password) {
94+
UserIdPrincipal(credentials.name)
95+
} else {
96+
null
97+
}
98+
}
99+
{{/isBasicBasic}}
100+
{{#isApiKey}}
101+
// "Implement API key auth ({{{name}}}) for parameter name '{{{keyParamName}}}'."
102+
apiKeyAuth("{{{name}}}") {
103+
validate { apikeyCredential: ApiKeyCredential ->
104+
when {
105+
apikeyCredential.value == "keyboardcat" -> ApiPrincipal(apikeyCredential)
106+
else -> null
107+
}
108+
}
109+
}
110+
{{/isApiKey}}
111+
{{#isOAuth}}
112+
{{#bodyAllowed}}
113+
{{/bodyAllowed}}
114+
{{^bodyAllowed}}
115+
oauth("{{name}}") {
116+
client = HttpClient(Apache)
117+
providerLookup = { applicationAuthProvider([email protected]) }
118+
urlProvider = { _ ->
119+
// TODO: define a callback url here.
120+
"/"
121+
}
122+
}
123+
{{/bodyAllowed}}
124+
{{/isOAuth}}
125+
{{/authMethods}}
126+
}
127+
{{/hasAuthMethods}}
128+
install(Routing) {
129+
{{#apiInfo}}
130+
{{#apis}}
131+
{{#operations}}
132+
{{classname}}()
133+
{{/operations}}
134+
{{/apis}}
135+
{{/apiInfo}}
136+
}
137+
138+
{{/generateApis}}
139+
}

0 commit comments

Comments
 (0)