Skip to content

Commit

Permalink
[Ktor] Update generator to latest Ktor version OpenAPITools#14061
Browse files Browse the repository at this point in the history
  • Loading branch information
rsinukov committed May 16, 2023
1 parent 04b34e7 commit 431275c
Show file tree
Hide file tree
Showing 36 changed files with 1,126 additions and 382 deletions.
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

0 comments on commit 431275c

Please sign in to comment.