Search code examples
spring-bootopenapi-generator

Making spring-boot obey enum values with generated openapi code


I'm trying the "api first" approach with a spring-boot app and a very basic openapi.yaml I defined.

I created a basic API which gets a single mandatory parameter named type which is an array of an enum I created (sophisticatedly named SomeEnum).

When I try to generate a request via the Swagger editor - I get this request generated:

curl -X 'GET' \
  'https://localhost:8080/api/1/demo?type=best_value' \
  -H 'accept: application/json'

enter image description here

The problem is when I try it - I get a 400 error from Spring and this error from the log (formatted for readability):

2023-04-06 15:52:05.223  WARN 16396 --- [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'java.util.List';   
nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [@javax.validation.constraints.NotNull @io.swagger.v3.oas.annotations.Parameter @javax.validation.Valid @org.springframework.web.bind.annotation.RequestParam com.ronkitay.openapidemo.model.SomeEnum] for value 'another-value'; 
nested exception is java.lang.IllegalArgumentException: No enum constant com.ronkitay.openapidemo.model.SomeEnum.another-value]

Sending an API request like the one below works fine:

curl  http://localhost:8080/api/1/demo?type=ANOTHER_VALUE

The generated enum seems fine to me:

@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-04-06T15:51:49.262+03:00[Asia/Jerusalem]")
public enum SomeEnum {
  
  VALUE1("value1"),
  
  ANOTHER_VALUE("another-value"),
  
  BEST_VALUE("best_value"),
  
  THEVALUE("thevalue");

  private String value;

  SomeEnum(String value) {
    this.value = value;
  }

  @JsonValue
  public String getValue() {
    return value;
  }

  @Override
  public String toString() {
    return String.valueOf(value);
  }

  @JsonCreator
  public static SomeEnum fromValue(String value) {
    for (SomeEnum b : SomeEnum.values()) {
      if (b.value.equals(value)) {
        return b;
      }
    }
    throw new IllegalArgumentException("Unexpected value '" + value + "'");
  }
}

And I can make spring act as expected by adding this configuration:

@Configuration
public class Config implements WebMvcConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverterFactory(new SomeEnumFormatter());
    }

    public static class SomeEnumFormatter implements ConverterFactory<String,SomeEnum> {

        @Override
        public <T extends SomeEnum> Converter<String, T> getConverter(Class<T> targetType) {
            return new StringToSomeEnumConverter<>(targetType);
        }

        public static class StringToSomeEnumConverter<T extends SomeEnum> implements Converter<String, T> {

            private Class<T> targetClass;

            public StringToSomeEnumConverter(Class<T> targetClass) {
                this.targetClass = targetClass;
            }

            @Override
            public T convert(String source) {
                return (T) SomeEnum.fromValue(source);
            }
        }
    }
}

My question is - is there some hidden configuration in Spring or in the openApiGenerator which can do this for me automatically so I do not need to implement a custom converter for every enum I have?


Solution

  • Seems that this issue is addressed through this merged pull-request

    We need to add converters because spring doesn't use jackson for path and query params (cf spring-projects/spring-boot#24233 ) This will fix #12874

    Hence, bumping to openapi-generator:6.5.0 (which is latest when writing this) should solve the problem.