Ktor-OpenAPI-Generator icon indicating copy to clipboard operation
Ktor-OpenAPI-Generator copied to clipboard

Create a proper Example system

Open Wicpar opened this issue 4 years ago • 9 comments

A system needs to be created that allows to provide one or more examples with their metadata without adding bulk to the minimal configuration.

Wicpar avatar May 15 '20 14:05 Wicpar

I am really waiting for this feature to show all possible errors in my api image

y9san9 avatar May 15 '20 20:05 y9san9

Oh, I'm still waiting... I hope you'll make it as soon as you can...

y9san9 avatar Jun 09 '20 17:06 y9san9

Hi @Wicpar, I implemented an @Example annotation for the fields and wanted to run it by you in case you think it's useful and want me to add a PR.

Let me know if you prefer me to open a different issue for this, I thought the subjects were somewhat connected.

The rationale behind the change is that I'm using Rapidoc instead of Swagger UI, and Rapidoc ignores the examples entirely and uses only the "example" attribute each field.

The annotation is used like this:

data class Result(
    @Description("Hexadecimal UID of the card")
    @Example("04D3AC7A124A80")
    var cardUID: String = "",
    @Description("Card type: A, B o M")
    @Example("M")
    var cardType: String = "",
    @Example(intValue = 200)
    var someIntField: Int = 0
)

This is the implementation:

@Target(AnnotationTarget.TYPE, AnnotationTarget.PROPERTY)
@SchemaProcessorAnnotation(ExampleValueProcessor::class)
annotation class Example(
    val stringValue: String = "",
    val intValue: Int = 0,
    val longValue: Long = 0,
    val doubleValue: Double = 0.0,
    val boolValue: Boolean = false,
    val isNull: Boolean = false
)

The processor is the only thing that's a little ugly, but probably can be improved:

object ExampleValueProcessor: SchemaProcessor<Example> {
    @Suppress("UNCHECKED_CAST")
    override fun process(model: SchemaModel<*>, type: KType, annotation: Example): SchemaModel<*> {
        when (model) {
            is SchemaModel.SchemaModelLitteral<*> -> {
                (model as SchemaModel.SchemaModelLitteral<Any?>).apply {
                    when(model.type) {
                        DataType.integer, DataType.number -> {
                            example = when {
                                annotation.intValue != 0 -> annotation.intValue
                                annotation.longValue != 0L -> annotation.longValue
                                annotation.doubleValue != 0.0 -> annotation.doubleValue
                                annotation.stringValue.isNotBlank() -> BigDecimal(annotation.stringValue)
                                else -> 0
                            }
                        }
                        DataType.boolean -> {
                            example = annotation.boolValue
                        }
                        DataType.`object`, DataType.array -> {
                            throw Exception("Type ${model.type} not supported for examples")
                        }
                        else -> { /* string */
                            example = annotation.stringValue
                        }
                    }
                }
            }
            is SchemaModel.SchemaModelEnum<*> -> {
                (model as SchemaModel.SchemaModelEnum<Any?>).apply {
                    example = annotation.stringValue
                }
            }
            else -> {
                throw Exception("${annotation::class} can't be applied to $model")
            }
        }
        return model
    }
}

JavierPAYTEF avatar Jul 28 '20 15:07 JavierPAYTEF

The approach I would have taken is to just take a string and convert it based on type, this would allow for objects as json examples without additional effort.

Wicpar avatar Jul 28 '20 18:07 Wicpar

@JavierPAYTEF What do you think about that, but a purely string based annotation that parses the content as json (or other based on a secondary optional property) ?

Wicpar avatar Jul 28 '20 21:07 Wicpar

@Wicpar Hi, sorry, I've had a lot of work this past few weeks, I couldn't work on this. Regarding your approach, could you explain a little more? The advantage with your approach would be that we could use non-native types, like arrays for example, but the examples would still need to be simple, and not objects, since this tag is for field examples. Probably another approach would be needed for full object examples. Do you maybe have anything you think would be useful as a guide to implement this? If you saw my code it's not exhaustive with the types at all.

JavierPAYTEF avatar Aug 16 '20 01:08 JavierPAYTEF

There is no way to handle multiple types of primitives in annotations in a cleaner way than you did. The cleanest is really to use a string with intellij @Language injection for json. But if you want to keep it with primitives the way you went with is the way to go. Maybe have a way to allow 0 values by selecting the annotation value based on KType.

Wicpar avatar Aug 16 '20 08:08 Wicpar

Do you maybe have an example or somewhere I can read some documentation? I tried researching on my own but my knowledge of annotations is not the best, so I will follow your lead since you probably know more than me in that regard. Let me know a little more about how you would go about implementing it and I'll try to follow your lead.

JavierPAYTEF avatar Aug 18 '20 22:08 JavierPAYTEF

I don't know if an exhaustive guide about annotations exist, i just learned how to use them by fiddling around with them, and looking at annotation-heavy projects like spring.

Wicpar avatar Aug 19 '20 00:08 Wicpar