OpenAPITools / openapi-generator

OpenAPI Generator allows generation of API client libraries (SDK generation), server stubs, documentation and configuration automatically given an OpenAPI Spec (v2, v3)
https://openapi-generator.tech
Apache License 2.0
20.56k stars 6.28k forks source link

[BUG] Generation with `kotlinx_serialization` generates unnecessary `@Serializable` on interfaces and double `@Serializable@Serializable` on classes #18904

Open javac9 opened 3 weeks ago

javac9 commented 3 weeks ago

Kotlin Client openapi generator version 7.6.0

Description

Generated interfaces have @Serializable and classes @Serializable@Serializable annotations which requires manual correction.

openapi-generator version

7.6.0

Steps to reproduce

My gradle task uses this to generate:

    generatorName.set("kotlin")
    library.set("multiplatform")
    inputSpec.set("path/to/my/openapi-spec.json")
    outputDir.set("$projectDir/build")
    ....
    configOptions.set(
        mapOf(
            "serializationLibrary" to "kotlinx_serialization",
            "dateLibrary" to "kotlinx-datetime",
            "idea" to "true",
           ....
cbeams commented 2 weeks ago

I just ran into this as well. Have not found a workaround as yet.

cbeams commented 2 weeks ago

Here's a quick workaround I'm using that effectively edits generated sources in place after generation and before compilation:

// work around https://github.com/OpenAPITools/openapi-generator/issues/18904
tasks.register<ReplaceInFilesTask>("dedupeSerializableAnnotation") {
    group = "Custom"
    description = "Replaces occurrences of @Serializable@Serializable with @Serializable in source files " +
            "to work around the bug described at https://github.com/OpenAPITools/openapi-generator/issues/18904"

    sourceDir.set(file("src"))
    targetString.set("@Serializable@Serializable")
    replacementString.set("@Serializable")
}

abstract class ReplaceInFilesTask : DefaultTask() {
    @get:InputDirectory
    abstract val sourceDir: DirectoryProperty

    @get:Input
    abstract val targetString: Property<String>

    @get:Input
    abstract val replacementString: Property<String>

    @TaskAction
    fun replaceStrings() {
        val target = targetString.get()
        val replacement = replacementString.get()
        val sourceDir = sourceDir.get().asFile

        if (sourceDir.exists()) {
            sourceDir.walkTopDown()
                .filter { it.isFile }
                .forEach { file ->
                    val content = file.readText()
                    val newContent = content.replace(target, replacement)
                    if (content != newContent) {
                        file.writeText(newContent)
                        println("Replaced in: ${file.absolutePath}")
                    }
                }
        } else {
            println("Source directory does not exist: $sourceDir")
        }
    }
}

tasks.getByName("openApiGenerate").finalizedBy("dedupeSerializableAnnotation")
tasks.getByName("compileKotlinJvm").dependsOn("openApiGenerate")
tasks.getByName("allMetadataJar").dependsOn("openApiGenerate") // to avoid "implicit dependency" error at [1]

// [1]: https://docs.gradle.org/8.7/userguide/validation_problems.html#implicit_dependency