BAEL-4605: code and tests for composing constraints (#12180)

* BAEL-4605: code and tests for composing constraints

* BAEL-4605: small fix and formatted the code

* BAEL-4605: code review
This commit is contained in:
etrandafir93 2022-05-21 21:47:49 +02:00 committed by GitHub
parent 9c61c1ce39
commit 296b77b909
8 changed files with 302 additions and 0 deletions

View File

@ -0,0 +1,37 @@
package com.baeldung.javaxval.constraint.composition;
public class Account {
@ValidAlphanumeric
private String username;
@ValidAlphanumericWithSingleViolation
private String password;
@ValidLengthOrNumericCharacter
private String nickname;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
}

View File

@ -0,0 +1,19 @@
package com.baeldung.javaxval.constraint.composition;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
@Component
@Validated
public class AccountService {
@AlphanumericReturnValue
public String getAnInvalidAlphanumericValue() {
return "john";
}
@AlphanumericReturnValue
public String getValidAlphanumericValue() {
return "johnDoe1234";
}
}

View File

@ -0,0 +1,36 @@
package com.baeldung.javaxval.constraint.composition;
import org.hibernate.validator.constraints.Length;
import javax.validation.Constraint;
import javax.validation.Payload;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraintvalidation.SupportedValidationTarget;
import javax.validation.constraintvalidation.ValidationTarget;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@NotNull
@Pattern(regexp = ".*\\d.*", message = "must contain at least one numeric character")
@Length(min = 6, max = 32, message = "must have between 6 and 32 characters")
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {})
@SupportedValidationTarget(ValidationTarget.ANNOTATED_ELEMENT)
public @interface AlphanumericReturnValue {
String message() default "method return value should have a valid length and contain numeric character(s).";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@ -0,0 +1,21 @@
package com.baeldung.javaxval.constraint.composition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
@Configuration
@ComponentScan({ "com.baeldung.javaxval.constraint.composition" })
public class ConstraintCompositionConfig {
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
@Bean
public AccountService accountService() {
return new AccountService();
}
}

View File

@ -0,0 +1,37 @@
package com.baeldung.javaxval.constraint.composition;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import org.hibernate.validator.constraints.Length;
@NotNull
@Pattern(regexp = ".*\\d.*", message = "must contain at least one numeric character")
@Length(min = 6, max = 32, message = "must have between 6 and 32 characters")
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {})
public @interface ValidAlphanumeric {
String message() default "field should have a valid length and contain numeric character(s).";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@ -0,0 +1,38 @@
package com.baeldung.javaxval.constraint.composition;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
import javax.validation.ReportAsSingleViolation;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import org.hibernate.validator.constraints.Length;
@NotNull
@Pattern(regexp = ".*\\d.*", message = "must contain at least one numeric character")
@Length(min = 6, max = 32, message = "must have between 6 and 32 characters")
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {})
@ReportAsSingleViolation
public @interface ValidAlphanumericWithSingleViolation {
String message() default "field should have a valid length and contain numeric character(s).";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@ -0,0 +1,34 @@
package com.baeldung.javaxval.constraint.composition;
import org.hibernate.validator.constraints.CompositionType;
import org.hibernate.validator.constraints.ConstraintComposition;
import org.hibernate.validator.constraints.Length;
import javax.validation.Constraint;
import javax.validation.Payload;
import javax.validation.constraints.Pattern;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Pattern(regexp = ".*\\d.*", message = "must contain at least one numeric character")
@Length(min = 6, max = 32, message = "must have between 6 and 32 characters")
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {})
@ConstraintComposition(CompositionType.OR)
public @interface ValidLengthOrNumericCharacter {
String message() default "field should have a valid length or contain numeric character(s).";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@ -0,0 +1,80 @@
package com.baeldung.javaxval.constraint.composition;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { ConstraintCompositionConfig.class }, loader = AnnotationConfigContextLoader.class)
public class ConstraintCompositionUnitTest {
@Autowired
private AccountService accountService;
private Validator validator;
@Before
public void setup() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
@Test
public void whenUsernameIsInvalid_validationShouldReturnTwoViolations() {
Account account = new Account();
account.setNickname("valid_nickname123");
account.setPassword("valid_password123");
account.setUsername("john");
Set<ConstraintViolation<Account>> violations = validator.validate(account);
assertThat(violations).hasSize(2);
}
@Test
public void whenPasswordIsInvalid_validationShouldReturnSingleViolation() {
Account account = new Account();
account.setUsername("valid_username123");
account.setNickname("valid_nickname123");
account.setPassword("john");
Set<ConstraintViolation<Account>> violations = validator.validate(account);
assertThat(violations).hasSize(1);
}
@Test
public void whenNicknameIsTooShortButContainsNumericCharacter_validationShouldPass() {
Account account = new Account();
account.setUsername("valid_username123");
account.setPassword("valid_password123");
account.setNickname("doe1");
Set<ConstraintViolation<Account>> violations = validator.validate(account);
assertThat(violations).isEmpty();
}
@Test
public void whenMethodReturnValuesIsInvalid_validationShouldFail() {
assertThatThrownBy(() -> accountService.getAnInvalidAlphanumericValue()).isInstanceOf(ConstraintViolationException.class)
.hasMessageContaining("must contain at least one numeric character")
.hasMessageContaining("must have between 6 and 32 characters");
}
}