Gostaria de usar o SpringBoot @Valid para validar um campo de solicitação http, mas com base em outro campo da mesma solicitação http.
Tenho o seguinte código:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<artifactId>question</artifactId>
<properties>
<maven.compiler.source>23</maven.compiler.source>
<maven.compiler.target>23</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
class FieldValidationApplication {
public static void main(String[] args) {
SpringApplication.run(FieldValidationApplication.class, args);
}
}
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
class FieldValidationController {
@PostMapping("/validate")
String question(@Valid @RequestBody SomeRequest someRequest) {
return "please validate the field";
}
}
record SomeRequest(int score,
String fieldPositive,
String fieldZeroAndNegative
)
{ }
As regras de validação são bastante simples:
O payload da requisição tem um field score. Se o valor do field score for estritamente positivo, então preciso verificar se o campo fieldPositive é uma string válida e também se fieldZeroAndNegative é nulo.
Por exemplo:
{
"score": 1,
"fieldPositive": "thisisok"
}
Mas estes não são:
{
"score": 1
}
{
"score": 1,
"fieldPositive": ""
}
{
"score": 1,
"fieldPositive": "below fieldZeroAndNegative should be null",
"fieldZeroAndNegative": "not ok"
}
Regra semelhante para o outro campo (código logo abaixo).
Foi isso que tentei, criei uma anotação personalizada:
record SomeRequest(int score,
@ValidateThisFieldOnlyIfScoreIsPositive String fieldPositive,
@ValidateThisFieldOnlyIfScoreIsZeroOrNegative String fieldZeroAndNegative
)
{ }
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Constraint(validatedBy = ValidateThisFieldOnlyIfScoreIsPositiveValidator.class)
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@interface ValidateThisFieldOnlyIfScoreIsPositive
{
String message() default "Field is invalid";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
class ValidateThisFieldOnlyIfScoreIsPositiveValidator implements ConstraintValidator<ValidateThisFieldOnlyIfScoreIsPositive, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
System.out.println("hello, the value of the field fieldPositive is " + value);
System.out.println("However, I cannot get the value of the field score");
if (" SomeRequest score " > 0) { //how to get the value of the field score here?
return value != null && !value.isEmpty() && value.length() > 3;
}
if (" SomeRequest score" <= 0) {
return value == null;
}
...
}
}
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Constraint(validatedBy = ValidateThisFieldOnlyIfScoreIsZeroOrNegativeValidator.class)
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@interface ValidateThisFieldOnlyIfScoreIsZeroOrNegative
{
String message() default "Field is invalid";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
class ValidateThisFieldOnlyIfScoreIsZeroOrNegativeValidator implements ConstraintValidator<ValidateThisFieldOnlyIfScoreIsZeroOrNegative, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
System.out.println("hello, the value of the field fieldZeroAndNegative is " + value);
System.out.println("However, I cannot get the value of the field score");
if (" SomeRequest score " <= 0) { //how to get the value of the field score here?
return value != null && !value.isEmpty() && value.length() > 3;
}
if (" SomeRequest score" > 0) {
return value == null;
}
}
}
Não tenho certeza se usar uma anotação por campo é a melhor opção para começar.
Pergunta:
Como obter ambos os campos (ou vários campos) da mesma solicitação no validador?
Para validar um campo com base no valor de outro campo no mesmo objeto de solicitação, você precisa aplicar a validação no nÃvel de classe em vez do nÃvel de campo. Dessa forma, o validador tem acesso a todos os campos do objeto.
Solução: Use uma restrição de nÃvel de classe
Crie uma anotação personalizada que pode ser aplicada a toda a classe:
Crie um validador que possa acessar todos os campos de SomeRequest:
Modifique
SomeRequest
para usar a nova anotação de validação:O Spring Boot validará automaticamente SomeRequest usando a anotação ao manipular a solicitação:
Por que isso funciona
score
campos e os campos dependentes em um só lugar.addPropertyNode()
, melhorando a clareza nas respostas da API.Essa abordagem garante que sua solicitação seja validada corretamente com base na pontuação, sem a necessidade de anotações separadas para cada campo.