Kora облачно ориентированный серверный фреймворк написанный на Java для написания Java / Kotlin приложений с упором на производительность, эффективность, прозрачность сделанный разработчиками Т-Банк / Тинькофф

Kora is a cloud-oriented server-side Java framework for writing Java / Kotlin applications with a focus on performance, efficiency and transparency made by T-Bank / Tinkoff developers

Skip to content

OpenAPI codegen

Module for creating declarative HTTP handlers HTTP server or create declarative HTTP clients from OpenAPI contracts using OpenAPI Generator plugin.

Dependency

Dependency build.gradle:

buildscript {
    dependencies {
        classpath("ru.tinkoff.kora:openapi-generator:1.1.25")
    }
}

Plugin dependency build.gradle:

plugins {
    id "org.openapi.generator" version "7.4.0"
}

Use of other versions of the plugin is not guaranteed as it may not be compatible at the code level.

Dependency build.gradle.kts:

buildscript {
    dependencies {
        classpath("ru.tinkoff.kora:openapi-generator:1.1.25")
    }
}

Plugin dependency build.gradle.kts:

plugins {
    id("org.openapi.generator") version("7.4.0")
}

Use of other versions of the plugin is not guaranteed as it may not be compatible at the code level.

Requires HTTP server or HTTP client module.

Configuration

Configuration is required for OpenAPI Generator plugin parameters:

Client

A minimal example of configuring a plugin to create a declarative HTTP client:

Kora's available plugin options:

  • clientConfigPrefix - configuration prefix of created HTTP clients
  • tags - possibility to put additional tags on created HTTP-clients
  • interceptors - ability to specify interceptors for HTTP clients
  • primaryAuth - specify which authorization mechanism to use as the primary one if several securitySchemes are specified in OpenAPI
  • securityConfigPrefix - prefix of authorization mechanism configuration Basic/ApiKey (configuration path will be specified prefix + name securitySchemes in OpenAPI, or just name in OpenAPI if prefix is not specified).
  • authAsMethodArgument - ability to specify authorization as an argument of an HTTP client method rather than through an interceptor
  • additionalContractAnnotations - ability to specify additional annotations over HTTP client methods
  • enableJsonNullable - Treat nullable=true and required=false schema fields as a JsonNullable wrapper
  • filterWithModels - filter and exclude also unnecessary models from generation when the FILTER option in openapiNormalizer is specified
  • mode in which mode the generator should operate, available values:
def openApiGenerateHttpClient = tasks.register("openApiGenerateHttpClient", GenerateTask) {
    generatorName = "kora"
    group = "openapi tools"
    inputSpec = "$projectDir/src/main/resources/openapi/openapi.yaml" //(1)!
    outputDir = "$buildDir/generated/openapi" //(2)!  
    def corePackage = "ru.tinkoff.kora.example.openapi"
    apiPackage = "${corePackage}.api" //(3)!
    modelPackage = "${corePackage}.model" //(4)!
    invokerPackage = "${corePackage}.invoker" //(5)!
    openapiNormalizer = [
        DISABLE_ALL: "true"
    ]
    configOptions = [
        mode: "java-client", //(6)!
        clientConfigPrefix: "httpClient.myclient" //(7)!
    ]
}
sourceSets.main { java.srcDirs += openApiGenerateHttpClient.get().outputDir } //(8)!
compileJava.dependsOn openApiGenerateHttpClient //(9)!
  1. Path to OpenAPI file from which classes will be created
  2. Directory where the files will be created
  3. Package from classes of delegates, controllers, converters, etc.
  4. Package from classes of models, DTOs, etc.
  5. Package from calling classes
  6. Mode of plugin operation (creating Java client / Kotlin / Java server, etc.)
  7. Prefix path to client configuration file
  8. Register the generated classes as the source code of the project
  9. Make code compilation dependent on HTTP client class generation (first generate, then compile)

Kora's available plugin options:

  • clientConfigPrefix - configuration prefix of created HTTP clients
  • tags - possibility to put additional tags on created HTTP-clients
  • interceptors - ability to specify interceptors for HTTP clients
  • primaryAuth - specify which authorization mechanism to use as the primary one if several securitySchemes are specified in OpenAPI
  • securityConfigPrefix - prefix of authorization mechanism configuration Basic/ApiKey (configuration path will be specified prefix + name securitySchemes in OpenAPI, or just name in OpenAPI if prefix is not specified).
  • authAsMethodArgument - ability to specify authorization as an argument of an HTTP client method rather than through an interceptor
  • additionalContractAnnotations - ability to specify additional annotations over HTTP client methods
  • enableJsonNullable - Treat nullable=true and required=false schema fields as a JsonNullable wrapper
  • filterWithModels - filter and exclude also unnecessary models from generation when the FILTER option in openapiNormalizer is specified
  • mode in which mode the generator should operate, available values:
    • kotlin-client - create synchronous client
    • kotlin-suspend-client - create suspend client
val openApiGenerateHttpClient = tasks.register<GenerateTask>("openApiGenerateHttpClient") {
    generatorName = "kora"
    group = "openapi tools"
    inputSpec = "$projectDir/src/main/resources/openapi/openapi.yaml" //(1)!
    outputDir = "$buildDir/generated/openapi" //(2)!
    val corePackage = "ru.tinkoff.kora.example.openapi"
    apiPackage = "${corePackage}.api" //(3)!
    modelPackage = "${corePackage}.model" //(4)!
    invokerPackage = "${corePackage}.invoker" //(5)!
    openapiNormalizer = mapOf(
        "DISABLE_ALL" to "true"
    )
    configOptions = mapOf(
        "mode" to "kotlin-client", //(6)!
        "clientConfigPrefix" to "httpClient.myclient" //(7)!
    )
}
kotlin.sourceSets.main { kotlin.srcDir(openApiGenerateHttpClient.get().outputDir) } //(8)!
tasks.withType<KspTask> { dependsOn(openApiGenerateHttpClient) } //(9)!
  1. Path to OpenAPI file from which classes will be created
  2. Directory where the files will be created
  3. Package from classes of delegates, controllers, converters, etc.
  4. Package from classes of models, DTOs, etc.
  5. Package from calling classes
  6. Mode of plugin operation (creating Java client / Kotlin / Java server, etc.)
  7. Prefix path to client configuration file
  8. Register the generated classes as the source code of the project
  9. Make code compilation dependent on HTTP client class generation (first generate, then compile)

Once created, the HTTP client will be available for deployment as a dependency on the created interface.

Interceptors

It is possible to put interceptors on created clients with @HttpClient annotation.

The value is a Json object whose key is the api tag from the contract, and the value is an object with type and tag fields, it is possible to specify both fields at the same time, or optionally one of them:

  • type - the implementation class of a particular interceptor
  • tag - tags of the interceptor (can be specified as an array of strings).

In order to do this, set the configOptions.interceptors parameter:

def openApiGenerateHttpClient = tasks.register("openApiGenerateHttpClient", GenerateTask) {
    generatorName = "kora"
    group = "openapi tools"
    inputSpec = "$projectDir/src/main/resources/openapi/openapi.yaml"
    outputDir = "$buildDir/generated/openapi"
    def corePackage = "ru.tinkoff.kora.example.openapi"
    apiPackage = "${corePackage}.api"
    modelPackage = "${corePackage}.model"
    invokerPackage = "${corePackage}.invoker"
    openapiNormalizer = [
        DISABLE_ALL: "true"
    ]
    configOptions = [
        mode: "java-client",
        interceptors: """
                {
                  "*": [
                    {
                      "tag": "ru.tinkoff.example.MyTag"
                    }
                  ],
                  "pet": [
                    {
                      "type": "ru.tinkoff.example.MyInterceptor"
                    }
                  ],
                  "shop": [
                    {
                      "type": "ru.tinkoff.example.MyInterceptor",
                      "tag": "ru.tinkoff.example.MyTag"
                    }
                  ]
                }
                """
    ]
}
sourceSets.main { java.srcDirs += openApiGenerateHttpClient.get().outputDir }
compileJava.dependsOn openApiGenerateHttpClient
val openApiGenerateHttpClient = tasks.register<GenerateTask>("openApiGenerateHttpClient") {
    generatorName = "kora"
    group = "openapi tools"
    inputSpec = "$projectDir/src/main/resources/openapi/openapi.yaml"
    outputDir = "$buildDir/generated/openapi"
    val corePackage = "ru.tinkoff.kora.example.openapi"
    apiPackage = "${corePackage}.api"
    modelPackage = "${corePackage}.model"
    invokerPackage = "${corePackage}.invoker"
    openapiNormalizer = mapOf(
        "DISABLE_ALL" to "true"
    )
    configOptions = mapOf(
        "mode" to "kotlin-client",
        "interceptors" to """{
                  "*": [
                    {
                      "tag": "ru.tinkoff.example.MyTag"
                    }
                  ],
                  "pet": [
                    {
                      "type": "ru.tinkoff.example.MyInterceptor"
                    }
                  ],
                  "shop": [
                    {
                      "type": "ru.tinkoff.example.MyInterceptor",
                      "tag": "ru.tinkoff.example.MyTag"
                    }
                  ]
                }
                """
    )
}
kotlin.sourceSets.main { kotlin.srcDir(openApiGenerateHttpClient.get().outputDir) }
tasks.withType<KspTask> { dependsOn(openApiGenerateHttpClient) }

Tags

It is possible to put parameters httpClientTag and telemetryTag on created clients with @HttpClient annotation. The value is a Json object, the key of which is the api tag from the contract, and the value is the object with the fields httpClientTag and telemetryTag.

For this purpose it is necessary to set the configOptions.tags parameter:

def openApiGenerateHttpClient = tasks.register("openApiGenerateHttpClient", GenerateTask) {
    generatorName = "kora"
    group = "openapi tools"
    inputSpec = "$projectDir/src/main/resources/openapi/openapi.yaml"
    outputDir = "$buildDir/generated/openapi"
    def corePackage = "ru.tinkoff.kora.example.openapi"
    apiPackage = "${corePackage}.api"
    modelPackage = "${corePackage}.model"
    invokerPackage = "${corePackage}.invoker"
    openapiNormalizer = [
        DISABLE_ALL: "true"
    ]
    configOptions = [
        mode: "java-client",
        clientConfigPrefix: "httpClient.myclient",
        tags: """
              {
                "*": { // применится для всех тегов, кроме явно указанных (в данном случае instrument)
                  "httpClientTag": "some.tag.Common",
                  "telemetryTag": "some.tag.Common"
                },
                "instrument": { // применится для instrument
                  "httpClientTag": "some.tag.Instrument",
                  "telemetryTag": "some.tag.Instrument"
                }
              }
              """
    ]
}
sourceSets.main { java.srcDirs += openApiGenerateHttpClient.get().outputDir }
compileJava.dependsOn openApiGenerateHttpClient
val openApiGenerateHttpClient = tasks.register<GenerateTask>("openApiGenerateHttpClient") {
    generatorName = "kora"
    group = "openapi tools"
    inputSpec = "$projectDir/src/main/resources/openapi/openapi.yaml"
    outputDir = "$buildDir/generated/openapi"
    val corePackage = "ru.tinkoff.kora.example.openapi"
    apiPackage = "${corePackage}.api"
    modelPackage = "${corePackage}.model"
    invokerPackage = "${corePackage}.invoker"
    openapiNormalizer = mapOf(
        "DISABLE_ALL" to "true"
    )
    configOptions = mapOf(
        "mode" to "kotlin-client",
        "clientConfigPrefix" to "httpClient.myclient",
        "tags" to """{
                        "*": { // применится для всех тегов, кроме явно указанных (в данном случае instrument)
                          "httpClientTag": "some.tag.Common",
                          "telemetryTag": "some.tag.Common"
                        },
                        "instrument": { // применится для instrument
                          "httpClientTag": "some.tag.Instrument",
                          "telemetryTag": "some.tag.Instrument"
                        }
                     }
                     """
    )
}
kotlin.sourceSets.main { kotlin.srcDir(openApiGenerateHttpClient.get().outputDir) }
tasks.withType<KspTask> { dependsOn(openApiGenerateHttpClient) }

Server

A minimal example of configuring a plugin to create HTTP server handlers:

Available Kora plugin parameters:

  • enableServerValidation - whether to create validators according to the OpenAPI secification description for the server and whether to enable validation on HTTP handlers: true, false.
  • requestInDelegateParams - whether to expected HttpServerRequest as a method argument: true, false
  • interceptors - ability to specify interceptors for HTTP controllers
  • additionalContractAnnotations - ability to specify additional annotations for controller methods
  • enableJsonNullable - Treat nullable=true and required=false schema fields as a JsonNullable wrapper
  • filterWithModels - filter and exclude also unnecessary models from generation when the FILTER option in openapiNormalizer is specified
  • prefixPath - path prefix for HTTP-server controllers
  • mode in which mode the generator should operate, available values:
    • java-server - create a synchronous server
    • java-async-server - create a CompletionStage server
    • java-reactive-server - create a reactive server, you need to connect Project Reactor yourself.
def openApiGenerateHttpServer = tasks.register("openApiGenerateHttpServer", GenerateTask) {
    generatorName = "kora"
    group = "openapi tools"
    inputSpec = "$projectDir/src/main/resources/openapi/openapi.yaml" //(1)!
    outputDir = "$buildDir/generated/openapi" //(2)!  
    def corePackage = "ru.tinkoff.kora.example.openapi"
    apiPackage = "${corePackage}.api" //(3)!
    modelPackage = "${corePackage}.model" //(4)!
    invokerPackage = "${corePackage}.invoker" //(5)!
    openapiNormalizer = [
        DISABLE_ALL: "true"
    ]
    configOptions = [
        mode: "java-server" //(6)!
    ]
}
sourceSets.main { java.srcDirs += openApiGenerateHttpServer.get().outputDir } //(7)!
compileJava.dependsOn openApiGenerateHttpServer //(8)!
  1. Path to OpenAPI file from which classes will be created
  2. Directory where the files will be created
  3. Package from classes of delegates, controllers, converters, etc.
  4. Package from classes of models, DTOs, etc.
  5. Package from calling classes
  6. Mode of plugin operation (creating Java client / Kotlin / Java server, etc.)
  7. Register the generated classes as the source code of the project
  8. Make code compilation dependent on HTTP client class generation (first generate, then compile)

Available Kora plugin parameters:

  • enableServerValidation - whether to create validators according to the OpenAPI secification description for the server and whether to enable validation on HTTP handlers: true, false.
  • requestInDelegateParams - whether to expected HttpServerRequest as a method argument: true, false
  • interceptors - ability to specify interceptors for HTTP controllers
  • additionalContractAnnotations - ability to specify additional annotations for controller methods
  • enableJsonNullable - Treat nullable=true and required=false schema fields as a JsonNullable wrapper
  • filterWithModels - filter and exclude also unnecessary models from generation when the FILTER option in openapiNormalizer is specified
  • prefixPath - path prefix for HTTP-server controllers
  • mode in which mode the generator should operate, available values:
    • kotlin-server - create synchronous server
    • kotlin-suspend-server - create suspend server
val openApiGenerateHttpServer = tasks.register<GenerateTask>("openApiGenerateHttpServer") {
    generatorName = "kora"
    group = "openapi tools"
    inputSpec = "$projectDir/src/main/resources/openapi/openapi.yaml" //(1)!
    outputDir = "$buildDir/generated/openapi" //(2)!
    val corePackage = "ru.tinkoff.kora.example.openapi"
    apiPackage = "${corePackage}.api" //(3)!
    modelPackage = "${corePackage}.model" //(4)!
    invokerPackage = "${corePackage}.invoker" //(5)!
    openapiNormalizer = mapOf(
        "DISABLE_ALL" to "true"
    )
    configOptions = mapOf(
        "mode" to "kotlin-server" //(6)!
    )
}
kotlin.sourceSets.main { kotlin.srcDir(openApiGenerateHttpServer.get().outputDir) } //(7)!
tasks.withType<KspTask> { dependsOn(openApiGenerateHttpServer) } //(8)!
  1. Path to OpenAPI file from which classes will be created
  2. Directory where the files will be created
  3. Package from classes of delegates, controllers, converters, etc.
  4. Package from classes of models, DTOs, etc.
  5. Package from calling classes
  6. Mode of plugin operation (creating Java client / Kotlin / Java server, etc.)
  7. Register the generated classes as the source code of the project
  8. Make code compilation dependent on HTTP client class generation (first generate, then compile)

Once created, the handlers will be automatically registered.

Validation

In order to generate models and controllers with annotations from the validation module, the enableServerValidation option must be set:

def openApiGenerateHttpServer = tasks.register("openApiGenerateHttpServer", GenerateTask) {
    generatorName = "kora"
    group = "openapi tools"
    inputSpec = "$projectDir/src/main/resources/openapi/openapi.yaml"
    outputDir = "$buildDir/generated/openapi"
    def corePackage = "ru.tinkoff.kora.example.openapi"
    apiPackage = "${corePackage}.api"
    modelPackage = "${corePackage}.model"
    invokerPackage = "${corePackage}.invoker"
    openapiNormalizer = [
        DISABLE_ALL: "true"
    ]
    configOptions = [
        mode: "java-server",
        enableServerValidation: "true"  //(1)!
    ]
}
sourceSets.main { java.srcDirs += openApiGenerateHttpServer.get().outputDir }
compileJava.dependsOn openApiGenerateHttpServer
  1. Enabling validation on the HTTP server controller side
val openApiGenerateHttpServer = tasks.register<GenerateTask>("openApiGenerateHttpServer") {
    generatorName = "kora"
    group = "openapi tools"
    inputSpec = "$projectDir/src/main/resources/openapi/openapi.yaml"
    outputDir = "$buildDir/generated/openapi"
    val corePackage = "ru.tinkoff.kora.example.openapi"
    apiPackage = "${corePackage}.api"
    modelPackage = "${corePackage}.model"
    invokerPackage = "${corePackage}.invoker"
    openapiNormalizer = mapOf(
        "DISABLE_ALL" to "true"
    )
    configOptions = mapOf(
        "mode" to "kotlin-server",
        "enableServerValidation" to "true" //(1)!
    )
}
kotlin.sourceSets.main { kotlin.srcDir(openApiGenerateHttpServer.get().outputDir) }
tasks.withType<KspTask> { dependsOn(openApiGenerateHttpServer) }
  1. Enabling validation on the HTTP server controller side

Interceptors

It is possible to put interceptors on created controllers with @HttpController annotation.

The value is a Json object whose key is the api tag from the contract, and the value is an object with type and tag fields, it is possible to specify both fields at the same time, or optionally one of them:

  • type - the implementation class of a particular interceptor
  • tag - tags of the interceptor (can be specified as an array of strings).

In order to do this, set the configOptions.interceptors parameter:

def openApiGenerateHttpServer = tasks.register("openApiGenerateHttpServer", GenerateTask) {
    generatorName = "kora"
    group = "openapi tools"
    inputSpec = "$projectDir/src/main/resources/openapi/openapi.yaml"
    outputDir = "$buildDir/generated/openapi"
    def corePackage = "ru.tinkoff.kora.example.openapi"
    apiPackage = "${corePackage}.api"
    modelPackage = "${corePackage}.model"
    invokerPackage = "${corePackage}.invoker"
    openapiNormalizer = [
        DISABLE_ALL: "true"
    ]
    configOptions = [
        mode: "java-server",
        interceptors: """
                {
                  "*": [
                    {
                      "tag": "ru.tinkoff.example.MyTag"
                    }
                  ],
                  "pet": [
                    {
                      "type": "ru.tinkoff.example.MyInterceptor"
                    }
                  ],
                  "shop": [
                    {
                      "type": "ru.tinkoff.example.MyInterceptor",
                      "tag": "ru.tinkoff.example.MyTag"
                    }
                  ]
                }
                """
    ]
}
sourceSets.main { java.srcDirs += openApiGenerateHttpServer.get().outputDir }
compileJava.dependsOn openApiGenerateHttpServer
val openApiGenerateHttpServer = tasks.register<GenerateTask>("openApiGenerateHttpServer") {
    generatorName = "kora"
    group = "openapi tools"
    inputSpec = "$projectDir/src/main/resources/openapi/openapi.yaml"
    outputDir = "$buildDir/generated/openapi"
    val corePackage = "ru.tinkoff.kora.example.openapi"
    apiPackage = "${corePackage}.api"
    modelPackage = "${corePackage}.model"
    invokerPackage = "${corePackage}.invoker"
    openapiNormalizer = mapOf(
        "DISABLE_ALL" to "true"
    )
    configOptions = mapOf(
        "mode" to "kotlin-server",
        "interceptors" to """{
                  "*": [
                    {
                      "tag": "ru.tinkoff.example.MyTag"
                    }
                  ],
                  "pet": [
                    {
                      "type": "ru.tinkoff.example.MyInterceptor"
                    }
                  ],
                  "shop": [
                    {
                      "type": "ru.tinkoff.example.MyInterceptor",
                      "tag": "ru.tinkoff.example.MyTag"
                    }
                  ]
                }
                """
    )
}
kotlin.sourceSets.main { kotlin.srcDir(openApiGenerateHttpServer.get().outputDir) }
tasks.withType<KspTask> { dependsOn(openApiGenerateHttpServer) }

Authorization

Kora provides an interface to extract authorization information within the interceptor, created for the server from OpenAPI, you can pull any type of authorization Basic/ApiKey/Bearer/OAuth

@Module
public interface AuthModule {

    @Tag(ApiSecurity.BearerAuth.class)
    default HttpServerPrincipalExtractor<Principal> bearerHttpServerPrincipalExtractor() {
        return (request, value) -> CompletableFuture.completedFuture(new MyPrincipal(request.headers().getFirst(Authorization)));
    }
}
@Module
interface AuthModule {

    @Tag(ApiSecurity.BearerAuth::class)
    fun bearerHttpServerPrincipalExtractor(): HttpServerPrincipalExtractor<Principal> {
        return HttpServerPrincipalExtractor<Principal> { request, value ->
            CompletableFuture.completedFuture<Principal>(
                MyPrincipal(request.headers().getFirst(Authorization)))
            )
        }
    }
}

Recommendations

????+ warning "Advice"

In case you have something that is not created by the plugin, or the behavior is different from what you want or other versions,
you should carefully check the [plugin configuration](#configuration) settings and examine them, 
as they may affect the results of how classes are created.

Starting with `7.0.0` version of the plugin, the `SIMPLIFY_ONEOF_ANYOF` rule enabled by default at the `openapiNormalizer` parameter 
may lead to some not obvious generator results.