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
:
Plugin dependency build.gradle
:
Dependency build.gradle.kts
:
Plugin dependency build.gradle.kts
:
Requires HTTP server or HTTP client module.
Configuration¶
Configuration is required for OpenAPI Generator plugin parameters:
- Configuring Gradle plugin parameters in documentation.
- Configuring
configOptions
plugin parameter in documentation. - Configuring
openapiNormalizer
plugin parameter in documentation.
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 clientstags
- possibility to put additional tags on created HTTP-clientsinterceptors
- ability to specify interceptors for HTTP clientsprimaryAuth
- specify which authorization mechanism to use as the primary one if several securitySchemes are specified in OpenAPIsecurityConfigPrefix
- 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 interceptoradditionalContractAnnotations
- ability to specify additional annotations over HTTP client methodsenableJsonNullable
- Treatnullable=true
andrequired=false
schema fields as a JsonNullable wrapperfilterWithModels
- filter and exclude also unnecessary models from generation when the FILTER option inopenapiNormalizer
is specifiedmode
in which mode the generator should operate, available values:java-client
- create synchronous clientjava-async-client
- create CompletionStage clientjava-reactive-client
- create reactive client, you need to connect Project Reactor yourself.
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)!
- Path to OpenAPI file from which classes will be created
- Directory where the files will be created
- Package from classes of delegates, controllers, converters, etc.
- Package from classes of models, DTOs, etc.
- Package from calling classes
- Mode of plugin operation (creating Java client / Kotlin / Java server, etc.)
- Prefix path to client configuration file
- Register the generated classes as the source code of the project
- Make code compilation dependent on HTTP client class generation (first generate, then compile)
Kora's available plugin options:
clientConfigPrefix
- configuration prefix of created HTTP clientstags
- possibility to put additional tags on created HTTP-clientsinterceptors
- ability to specify interceptors for HTTP clientsprimaryAuth
- specify which authorization mechanism to use as the primary one if several securitySchemes are specified in OpenAPIsecurityConfigPrefix
- 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 interceptoradditionalContractAnnotations
- ability to specify additional annotations over HTTP client methodsenableJsonNullable
- Treatnullable=true
andrequired=false
schema fields as a JsonNullable wrapperfilterWithModels
- filter and exclude also unnecessary models from generation when the FILTER option inopenapiNormalizer
is specifiedmode
in which mode the generator should operate, available values:kotlin-client
- create synchronous clientkotlin-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)!
- Path to OpenAPI file from which classes will be created
- Directory where the files will be created
- Package from classes of delegates, controllers, converters, etc.
- Package from classes of models, DTOs, etc.
- Package from calling classes
- Mode of plugin operation (creating Java client / Kotlin / Java server, etc.)
- Prefix path to client configuration file
- Register the generated classes as the source code of the project
- 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 interceptortag
- 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 expectedHttpServerRequest
as a method argument:true, false
interceptors
- ability to specify interceptors for HTTP controllersadditionalContractAnnotations
- ability to specify additional annotations for controller methodsenableJsonNullable
- Treatnullable=true
andrequired=false
schema fields as a JsonNullable wrapperfilterWithModels
- filter and exclude also unnecessary models from generation when the FILTER option inopenapiNormalizer
is specifiedprefixPath
- path prefix for HTTP-server controllersmode
in which mode the generator should operate, available values:java-server
- create a synchronous serverjava-async-server
- create a CompletionStage serverjava-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)!
- Path to OpenAPI file from which classes will be created
- Directory where the files will be created
- Package from classes of delegates, controllers, converters, etc.
- Package from classes of models, DTOs, etc.
- Package from calling classes
- Mode of plugin operation (creating Java client / Kotlin / Java server, etc.)
- Register the generated classes as the source code of the project
- 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 expectedHttpServerRequest
as a method argument:true, false
interceptors
- ability to specify interceptors for HTTP controllersadditionalContractAnnotations
- ability to specify additional annotations for controller methodsenableJsonNullable
- Treatnullable=true
andrequired=false
schema fields as a JsonNullable wrapperfilterWithModels
- filter and exclude also unnecessary models from generation when the FILTER option inopenapiNormalizer
is specifiedprefixPath
- path prefix for HTTP-server controllersmode
in which mode the generator should operate, available values:kotlin-server
- create synchronous serverkotlin-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)!
- Path to OpenAPI file from which classes will be created
- Directory where the files will be created
- Package from classes of delegates, controllers, converters, etc.
- Package from classes of models, DTOs, etc.
- Package from calling classes
- Mode of plugin operation (creating Java client / Kotlin / Java server, etc.)
- Register the generated classes as the source code of the project
- 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
- 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) }
- 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 interceptortag
- 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
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.