Repository: cjbooms/fabrikt
Pull request #221 (2023-07-05 05:12:12)

Remove redundant checks for co-occurrence of properties and combiners
#### Context
Was browsing through the codebase and found these two redundant conditions. Unless I'm missing something, these conditions are already subsumed in the `if` block above, correct? We could remove them.


Patch:
diff --git a/README.md b/README.md
index 887decf..ab983c3 100644
--- a/README.md
+++ b/README.md
@@ -193,7 +193,12 @@ This section documents the available CLI parameters for controlling what gets ge
 | * `--base-package`           | The base package which all code will be generated under. |
 |   `--http-client-opts`       | Select the options for the http client code that you want to be generated. |
 |                              | CHOOSE ANY OF: |
-|                              |   `RESILIENCE4J` - Generates a fault tolerance service for the client using the following library "io.github.resilience4j:resilience4j-all:+" |
+|                              |   `RESILIENCE4J` - Generates a fault tolerance service for the client using the following library "io.github.resilience4j:resilience4j-all:+" (only for OkHttp clients) |
+|                              |   `SUSPEND_MODIFIER` - This option adds the suspend modifier to the generated client functions (only for OpenFeign clients) |
+|   `--http-client-target`     | Optionally select the target client that you want to be generated. Defaults to OK_HTTP |
+|                              | CHOOSE ONE OF: |
+|                              |   `OK_HTTP` - Generate OkHttp client. |
+|                              |   `OPEN_FEIGN` - Generate OpenFeign client. |
 |   `--http-controller-opts`   | Select the options for the controllers that you want to be generated. |
 |                              | CHOOSE ANY OF: |
 |                              |   `SUSPEND_MODIFIER` - This option adds the suspend modifier to the generated controller functions |
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGen.kt b/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGen.kt
index 36bde80..f34424b 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGen.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGen.kt
@@ -29,6 +29,7 @@ object CodeGen {
             codeGenArgs.controllerTarget,
             codeGenArgs.modelOptions,
             codeGenArgs.clientOptions,
+            codeGenArgs.clientTarget,
             codeGenArgs.typeOverrides,
             codeGenArgs.srcPath,
             codeGenArgs.resourcesPath,
@@ -46,12 +47,13 @@ object CodeGen {
         controllerTarget: ControllerCodeGenTargetType,
         modelOptions: Set<ModelCodeGenOptionType>,
         clientOptions: Set<ClientCodeGenOptionType>,
+        clientTarget: ClientCodeGenTargetType,
         typeOverrides: Set<CodeGenTypeOverride>,
         srcPath: Path,
         resourcesPath: Path,
         validationLibrary: ValidationLibrary
     ) {
-        MutableSettings.updateSettings(codeGenTypes, controllerOptions, controllerTarget, modelOptions, clientOptions, typeOverrides, validationLibrary)
+        MutableSettings.updateSettings(codeGenTypes, controllerOptions, controllerTarget, modelOptions, clientOptions, clientTarget, typeOverrides, validationLibrary)
 
         val suppliedApi = pathToApi.toFile().readText()
         val baseDir = pathToApi.parent
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenArgs.kt b/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenArgs.kt
index 8648045..b4f30f8 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenArgs.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenArgs.kt
@@ -106,6 +106,13 @@ class CodeGenArgs {
     )
     var clientOptions: Set<ClientCodeGenOptionType> = emptySet()
 
+    @Parameter(
+        names = ["--http-client-target"],
+        description = "Optionally select the target client that you want to be generated. Defaults to OK_HTTP",
+        converter = ClientCodeGenTargetConverter::class
+    )
+    var clientTarget: ClientCodeGenTargetType = ClientCodeGenTargetType.OK_HTTP
+
     @Parameter(
         names = ["--src-path"],
         description = "Allows the path for generated source files to be overridden. Defaults to `src/main/kotlin`",
@@ -157,6 +164,10 @@ class ClientCodeGenOptionConverter : IStringConverter<ClientCodeGenOptionType> {
         convertToEnumValue(value)
 }
 
+class ClientCodeGenTargetConverter : IStringConverter<ClientCodeGenTargetType> {
+    override fun convert(value: String): ClientCodeGenTargetType = convertToEnumValue(value)
+}
+
 class ValidationLibraryOptionConverter : IStringConverter<ValidationLibrary> {
     override fun convert(value: String): ValidationLibrary = convertToEnumValue(value)
 }
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenOptions.kt b/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenOptions.kt
index 7a9aefe..0bdf7ea 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenOptions.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenOptions.kt
@@ -22,7 +22,15 @@ enum class CodeGenerationType(val description: String) {
 }
 
 enum class ClientCodeGenOptionType(private val description: String) {
-    RESILIENCE4J("Generates a fault tolerance service for the client using the following library \"io.github.resilience4j:resilience4j-all:+\"");
+    RESILIENCE4J("Generates a fault tolerance service for the client using the following library \"io.github.resilience4j:resilience4j-all:+\" (only for OkHttp clients)"),
+    SUSPEND_MODIFIER("This option adds the suspend modifier to the generated client functions (only for OpenFeign clients)");
+
+    override fun toString() = "`${super.toString()}` - $description"
+}
+
+enum class ClientCodeGenTargetType(val description: String) {
+    OK_HTTP("Generate OkHttp client."),
+    OPEN_FEIGN("Generate OpenFeign client.");
 
     override fun toString() = "`${super.toString()}` - $description"
 }
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenerator.kt b/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenerator.kt
index 5186e29..4da27b7 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenerator.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenerator.kt
@@ -7,6 +7,7 @@ import com.cjbooms.fabrikt.cli.CodeGenerationType.QUARKUS_REFLECTION_CONFIG
 import com.cjbooms.fabrikt.configurations.Packages
 import com.cjbooms.fabrikt.generators.MutableSettings
 import com.cjbooms.fabrikt.generators.client.OkHttpClientGenerator
+import com.cjbooms.fabrikt.generators.client.OpenFeignInterfaceGenerator
 import com.cjbooms.fabrikt.generators.controller.MicronautControllerInterfaceGenerator
 import com.cjbooms.fabrikt.generators.controller.SpringControllerInterfaceGenerator
 import com.cjbooms.fabrikt.generators.model.JacksonModelGenerator
@@ -45,8 +46,14 @@ class CodeGenerator(
         sourceSet(controllers().files).plus(sourceSet(models().files))
 
     private fun generateClient(): Collection<GeneratedFile> {
-        val lib = OkHttpClientGenerator(packages, sourceApi, srcPath).generateLibrary(MutableSettings.clientOptions())
-        return sourceSet(client().files).plus(lib).plus(sourceSet(models().files))
+        val clientGenerator = when (MutableSettings.clientTarget()) {
+            ClientCodeGenTargetType.OK_HTTP -> OkHttpClientGenerator(packages, sourceApi, srcPath)
+            ClientCodeGenTargetType.OPEN_FEIGN -> OpenFeignInterfaceGenerator(packages, sourceApi)
+        }
+        val options = MutableSettings.clientOptions()
+        val clientFiles = clientGenerator.generate(options).files
+        val libFiles = clientGenerator.generateLibrary(options)
+        return sourceSet(clientFiles).plus(libFiles).plus(sourceSet(models().files))
     }
 
     private fun generateQuarkusReflectionResource(): Collection<GeneratedFile> = resourceSet(resources(models()))
@@ -78,7 +85,4 @@ class CodeGenerator(
             }
         return generator.generate()
     }
-
-    private fun client(): Clients =
-        OkHttpClientGenerator(packages, sourceApi, srcPath).generate(MutableSettings.clientOptions())
 }
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/generators/GeneratorUtils.kt b/src/main/kotlin/com/cjbooms/fabrikt/generators/GeneratorUtils.kt
index a8d7461..687806a 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/generators/GeneratorUtils.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/GeneratorUtils.kt
@@ -131,8 +131,8 @@ object GeneratorUtils {
 
     fun Operation.hasMultipleContentMediaTypes(): Boolean? = this.firstResponse()?.hasMultipleContentMediaTypes()
 
-    fun Operation.hasMultipleResponseSchemas(): Boolean =
-            getBodyResponses().flatMap { it.contentMediaTypes.values }.map { it.schema.name }.distinct().size > 1
+    fun Operation.hasMultipleSuccessResponseSchemas(): Boolean =
+            getBodySuccessResponses().flatMap { it.contentMediaTypes.values }.map { it.schema.name }.distinct().size > 1
 
     fun Operation.getPathParams(): List<Parameter> = this.filterParams("path")
 
@@ -143,6 +143,12 @@ object GeneratorUtils {
     private fun Operation.getBodyResponses(): List<Response> =
         this.responses.filter { it.key != "default" }.values.filter(Response::hasContentMediaTypes)
 
+    private fun Operation.getBodySuccessResponses(): List<Response> =
+        getSuccessResponses().values.filter(Response::hasContentMediaTypes)
+
+    private fun Operation.getSuccessResponses(): Map<String, Response> =
+        this.responses.filter { it.key.toIntOrNull()?.let { status -> status in 200..399 } ?: false }
+
     private fun Operation.filterParams(paramType: String): List<Parameter> = this.parameters.filter { it.`in` == paramType }
 
     /**
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/generators/MutableSettings.kt b/src/main/kotlin/com/cjbooms/fabrikt/generators/MutableSettings.kt
index 1d253a3..f3b85e2 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/generators/MutableSettings.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/MutableSettings.kt
@@ -8,6 +8,7 @@ object MutableSettings {
     private lateinit var controllerTarget: ControllerCodeGenTargetType
     private lateinit var modelOptions: MutableSet<ModelCodeGenOptionType>
     private lateinit var clientOptions: MutableSet<ClientCodeGenOptionType>
+    private lateinit var clientTarget: ClientCodeGenTargetType
     private lateinit var typeOverrides: MutableSet<CodeGenTypeOverride>
     private lateinit var validationLibrary: ValidationLibrary
 
@@ -17,6 +18,7 @@ object MutableSettings {
         controllerTarget: ControllerCodeGenTargetType = ControllerCodeGenTargetType.SPRING,
         modelOptions: Set<ModelCodeGenOptionType> = emptySet(),
         clientOptions: Set<ClientCodeGenOptionType> = emptySet(),
+        clientTarget: ClientCodeGenTargetType = ClientCodeGenTargetType.OK_HTTP,
         typeOverrides: Set<CodeGenTypeOverride> = emptySet(),
         validationLibrary: ValidationLibrary = ValidationLibrary.JAVAX_VALIDATION
     ) {
@@ -25,6 +27,7 @@ object MutableSettings {
         this.controllerTarget = controllerTarget
         this.modelOptions = modelOptions.toMutableSet()
         this.clientOptions = clientOptions.toMutableSet()
+        this.clientTarget = clientTarget
         this.typeOverrides = typeOverrides.toMutableSet()
         this.validationLibrary = validationLibrary
     }
@@ -37,6 +40,7 @@ object MutableSettings {
     fun controllerTarget() = this.controllerTarget
     fun modelOptions() = this.modelOptions.toSet()
     fun clientOptions() = this.clientOptions.toSet()
+    fun clientTarget() = this.clientTarget
     fun typeOverrides() = this.typeOverrides.toSet()
     fun validationLibrary() = this.validationLibrary
 }
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/generators/client/ClientGenerator.kt b/src/main/kotlin/com/cjbooms/fabrikt/generators/client/ClientGenerator.kt
new file mode 100644
index 0000000..94733de
--- /dev/null
+++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/client/ClientGenerator.kt
@@ -0,0 +1,10 @@
+package com.cjbooms.fabrikt.generators.client
+
+import com.cjbooms.fabrikt.cli.ClientCodeGenOptionType
+import com.cjbooms.fabrikt.model.Clients
+import com.cjbooms.fabrikt.model.GeneratedFile
+
+interface ClientGenerator {
+    fun generate(options: Set<ClientCodeGenOptionType>): Clients
+    fun generateLibrary(options: Set<ClientCodeGenOptionType>): Collection<GeneratedFile>
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/generators/client/ClientGeneratorUtils.kt b/src/main/kotlin/com/cjbooms/fabrikt/generators/client/ClientGeneratorUtils.kt
index b26137a..31b9892 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/generators/client/ClientGeneratorUtils.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/client/ClientGeneratorUtils.kt
@@ -5,7 +5,7 @@ import com.cjbooms.fabrikt.generators.GeneratorUtils
 import com.cjbooms.fabrikt.generators.GeneratorUtils.getPrimaryContentMediaType
 import com.cjbooms.fabrikt.generators.GeneratorUtils.getPrimaryContentMediaTypeKey
 import com.cjbooms.fabrikt.generators.GeneratorUtils.hasMultipleContentMediaTypes
-import com.cjbooms.fabrikt.generators.GeneratorUtils.hasMultipleResponseSchemas
+import com.cjbooms.fabrikt.generators.GeneratorUtils.hasMultipleSuccessResponseSchemas
 import com.cjbooms.fabrikt.generators.GeneratorUtils.toClassName
 import com.cjbooms.fabrikt.generators.GeneratorUtils.toIncomingParameters
 import com.cjbooms.fabrikt.generators.OasDefault
@@ -18,6 +18,7 @@ import com.cjbooms.fabrikt.model.RequestParameter
 import com.fasterxml.jackson.databind.JsonNode
 import com.reprezen.kaizen.oasparser.model3.Operation
 import com.reprezen.kaizen.oasparser.model3.Path
+import com.squareup.kotlinpoet.AnnotationSpec
 import com.squareup.kotlinpoet.FunSpec
 import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
 import com.squareup.kotlinpoet.TypeName
@@ -34,20 +35,21 @@ object ClientGeneratorUtils {
      * If there are several possible response schemas, then the return type is JsonNode, so they are all covered.
      * If no response body is found, Unit is returned.
      */
-    fun Operation.toClientReturnType(packages: Packages): TypeName {
-        val returnType =
-                if (hasMultipleResponseSchemas()) {
-                    JsonNode::class.asTypeName()
-                } else {
-                    this.getPrimaryContentMediaType()?.let {
-                        toModelType(
-                                packages.base,
-                                KotlinTypeInfo.from(it.value.schema)
-                        )
-                    } ?: Unit::class.asTypeName()
-                }
+    fun Operation.getReturnType(packages: Packages): TypeName {
+        return if (hasMultipleSuccessResponseSchemas()) {
+                JsonNode::class.asTypeName()
+            } else {
+                this.getPrimaryContentMediaType()?.let {
+                    toModelType(
+                        packages.base,
+                        KotlinTypeInfo.from(it.value.schema)
+                    )
+                } ?: Unit::class.asTypeName()
+            }
+    }
 
-        return "ApiResponse".toClassName(packages.client).parameterizedBy(returnType)
+    fun Operation.toClientReturnType(packages: Packages): TypeName {
+        return "ApiResponse".toClassName(packages.client).parameterizedBy(getReturnType(packages))
     }
 
     fun simpleClientName(resourceName: String) = "$resourceName${ClientType.SIMPLE_CLIENT_SUFFIX}"
@@ -83,13 +85,19 @@ object ClientGeneratorUtils {
         )
     }
 
-    fun FunSpec.Builder.addIncomingParameters(parameters: List<IncomingParameter>): FunSpec.Builder {
+    fun FunSpec.Builder.addIncomingParameters(
+        parameters: List<IncomingParameter>,
+        annotateRequestParameterWith: ((parameter: RequestParameter) -> AnnotationSpec?)? = null
+    ): FunSpec.Builder {
         val specs = parameters.map {
             val builder = it.toParameterSpecBuilder()
             if (it is RequestParameter) {
                 if (it.defaultValue != null) OasDefault.from(it.typeInfo, it.type, it.defaultValue)
                     ?.let { builder.defaultValue(it.getDefault()) }
                 else if (!it.isRequired) builder.defaultValue("null")
+                annotateRequestParameterWith?.invoke(it)?.let { annotationSpec ->
+                    builder.addAnnotation(annotationSpec)
+                }
             }
             builder.build()
         }
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/generators/client/OkHttpClientGenerator.kt b/src/main/kotlin/com/cjbooms/fabrikt/generators/client/OkHttpClientGenerator.kt
index c874123..d10b147 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/generators/client/OkHttpClientGenerator.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/client/OkHttpClientGenerator.kt
@@ -11,18 +11,18 @@ class OkHttpClientGenerator(
     packages: Packages,
     api: SourceApi,
     srcPath: Path,
-) {
+) : ClientGenerator {
     private val simpleClientGenerator = OkHttpSimpleClientGenerator(packages, api, srcPath)
     private val enhancedClientGenerator = OkHttpEnhancedClientGenerator(packages, api, srcPath)
 
-    fun generate(options: Set<ClientCodeGenOptionType>): Clients {
+    override fun generate(options: Set<ClientCodeGenOptionType>): Clients {
         val simpleClient = simpleClientGenerator.generateDynamicClientCode()
         val enhancedClient = enhancedClientGenerator.generateDynamicClientCode(options)
 
         return Clients(enhancedClient.plus(simpleClient).toSet())
     }
 
-    fun generateLibrary(options: Set<ClientCodeGenOptionType>): Collection<GeneratedFile> {
+    override fun generateLibrary(options: Set<ClientCodeGenOptionType>): Collection<GeneratedFile> {
         val simpleClientLibrary = simpleClientGenerator.generateLibrary()
         val enhancedClientLibrary = enhancedClientGenerator.generateLibrary(options)
 
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/generators/client/OpenFeignInterfaceGenerator.kt b/src/main/kotlin/com/cjbooms/fabrikt/generators/client/OpenFeignInterfaceGenerator.kt
new file mode 100644
index 0000000..9a5a66f
--- /dev/null
+++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/client/OpenFeignInterfaceGenerator.kt
@@ -0,0 +1,269 @@
+package com.cjbooms.fabrikt.generators.client
+
+import com.cjbooms.fabrikt.cli.ClientCodeGenOptionType
+import com.cjbooms.fabrikt.configurations.Packages
+import com.cjbooms.fabrikt.generators.GeneratorUtils.functionName
+import com.cjbooms.fabrikt.generators.GeneratorUtils.getPrimaryContentMediaType
+import com.cjbooms.fabrikt.generators.GeneratorUtils.toKdoc
+import com.cjbooms.fabrikt.generators.TypeFactory
+import com.cjbooms.fabrikt.generators.client.ClientGeneratorUtils.ADDITIONAL_HEADERS_PARAMETER_NAME
+import com.cjbooms.fabrikt.generators.client.ClientGeneratorUtils.addIncomingParameters
+import com.cjbooms.fabrikt.generators.client.ClientGeneratorUtils.deriveClientParameters
+import com.cjbooms.fabrikt.generators.client.ClientGeneratorUtils.getReturnType
+import com.cjbooms.fabrikt.generators.client.ClientGeneratorUtils.simpleClientName
+import com.cjbooms.fabrikt.generators.client.metadata.OpenFeignAnnotations
+import com.cjbooms.fabrikt.model.ClientType
+import com.cjbooms.fabrikt.model.Clients
+import com.cjbooms.fabrikt.model.GeneratedFile
+import com.cjbooms.fabrikt.model.HeaderParam
+import com.cjbooms.fabrikt.model.IncomingParameter
+import com.cjbooms.fabrikt.model.KotlinTypeInfo
+import com.cjbooms.fabrikt.model.PathParam
+import com.cjbooms.fabrikt.model.QueryParam
+import com.cjbooms.fabrikt.model.RequestParameter
+import com.cjbooms.fabrikt.model.SourceApi
+import com.cjbooms.fabrikt.util.KaizenParserExtensions.routeToPaths
+import com.cjbooms.fabrikt.util.toUpperCase
+import com.reprezen.kaizen.oasparser.model3.Operation
+import com.reprezen.kaizen.oasparser.model3.Path
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.FunSpec
+import com.squareup.kotlinpoet.KModifier
+import com.squareup.kotlinpoet.ParameterSpec
+import com.squareup.kotlinpoet.TypeSpec
+import com.squareup.kotlinpoet.asTypeName
+import com.squareup.kotlinpoet.buildCodeBlock
+
+class OpenFeignInterfaceGenerator(
+    private val packages: Packages,
+    private val api: SourceApi,
+) : ClientGenerator {
+    override fun generate(options: Set<ClientCodeGenOptionType>): Clients {
+        val clientTypes = api.openApi3.routeToPaths().map { (resourceName, paths) ->
+            val funcSpecs: List<FunSpec> = paths.flatMap { (resource, path) ->
+                path.operations.map { (verb, operation) ->
+                    buildFunction(path, resource, operation, verb, options)
+                }
+            }
+
+            val clientType = TypeSpec.interfaceBuilder(simpleClientName(resourceName))
+                .addAnnotation(AnnotationSpec.builder(Suppress::class).addMember("%S", "unused").build())
+                .addFunctions(funcSpecs)
+                .build()
+
+            ClientType(clientType, packages.base)
+        }.toSet()
+
+        return Clients(clientTypes)
+    }
+
+    override fun generateLibrary(options: Set<ClientCodeGenOptionType>): Collection<GeneratedFile> = emptyList()
+
+    private fun buildFunction(
+        path: Path,
+        resource: String,
+        operation: Operation,
+        verb: String,
+        options: Set<ClientCodeGenOptionType>,
+    ): FunSpec {
+        val parameters = deriveClientParameters(path, operation, packages.base)
+        return FunSpec
+            .builder(functionName(operation, resource, verb))
+            .addModifiers(KModifier.ABSTRACT)
+            .addKdoc(operation.toKdoc(parameters))
+            .addRequestLineAnnotation(resource, verb, parameters)
+            .addHeadersAnnotation(operation, parameters)
+            .addSuspendModifier(options)
+            .addIncomingParameters(
+                parameters,
+                annotateRequestParameterWith = { parameter ->
+                    OpenFeignAnnotations.paramBuilder()
+                        .addMember("%S", parameter.name)
+                        .build()
+                },
+            )
+            .addParameter(
+                ParameterSpec.builder(
+                    ADDITIONAL_HEADERS_PARAMETER_NAME,
+                    TypeFactory.createMapOfStringToType(String::class.asTypeName()),
+                )
+                    .addAnnotation(OpenFeignAnnotations.HEADER_MAP)
+                    .defaultValue("emptyMap()")
+                    .build(),
+            )
+            .returns(operation.getReturnType(packages))
+            .build()
+    }
+
+    /**
+     * Adds OpenFeign's @RequestLine annotation to the function spec
+     */
+    private fun FunSpec.Builder.addRequestLineAnnotation(
+        resource: String,
+        verb: String,
+        parameters: List<IncomingParameter>,
+    ): FunSpec.Builder {
+        val annotation = RequestLineBuilder(resource, verb, parameters).build()
+        addAnnotation(annotation)
+        return this
+    }
+
+    private class RequestLineBuilder(
+        private val resource: String,
+        private val verb: String,
+        private val parameters: List<IncomingParameter>,
+    ) {
+        /**
+         * Gives the url path so that it can be used as input for OpenFeign's @RequestLine.
+         * For query and path variables, the deduplicated parameter name will be used instead of the original name.
+         */
+        private fun buildUrlPath(): String {
+            val (pathParameters, queryParameters) = parameters.getPathAndQueryParameters()
+            val resolvedResource = resourceWithDeduplicatedParamNames(resource, pathParameters)
+            return if (queryParameters.isNotEmpty()) {
+                "$resolvedResource?${queryParameters.joinToString("&") { "${it.originalName}={${it.name}}" }}"
+            } else {
+                resolvedResource
+            }
+        }
+
+        private fun List<IncomingParameter>.getPathAndQueryParameters():
+            Pair<List<RequestParameter>, List<RequestParameter>> {
+            val queryParameters = mutableListOf<RequestParameter>()
+            val pathVariables = mutableListOf<RequestParameter>()
+            for (parameter in this) {
+                if (parameter is RequestParameter) {
+                    when (parameter.parameterLocation) {
+                        is QueryParam -> queryParameters.add(parameter)
+                        is PathParam -> pathVariables.add(parameter)
+                        else -> {
+                            // ignore
+                        }
+                    }
+                }
+            }
+            return Pair(pathVariables, queryParameters)
+        }
+
+        private fun resourceWithDeduplicatedParamNames(
+            resource: String,
+            pathParameters: List<RequestParameter>,
+        ): String {
+            var resolvedResource = resource
+            pathParameters.forEach { parameter ->
+                if (parameter.originalName != parameter.name) {
+                    resolvedResource = resolvedResource.replace("{${parameter.originalName}}", "{${parameter.name}}")
+                }
+            }
+            return resolvedResource
+        }
+
+        fun build(): AnnotationSpec {
+            val urlPath = buildUrlPath()
+            return OpenFeignAnnotations.requestLineBuilder()
+                .addMember("%S", "${verb.toUpperCase()} $urlPath")
+                .build()
+        }
+    }
+
+    /**
+     * Adds OpenFeign @Headers annotation to the function spec if any headers are detected.
+     * Formatting:
+     *  - If only one header is used, it will be formatted like @Headers("Accept: application/json").
+     *  - If multiple headers are used, it will be formatted as an array like
+     *    @Headers(["Brand: {brand}", "Accept: application/json"]).
+     * Defined as constant when:
+     * - If the defined header in the OAS spec is an enum with only one value, the header will be set as a constant.
+     * - If it is the added accepted header
+     * Accept header is added when:
+     * - If no Accept header is specified yet, then it will be added based on the primary content media type
+     *   as specified in the response.
+     */
+    private fun FunSpec.Builder.addHeadersAnnotation(
+        operation: Operation,
+        parameters: List<IncomingParameter>,
+    ): FunSpec.Builder {
+        HeadersAnnotationBuilder(operation, parameters).build()?.let { annotation ->
+            addAnnotation(annotation)
+        }
+
+        return this
+    }
+
+    private class HeadersAnnotationBuilder(
+        private val operation: Operation,
+        private val parameters: List<IncomingParameter>,
+    ) {
+        fun build(): AnnotationSpec? {
+            val headersValueParts = mutableListOf<String>()
+            var acceptHeaderExists = false
+            val headerParameters = parameters.getHeaderParameters()
+            for (parameter in headerParameters) {
+                headersValueParts.add(buildHeadersAnnotationValue(parameter))
+                acceptHeaderExists =
+                    acceptHeaderExists || parameter.name == ClientGeneratorUtils.ACCEPT_HEADER_NAME
+            }
+            // Add default accept header
+            if (!acceptHeaderExists) {
+                getDefaultAcceptHeaderAnnotationValue()?.let {
+                    headersValueParts.add(it)
+                }
+            }
+
+            return if (headersValueParts.isNotEmpty()) {
+                val format = if (headersValueParts.size == 1) "%L" else "value = [%L]"
+                OpenFeignAnnotations.headersBuilder()
+                    .addMember(format, headersValueParts.joinToString(", "))
+                    .build()
+            } else {
+                null
+            }
+        }
+
+        private fun List<IncomingParameter>.getHeaderParameters(): List<RequestParameter> {
+            val headerRequestParameters = mutableListOf<RequestParameter>()
+            for (parameter in this) {
+                if (parameter is RequestParameter && parameter.parameterLocation is HeaderParam) {
+                    headerRequestParameters.add(parameter)
+                }
+            }
+            return headerRequestParameters
+        }
+
+        private fun buildHeadersAnnotationValue(parameter: RequestParameter): String {
+            val parameterValue =
+                if (
+                    parameter.typeInfo is KotlinTypeInfo.Enum &&
+                    parameter.typeInfo.entries.size == 1
+                ) {
+                    parameter.typeInfo.entries.first()
+                } else {
+                    "{${parameter.name}}"
+                }
+            return buildCodeBlock {
+                add("%S", "${parameter.originalName}: $parameterValue")
+            }.toString()
+        }
+
+        private fun getDefaultAcceptHeaderAnnotationValue(): String? {
+            return operation.getPrimaryContentMediaType()?.key?.let { mediaType ->
+                buildCodeBlock {
+                    add(
+                        "%S",
+                        "${ClientGeneratorUtils.ACCEPT_HEADER_NAME}: $mediaType",
+                    )
+                }.toString()
+            }
+        }
+    }
+
+    /**
+     * Adds suspend as modified to the func spec so that i can be used with CoroutineFeign
+     */
+    private fun FunSpec.Builder.addSuspendModifier(options: Set<ClientCodeGenOptionType>): FunSpec.Builder {
+        if (options.contains(ClientCodeGenOptionType.SUSPEND_MODIFIER)) {
+            this.addModifiers(KModifier.SUSPEND)
+        }
+        return this
+    }
+}
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/generators/client/metadata/OpenFeign.kt b/src/main/kotlin/com/cjbooms/fabrikt/generators/client/metadata/OpenFeign.kt
new file mode 100644
index 0000000..fdff24a
--- /dev/null
+++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/client/metadata/OpenFeign.kt
@@ -0,0 +1,38 @@
+package com.cjbooms.fabrikt.generators.client.metadata
+
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.ClassName
+
+object OpenFeignImports {
+
+    private object Packages {
+        const val FEIGN = "feign"
+    }
+
+    val REQUEST_LINE = ClassName(Packages.FEIGN, "RequestLine")
+
+    val HEADERS = ClassName(Packages.FEIGN, "Headers")
+
+    val HEADER_MAP = ClassName(Packages.FEIGN, "HeaderMap")
+
+    val PARAM = ClassName(Packages.FEIGN, "Param")
+}
+
+object OpenFeignAnnotations {
+    val HEADER_MAP: AnnotationSpec =
+        AnnotationSpec
+            .builder(OpenFeignImports.HEADER_MAP)
+            .build()
+
+    fun requestLineBuilder(): AnnotationSpec.Builder =
+        AnnotationSpec
+            .builder(OpenFeignImports.REQUEST_LINE)
+
+    fun headersBuilder(): AnnotationSpec.Builder =
+        AnnotationSpec
+            .builder(OpenFeignImports.HEADERS)
+
+    fun paramBuilder(): AnnotationSpec.Builder =
+        AnnotationSpec
+            .builder(OpenFeignImports.PARAM)
+}
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/model/SourceApi.kt b/src/main/kotlin/com/cjbooms/fabrikt/model/SourceApi.kt
index c66cb0d..6344d57 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/model/SourceApi.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/model/SourceApi.kt
@@ -61,10 +61,6 @@ data class SourceApi(
                             "Do not use properties and a combiner at the same level."
                     )
                 )
-            else if (schema.type == OasType.Object.type && schema.oneOfSchemas?.isNotEmpty() == true)
-                errors + listOf(ValidationError("The $name object contains invalid use of both properties and `oneOf`."))
-            else if (schema.type == OasType.Object.type && schema.oneOfSchemas?.isNotEmpty() == true)
-                errors + listOf(ValidationError("The $name object contains invalid use of both properties and `oneOf`."))
             else if (schema.type == null && schema.properties?.isNotEmpty() == true) {
                 logger.warning("Schema '$name' has 'type: null' but defines properties. Assuming: 'type: object'")
                 errors


Test patch:
diff --git a/src/test/kotlin/com/cjbooms/fabrikt/generators/OkHttpClientGeneratorTest.kt b/src/test/kotlin/com/cjbooms/fabrikt/generators/OkHttpClientGeneratorTest.kt
index 23dadc9..de4b1a2 100644
--- a/src/test/kotlin/com/cjbooms/fabrikt/generators/OkHttpClientGeneratorTest.kt
+++ b/src/test/kotlin/com/cjbooms/fabrikt/generators/OkHttpClientGeneratorTest.kt
@@ -1,6 +1,7 @@
 package com.cjbooms.fabrikt.generators
 
 import com.cjbooms.fabrikt.cli.ClientCodeGenOptionType
+import com.cjbooms.fabrikt.cli.ClientCodeGenTargetType
 import com.cjbooms.fabrikt.cli.CodeGenerationType
 import com.cjbooms.fabrikt.cli.ModelCodeGenOptionType
 import com.cjbooms.fabrikt.configurations.Packages
@@ -28,7 +29,7 @@ class OkHttpClientGeneratorTest {
     @Suppress("unused")
     private fun fullApiTestCases(): Stream<String> = Stream.of(
         "okHttpClient",
-        "okHttpClientMultiMediaType",
+        "multiMediaType",
         "okHttpClientPostWithoutRequestBody",
         "pathLevelParameters",
         "parameterNameClash",
@@ -38,6 +39,7 @@ class OkHttpClientGeneratorTest {
     fun init() {
         MutableSettings.updateSettings(
             genTypes = setOf(CodeGenerationType.CLIENT),
+            clientTarget = ClientCodeGenTargetType.OK_HTTP,
             modelOptions = setOf(ModelCodeGenOptionType.X_EXTENSIBLE_ENUMS),
         )
     }
diff --git a/src/test/kotlin/com/cjbooms/fabrikt/generators/OpenFeignClientGeneratorTest.kt b/src/test/kotlin/com/cjbooms/fabrikt/generators/OpenFeignClientGeneratorTest.kt
new file mode 100644
index 0000000..e5c3dff
--- /dev/null
+++ b/src/test/kotlin/com/cjbooms/fabrikt/generators/OpenFeignClientGeneratorTest.kt
@@ -0,0 +1,109 @@
+package com.cjbooms.fabrikt.generators
+
+import com.cjbooms.fabrikt.cli.ClientCodeGenOptionType
+import com.cjbooms.fabrikt.cli.ClientCodeGenTargetType
+import com.cjbooms.fabrikt.cli.CodeGenerationType
+import com.cjbooms.fabrikt.cli.ModelCodeGenOptionType
+import com.cjbooms.fabrikt.configurations.Packages
+import com.cjbooms.fabrikt.generators.client.OpenFeignInterfaceGenerator
+import com.cjbooms.fabrikt.generators.model.JacksonModelGenerator
+import com.cjbooms.fabrikt.model.ClientType
+import com.cjbooms.fabrikt.model.Models
+import com.cjbooms.fabrikt.model.SourceApi
+import com.cjbooms.fabrikt.util.Linter
+import com.cjbooms.fabrikt.util.ResourceHelper.readTextResource
+import com.squareup.kotlinpoet.FileSpec
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.TestInstance
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.MethodSource
+import java.util.stream.Stream
+
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+class OpenFeignClientGeneratorTest {
+
+    @Suppress("unused")
+    private fun fullApiTestCases(): Stream<String> = Stream.of(
+        "openFeignClient",
+        "multiMediaType",
+        "pathLevelParameters",
+        "parameterNameClash",
+    )
+
+    @BeforeEach
+    fun init() {
+        MutableSettings.updateSettings(
+            genTypes = setOf(CodeGenerationType.CLIENT),
+            clientTarget = ClientCodeGenTargetType.OPEN_FEIGN,
+            modelOptions = setOf(ModelCodeGenOptionType.X_EXTENSIBLE_ENUMS),
+        )
+    }
+
+    @ParameterizedTest
+    @MethodSource("fullApiTestCases")
+    fun `correct Open Feign interfaces are generated from a full API definition`(testCaseName: String) {
+        runTestCase(testCaseName)
+    }
+
+    @Test
+    fun `correct Open Feign interfaces are generated with suspend modifier`() {
+        runTestCase(
+            testCaseName = "openFeignClient",
+            clientFileName = "SuspendableOpenFeignClient.kt",
+            options = setOf(ClientCodeGenOptionType.SUSPEND_MODIFIER),
+        )
+    }
+
+    private fun runTestCase(
+        testCaseName: String,
+        clientFileName: String = "OpenFeignClient.kt",
+        options: Set<ClientCodeGenOptionType> = emptySet(),
+    ) {
+        val packages = Packages("examples.$testCaseName")
+        val sourceApi = SourceApi(readTextResource("/examples/$testCaseName/api.yaml"))
+
+        val expectedModel = readTextResource("/examples/$testCaseName/models/Models.kt")
+        val expectedClient = readTextResource("/examples/$testCaseName/client/$clientFileName")
+
+        val models = JacksonModelGenerator(
+            packages,
+            sourceApi,
+        ).generate().toSingleFile()
+        val clientCode = OpenFeignInterfaceGenerator(
+            packages,
+            sourceApi,
+        )
+            .generate(options)
+            .clients
+            .toSingleFile()
+
+        assertThat(clientCode).isEqualTo(expectedClient)
+        assertThat(models).isEqualTo(expectedModel)
+    }
+
+    private fun Collection<ClientType>.toSingleFile(): String {
+        val destPackage = if (this.isNotEmpty()) first().destinationPackage else ""
+        val singleFileBuilder = FileSpec.builder(destPackage, "dummyFilename")
+        this.forEach {
+            val builder = singleFileBuilder
+                .addType(it.spec)
+            builder.build()
+        }
+        return Linter.lintString(singleFileBuilder.build().toString())
+    }
+
+    private fun Models.toSingleFile(): String {
+        val destPackage = if (models.isNotEmpty()) models.first().destinationPackage else ""
+        val singleFileBuilder = FileSpec.builder(destPackage, "dummyFilename")
+        models
+            .sortedBy { it.spec.name }
+            .forEach {
+                val builder = singleFileBuilder
+                    .addType(it.spec)
+                builder.build()
+            }
+        return Linter.lintString(singleFileBuilder.build().toString())
+    }
+}
diff --git a/src/test/resources/examples/okHttpClientMultiMediaType/api.yaml b/src/test/resources/examples/multiMediaType/api.yaml
similarity index 91%
rename from src/test/resources/examples/okHttpClientMultiMediaType/api.yaml
rename to src/test/resources/examples/multiMediaType/api.yaml
index 3f775e0..7e24be8 100644
--- a/src/test/resources/examples/okHttpClientMultiMediaType/api.yaml
+++ b/src/test/resources/examples/multiMediaType/api.yaml
@@ -69,6 +69,21 @@ paths:
             application/vnd.custom.media+json:
               schema:
                 $ref: '#/components/schemas/OtherQueryResult'
+  /different-success-and-error-response-schema:
+    get:
+      responses:
+        200:
+          description: success
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/SuccessResponse"
+        400:
+          description: bad request
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/ErrorResponse"
 
 components:
   parameters:
@@ -239,4 +254,14 @@ components:
         extra_second_attr:
           description: "The attribute 2 for model 3"
           type: integer
-          readOnly: true
\ No newline at end of file
+          readOnly: true
+    SuccessResponse:
+      type: object
+      properties:
+        successMessage:
+          type: string
+    ErrorResponse:
+      type: object
+      properties:
+        errorMessage:
+          type: string
\ No newline at end of file
diff --git a/src/test/resources/examples/okHttpClientMultiMediaType/client/ApiClient.kt b/src/test/resources/examples/multiMediaType/client/ApiClient.kt
similarity index 78%
rename from src/test/resources/examples/okHttpClientMultiMediaType/client/ApiClient.kt
rename to src/test/resources/examples/multiMediaType/client/ApiClient.kt
index 8e41834..061995d 100644
--- a/src/test/resources/examples/okHttpClientMultiMediaType/client/ApiClient.kt
+++ b/src/test/resources/examples/multiMediaType/client/ApiClient.kt
@@ -1,10 +1,11 @@
-package examples.okHttpClientMultiMediaType.client
+package examples.multiMediaType.client
 
 import com.fasterxml.jackson.databind.JsonNode
 import com.fasterxml.jackson.databind.ObjectMapper
 import com.fasterxml.jackson.module.kotlin.jacksonTypeRef
-import examples.okHttpClientMultiMediaType.models.ContentType
-import examples.okHttpClientMultiMediaType.models.QueryResult
+import examples.multiMediaType.models.ContentType
+import examples.multiMediaType.models.QueryResult
+import examples.multiMediaType.models.SuccessResponse
 import okhttp3.Headers
 import okhttp3.HttpUrl
 import okhttp3.HttpUrl.Companion.toHttpUrl
@@ -137,3 +138,36 @@ class MultipleResponseSchemasClient(
         return request.execute(client, objectMapper, jacksonTypeRef())
     }
 }
+
+@Suppress("unused")
+class DifferentSuccessAndErrorResponseSchemaClient(
+    private val objectMapper: ObjectMapper,
+    private val baseUrl: String,
+    private val client: OkHttpClient
+) {
+    /**
+     *
+     */
+    @Throws(ApiException::class)
+    fun getDifferentSuccessAndErrorResponseSchema(
+        additionalHeaders: Map<String, String> =
+            emptyMap()
+    ): ApiResponse<SuccessResponse> {
+        val httpUrl: HttpUrl = "$baseUrl/different-success-and-error-response-schema"
+            .toHttpUrl()
+            .newBuilder()
+            .build()
+
+        val headerBuilder = Headers.Builder()
+        additionalHeaders.forEach { headerBuilder.header(it.key, it.value) }
+        val httpHeaders: Headers = headerBuilder.build()
+
+        val request: Request = Request.Builder()
+            .url(httpUrl)
+            .headers(httpHeaders)
+            .get()
+            .build()
+
+        return request.execute(client, objectMapper, jacksonTypeRef())
+    }
+}
diff --git a/src/test/resources/examples/okHttpClientMultiMediaType/client/ApiModels.kt b/src/test/resources/examples/multiMediaType/client/ApiModels.kt
similarity index 93%
rename from src/test/resources/examples/okHttpClientMultiMediaType/client/ApiModels.kt
rename to src/test/resources/examples/multiMediaType/client/ApiModels.kt
index a4d9699..f5ea731 100644
--- a/src/test/resources/examples/okHttpClientMultiMediaType/client/ApiModels.kt
+++ b/src/test/resources/examples/multiMediaType/client/ApiModels.kt
@@ -1,4 +1,4 @@
-package examples.okHttpClientMultiMediaType.client
+package examples.multiMediaType.client
 
 import okhttp3.Headers
 
diff --git a/src/test/resources/examples/okHttpClientMultiMediaType/client/ApiService.kt b/src/test/resources/examples/multiMediaType/client/ApiService.kt
similarity index 73%
rename from src/test/resources/examples/okHttpClientMultiMediaType/client/ApiService.kt
rename to src/test/resources/examples/multiMediaType/client/ApiService.kt
index d9153b6..b4310d3 100644
--- a/src/test/resources/examples/okHttpClientMultiMediaType/client/ApiService.kt
+++ b/src/test/resources/examples/multiMediaType/client/ApiService.kt
@@ -1,9 +1,10 @@
-package examples.okHttpClientMultiMediaType.client
+package examples.multiMediaType.client
 
 import com.fasterxml.jackson.databind.JsonNode
 import com.fasterxml.jackson.databind.ObjectMapper
-import examples.okHttpClientMultiMediaType.models.ContentType
-import examples.okHttpClientMultiMediaType.models.QueryResult
+import examples.multiMediaType.models.ContentType
+import examples.multiMediaType.models.QueryResult
+import examples.multiMediaType.models.SuccessResponse
 import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry
 import okhttp3.OkHttpClient
 import kotlin.Int
@@ -108,3 +109,33 @@ class MultipleResponseSchemasService(
             apiClient.getMultipleResponseSchemas(accept, additionalHeaders)
         }
 }
+
+/**
+ * The circuit breaker registry should have the proper configuration to correctly action on circuit
+ * breaker transitions based on the client exceptions [ApiClientException], [ApiServerException] and
+ * [IOException].
+ *
+ * @see ApiClientException
+ * @see ApiServerException
+ */
+@Suppress("unused")
+class DifferentSuccessAndErrorResponseSchemaService(
+    private val circuitBreakerRegistry: CircuitBreakerRegistry,
+    objectMapper: ObjectMapper,
+    baseUrl: String,
+    client: OkHttpClient
+) {
+    var circuitBreakerName: String = "differentSuccessAndErrorResponseSchemaClient"
+
+    private val apiClient: DifferentSuccessAndErrorResponseSchemaClient =
+        DifferentSuccessAndErrorResponseSchemaClient(objectMapper, baseUrl, client)
+
+    @Throws(ApiException::class)
+    fun getDifferentSuccessAndErrorResponseSchema(
+        additionalHeaders: Map<String, String> =
+            emptyMap()
+    ): ApiResponse<SuccessResponse> =
+        withCircuitBreaker(circuitBreakerRegistry, circuitBreakerName) {
+            apiClient.getDifferentSuccessAndErrorResponseSchema(additionalHeaders)
+        }
+}
diff --git a/src/test/resources/examples/okHttpClientMultiMediaType/client/HttpResilience4jUtil.kt b/src/test/resources/examples/multiMediaType/client/HttpResilience4jUtil.kt
similarity index 89%
rename from src/test/resources/examples/okHttpClientMultiMediaType/client/HttpResilience4jUtil.kt
rename to src/test/resources/examples/multiMediaType/client/HttpResilience4jUtil.kt
index aac6956..c9b65d9 100644
--- a/src/test/resources/examples/okHttpClientMultiMediaType/client/HttpResilience4jUtil.kt
+++ b/src/test/resources/examples/multiMediaType/client/HttpResilience4jUtil.kt
@@ -1,4 +1,4 @@
-package examples.okHttpClientMultiMediaType.client
+package examples.multiMediaType.client
 
 import io.github.resilience4j.circuitbreaker.CircuitBreaker
 import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry
diff --git a/src/test/resources/examples/okHttpClientMultiMediaType/client/HttpUtil.kt b/src/test/resources/examples/multiMediaType/client/HttpUtil.kt
similarity index 98%
rename from src/test/resources/examples/okHttpClientMultiMediaType/client/HttpUtil.kt
rename to src/test/resources/examples/multiMediaType/client/HttpUtil.kt
index 2b93018..9979101 100644
--- a/src/test/resources/examples/okHttpClientMultiMediaType/client/HttpUtil.kt
+++ b/src/test/resources/examples/multiMediaType/client/HttpUtil.kt
@@ -1,4 +1,4 @@
-package examples.okHttpClientMultiMediaType.client
+package examples.multiMediaType.client
 
 import com.fasterxml.jackson.core.type.TypeReference
 import com.fasterxml.jackson.databind.ObjectMapper
diff --git a/src/test/resources/examples/okHttpClientMultiMediaType/client/OAuth.kt b/src/test/resources/examples/multiMediaType/client/OAuth.kt
similarity index 92%
rename from src/test/resources/examples/okHttpClientMultiMediaType/client/OAuth.kt
rename to src/test/resources/examples/multiMediaType/client/OAuth.kt
index 687fda4..1201b4d 100644
--- a/src/test/resources/examples/okHttpClientMultiMediaType/client/OAuth.kt
+++ b/src/test/resources/examples/multiMediaType/client/OAuth.kt
@@ -1,4 +1,4 @@
-package examples.okHttpClientMultiMediaType.client
+package examples.multiMediaType.client
 
 import okhttp3.Authenticator
 import okhttp3.Interceptor
diff --git a/src/test/resources/examples/multiMediaType/client/OpenFeignClient.kt b/src/test/resources/examples/multiMediaType/client/OpenFeignClient.kt
new file mode 100644
index 0000000..eafcd53
--- /dev/null
+++ b/src/test/resources/examples/multiMediaType/client/OpenFeignClient.kt
@@ -0,0 +1,82 @@
+package examples.multiMediaType.client
+
+import com.fasterxml.jackson.databind.JsonNode
+import examples.multiMediaType.models.ContentType
+import examples.multiMediaType.models.QueryResult
+import examples.multiMediaType.models.SuccessResponse
+import feign.HeaderMap
+import feign.Headers
+import feign.Param
+import feign.RequestLine
+import kotlin.Int
+import kotlin.String
+import kotlin.Suppress
+import kotlin.collections.List
+import kotlin.collections.Map
+
+@Suppress("unused")
+interface ExamplePath1Client {
+    /**
+     * GET example path 1
+     *
+     * @param explodeListQueryParam
+     * @param queryParam2
+     * @param acceptHeader
+     */
+    @RequestLine("GET /example-path-1?explode_list_query_param={explodeListQueryParam}&query_param2={queryParam2}")
+    @Headers(value = ["Accept: {acceptHeader}", "Accept: application/vnd.custom.media+xml"])
+    fun getExamplePath1(
+        @Param("explodeListQueryParam") explodeListQueryParam: List<String>? = null,
+        @Param("queryParam2") queryParam2: Int? = null,
+        @Param("acceptHeader") acceptHeader: String = "application/vnd.custom.media+xml",
+        @HeaderMap additionalHeaders: Map<String, String> = emptyMap()
+    ): QueryResult
+}
+
+@Suppress("unused")
+interface ExamplePath2Client {
+    /**
+     * GET example path 1
+     *
+     * @param explodeListQueryParam
+     * @param queryParam2
+     * @param accept the content type accepted by the client
+     */
+    @RequestLine("GET /example-path-2?explode_list_query_param={explodeListQueryParam}&query_param2={queryParam2}")
+    @Headers(value = ["Accept: {accept}", "Accept: application/vnd.custom.media+xml"])
+    fun getExamplePath2(
+        @Param("explodeListQueryParam") explodeListQueryParam: List<String>? = null,
+        @Param("queryParam2") queryParam2: Int? = null,
+        @Param("accept") accept: ContentType? = null,
+        @HeaderMap additionalHeaders: Map<String, String> = emptyMap()
+    ): QueryResult
+}
+
+@Suppress("unused")
+interface MultipleResponseSchemasClient {
+    /**
+     * GET with multiple response content schemas
+     *
+     * @param accept the content type accepted by the client
+     */
+    @RequestLine("GET /multiple-response-schemas")
+    @Headers(value = ["Accept: {accept}", "Accept: application/json"])
+    fun getMultipleResponseSchemas(
+        @Param("accept") accept: ContentType? = null,
+        @HeaderMap
+        additionalHeaders: Map<String, String> = emptyMap()
+    ): JsonNode
+}
+
+@Suppress("unused")
+interface DifferentSuccessAndErrorResponseSchemaClient {
+    /**
+     *
+     */
+    @RequestLine("GET /different-success-and-error-response-schema")
+    @Headers("Accept: application/json")
+    fun getDifferentSuccessAndErrorResponseSchema(
+        @HeaderMap additionalHeaders: Map<String, String> =
+            emptyMap()
+    ): SuccessResponse
+}
diff --git a/src/test/resources/examples/okHttpClientMultiMediaType/models/Models.kt b/src/test/resources/examples/multiMediaType/models/Models.kt
similarity index 94%
rename from src/test/resources/examples/okHttpClientMultiMediaType/models/Models.kt
rename to src/test/resources/examples/multiMediaType/models/Models.kt
index aca4d17..2595659 100644
--- a/src/test/resources/examples/okHttpClientMultiMediaType/models/Models.kt
+++ b/src/test/resources/examples/multiMediaType/models/Models.kt
@@ -1,4 +1,4 @@
-package examples.okHttpClientMultiMediaType.models
+package examples.multiMediaType.models
 
 import com.fasterxml.jackson.annotation.JsonProperty
 import com.fasterxml.jackson.annotation.JsonSubTypes
@@ -101,6 +101,12 @@ enum class ContentType(
     }
 }
 
+data class ErrorResponse(
+    @param:JsonProperty("errorMessage")
+    @get:JsonProperty("errorMessage")
+    val errorMessage: String? = null
+)
+
 data class FirstModel(
     @param:JsonProperty("id")
     @get:JsonProperty("id")
@@ -172,6 +178,12 @@ data class SecondModel(
     override val modelType: ContentModelType = ContentModelType.SECOND_MODEL
 ) : Content(id, firstAttr, secondAttr, thirdAttr, etag)
 
+data class SuccessResponse(
+    @param:JsonProperty("successMessage")
+    @get:JsonProperty("successMessage")
+    val successMessage: String? = null
+)
+
 data class ThirdModel(
     @param:JsonProperty("id")
     @get:JsonProperty("id")
diff --git a/src/test/resources/examples/openFeignClient/api.yaml b/src/test/resources/examples/openFeignClient/api.yaml
new file mode 100644
index 0000000..cb3bff0
--- /dev/null
+++ b/src/test/resources/examples/openFeignClient/api.yaml
@@ -0,0 +1,310 @@
+openapi: "3.0.0"
+info:
+  title: "API Example"
+  version: "1.0"
+paths:
+  /example-path-1:
+    get:
+      summary: "GET example path 1"
+      parameters:
+        - $ref: "#/components/parameters/ListQueryParamExploded"
+        - $ref: "#/components/parameters/QueryParam2"
+      responses:
+        200:
+          description: "successful operation"
+          headers:
+            Cache-Control:
+              $ref: "#/components/headers/CacheControl"
+          content:
+            application/vnd.custom.media+json:
+              schema:
+                $ref: "#/components/schemas/QueryResult"
+
+    post:
+      summary: "POST example path 1"
+      parameters:
+        - $ref: "#/components/parameters/ListQueryParamExploded"
+      requestBody:
+        $ref: "#/components/requestBodies/PostBody"
+      responses:
+        201:
+          headers:
+            Location:
+              $ref: "#/components/headers/Location"
+          description: "Successful operation"
+
+  /example-path-2/{path_param}:
+    get:
+      summary: "GET example path 2"
+      parameters:
+        - $ref: "#/components/parameters/PathParam"
+        - $ref: "#/components/parameters/QueryParam2"
+        - $ref: "#/components/parameters/IfNoneMatch"
+        - $ref: "#/components/parameters/PaginationLimitParam"
+      responses:
+        200:
+          description: "successful operation"
+          headers:
+            Cache-Control:
+              $ref: "#/components/headers/CacheControl"
+          content:
+            application/json:
+              schema:
+                discriminator:
+                  propertyName: "model_type"
+                oneOf:
+                  - $ref: "#/components/schemas/FirstModel"
+                  - $ref: "#/components/schemas/SecondModel"
+                  - $ref: "#/components/schemas/ThirdModel"
+        304:
+          description: "Returned in conjunction with use of the If-None-Match header.\
+            \ Indicates that the resource as described by the passed etag value has\
+            \ not changed"
+    head:
+      operationId: headOperationIdExample
+      summary: "HEAD example path 2"
+      parameters:
+        - $ref: "#/components/parameters/PathParam"
+        - $ref: "#/components/parameters/QueryParam3"
+        - $ref: "#/components/parameters/IfNoneMatch"
+      responses:
+        200:
+          description: "Record Exists"
+        304:
+          description: "Returned in conjunction with use of the If-None-Match header.\
+            \ Indicates that the resource as described by the passed etag value has\
+            \ not changed"
+
+    put:
+      summary: "PUT example path 2"
+      parameters:
+        - $ref: "#/components/parameters/PathParam"
+        - $ref: "#/components/parameters/IfMatch"
+      requestBody:
+        $ref: "#/components/requestBodies/PutBody"
+      responses:
+        204:
+          description: "Operation successful"
+
+  /example-path-3/{path_param}/subresource:
+    put:
+      summary: "PUT example path 3"
+      parameters:
+        - $ref: "#/components/parameters/ListQueryParamCsv"
+        - $ref: "#/components/parameters/PathParam"
+        - $ref: "#/components/parameters/IfMatch"
+      requestBody:
+        $ref: "#/components/requestBodies/PutBody"
+      responses:
+        204:
+          description: "Operation successful"
+
+components:
+  parameters:
+    PathParam:
+      name: "path_param"
+      description: "The resource id"
+      in: "path"
+      required: true
+      schema:
+        type: "string"
+
+    IfMatch:
+      name: "If-Match"
+      description: "The RFC7232 If-Match header field"
+      in: "header"
+      schema:
+        type: "string"
+      required: true
+
+    IfNoneMatch:
+      name: "If-None-Match"
+      description: "The RFC7232 If-None-Match header field"
+      in: "header"
+      schema:
+        type: "string"
+      required: false
+
+    ListQueryParamExploded:
+      name: "explode_list_query_param"
+      in: "query"
+      schema:
+        type: "array"
+        items:
+          type: "string"
+      required: false
+      allowEmptyValue: false
+      
+    ListQueryParamCsv:
+      name: "csv_list_query_param"
+      in: "query"
+      explode: false
+      schema:
+        type: "array"
+        items:
+          type: "string"
+      required: false
+      allowEmptyValue: false
+      
+    PaginationLimitParam:
+      name: limit
+      in: query
+      schema:
+        type: integer
+        minimum: 1
+        maximum: 1000
+        default: 500
+      required: false
+
+    QueryParam2:
+      name: "query_param2"
+      in: "query"
+      schema:
+        type: integer
+      required: false
+
+    QueryParam3:
+      name: "query_param3"
+      in: "query"
+      schema:
+        type: boolean
+      required: false
+
+  headers:
+    Location:
+      description: "The Location header indicates the URL of a newly created resource"
+      schema:
+        type: "string"
+
+    CacheControl:
+      description: "The RFC7234 Cache Control header"
+      schema:
+        type: "string"
+      example: "must-revalidate, max-age=5"
+
+  requestBodies:
+    PostBody:
+      required: true
+      content:
+        application/json:
+          schema:
+            discriminator:
+              propertyName: "model_type"
+            oneOf:
+              - $ref: "#/components/schemas/FirstModel"
+              - $ref: "#/components/schemas/SecondModel"
+              - $ref: "#/components/schemas/ThirdModel"
+    PutBody:
+      required: true
+      content:
+        application/json:
+          schema:
+            $ref: "#/components/schemas/FirstModel"
+
+
+  schemas:
+    QueryResult:
+      type: "object"
+      required:
+        - "items"
+      properties:
+        items:
+          type: "array"
+          minItems: 0
+          items:
+            oneOf:
+              - $ref: "#/components/schemas/FirstModel"
+              - $ref: "#/components/schemas/SecondModel"
+              - $ref: "#/components/schemas/ThirdModel"
+    Content:
+      type: "object"
+      required:
+        - "id"
+        - "attr_1"
+        - "attr_2"
+        - "attr_3"
+        - "etag"
+        - "model_type"
+      properties:
+        id:
+          description: "The unique resource id"
+          type: "string"
+          readOnly: true
+        first_attr:
+          description: "The attribute 1"
+          type: "string"
+          format: "date-time"
+          example: "2016-01-27T10:52:46.406Z"
+          readOnly: true
+        second_attr:
+          description: "The attribute 2"
+          type: "string"
+          readOnly: true
+        third_attr:
+          type: "string"
+          enum:
+            - "enum_type_1"
+            - "enum_type_2"
+          description: "Enum types for attribute 3"
+          example: "enum_type_2"
+        etag:
+          type: "string"
+          description: "Etag value to be used in conjunction with If-Match headers for optimistic locking purposes"
+          readOnly: true
+        model_type:
+          type: "string"
+          description: "The model discrimination type"
+          enum:
+            - "first_model"
+            - "second_model"
+            - "third_model"
+          example: "third_model"
+      discriminator:
+        propertyName: "model_type"
+        mapping:
+          first_model: "#/components/schemas/FirstModel"
+          second_model: "#/components/schemas/SecondModel"
+          third_model: "#/components/schemas/ThirdModel"
+
+    FirstModel:
+      allOf:
+        - $ref: "#/components/schemas/Content"
+        - type: "object"
+          properties:
+            extra_first_attr:
+              description: "The attribute 1 for model 1"
+              type: array
+              items:
+                type: "string"
+                minItems: 1
+                maxItems: 10
+              readOnly: true
+    SecondModel:
+      allOf:
+        - $ref: "#/components/schemas/Content"
+        - type: "object"
+          properties:
+            extra_first_attr:
+              description: "The attribute 1 for model 2"
+              type: "string"
+              readOnly: true
+            extra_second_attr:
+              description: "The attribute 2 for model 2"
+              type: boolean
+              readOnly: true
+
+    ThirdModel:
+      allOf:
+        - $ref: "#/components/schemas/Content"
+        - type: "object"
+          properties:
+            extra_first_attr:
+              description: "The attribute 1 for model 3"
+              type: "string"
+              format: "date-time"
+              example: "2016-01-27T10:52:46.406Z"
+              readOnly: true
+            extra_second_attr:
+              description: "The attribute 2 for model 3"
+              type: integer
+              readOnly: true
diff --git a/src/test/resources/examples/openFeignClient/client/OpenFeignClient.kt b/src/test/resources/examples/openFeignClient/client/OpenFeignClient.kt
new file mode 100644
index 0000000..583b93c
--- /dev/null
+++ b/src/test/resources/examples/openFeignClient/client/OpenFeignClient.kt
@@ -0,0 +1,119 @@
+package examples.openFeignClient.client
+
+import examples.openFeignClient.models.Content
+import examples.openFeignClient.models.FirstModel
+import examples.openFeignClient.models.QueryResult
+import feign.HeaderMap
+import feign.Headers
+import feign.Param
+import feign.RequestLine
+import kotlin.Boolean
+import kotlin.Int
+import kotlin.String
+import kotlin.Suppress
+import kotlin.collections.List
+import kotlin.collections.Map
+
+@Suppress("unused")
+interface ExamplePath1Client {
+    /**
+     * GET example path 1
+     *
+     * @param explodeListQueryParam
+     * @param queryParam2
+     */
+    @RequestLine("GET /example-path-1?explode_list_query_param={explodeListQueryParam}&query_param2={queryParam2}")
+    @Headers("Accept: application/vnd.custom.media+json")
+    fun getExamplePath1(
+        @Param("explodeListQueryParam") explodeListQueryParam: List<String>? = null,
+        @Param("queryParam2") queryParam2: Int? = null,
+        @HeaderMap additionalHeaders: Map<String, String> = emptyMap()
+    ): QueryResult
+
+    /**
+     * POST example path 1
+     *
+     * @param content
+     * @param explodeListQueryParam
+     */
+    @RequestLine("POST /example-path-1?explode_list_query_param={explodeListQueryParam}")
+    fun postExamplePath1(
+        content: Content,
+        @Param("explodeListQueryParam") explodeListQueryParam: List<String>? = null,
+        @HeaderMap additionalHeaders: Map<String, String> = emptyMap()
+    )
+}
+
+@Suppress("unused")
+interface ExamplePath2Client {
+    /**
+     * GET example path 2
+     *
+     * @param pathParam The resource id
+     * @param limit
+     * @param queryParam2
+     * @param ifNoneMatch The RFC7232 If-None-Match header field
+     */
+    @RequestLine("GET /example-path-2/{pathParam}?limit={limit}&query_param2={queryParam2}")
+    @Headers(value = ["If-None-Match: {ifNoneMatch}", "Accept: application/json"])
+    fun getExamplePath2PathParam(
+        @Param("pathParam") pathParam: String,
+        @Param("limit") limit: Int = 500,
+        @Param("queryParam2") queryParam2: Int? = null,
+        @Param("ifNoneMatch") ifNoneMatch: String? = null,
+        @HeaderMap additionalHeaders: Map<String, String> = emptyMap()
+    ): Content
+
+    /**
+     * HEAD example path 2
+     *
+     * @param pathParam The resource id
+     * @param queryParam3
+     * @param ifNoneMatch The RFC7232 If-None-Match header field
+     */
+    @RequestLine("HEAD /example-path-2/{pathParam}?query_param3={queryParam3}")
+    @Headers("If-None-Match: {ifNoneMatch}")
+    fun headOperationIdExample(
+        @Param("pathParam") pathParam: String,
+        @Param("queryParam3") queryParam3: Boolean? = null,
+        @Param("ifNoneMatch") ifNoneMatch: String? = null,
+        @HeaderMap additionalHeaders: Map<String, String> = emptyMap()
+    )
+
+    /**
+     * PUT example path 2
+     *
+     * @param firstModel
+     * @param pathParam The resource id
+     * @param ifMatch The RFC7232 If-Match header field
+     */
+    @RequestLine("PUT /example-path-2/{pathParam}")
+    @Headers("If-Match: {ifMatch}")
+    fun putExamplePath2PathParam(
+        firstModel: FirstModel,
+        @Param("pathParam") pathParam: String,
+        @Param("ifMatch") ifMatch: String,
+        @HeaderMap additionalHeaders: Map<String, String> = emptyMap()
+    )
+}
+
+@Suppress("unused")
+interface ExamplePath3SubresourceClient {
+    /**
+     * PUT example path 3
+     *
+     * @param firstModel
+     * @param pathParam The resource id
+     * @param ifMatch The RFC7232 If-Match header field
+     * @param csvListQueryParam
+     */
+    @RequestLine("PUT /example-path-3/{pathParam}/subresource?csv_list_query_param={csvListQueryParam}")
+    @Headers("If-Match: {ifMatch}")
+    fun putExamplePath3PathParamSubresource(
+        firstModel: FirstModel,
+        @Param("pathParam") pathParam: String,
+        @Param("ifMatch") ifMatch: String,
+        @Param("csvListQueryParam") csvListQueryParam: List<String>? = null,
+        @HeaderMap additionalHeaders: Map<String, String> = emptyMap()
+    )
+}
diff --git a/src/test/resources/examples/openFeignClient/client/SuspendableOpenFeignClient.kt b/src/test/resources/examples/openFeignClient/client/SuspendableOpenFeignClient.kt
new file mode 100644
index 0000000..f4146cc
--- /dev/null
+++ b/src/test/resources/examples/openFeignClient/client/SuspendableOpenFeignClient.kt
@@ -0,0 +1,119 @@
+package examples.openFeignClient.client
+
+import examples.openFeignClient.models.Content
+import examples.openFeignClient.models.FirstModel
+import examples.openFeignClient.models.QueryResult
+import feign.HeaderMap
+import feign.Headers
+import feign.Param
+import feign.RequestLine
+import kotlin.Boolean
+import kotlin.Int
+import kotlin.String
+import kotlin.Suppress
+import kotlin.collections.List
+import kotlin.collections.Map
+
+@Suppress("unused")
+interface ExamplePath1Client {
+    /**
+     * GET example path 1
+     *
+     * @param explodeListQueryParam
+     * @param queryParam2
+     */
+    @RequestLine("GET /example-path-1?explode_list_query_param={explodeListQueryParam}&query_param2={queryParam2}")
+    @Headers("Accept: application/vnd.custom.media+json")
+    suspend fun getExamplePath1(
+        @Param("explodeListQueryParam") explodeListQueryParam: List<String>? = null,
+        @Param("queryParam2") queryParam2: Int? = null,
+        @HeaderMap additionalHeaders: Map<String, String> = emptyMap()
+    ): QueryResult
+
+    /**
+     * POST example path 1
+     *
+     * @param content
+     * @param explodeListQueryParam
+     */
+    @RequestLine("POST /example-path-1?explode_list_query_param={explodeListQueryParam}")
+    suspend fun postExamplePath1(
+        content: Content,
+        @Param("explodeListQueryParam") explodeListQueryParam: List<String>? = null,
+        @HeaderMap additionalHeaders: Map<String, String> = emptyMap()
+    )
+}
+
+@Suppress("unused")
+interface ExamplePath2Client {
+    /**
+     * GET example path 2
+     *
+     * @param pathParam The resource id
+     * @param limit
+     * @param queryParam2
+     * @param ifNoneMatch The RFC7232 If-None-Match header field
+     */
+    @RequestLine("GET /example-path-2/{pathParam}?limit={limit}&query_param2={queryParam2}")
+    @Headers(value = ["If-None-Match: {ifNoneMatch}", "Accept: application/json"])
+    suspend fun getExamplePath2PathParam(
+        @Param("pathParam") pathParam: String,
+        @Param("limit") limit: Int = 500,
+        @Param("queryParam2") queryParam2: Int? = null,
+        @Param("ifNoneMatch") ifNoneMatch: String? = null,
+        @HeaderMap additionalHeaders: Map<String, String> = emptyMap()
+    ): Content
+
+    /**
+     * HEAD example path 2
+     *
+     * @param pathParam The resource id
+     * @param queryParam3
+     * @param ifNoneMatch The RFC7232 If-None-Match header field
+     */
+    @RequestLine("HEAD /example-path-2/{pathParam}?query_param3={queryParam3}")
+    @Headers("If-None-Match: {ifNoneMatch}")
+    suspend fun headOperationIdExample(
+        @Param("pathParam") pathParam: String,
+        @Param("queryParam3") queryParam3: Boolean? = null,
+        @Param("ifNoneMatch") ifNoneMatch: String? = null,
+        @HeaderMap additionalHeaders: Map<String, String> = emptyMap()
+    )
+
+    /**
+     * PUT example path 2
+     *
+     * @param firstModel
+     * @param pathParam The resource id
+     * @param ifMatch The RFC7232 If-Match header field
+     */
+    @RequestLine("PUT /example-path-2/{pathParam}")
+    @Headers("If-Match: {ifMatch}")
+    suspend fun putExamplePath2PathParam(
+        firstModel: FirstModel,
+        @Param("pathParam") pathParam: String,
+        @Param("ifMatch") ifMatch: String,
+        @HeaderMap additionalHeaders: Map<String, String> = emptyMap()
+    )
+}
+
+@Suppress("unused")
+interface ExamplePath3SubresourceClient {
+    /**
+     * PUT example path 3
+     *
+     * @param firstModel
+     * @param pathParam The resource id
+     * @param ifMatch The RFC7232 If-Match header field
+     * @param csvListQueryParam
+     */
+    @RequestLine("PUT /example-path-3/{pathParam}/subresource?csv_list_query_param={csvListQueryParam}")
+    @Headers("If-Match: {ifMatch}")
+    suspend fun putExamplePath3PathParamSubresource(
+        firstModel: FirstModel,
+        @Param("pathParam") pathParam: String,
+        @Param("ifMatch") ifMatch: String,
+        @Param("csvListQueryParam") csvListQueryParam: List<String>? = null,
+        @HeaderMap additionalHeaders: Map<String, String> = emptyMap()
+    )
+}
diff --git a/src/test/resources/examples/openFeignClient/models/Models.kt b/src/test/resources/examples/openFeignClient/models/Models.kt
new file mode 100644
index 0000000..c0faf13
--- /dev/null
+++ b/src/test/resources/examples/openFeignClient/models/Models.kt
@@ -0,0 +1,168 @@
+package examples.openFeignClient.models
+
+import com.fasterxml.jackson.annotation.JsonProperty
+import com.fasterxml.jackson.annotation.JsonSubTypes
+import com.fasterxml.jackson.annotation.JsonTypeInfo
+import com.fasterxml.jackson.annotation.JsonValue
+import java.time.OffsetDateTime
+import javax.validation.Valid
+import javax.validation.constraints.NotNull
+import javax.validation.constraints.Size
+import kotlin.Boolean
+import kotlin.Int
+import kotlin.String
+import kotlin.collections.List
+import kotlin.collections.Map
+
+@JsonTypeInfo(
+    use = JsonTypeInfo.Id.NAME,
+    include = JsonTypeInfo.As.EXISTING_PROPERTY,
+    property = "model_type",
+    visible = true
+)
+@JsonSubTypes(
+    JsonSubTypes.Type(
+        value = FirstModel::class,
+        name =
+        "first_model"
+    ),
+    JsonSubTypes.Type(
+        value = SecondModel::class,
+        name =
+        "second_model"
+    ),
+    JsonSubTypes.Type(value = ThirdModel::class, name = "third_model")
+)
+sealed class Content(
+    open val id: String? = null,
+    open val firstAttr: OffsetDateTime? = null,
+    open val secondAttr: String? = null,
+    open val thirdAttr: ContentThirdAttr? = null,
+    open val etag: String? = null
+) {
+    abstract val modelType: ContentModelType
+}
+
+enum class ContentModelType(
+    @JsonValue
+    val value: String
+) {
+    FIRST_MODEL("first_model"),
+
+    SECOND_MODEL("second_model"),
+
+    THIRD_MODEL("third_model");
+
+    companion object {
+        private val mapping: Map<String, ContentModelType> =
+            values().associateBy(ContentModelType::value)
+
+        fun fromValue(value: String): ContentModelType? = mapping[value]
+    }
+}
+
+enum class ContentThirdAttr(
+    @JsonValue
+    val value: String
+) {
+    ENUM_TYPE_1("enum_type_1"),
+
+    ENUM_TYPE_2("enum_type_2");
+
+    companion object {
+        private val mapping: Map<String, ContentThirdAttr> =
+            values().associateBy(ContentThirdAttr::value)
+
+        fun fromValue(value: String): ContentThirdAttr? = mapping[value]
+    }
+}
+
+data class FirstModel(
+    @param:JsonProperty("id")
+    @get:JsonProperty("id")
+    override val id: String? = null,
+    @param:JsonProperty("first_attr")
+    @get:JsonProperty("first_attr")
+    override val firstAttr: OffsetDateTime? = null,
+    @param:JsonProperty("second_attr")
+    @get:JsonProperty("second_attr")
+    override val secondAttr: String? = null,
+    @param:JsonProperty("third_attr")
+    @get:JsonProperty("third_attr")
+    override val thirdAttr: ContentThirdAttr? = null,
+    @param:JsonProperty("etag")
+    @get:JsonProperty("etag")
+    override val etag: String? = null,
+    @param:JsonProperty("extra_first_attr")
+    @get:JsonProperty("extra_first_attr")
+    val extraFirstAttr: List<String>? = null,
+    @get:JsonProperty("model_type")
+    @get:NotNull
+    @param:JsonProperty("model_type")
+    override val modelType: ContentModelType = ContentModelType.FIRST_MODEL
+) : Content(id, firstAttr, secondAttr, thirdAttr, etag)
+
+data class QueryResult(
+    @param:JsonProperty("items")
+    @get:JsonProperty("items")
+    @get:NotNull
+    @get:Size(min = 0)
+    @get:Valid
+    val items: List<Content>
+)
+
+data class SecondModel(
+    @param:JsonProperty("id")
+    @get:JsonProperty("id")
+    override val id: String? = null,
+    @param:JsonProperty("first_attr")
+    @get:JsonProperty("first_attr")
+    override val firstAttr: OffsetDateTime? = null,
+    @param:JsonProperty("second_attr")
+    @get:JsonProperty("second_attr")
+    override val secondAttr: String? = null,
+    @param:JsonProperty("third_attr")
+    @get:JsonProperty("third_attr")
+    override val thirdAttr: ContentThirdAttr? = null,
+    @param:JsonProperty("etag")
+    @get:JsonProperty("etag")
+    override val etag: String? = null,
+    @param:JsonProperty("extra_first_attr")
+    @get:JsonProperty("extra_first_attr")
+    val extraFirstAttr: String? = null,
+    @param:JsonProperty("extra_second_attr")
+    @get:JsonProperty("extra_second_attr")
+    val extraSecondAttr: Boolean? = null,
+    @get:JsonProperty("model_type")
+    @get:NotNull
+    @param:JsonProperty("model_type")
+    override val modelType: ContentModelType = ContentModelType.SECOND_MODEL
+) : Content(id, firstAttr, secondAttr, thirdAttr, etag)
+
+data class ThirdModel(
+    @param:JsonProperty("id")
+    @get:JsonProperty("id")
+    override val id: String? = null,
+    @param:JsonProperty("first_attr")
+    @get:JsonProperty("first_attr")
+    override val firstAttr: OffsetDateTime? = null,
+    @param:JsonProperty("second_attr")
+    @get:JsonProperty("second_attr")
+    override val secondAttr: String? = null,
+    @param:JsonProperty("third_attr")
+    @get:JsonProperty("third_attr")
+    override val thirdAttr: ContentThirdAttr? = null,
+    @param:JsonProperty("etag")
+    @get:JsonProperty("etag")
+    override val etag: String? = null,
+    @param:JsonProperty("extra_first_attr")
+    @get:JsonProperty("extra_first_attr")
+    val extraFirstAttr: OffsetDateTime? = null,
+    @param:JsonProperty("extra_second_attr")
+    @get:JsonProperty("extra_second_attr")
+    val extraSecondAttr: Int? = null,
+    @get:JsonProperty("model_type")
+    @get:NotNull
+    @param:JsonProperty("model_type")
+    override val modelType: ContentModelType = ContentModelType.THIRD_MODEL
+) : Content(id, firstAttr, secondAttr, thirdAttr, etag)
diff --git a/src/test/resources/examples/parameterNameClash/client/OpenFeignClient.kt b/src/test/resources/examples/parameterNameClash/client/OpenFeignClient.kt
new file mode 100644
index 0000000..cb84e17
--- /dev/null
+++ b/src/test/resources/examples/parameterNameClash/client/OpenFeignClient.kt
@@ -0,0 +1,38 @@
+package examples.parameterNameClash.client
+
+import examples.parameterNameClash.models.SomeObject
+import feign.HeaderMap
+import feign.Param
+import feign.RequestLine
+import kotlin.String
+import kotlin.Suppress
+import kotlin.collections.Map
+
+@Suppress("unused")
+interface ExampleClient {
+    /**
+     *
+     *
+     * @param pathB
+     * @param queryB
+     */
+    @RequestLine("GET /example/{pathB}?b={queryB}")
+    fun getExampleB(
+        @Param("pathB") pathB: String,
+        @Param("queryB") queryB: String,
+        @HeaderMap additionalHeaders: Map<String, String> = emptyMap()
+    )
+
+    /**
+     *
+     *
+     * @param bodySomeObject example
+     * @param querySomeObject
+     */
+    @RequestLine("POST /example?someObject={querySomeObject}")
+    fun postExample(
+        bodySomeObject: SomeObject,
+        @Param("querySomeObject") querySomeObject: String,
+        @HeaderMap additionalHeaders: Map<String, String> = emptyMap()
+    )
+}
diff --git a/src/test/resources/examples/pathLevelParameters/client/OpenFeignClient.kt b/src/test/resources/examples/pathLevelParameters/client/OpenFeignClient.kt
new file mode 100644
index 0000000..b6b90e3
--- /dev/null
+++ b/src/test/resources/examples/pathLevelParameters/client/OpenFeignClient.kt
@@ -0,0 +1,24 @@
+package examples.pathLevelParameters.client
+
+import feign.HeaderMap
+import feign.Param
+import feign.RequestLine
+import kotlin.String
+import kotlin.Suppress
+import kotlin.collections.Map
+
+@Suppress("unused")
+interface ExampleClient {
+    /**
+     *
+     *
+     * @param a
+     * @param b
+     */
+    @RequestLine("GET /example?a={a}&b={b}")
+    fun getExample(
+        @Param("a") a: String,
+        @Param("b") b: String,
+        @HeaderMap additionalHeaders: Map<String, String> = emptyMap()
+    )
+}