papsign / Ktor-OpenAPI-Generator

Ktor OpenAPI/Swagger 3 Generator
Apache License 2.0
241 stars 42 forks source link

Create a proper Example system #48

Open Wicpar opened 4 years ago

Wicpar commented 4 years ago

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

y9san9 commented 4 years ago

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

y9san9 commented 4 years ago

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

JavierPAYTEF commented 4 years ago

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
    }
}
Wicpar commented 4 years ago

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 commented 4 years ago

@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) ?

JavierPAYTEF commented 4 years ago

@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.

Wicpar commented 4 years ago

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.

JavierPAYTEF commented 4 years ago

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.

Wicpar commented 4 years ago

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.