openapi-processor / openapi-processor-base

re-usable code of openapi-processor
https://openapiprocessor.io
Apache License 2.0
2 stars 3 forks source link

Add enum names via x-enumNames parameter #144

Closed kirillsulim closed 4 months ago

kirillsulim commented 4 months ago

In some of my projects I have to describe an openAPI interface with enum with values like "01", "02", etc. Those are codes for specific business cases. I would like to operate in my code with enum values like ClientType.BASIC instead of ClientType._01 or use string values for such cases. As I found out there are some openAPI generators that uses extensions like x-enumNames for such cases (see this stackowerflow thread for more info).

So I propose following logic: For enum schema like:

NamedEnum:
  type: string
  enum:
    - "1"
    - "2"
    - "3"
  x-enumNames:
    - "ONE"
    - "TWO"
    - "THREE"

generate following java code:

public enum NamedEnum {
    ONE("1"),
    TWO("2"),
    THREE("3");

    // ... some code ommited...
}
hauner commented 4 months ago

I see the issue..

using x-tension keywords like this is a bit against the philosophy of openapi-processor.

I would like to keep tooling specific stuff out of the OpenAPI description. Let's assume we have multiple consumers of the api. Each consumer may use another programming languages and different tooling. Now we would have to add the x-enumNames equivalent of each tooling to the OpenAPI... Things get more difficult when we don't have control over the OpenAPI description. We would have to add some pre-processing to add the enum names to the api.

Maybe this is a bit academic. ;-) In most cases this may not be an issue.

The oneOf enum construction from stackoverflow is interesting. I guess I have see this before but I wonder if anyone is really using it.

I can also imagine to "hide" the x-tension keyword behind a "use at your own risk" option. So I can say here is a solution but it has its drawbacks.

The typical openapi-processor way would be to create some kind of mapping. That would be possible, but I fear this may be cumbersome to work with. Not sure. Probably depends on how many enums I need to map and how often I have to change them.

kirillsulim commented 4 months ago

Hello, Martin!

I think I've understood your point of view. OpenAPI spec should be language independent and restrictions of enum values in .yaml config relates to JSON structure, not Java implementation. So possibly "01" would be absolutely legal enum value in some language and the developer shouldn't restrict enum names for languages in API spec.

It surely would be more convenient for me in my case to use extensions and generate enum with codes. However the main issue her as I think is that I'm trying to describe existing API that wasn't designed with OpenAPI in mind. There wouldn't be such issue if this API was developed with OpenAPI in first place.

Probably there is no much sense in proposed functionality in such case. I will try to use mapping config and describe enums with plain Java. Thanks for your review.

hauner commented 4 months ago

To have better names for enum values, you can create an enum with proper names yourself and tell the processor to use it. This is a strategy if there are only a few enums you like to improve.

For example the enum could look like this (in kotlin, apart from the enum names it is identical to the java enum code generated by the processor):

package io.openapiprocessor.samples

import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonValue
import io.openapiprocessor.samples.model.BarBar

enum class NamedEnum(@JsonValue val value: String) {
    ONE("1"),
    TWO("2");

    companion object {
        @JsonCreator
        fun fromValue(value: String): NamedEnum {
            for (v in NamedEnum.entries) {
                if (v.value == value) {
                    return v
                }
            }
            throw IllegalArgumentException(value)
        }
    }
}

for an OpenAPI enum like this

// OpenAPI snippet
// ...
components:
  schemas:
    AnEnum:
      type: string
      enum:
        - "1"
        - "2"

After telling the processor to use our custom annotation by adding a type mapping

// mapping.yaml
openapi-processor-mapping: v7

options:
  // ...

map:
  types:
    - type: AnEnum => io.openapiprocessor.samples.NamedEnum

the processor will use the custom annotation instead of generating an enum from the OpenAPI description. We can now use NamedEnum.ONE in our code.

This works if the enum is a property of a (body) schema. If the enum is a query parameter you will probably need a custom Spring converter for string to enum conversion.

hauner commented 4 months ago

here is the Spring converter (kotlin)

package io.openapiprocessor.samples

import org.springframework.core.convert.converter.Converter

class NamedEnumConverter: Converter<String, NamedEnum> {
    override fun convert(source: String): NamedEnum {
        return NamedEnum.fromValue(source)
    }
}

and the code to enable it

package io.openapiprocessor.samples

import org.springframework.context.annotation.Configuration
import org.springframework.format.FormatterRegistry
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer

@Configuration
open class WebConfig : WebMvcConfigurer {
    override fun addFormatters(registry: FormatterRegistry) {
        registry.addConverter(NamedEnumConverter())
    }
}
hauner commented 4 months ago

summarized in an article https://openapiprocessor.io/oap/home/articles/mapping/custom-enum-mapping.html :-)

kirillsulim commented 4 months ago

Thanks. That will be very helpful if someone would have similar issues with existing APIs.