openapi-generator: [BUG][spring] Enum query parameter parsing fails due to enum constants being generated in uppercase

Description

Even when enum values are lower-case in the specification, openapi-generator produces uppercased enum constants in Java, apparently due to the convention. However, this means that lowercased values in enum query parameters are not parsed correctly, resulting in 400 Bad requests for valid requests.

openapi-generator version

4.0.3, was present in 3.2 as well

OpenAPI declaration file content or url
openapi: 3.0.2
info:
  title: Catalog
  version: '1.0'
servers:
  - url: 'http://localhost:8080'

components:
  schemas:
    ProductType:
      type: string
      enum:
        - hardware
        - SOFTWARE
paths:
  /products:
    get:
      operationId: getProducts
      parameters:
        - in: query
          name: type
          schema:
            $ref: '#/components/schemas/ProductType'
      responses:
        '200':
          description: Ok
Command line used for generation

java -jar openapi-generator-cli.jar generate -i catalog.yaml -g spring

Steps to reproduce
  1. mvn spring-boot:run
  2. curl -i "http://localhost:8080/products?type=hardware"

Expected outcome: HTTP/1.1 501 due to missing implementation, and no warnings output by Spring. (This is the case for the uppercase type=SOFTWARE) Actual outcome: HTTP/1.1 400 and the following warning:

2019-07-11 12:29:39.186  WARN 12951 --- [nio-8080-exec-4] .w.s.m.s.DefaultHandlerExceptionResolver : Failed to bind request element:
  org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'org.openapitools.model.ProductType'; nested exception is
    org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [@io.swagger.annotations.ApiParam @javax.validation.Valid org.openapitools.model.ProductType] for value 'hardware'; nested exception is
      java.lang.IllegalArgumentException: No enum constant org.openapitools.model.ProductType.hardware

Spring is trying to do Enum.valueOf(ProductType.class, "hardware") which fails, since openapi-generator produces the constant as ProductType.HARDWARE.

Related issues/PRs

Issue #1927 is related: producing enum in MACRO_CASE also results in non-working @DefaultValue annotations.

Suggest a fix

I would consider just reverting to producing enum constants matching the API specification. If this is considered unacceptable, then probably the way to fix this bug would be to:

  1. Produce a model.FooConverter extends java.Beans.PropertyEditorSupport for each enum schema Foo, implementing setAsText utilizing model.Foo::fromValue
  2. For each controller where an enum is used as a parameter, add @InitBinder public void initBinder(WebDataBinder dataBinder) { dataBinder.registerCustomEditor(Foo.class, new FooConverter()) }

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 13
  • Comments: 22 (1 by maintainers)

Commits related to this issue

Most upvoted comments

Ran into this issue today. What worked for is creating a custom Converter in Spring boot to convert the Enum to String

@Component
public class MyCustomEnumConverter implements Converter<String, SortEnum> {
    @Override
    public SortEnum convert(String source) {
       try {
          return SortEnum.valueOf(source);
       } catch(Exception e) {
          return null; // or SortEnum.asc
       }
    }
}

same situation

schema

    imgFormat:
      type: string
      enum:
        - "lol"
        - "image/gif"
        - "image/jpeg"
        - "image/png"
        - "image/png8"
        - "image/vnd.jpeg-png"
        - "image/vnd.jpeg-png8"

generated enum

public enum ImgFormat {
  
  LOL("lol"),
  
  IMAGE_GIF("image/gif"),
  
  IMAGE_JPEG("image/jpeg"),
  
  IMAGE_PNG("image/png"),
  
  IMAGE_PNG8("image/png8"),
  
  IMAGE_VND_JPEG_PNG("image/vnd.jpeg-png"),
  
  IMAGE_VND_JPEG_PNG8("image/vnd.jpeg-png8");

  private String value;

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

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

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

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


Resolved [org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'com.myapp.model.ImgFormat'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [@javax.validation.constraints.NotNull @io.swagger.annotations.ApiParam @javax.validation.Valid @org.springframework.web.bind.annotation.RequestParam com.myapp.model.ImgFormat] for value 'image/png'; nested exception is java.lang.IllegalArgumentException: No enum constant com.myapp.model.ImgFormat.image/png]

Any news regarding this issue?

Not clear from the above if this is solved (I’m using 5.1.1 and still happening), but I found a simple enough way to get around it without needing to customise the enum templates.

Instead of defining the enum values only, add coressponding values under the x-enum-varnames as in the example below. This will overwrite the uppercase constant with the values you specify with no uppercase conversion. i.e.

DistributionType:
      type: string
      enum:
        - All
        - List
      x-enum-varnames:
        - All
        - List

Generates

public enum DistributionType {
  
  All("All"),
  
  List("List");

  private String value;

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

Repetitive I know but preferable (for me anyway) to forcing everything to uppercase. Project I’m working on has a lot of configuration and properties identifiers in enums so retaining case sensitivity is essential.

Any updates here? A solution would be greatly appreciated.

Something seems to be wrong in Spring validation as the ENUMs are generated with correct lowercase value. Still values sent with lowercase fail with: java.lang.IllegalArgumentException: No enum constant name

Spring is not using the generated fromValue method (which works with lowercase) but instead Enum.valueOf(this.enumType, source.trim()) in StringToEnumConverterFactory.

public enum SortByEnum {
  
  NAME("name"),
  
  ID("id"),
  
  LASTUPDATEDTS("lastUpdatedTs");

  private String value;

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

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

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

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

This issue appears to have been fixed in v6.2.1 with #13349. Code generated by the current openapi-generator now produces 501 for /products?type=hardware.

I am not sure how that MR fixed anything @tkalliom. It doesn’t generate a converter to register to spring, it just shows what a converter would look like in the samples 🤔