Bean Validation with Extra Information

aisensiy :

I am trying to create a UniqueName annotation as a cutomize bean validation annotation for a create project api:

@PostMapping("/users/{userId}/projects")
public ResponseEntity createNewProject(@PathVariable("userId") String userId,
                                       @RequestBody @Valid ProjectParam projectParam) {
    User projectOwner = userRepository.ofId(userId).orElseThrow(ResourceNotFoundException::new);

    Project project = new Project(
        IdGenerator.nextId(),
        userId,
        projectParam.getName(),
        projectParam.getDescription()
    );
    ...
  }

@Getter
@NoArgsConstructor(access = AccessLevel.PRIVATE)
class ProjectParam {

  @NotBlank
  @NameConstraint
  private String name;
  private String description;
}

@Constraint(validatedBy = UniqueProjectNameValidator.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD })
public @interface UniqueName {

    public String message() default "already existed";

    public Class<?>[] groups() default {};

    public Class<? extends Payload>[] payload() default{};
}

public class UniqueProjectNameValidator implements ConstraintValidator<UniqueName, String> {
   @Autowired
   private ProjectQueryMapper mapper;

   public void initialize(UniqueName constraint) {
   }

   public boolean isValid(String value, ConstraintValidatorContext context) {
      // how can I get the userId info??
      return mapper.findByName(userId, value) == null;
   }
}

The problem is that name field just need uniqueness for user level. So I need to get the {userId} from the URL field for validation. But how can I add this into the UniqueProjectNameValidator? Or is there some better way to handle this validation? This is just a small part of a large object, the real object has many other complex validations in the request handler which make the code quite dirty.

crizzis :

As @Abhijeet mentioned, dynamically passing the userId property to the constraint validator is impossible. As to how to handle this validation case better, there's the clean solution and the dirty solution.

The clean solution is to extract all the business logic to a service method, and validate the ProjectParam at the service level. This way, you can add a userId property to ProjectParam, and map it from the @PathVariable onto the @RequestBody before calling the service. You then adjust UniqueProjectNameValidator to validate ProjectParams rather than Strings.

The dirty solution is to use Hibernate Validator's cross-parameter constraints (see also this link for an example). You essentially treat both of your controller method parameters as the input for your custom validator.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=121352&siteId=1