SMILEY4 / ktor-swagger-ui

Kotlin Ktor plugin to generate OpenAPI and provide Swagger UI
Apache License 2.0
150 stars 25 forks source link

overwrite<T> doesn't apply format to property of request body #115

Open outlndrr opened 1 month ago

outlndrr commented 1 month ago

I have overwrite block inside Swagger plugin:

overwrite<LocalDateTime>(
                Schema<String>().also {
                    it.type = "string"
                    it.format = "date-time"
            }
       )

But it doesn't apply the format if it's the body of the request and displays as a common string:

"timestamp" : {
    "title": "String",
    "type": "string"
}

Is it possible to apply overwriting to the body as well?

Library version: 3.0.0

SMILEY4 commented 1 month ago

Hi @outlndrr i'm having issues reproducing your described problem. The setup

    install(SwaggerUI) {
        schemas {
            overwrite<LocalDateTime>( // overwrite LocalDateTime
                Schema<String>().also {
                    it.type = "string"
                    it.format = "date-time" // with format
                }
            )
        }
    }

    routing {
        route("swagger") { swaggerUI("/api.json") }
        route("api.json") { openApiSpec() }

        get("test", {
            request {
                body<LocalDateTime>() // use LocalDateTime as request body 
            }
        }) {}
    }

produces

       "requestBody" : {
          "content" : {
            "text/plain" : {
              "schema" : {
                "type" : "string",
                "format" : "date-time"   <- includes format
              }
            }
          },
          "required" : false
        },

Are you using LocalDateTime as the type of a fields of another type (e.g. class SomeResponseData(val date: LocalDateTime)) ? In this case overwrite in the swagger plugin-config will not work, since it only applies to "root" types.

For applying custom schemas to all types (including nested ones), the schema-generator has to be customized directly. See https://github.com/SMILEY4/ktor-swagger-ui/wiki/Customizing-Schema-Generation and https://github.com/SMILEY4/schema-kenerator/wiki/Type-Redirects for more information.

outlndrr commented 1 month ago

Hi @SMILEY4 Can you please provide example how to redirect LocalDateTime into the string with the specific format? The example in "Type Redirects" is not very helpful for my situation.

SMILEY4 commented 1 month ago

Ah sorry, i think i missed something here.

There are ~two options to properly customize the resulting schema of a type

There is one possible (maybe slightly hacky) workaround though:

val result = typeOf<ClassWithLocalDateTime>()
    .processKotlinxSerialization {
        //  REGISTER A CUSTOM PROCESSOR FOR  'LocalDateTime' HERE (works the same when using 'processReflection')
        customProcessor<LocalDateTime> {
            PrimitiveTypeData( // build a custom type for the generation of the localdatetime-schema based on String
                id = TypeId.build(String::class.qualifiedName!!),
                simpleName = String::class.simpleName!!,
                qualifiedName = String::class.qualifiedName!!,
                annotations = mutableListOf(
                    AnnotationData( // add a custom annotation to the type that tells a schema-generation step to set the "format" property
                        name = "swagger-format",
                        values = mutableMapOf("format" to "date-time"),
                        annotation = null
                    )
                )
            )
        }
    }
    .generateSwaggerSchema()
    // ADD A STEP TO CUSTOMIZE THE GENERATED SWAGGER_SCHEMA 
    .let { bundle ->
        val typeDataMap = bundle.buildTypeDataMap()
        bundle.also { schema ->
            process(schema.data, typeDataMap) // check the root schema
            schema.supporting.forEach { process(it, typeDataMap) } // check additional schemas
        }
    }
    .compileInlining()

fun process(schema: SwaggerSchema, typeDataMap: Map<TypeId, BaseTypeData>) {
    schema.typeData.annotations
        .find { it.name == "swagger-format" } // find our custom annotation
        ?.also { annotation ->
            schema.swagger.format = annotation.values["format"] as String // set the "format"-property of the swagger-schema
        }
}

println(json.writeValueAsString(result.swagger))
result.componentSchemas.forEach { (name, schema) ->
    println("$name: ${json.writeValueAsString(schema)}")
}

Adding the custom processor and the extra step to the pipeline should produce the correct schema, though you might have to adapt the surrounding configuration a bit for your use case. I'll try to make this whole thing a bit more convenient with the next version.

If you try this, please let me know it it worked for you and if you encounter any more questions or issues :)