I’m trying to validate an Enum using the custom validator, in my custom validator I’m trying to return a custom message when a parameter does not exist in the enum values.
Bellow my enum
public enum Type { MISSING_SITE, INACTIVE_SITE; }
Bellow my PostMapping
method
@PostMapping(value = "/line-kpi", produces = MediaType.APPLICATION_JSON_VALUE) @Operation(summary = "Find Kpis by one or more customer property") public ResponseEntity<List<KpiDTO>> findKPILineByCustomer(@RequestBody @ValidCustomerParameter CustomerParameter customerParameter, @RequestParam @ValidExtractionDate String extractionDate) { var linesKpi = Optional.ofNullable( kpiService.findKPILineByCustomer( Optional.ofNullable(customerParameter.getEntityPerimeter()).orElse(List.of()), Optional.ofNullable(customerParameter.getName()).orElse(List.of()), Optional.ofNullable(customerParameter.getIc01()).orElse(List.of()), Optional.ofNullable(customerParameter.getSiren()).orElse(List.of()), Optional.ofNullable(customerParameter.getEnterpriseId()).orElse(List.of()), LocalDate.parse(extractionDate) ) ); return linesKpi.map(ResponseEntity::ok).orElseThrow(() -> new ResourceNotFoundException(KPIS)); }
I can’t switch the type of enum to string in the method itself because I’m using swagger which displays a nice selection list for enums.
Unfortunately, when I try to give a different value for Type, it returns a bad request and my validators are not triggered.
So I’m trying to serialize my enum to be interpreted as String when it arrives at the controller and to do that I need to use Jackson, I tried to look for a solution but I can’t find a good one for my case.
Bellow are my validators
public class ReportTypeValidator implements ConstraintValidator<ValidReportType, Type> { private String globalMessage; @Override public void initialize(ValidReportType constraintAnnotation) { ConstraintValidator.super.initialize(constraintAnnotation); globalMessage = constraintAnnotation.message(); } @Override public boolean isValid(Type type, ConstraintValidatorContext constraintValidatorContext) { if (Arrays.stream(Type.values()).filter(type1 -> type1.equals(type)).toList().isEmpty()) { constraintValidatorContext .buildConstraintViolationWithTemplate(globalMessage + ", report type does not exist") .addConstraintViolation(); return false; } return true; } }
@Constraint(validatedBy = ReportTypeValidator.class) @Target( { ElementType.PARAMETER }) @Retention(RetentionPolicy.RUNTIME) @Valid public @interface ValidReportType { String message() default "Invalid value for report type"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
Could anyone tell me how can I turn my enum as a string so my validator could handle it?
Advertisement
Answer
I found it, I was able to do it by implementing a new converter, which will convert string to a valid enum value or an INVALID value:
public class TypeConverter implements Converter<String, Type> { @Override public Type convert(String source) { if (Arrays.stream(Type.values()).filter(type -> Objects.equals(type.toString(), source)).toList().isEmpty()) { return Type.INVALID; } return Type.valueOf(source.toUpperCase()); } }
After that I added a new Configuration for my converter:
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new TypeConverter()); } }
Also I had to hide the INVALID value of my enum from swagger by adding the @Schema annotation:
@Schema(allowableValues = {"MISSING_SITE","INACTIVE_SITE"}, type = "String") public enum Type { MISSING_SITE, INACTIVE_SITE, INVALID }
Finally in the validators, I should reject the INVALID value and display a custom message:
public class ReportTypeValidator implements ConstraintValidator<ValidReportType, Type> { private String globalMessage; @Override public void initialize(ValidReportType constraintAnnotation) { ConstraintValidator.super.initialize(constraintAnnotation); globalMessage = constraintAnnotation.message(); } @Override public boolean isValid(Type type, ConstraintValidatorContext constraintValidatorContext) { if (type == Type.INVALID) { constraintValidatorContext .buildConstraintViolationWithTemplate(globalMessage) .addConstraintViolation(); return false; } return true; } }
The annotation for the previous validator:
@Constraint(validatedBy = ReportTypeValidator.class) @Target( { ElementType.PARAMETER }) @Retention(RetentionPolicy.RUNTIME) @Valid public @interface ValidReportType { String message() default "Invalid value for report type"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }