Skip to content

Commit

Permalink
Kotlin-Server: Optional metrics and upgrade to Ktor 1.5.4 (OpenAPIToo…
Browse files Browse the repository at this point in the history
…ls#9358)

Use Gradle 6.9 and Kotlin 1.4.32. Generate Paths for other HTTP verbs
(OpenAPITools#828) and fix imports (OpenAPITools#5640). Use 'object' when no parameters are
used. Introduce 'featureMetrics' to control metrics plugin usage. Remove
HOCON configuration parsing. This is provided by
`Application.environment.config already` and removes a dependency.

Resolves OpenAPITools#9087, resolves OpenAPITools#828, resolves OpenAPITools#5640
Relates-To OpenAPITools#5346
  • Loading branch information
saschpe authored Jun 20, 2021
1 parent 015886f commit 463d905
Show file tree
Hide file tree
Showing 16 changed files with 202 additions and 293 deletions.
1 change: 1 addition & 0 deletions docs/generators/kotlin-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|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|
|groupId|Generated artifact package's organization (i.e. maven groupId).| |org.openapitools|
|library|library template (sub-template)|<dl><dt>**ktor**</dt><dd>ktor framework</dd></dl>|ktor|
|modelMutable|Create mutable models| |false|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
import java.util.Map;

public class KotlinServerCodegen extends AbstractKotlinCodegen {

public static final String DEFAULT_LIBRARY = Constants.KTOR;
private final Logger LOGGER = LoggerFactory.getLogger(KotlinServerCodegen.class);
private Boolean autoHeadFeatureEnabled = true;
Expand All @@ -43,6 +42,7 @@ public class KotlinServerCodegen extends AbstractKotlinCodegen {
private Boolean corsFeatureEnabled = false;
private Boolean compressionFeatureEnabled = true;
private Boolean locationsFeatureEnabled = true;
private Boolean metricsFeatureEnabled = true;

// This is here to potentially warn the user when an option is not supported by the target framework.
private Map<String, List<String>> optionsSupportedPerFramework = new ImmutableMap.Builder<String, List<String>>()
Expand All @@ -52,7 +52,8 @@ public class KotlinServerCodegen extends AbstractKotlinCodegen {
Constants.HSTS,
Constants.CORS,
Constants.COMPRESSION,
Constants.LOCATIONS
Constants.LOCATIONS,
Constants.METRICS
))
.build();

Expand Down Expand Up @@ -115,6 +116,7 @@ public KotlinServerCodegen() {
addSwitch(Constants.CORS, Constants.CORS_DESC, getCorsFeatureEnabled());
addSwitch(Constants.COMPRESSION, Constants.COMPRESSION_DESC, getCompressionFeatureEnabled());
addSwitch(Constants.LOCATIONS, Constants.LOCATIONS_DESC, getLocationsFeatureEnabled());
addSwitch(Constants.METRICS, Constants.METRICS_DESC, getMetricsFeatureEnabled());
}

public Boolean getAutoHeadFeatureEnabled() {
Expand Down Expand Up @@ -169,6 +171,14 @@ public void setLocationsFeatureEnabled(Boolean locationsFeatureEnabled) {
this.locationsFeatureEnabled = locationsFeatureEnabled;
}

public Boolean getMetricsFeatureEnabled() {
return metricsFeatureEnabled;
}

public void setMetricsFeatureEnabled(Boolean metricsEnabled) {
this.metricsFeatureEnabled = metricsEnabled;
}

public String getName() {
return "kotlin-server";
}
Expand Down Expand Up @@ -228,6 +238,12 @@ public void processOpts() {
additionalProperties.put(Constants.LOCATIONS, getLocationsFeatureEnabled());
}

if (additionalProperties.containsKey(Constants.METRICS)) {
setMetricsFeatureEnabled(convertPropertyToBooleanAndWriteBack(Constants.METRICS));
} else {
additionalProperties.put(Constants.METRICS, getMetricsFeatureEnabled());
}

boolean generateApis = additionalProperties.containsKey(CodegenConstants.GENERATE_APIS) && (Boolean) additionalProperties.get(CodegenConstants.GENERATE_APIS);
String packageFolder = (sourceFolder + File.separator + packageName).replace(".", File.separator);
String resourcesFolder = "src/main/resources"; // not sure this can be user configurable.
Expand Down Expand Up @@ -268,17 +284,19 @@ public static class Constants {
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 METRICS = "featureMetrics";
public final static String METRICS_DESC = "Enables metrics feature.";
}

@Override
public void postProcess() {
System.out.println("################################################################################");
System.out.println("# Thanks for using OpenAPI Generator. #");
System.out.println("# Please consider donation to help us maintain this project \uD83D\uDE4F #");
System.out.println("# Please consider donation to help us maintain this project \uD83D\uDE4F #");
System.out.println("# https://opencollective.com/openapi_generator/donate #");
System.out.println("# #");
System.out.println("# This generator's contributed by Jim Schubert (https://github.com/jimschubert)#");
System.out.println("# Please support his work directly via https://patreon.com/jimschubert \uD83D\uDE4F #");
System.out.println("# Please support his work directly via https://patreon.com/jimschubert \uD83D\uDE4F #");
System.out.println("################################################################################");
}
}
Original file line number Diff line number Diff line change
@@ -1,61 +1,47 @@
package {{packageName}}

{{#featureMetrics}}
import com.codahale.metrics.Slf4jReporter
import com.typesafe.config.ConfigFactory
import io.ktor.application.Application
import io.ktor.application.ApplicationStopping
import io.ktor.application.install
import io.ktor.application.log
import io.ktor.client.HttpClient
import io.ktor.client.engine.apache.Apache
import io.ktor.config.HoconApplicationConfig
{{#featureAutoHead}}
import io.ktor.features.AutoHeadResponse
{{/featureAutoHead}}
{{#featureCompression}}
import io.ktor.features.Compression
{{/featureCompression}}
{{#featureCORS}}
import io.ktor.features.CORS
{{/featureCORS}}
{{#featureConditionalHeaders}}
import io.ktor.features.ConditionalHeaders
{{/featureConditionalHeaders}}
import io.ktor.features.ContentNegotiation
import io.ktor.features.DefaultHeaders
{{#featureHSTS}}
import io.ktor.features.HSTS
{{/featureHSTS}}
import io.ktor.gson.GsonConverter
import io.ktor.http.ContentType
{{/featureMetrics}}
import io.ktor.application.*
import io.ktor.features.*
import io.ktor.gson.*
import io.ktor.http.*
{{#featureLocations}}
import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.locations.Locations
import io.ktor.locations.*
{{/featureLocations}}
import io.ktor.routing.Routing
{{#featureMetrics}}
import io.ktor.metrics.dropwizard.*
import java.util.concurrent.TimeUnit
{{/featureMetrics}}
import io.ktor.routing.*
import io.ktor.util.*
{{#hasAuthMethods}}
import io.ktor.auth.Authentication
import io.ktor.auth.oauth
import io.ktor.metrics.dropwizard.DropwizardMetrics
import org.openapitools.server.infrastructure.ApiKeyCredential
import org.openapitools.server.infrastructure.ApiPrincipal
import org.openapitools.server.infrastructure.apiKeyAuth
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 org.openapitools.server.infrastructure.*
{{/hasAuthMethods}}
{{#generateApis}}{{#apiInfo}}{{#apis}}import {{apiPackage}}.{{classname}}
{{/apis}}{{/apiInfo}}{{/generateApis}}

{{#hasAuthMethods}}
internal val settings = HoconApplicationConfig(ConfigFactory.defaultApplication(HTTP::class.java.classLoader))

object HTTP {
val client = HttpClient(Apache)
}
{{/hasAuthMethods}}

@KtorExperimentalAPI
{{#featureLocations}}
@KtorExperimentalLocationsAPI
{{/featureLocations}}
fun Application.main() {
install(DefaultHeaders)
{{#featureMetrics}}
install(DropwizardMetrics) {
val reporter = Slf4jReporter.forRegistry(registry)
.outputTo(log)
Expand All @@ -64,27 +50,28 @@ fun Application.main() {
.build()
reporter.start(10, TimeUnit.SECONDS)
}
{{/featureMetrics}}
{{#generateApis}}
install(ContentNegotiation) {
register(ContentType.Application.Json, GsonConverter())
}
{{#featureAutoHead}}
install(AutoHeadResponse) // see http://ktor.io/features/autoheadresponse.html
install(AutoHeadResponse) // see https://ktor.io/docs/autoheadresponse.html
{{/featureAutoHead}}
{{#featureConditionalHeaders}}
install(ConditionalHeaders) // see http://ktor.io/features/conditional-headers.html
install(ConditionalHeaders) // see https://ktor.io/docs/conditional-headers.html
{{/featureConditionalHeaders}}
{{#featureHSTS}}
install(HSTS, ApplicationHstsConfiguration()) // see http://ktor.io/features/hsts.html
{{/featureHSTS}}
{{#featureCORS}}
install(CORS, ApplicationCORSConfiguration()) // see http://ktor.io/features/cors.html
{{/featureCORS}}
{{#featureCompression}}
install(Compression, ApplicationCompressionConfiguration()) // see http://ktor.io/features/compression.html
install(Compression, ApplicationCompressionConfiguration()) // see https://ktor.io/docs/compression.html
{{/featureCompression}}
{{#featureCORS}}
install(CORS, ApplicationCORSConfiguration()) // see https://ktor.io/docs/cors.html
{{/featureCORS}}
{{#featureHSTS}}
install(HSTS, ApplicationHstsConfiguration()) // see https://ktor.io/docs/hsts.html
{{/featureHSTS}}
{{#featureLocations}}
install(Locations) // see http://ktor.io/features/locations.html
install(Locations) // see https://ktor.io/docs/features-locations.html
{{/featureLocations}}
{{#hasAuthMethods}}
install(Authentication) {
Expand Down Expand Up @@ -140,8 +127,4 @@ fun Application.main() {
}

{{/generateApis}}

environment.monitor.subscribe(ApplicationStopping) {
HTTP.client.close()
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
package {{packageName}}

// Use this file to hold package-level internal functions that return receiver object passed to the `install` method.
import io.ktor.auth.OAuthServerSettings
import io.ktor.features.Compression
import io.ktor.features.HSTS
import io.ktor.features.deflate
import io.ktor.features.gzip
import io.ktor.features.minimumSize
import io.ktor.http.HttpMethod
import io.ktor.auth.*
import io.ktor.features.*
import io.ktor.http.*
import io.ktor.util.*
import java.time.Duration
import java.util.concurrent.TimeUnit

{{#featureCORS}}

/**
* Application block for [CORS] configuration.
*
Expand All @@ -33,8 +31,8 @@ internal fun ApplicationCORSConfiguration(): CORS.Configuration.() -> Unit {
}
}
{{/featureCORS}}

{{#featureHSTS}}

/**
* Application block for [HSTS] configuration.
*
Expand All @@ -54,8 +52,8 @@ internal fun ApplicationHstsConfiguration(): HSTS.Configuration.() -> Unit {
}
}
{{/featureHSTS}}

{{#featureCompression}}

/**
* Application block for [Compression] configuration.
*
Expand Down Expand Up @@ -99,8 +97,8 @@ val ApplicationAuthProviders: Map<String, OAuthServerSettings> = listOf<OAuthSer
// 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()",
// clientId = settings.property("auth.oauth.facebook.clientId").getString(),
// clientSecret = settings.property("auth.oauth.facebook.clientSecret").getString(),
// defaultScopes = listOf("public_profile")
// )
).associateBy { it.name }
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
{{>licenseInfo}}
package {{packageName}}

import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.locations.Location
import io.ktor.locations.*
import {{packageName}}.models.*
{{#imports}}import {{import}}
{{/imports}}

{{#apiInfo}}
@KtorExperimentalLocationsAPI
object Paths {
{{#apis}}
{{#operations}}
{{#operation}}
/**
* {{summary}}
/**{{#summary}}
* {{summary}}{{/summary}}
* {{#unescapedNotes}}{{.}}{{/unescapedNotes}}
{{#allParams}}* @param {{paramName}} {{description}} {{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}
{{/allParams}}*/
@KtorExperimentalLocationsAPI
{{#hasParams}}
@Location("{{path}}") class {{operationId}}({{#allParams}}val {{paramName}}: {{{dataType}}}{{^required}}? = null{{/required}}{{#required}}{{#isNullable}}?{{/isNullable}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}})
{{/hasParams}}
{{^hasParams}}
@Location("{{path}}") object {{operationId}}
{{/hasParams}}

{{/operation}}
{{/operations}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ Generated by OpenAPI Generator {{generatorVersion}}{{^hideGenerationTimestamp}}

## Requires

* Kotlin 1.4.31
* Gradle 6.8.2
* Kotlin 1.4.32
* Gradle 6.9

## Build

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,50 +2,27 @@
package {{apiPackage}}

import com.google.gson.Gson
import io.ktor.application.call
import io.ktor.auth.UserIdPrincipal
import io.ktor.auth.authentication
import io.ktor.auth.authenticate
import io.ktor.auth.OAuthAccessTokenResponse
import io.ktor.auth.OAuthServerSettings
import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode
import io.ktor.response.respond
import io.ktor.response.respondText
import io.ktor.routing.Route
import io.ktor.application.*
import io.ktor.auth.*
import io.ktor.http.*
import io.ktor.response.*
{{#featureLocations}}
import {{packageName}}.Paths
import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.locations.delete
import io.ktor.locations.get
import io.ktor.locations.post
import io.ktor.locations.put
import io.ktor.locations.options
import io.ktor.locations.head
import io.ktor.locations.*
{{/featureLocations}}
{{^featureLocations}}
import io.ktor.routing.delete
import io.ktor.routing.get
import io.ktor.routing.post
import io.ktor.routing.put
import io.ktor.routing.options
import io.ktor.routing.head
import io.ktor.routing.route
{{/featureLocations}}

import io.ktor.routing.*
import {{packageName}}.infrastructure.ApiPrincipal


{{#imports}}import {{import}}
{{/imports}}

{{#operations}}
{{#featureLocations}}
{{#featureLocations}}
@KtorExperimentalLocationsAPI
{{/featureLocations}}
{{/featureLocations}}
fun Route.{{classname}}() {
val gson = Gson()
val empty = mutableMapOf<String, Any?>()
{{#operation}}
{{#hasAuthMethods}}
{{#authMethods}}
Expand Down
Loading

0 comments on commit 463d905

Please sign in to comment.