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?
CodePudding user response:
Add a special enum constant indicating the request JSON contained an invalid enum constant name. The request JSON should never actually contain the name of this enum constant. Also add a method that Jackson will invoke while deserializing to convert a JSON string to an enum constant. This method returns the special enum constant if the JSON string is not a known enum constant name.
public enum Type {
MISSING_SITE,
INACTIVE_SITE,
@JsonProperty("SHOULD NEVER ACTUALLY APPEAR IN REQUEST JSON")
INVALID;
/**
* Converts enum constant name to enum constant.
*
* @param name
* enum constant name
* @return enum constant, or {@link #INVALID} if there is no enum constant with that name
*/
@JsonCreator
public static Type valueOfOrInvalid(String name) {
try {
return Type.valueOf(name);
} catch (IllegalArgumentException e) {
return INVALID;
}
}
}
Inside the ReportTypeValidator.isValid(
method, check the enum constant is INVALID
.
if (type == Type.INVALID) {
// Add constraint violation.
CodePudding user response:
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 {};
}