Search code examples
javaspringspring-bootvalidationhibernate-validator

Implementatio of mutliple ConstraintValidator and their priority (enable/disable by requests endpoint)


Lets say I have an Object with two fields which should be validated:

public class AnyRQ {
    
    @MerchantAccountValidation
    @JsonProperty(value = "merchant-account")
    private MerchantAccount merchantAccount;
    
    @RequestIdValidation
    @JsonProperty(value = "request-id")
    private String requestId;

}

Both of the Annotations @MerchantAccountValidation and @RequestIdValidation implements a ConstraintValidator and including the rules to be valid or not. (Just show one class)

 public class RequestIdValidator
    implements ConstraintValidator<RequestIdValidation, String> {

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        return value != null && value.length() > 10;
    }    
}

Now I have a Controller with two endpoints. Endpoint 1 should validate both Fields but Request 2 should just validate requestId.

@RestController
@RequestMapping("/validate")
public class ValidController {
    
    @PostMapping("/endpoint1")
    public ResponseEntity<?> register(@Valid @RequestBody AnyRQ req, Errors errors) {
         if (errors.hasErrors()) {
         }
        return null;
    }

    @PostMapping("/endpoint2")
    public ResponseEntity<?> authorization(@Valid @RequestBody AnyRQ req, Errors errors) {
         if (errors.hasErrors()) {
         }
        return null;
    }
}

Is there any way to achive a kind of priority or inheritance to get this working? I was thinking about to have the Validation Annotation on the method level of the endpoints. But unfortunately this is not working.


Solution

  • Patrick!

    To achieve the desired outcome you can use @GroupSequence. It mostly meant for ordering validations (no need to check that entity exists in database, if id is null f.e.), but would work for your task. Let's say you have 2 validation groups (better names are required :) ):

    public interface InitialValidation {
    }
    
    @GroupSequence(InitialValidation.class)
    public interface InitialValidationGroup {
    }
    
    public interface FullValidation {
    }
    
    @GroupSequence(FullValidation.class)
    public interface FullValidationGroup {
    }
    

    Specify them in the DTO:

    public class AnyRQ {
    
    @MerchantAccountValidation(groups = FullValidation.class)
    @JsonProperty(value = "merchant-account")
    private MerchantAccount merchantAccount;
    
    @RequestIdValidation(groups = {InitialValidation.class, FullValidation.class})
    @JsonProperty(value = "request-id")
    private String requestId;
    
    }
    

    And in the controller use @Validated instead of @Valid to provide corresponding group:

    @RestController
    

    @RequestMapping("/validate") public class ValidController {

    @PostMapping("/endpoint1")
    public ResponseEntity<?> register(@Validated(FullValidationGroup.class) @RequestBody AnyRQ req, Errors errors) {
         if (errors.hasErrors()) {
         }
        return null;
    }
    
    @PostMapping("/endpoint2")
    public ResponseEntity<?> authorization(@Validated(InitialValidationGroup.class) @RequestBody AnyRQ req, Errors errors) {
         if (errors.hasErrors()) {
         }
        return null;
    }
    }
    

    The other option is to keep one group in DTO, but specify two groups in controller for @Validated.