使用生成的OpenAPI代码使Spring Boot 遵循枚举值。

huangapple go评论55阅读模式
英文:

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';

使用生成的OpenAPI代码使Spring Boot 遵循枚举值。

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 an 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?

英文:

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 &#39;GET&#39; \
  &#39;https://localhost:8080/api/1/demo?type=best_value&#39; \
  -H &#39;accept: application/json&#39;

使用生成的OpenAPI代码使Spring Boot 遵循枚举值。

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 &#39;java.lang.String&#39; to required type &#39;java.util.List&#39;;   
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 &#39;another-value&#39;; 
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 = &quot;org.openapitools.codegen.languages.SpringCodegen&quot;, date = &quot;2023-04-06T15:51:49.262+03:00[Asia/Jerusalem]&quot;)
public enum SomeEnum {
  
  VALUE1(&quot;value1&quot;),
  
  ANOTHER_VALUE(&quot;another-value&quot;),
  
  BEST_VALUE(&quot;best_value&quot;),
  
  THEVALUE(&quot;thevalue&quot;);

  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(&quot;Unexpected value &#39;&quot; + value + &quot;&#39;&quot;);
  }
}

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&lt;String,SomeEnum&gt; {

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

        public static class StringToSomeEnumConverter&lt;T extends SomeEnum&gt; implements Converter&lt;String, T&gt; {

            private Class&lt;T&gt; targetClass;

            public StringToSomeEnumConverter(Class&lt;T&gt; 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?

答案1

得分: 1

看起来这个问题是通过这个合并的拉取请求解决的。

我们需要添加转换器,因为Spring不使用Jackson来处理路径和查询参数(参见spring-projects/spring-boot#24233)。这将修复#12874。

因此,将openapi-generator升级到6.5.0(在撰写时为最新版本)应该解决这个问题。

英文:

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.

huangapple
  • 本文由 发表于 2023年4月6日 21:01:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/75949835.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定