Search code examples
javaspring-bootopenapi-generator

Values are not being validated against openani-generated enum inside spring-boot project


I am using openapi 3.0.3 spec with openapi-generator-maven-plugin for Java code generation to build interfaces that are implemented in a spring-boot project.

/user/search:
  get:
    parameters:
      - in: query
        name: sortBy
        description: Field to sort by
        required: true
        schema:
          $ref: "#/components/schemas/UserSearchSortBy"
# omitting some code to shorted the copy-pasted spec
schemas:
  UserSearchSortBy:
    type: string
    enum: [first_name, last_name, email, phone_number]

The above results in an interface that I can implement. The enum for UserSearchSortBy is created fine.

@RequestMapping(
    method = RequestMethod.GET,
    value = "/user/search",
    produces = { "application/json" }
)
default ResponseEntity<UsersResponsePageableModel> _searchUsers(
    @NotNull @Min(1) @Parameter(name = "currentPage", description = "Page number", required = true) @Valid @RequestParam(value = "currentPage", required = true) Integer currentPage,
    @NotNull @Min(1) @Max(100) @Parameter(name = "pageSize", description = "Number of records to show per page", required = true) @Valid @RequestParam(value = "pageSize", required = true) Integer pageSize,
    @NotNull @Parameter(name = "sortOrder", description = "Sort order", required = true) @Valid @RequestParam(value = "sortOrder", required = true) SortOrderEnumModel sortOrder,
    @NotNull @Parameter(name = "sortBy", description = "Field to sort by", required = true) @Valid @RequestParam(value = "sortBy", required = true) UserSearchSortByModel sortBy,
    @NotNull @Size(max = 128) @Parameter(name = "searchQuery", description = "Search field", required = true) @Valid @RequestParam(value = "searchQuery", required = true) String searchQuery
) {
    return searchUsers(currentPage, pageSize, sortOrder, sortBy, searchQuery);
}

I expect the values that are being submitted to an API to be validated against the UserSearchSortBy enum. The issue is that there is no validation present. It looks like the generator is not generating a piece that is responsible for validating values against the enum. Any help is appreciated.


Solution

  • The following post helped me to create a converter factory.

    @Component
    public class StringToEnumConverterFactory implements ConverterFactory<String, Enum> {
    
        private static class StringToEnumConverter<T extends Enum> implements Converter<String, T> {
            private Class<T> enumType;
            public StringToEnumConverter(Class<T> enumType) {
                this.enumType = enumType;
            }
            public T convert(String source) {
                return (T) Enum.valueOf(this.enumType, source.trim().toUpperCase());
            }
        }
    
        @Override
        public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
            return new StringToEnumConverter(targetType);
        }
    }
    

    And the following exception handler helps me craft a nice-looking error response.

    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public ResponseEntity<?> handleMismatchException(MethodArgumentTypeMismatchException e) {
        String message = e.getMessage();
        Class<?> parameterType = e.getParameter().getParameterType();
        if (parameterType.isEnum()) {
            Object[] enumConstants = parameterType.getEnumConstants();
            if (enumConstants != null && enumConstants.length > 0) {
                if (e.getName() != null && !e.getName().isEmpty() && e.getValue() != null) {
                    message = String.format("Invalid value '%s' for field '%s'.", e.getValue(), e.getName()) + " Valid values are " + Arrays.asList(enumConstants);
                }
            }
        }
        Map<String, String> errors = new HashMap<>();
        errors.put("message", message);
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).header("Content-Type", MediaType.APPLICATION_JSON_VALUE).body(errors);
    }