Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Ktor] Update generator to latest Ktor version #14061 #14296

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/generators/kotlin-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|featureCompression|Adds ability to compress outgoing content using gzip, deflate or custom encoder and thus reduce size of the response.| |true|
|featureConditionalHeaders|Avoid sending content if client already has same content, by checking ETag or LastModified properties.| |false|
|featureHSTS|Avoid sending content if client already has same content, by checking ETag or LastModified properties.| |true|
|featureLocations|Generates routes in a typed way, for both: constructing URLs and reading the parameters.| |true|
|featureMetrics|Enables metrics feature.| |true|
|featureResources|Generates routes in a typed way, for both: constructing URLs and reading the parameters.| |true|
|groupId|Generated artifact package's organization (i.e. maven groupId).| |org.openapitools|
|interfaceOnly|Whether to generate only API interface stubs without the server files. This option is currently supported only when using jaxrs-spec library.| |false|
|library|library template (sub-template)|<dl><dt>**ktor**</dt><dd>ktor framework</dd><dt>**jaxrs-spec**</dt><dd>JAX-RS spec only</dd></dl>|ktor|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public class KotlinServerCodegen extends AbstractKotlinCodegen implements BeanVa
private Boolean hstsFeatureEnabled = true;
private Boolean corsFeatureEnabled = false;
private Boolean compressionFeatureEnabled = true;
private Boolean locationsFeatureEnabled = true;
private Boolean resourcesFeatureEnabled = true;
private Boolean metricsFeatureEnabled = true;
private boolean interfaceOnly = false;
private boolean useBeanValidation = false;
Expand All @@ -62,7 +62,7 @@ public class KotlinServerCodegen extends AbstractKotlinCodegen implements BeanVa
Constants.HSTS,
Constants.CORS,
Constants.COMPRESSION,
Constants.LOCATIONS,
Constants.RESOURCES,
Constants.METRICS
))
.build();
Expand Down Expand Up @@ -126,7 +126,7 @@ public KotlinServerCodegen() {
addSwitch(Constants.HSTS, Constants.HSTS_DESC, getHstsFeatureEnabled());
addSwitch(Constants.CORS, Constants.CORS_DESC, getCorsFeatureEnabled());
addSwitch(Constants.COMPRESSION, Constants.COMPRESSION_DESC, getCompressionFeatureEnabled());
addSwitch(Constants.LOCATIONS, Constants.LOCATIONS_DESC, getLocationsFeatureEnabled());
addSwitch(Constants.RESOURCES, Constants.RESOURCES_DESC, getResourcesFeatureEnabled());
addSwitch(Constants.METRICS, Constants.METRICS_DESC, getMetricsFeatureEnabled());

cliOptions.add(CliOption.newBoolean(INTERFACE_ONLY, "Whether to generate only API interface stubs without the server files. This option is currently supported only when using jaxrs-spec library.").defaultValue(String.valueOf(interfaceOnly)));
Expand Down Expand Up @@ -179,12 +179,12 @@ public void setHstsFeatureEnabled(Boolean hstsFeatureEnabled) {
this.hstsFeatureEnabled = hstsFeatureEnabled;
}

public Boolean getLocationsFeatureEnabled() {
return locationsFeatureEnabled;
public Boolean getResourcesFeatureEnabled() {
return resourcesFeatureEnabled;
}

public void setLocationsFeatureEnabled(Boolean locationsFeatureEnabled) {
this.locationsFeatureEnabled = locationsFeatureEnabled;
public void setResourcesFeatureEnabled(Boolean resourcesFeatureEnabled) {
this.resourcesFeatureEnabled = resourcesFeatureEnabled;
}

public Boolean getMetricsFeatureEnabled() {
Expand Down Expand Up @@ -279,10 +279,10 @@ public void processOpts() {
additionalProperties.put(Constants.COMPRESSION, getCompressionFeatureEnabled());
}

if (additionalProperties.containsKey(Constants.LOCATIONS)) {
setLocationsFeatureEnabled(convertPropertyToBooleanAndWriteBack(Constants.LOCATIONS));
if (additionalProperties.containsKey(Constants.RESOURCES)) {
setResourcesFeatureEnabled(convertPropertyToBooleanAndWriteBack(Constants.RESOURCES));
} else {
additionalProperties.put(Constants.LOCATIONS, getLocationsFeatureEnabled());
additionalProperties.put(Constants.RESOURCES, getResourcesFeatureEnabled());
}

if (additionalProperties.containsKey(Constants.METRICS)) {
Expand All @@ -308,7 +308,7 @@ public void processOpts() {
supportingFiles.add(new SupportingFile("AppMain.kt.mustache", packageFolder, "AppMain.kt"));
supportingFiles.add(new SupportingFile("Configuration.kt.mustache", packageFolder, "Configuration.kt"));

if (generateApis && locationsFeatureEnabled) {
if (generateApis && resourcesFeatureEnabled) {
supportingFiles.add(new SupportingFile("Paths.kt.mustache", packageFolder, "Paths.kt"));
}

Expand Down Expand Up @@ -339,8 +339,8 @@ public static class Constants {
public final static String CORS_DESC = "Ktor by default provides an interceptor for implementing proper support for Cross-Origin Resource Sharing (CORS). See enable-cors.org.";
public final static String COMPRESSION = "featureCompression";
public final static String COMPRESSION_DESC = "Adds ability to compress outgoing content using gzip, deflate or custom encoder and thus reduce size of the response.";
public final static String LOCATIONS = "featureLocations";
public final static String LOCATIONS_DESC = "Generates routes in a typed way, for both: constructing URLs and reading the parameters.";
public final static String RESOURCES = "featureResources";
public final static String RESOURCES_DESC = "Generates routes in a typed way, for both: constructing URLs and reading the parameters.";
public final static String METRICS = "featureMetrics";
public final static String METRICS_DESC = "Enables metrics feature.";
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package {{packageName}}.infrastructure
package org.openapitools.server.infrastructure

import io.ktor.application.*
import io.ktor.auth.*
import io.ktor.http.auth.*
import io.ktor.request.*
import io.ktor.response.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.request.*
import io.ktor.server.response.*

enum class ApiKeyLocation(val location: String) {
QUERY("query"),
Expand All @@ -15,8 +15,7 @@ data class ApiKeyCredential(val value: String) : Credential
data class ApiPrincipal(val apiKeyCredential: ApiKeyCredential?) : Principal

/**
* Represents a Api Key authentication provider
* @param name is the name of the provider, or `null` for a default provider
* Represents an Api Key authentication provider
*/
class ApiKeyAuthenticationProvider(configuration: Configuration) : AuthenticationProvider(configuration) {

Expand All @@ -26,39 +25,38 @@ class ApiKeyAuthenticationProvider(configuration: Configuration) : Authenticatio

private val apiKeyLocation: ApiKeyLocation = configuration.apiKeyLocation

internal fun install() {
pipeline.intercept(AuthenticationPipeline.RequestAuthentication) { context ->
val credentials = call.request.apiKeyAuthenticationCredentials(apiKeyName, apiKeyLocation)
val principal = credentials?.let { authenticationFunction(call, it) }
override suspend fun onAuthenticate(context: AuthenticationContext) {
val call = context.call
val credentials = call.request.apiKeyAuthenticationCredentials(apiKeyName, apiKeyLocation)
val principal = credentials?.let { authenticationFunction.invoke(call, it) }

val cause = when {
credentials == null -> AuthenticationFailedCause.NoCredentials
principal == null -> AuthenticationFailedCause.InvalidCredentials
else -> null
}
val cause = when {
credentials == null -> AuthenticationFailedCause.NoCredentials
principal == null -> AuthenticationFailedCause.InvalidCredentials
else -> null
}

if (cause != null) {
context.challenge(apiKeyName, cause) {
call.respond(
UnauthorizedResponse(
HttpAuthHeader.Parameterized(
"API_KEY",
mapOf("key" to apiKeyName),
HeaderValueEncoding.QUOTED_ALWAYS
)
if (cause != null) {
context.challenge(apiKeyName, cause) { challenge, call ->
call.respond(
UnauthorizedResponse(
HttpAuthHeader.Parameterized(
"API_KEY",
mapOf("key" to apiKeyName),
HeaderValueEncoding.QUOTED_ALWAYS
)
)
it.complete()
}
)
challenge.complete()
}
}

if (principal != null) {
context.principal(principal)
}
if (principal != null) {
context.principal(principal)
}
}

class Configuration internal constructor(name: String?) : AuthenticationProvider.Configuration(name) {
class Configuration internal constructor(name: String?) : Config(name) {

internal var authenticationFunction: suspend ApplicationCall.(ApiKeyCredential) -> Principal? = {
throw NotImplementedError(
Expand All @@ -80,13 +78,12 @@ class ApiKeyAuthenticationProvider(configuration: Configuration) : Authenticatio
}
}

fun Authentication.Configuration.apiKeyAuth(
fun AuthenticationConfig.apiKeyAuth(
name: String? = null,
configure: ApiKeyAuthenticationProvider.Configuration.() -> Unit
) {
val configuration = ApiKeyAuthenticationProvider.Configuration(name).apply(configure)
val provider = ApiKeyAuthenticationProvider(configuration)
provider.install()
register(provider)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,40 @@
package {{packageName}}

{{#featureMetrics}}
import com.codahale.metrics.Slf4jReporter
{{/featureMetrics}}
import io.ktor.application.*
import io.ktor.features.*
import io.ktor.gson.*
import io.ktor.server.application.*
import io.ktor.serialization.gson.*
import io.ktor.http.*
{{#featureLocations}}
import io.ktor.locations.*
{{/featureLocations}}
{{#featureResources}}
import io.ktor.server.resources.*
{{/featureResources}}
{{#featureCORS}}
import io.ktor.server.plugins.cors.routing.*
{{/featureCORS}}
{{#featureAutoHead}}
import io.ktor.server.plugins.autohead.*
{{/featureAutoHead}}
{{#featureConditionalHeaders}}
import io.ktor.server.plugins.conditionalheaders.*
{{/featureConditionalHeaders}}
{{#featureCompression}}
import io.ktor.server.plugins.compression.*
{{/featureCompression}}
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.server.plugins.defaultheaders.*
{{#featureHSTS}}
import io.ktor.server.plugins.hsts.*
{{/featureHSTS}}
{{#featureMetrics}}
import io.ktor.metrics.dropwizard.*
import com.codahale.metrics.Slf4jReporter
import io.ktor.server.metrics.dropwizard.*
import java.util.concurrent.TimeUnit
{{/featureMetrics}}
import io.ktor.routing.*
import io.ktor.util.*
import io.ktor.server.routing.*
{{#hasAuthMethods}}
import com.typesafe.config.ConfigFactory
import io.ktor.auth.*
import io.ktor.client.HttpClient
import io.ktor.client.engine.apache.Apache
import io.ktor.config.HoconApplicationConfig
import io.ktor.server.config.HoconApplicationConfig
import io.ktor.server.auth.*
import org.openapitools.server.infrastructure.*
{{/hasAuthMethods}}
{{#generateApis}}{{#apiInfo}}{{#apis}}import {{apiPackage}}.{{classname}}
Expand All @@ -35,16 +48,12 @@ object HTTP {
}
{{/hasAuthMethods}}

@KtorExperimentalAPI
{{#featureLocations}}
@KtorExperimentalLocationsAPI
{{/featureLocations}}
fun Application.main() {
install(DefaultHeaders)
{{#featureMetrics}}
install(DropwizardMetrics) {
val reporter = Slf4jReporter.forRegistry(registry)
.outputTo(log)
.outputTo(this@main.log)
.convertRatesTo(TimeUnit.SECONDS)
.convertDurationsTo(TimeUnit.MILLISECONDS)
.build()
Expand All @@ -70,9 +79,9 @@ fun Application.main() {
{{#featureHSTS}}
install(HSTS, ApplicationHstsConfiguration()) // see https://ktor.io/docs/hsts.html
{{/featureHSTS}}
{{#featureLocations}}
install(Locations) // see https://ktor.io/docs/features-locations.html
{{/featureLocations}}
{{#featureResources}}
install(Resources)
{{/featureResources}}
{{#hasAuthMethods}}
install(Authentication) {
{{#authMethods}}
Expand Down Expand Up @@ -105,7 +114,7 @@ fun Application.main() {
{{^bodyAllowed}}
oauth("{{name}}") {
client = HttpClient(Apache)
providerLookup = { ApplicationAuthProviders["{{{name}}}"] }
providerLookup = { applicationAuthProvider([email protected]) }
urlProvider = { _ ->
// TODO: define a callback url here.
"/"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,33 @@
package {{packageName}}

// Use this file to hold package-level internal functions that return receiver object passed to the `install` method.
import io.ktor.auth.*
import io.ktor.features.*
import io.ktor.http.*
import io.ktor.server.auth.*
import io.ktor.server.config.*
import io.ktor.util.*
import java.time.Duration
import java.util.concurrent.TimeUnit
{{#featureCORS}}
import io.ktor.server.plugins.cors.routing.*
import io.ktor.server.plugins.cors.*
{{/featureCORS}}
{{#featureCompression}}
import io.ktor.server.plugins.compression.*
{{/featureCompression}}
{{#featureHSTS}}
import io.ktor.server.plugins.hsts.*
{{/featureHSTS}}

{{#featureCORS}}
/**
* Application block for [CORS] configuration.
*
* This file may be excluded in .openapi-generator-ignore,
* and application specific configuration can be applied in this function.
* and application-specific configuration can be applied in this function.
*
* See http://ktor.io/features/cors.html
*/
internal fun ApplicationCORSConfiguration(): CORS.Configuration.() -> Unit {
internal fun ApplicationCORSConfiguration(): CORSConfig.() -> Unit {
return {
// method(HttpMethod.Options)
// header(HttpHeaders.XForwardedProto)
Expand All @@ -37,11 +47,11 @@ internal fun ApplicationCORSConfiguration(): CORS.Configuration.() -> Unit {
* Application block for [HSTS] configuration.
*
* This file may be excluded in .openapi-generator-ignore,
* and application specific configuration can be applied in this function.
* and application-specific configuration can be applied in this function.
*
* See http://ktor.io/features/hsts.html
*/
internal fun ApplicationHstsConfiguration(): HSTS.Configuration.() -> Unit {
internal fun ApplicationHstsConfiguration(): HSTSConfig.() -> Unit {
return {
maxAgeInSeconds = TimeUnit.DAYS.toSeconds(365)
includeSubDomains = true
Expand All @@ -58,11 +68,11 @@ internal fun ApplicationHstsConfiguration(): HSTS.Configuration.() -> Unit {
* Application block for [Compression] configuration.
*
* This file may be excluded in .openapi-generator-ignore,
* and application specific configuration can be applied in this function.
* and application-specific configuration can be applied in this function.
*
* See http://ktor.io/features/compression.html
*/
internal fun ApplicationCompressionConfiguration(): Compression.Configuration.() -> Unit {
internal fun ApplicationCompressionConfiguration(): CompressionConfig.() -> Unit {
return {
gzip {
priority = 1.0
Expand All @@ -76,29 +86,13 @@ internal fun ApplicationCompressionConfiguration(): Compression.Configuration.()
{{/featureCompression}}

// Defines authentication mechanisms used throughout the application.
val ApplicationAuthProviders: Map<String, OAuthServerSettings> = listOf<OAuthServerSettings>(
{{#authMethods}}
{{#isOAuth}}
OAuthServerSettings.OAuth2ServerSettings(
name = "{{name}}",
authorizeUrl = "{{authorizationUrl}}",
accessTokenUrl = "{{tokenUrl}}",
requestMethod = HttpMethod.Get,
{{! TODO: flow, doesn't seem to be supported yet by ktor }}
clientId = settings.property("auth.oauth.{{name}}.clientId").getString(),
clientSecret = settings.property("auth.oauth.{{name}}.clientSecret").getString(),
defaultScopes = listOf({{#scopes}}"{{scope}}"{{^-last}}, {{/-last}}{{/scopes}})
){{^-last}},{{/-last}}
{{/isOAuth}}
{{/authMethods}}
// OAuthServerSettings.OAuth2ServerSettings(
// name = "facebook",
// authorizeUrl = "https://graph.facebook.com/oauth/authorize",
// accessTokenUrl = "https://graph.facebook.com/oauth/access_token",
// requestMethod = HttpMethod.Post,
//
// clientId = settings.property("auth.oauth.facebook.clientId").getString(),
// clientSecret = settings.property("auth.oauth.facebook.clientSecret").getString(),
// defaultScopes = listOf("public_profile")
// )
).associateBy { it.name }
fun applicationAuthProvider(config: ApplicationConfig): OAuthServerSettings =
OAuthServerSettings.OAuth2ServerSettings(
name = "petstore_auth",
authorizeUrl = "http://petstore.swagger.io/api/oauth/dialog",
accessTokenUrl = "",
requestMethod = HttpMethod.Get,
clientId = config.property("auth.oauth.petstore_auth.clientId").getString(),
clientSecret = config.property("auth.oauth.petstore_auth.clientSecret").getString(),
defaultScopes = listOf("write:pets", "read:pets")
)
Loading