diff --git a/annotation-processor/src/main/java/org/hibernate/validator/ap/internal/util/ConstraintHelper.java b/annotation-processor/src/main/java/org/hibernate/validator/ap/internal/util/ConstraintHelper.java index c7b477ae94..d3ac8f2ce9 100644 --- a/annotation-processor/src/main/java/org/hibernate/validator/ap/internal/util/ConstraintHelper.java +++ b/annotation-processor/src/main/java/org/hibernate/validator/ap/internal/util/ConstraintHelper.java @@ -292,6 +292,7 @@ public ConstraintHelper(Types typeUtils, AnnotationApiHelper annotationApiHelper registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.DURATION_MAX, Duration.class ); registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.DURATION_MIN, Duration.class ); registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.EMAIL, CharSequence.class ); + registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.IP_ADDRESS, CharSequence.class ); registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.ISBN, CharSequence.class ); registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.LENGTH, CharSequence.class ); registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.MOD_CHECK, CharSequence.class ); diff --git a/annotation-processor/src/main/java/org/hibernate/validator/ap/internal/util/TypeNames.java b/annotation-processor/src/main/java/org/hibernate/validator/ap/internal/util/TypeNames.java index 30d7c2dd4c..7726ad383a 100644 --- a/annotation-processor/src/main/java/org/hibernate/validator/ap/internal/util/TypeNames.java +++ b/annotation-processor/src/main/java/org/hibernate/validator/ap/internal/util/TypeNames.java @@ -69,6 +69,7 @@ public static class HibernateValidatorTypes { public static final String CODE_POINT_LENGTH = ORG_HIBERNATE_VALIDATOR_CONSTRAINTS + ".CodePointLength"; public static final String CURRENCY = ORG_HIBERNATE_VALIDATOR_CONSTRAINTS + ".Currency"; public static final String EMAIL = ORG_HIBERNATE_VALIDATOR_CONSTRAINTS + ".Email"; + public static final String IP_ADDRESS = ORG_HIBERNATE_VALIDATOR_CONSTRAINTS + ".IpAddress"; public static final String ISBN = ORG_HIBERNATE_VALIDATOR_CONSTRAINTS + ".ISBN"; public static final String LENGTH = ORG_HIBERNATE_VALIDATOR_CONSTRAINTS + ".Length"; public static final String MOD_CHECK = ORG_HIBERNATE_VALIDATOR_CONSTRAINTS + ".ModCheck"; diff --git a/annotation-processor/src/test/java/org/hibernate/validator/ap/ConstraintValidationProcessorIT.java b/annotation-processor/src/test/java/org/hibernate/validator/ap/ConstraintValidationProcessorIT.java index e4d19b0804..678e9295be 100644 --- a/annotation-processor/src/test/java/org/hibernate/validator/ap/ConstraintValidationProcessorIT.java +++ b/annotation-processor/src/test/java/org/hibernate/validator/ap/ConstraintValidationProcessorIT.java @@ -22,6 +22,7 @@ import org.hibernate.validator.ap.testmodel.ModelWithCodePointLengthConstraints; import org.hibernate.validator.ap.testmodel.ModelWithDateConstraints; import org.hibernate.validator.ap.testmodel.ModelWithISBNConstraints; +import org.hibernate.validator.ap.testmodel.ModelWithIpAddressConstraints; import org.hibernate.validator.ap.testmodel.ModelWithJava8DateTime; import org.hibernate.validator.ap.testmodel.ModelWithJavaMoneyTypes; import org.hibernate.validator.ap.testmodel.ModelWithJodaTypes; @@ -810,4 +811,21 @@ public void bitcoinAddressConstraints() { new DiagnosticExpectation( Kind.ERROR, 20 ) ); } + + @Test + @TestForIssue(jiraKey = "HV-2137") + public void ipAddressConstraints() { + File[] sourceFiles = new File[] { + compilerHelper.getSourceFile( ModelWithIpAddressConstraints.class ) + }; + + boolean compilationResult = + compilerHelper.compile( new ConstraintValidationProcessor(), diagnostics, false, true, sourceFiles ); + + assertFalse( compilationResult ); + assertThatDiagnosticsMatch( + diagnostics, + new DiagnosticExpectation( Kind.ERROR, 20 ) + ); + } } diff --git a/annotation-processor/src/test/java/org/hibernate/validator/ap/testmodel/ModelWithIpAddressConstraints.java b/annotation-processor/src/test/java/org/hibernate/validator/ap/testmodel/ModelWithIpAddressConstraints.java new file mode 100644 index 0000000000..84476994fb --- /dev/null +++ b/annotation-processor/src/test/java/org/hibernate/validator/ap/testmodel/ModelWithIpAddressConstraints.java @@ -0,0 +1,22 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.validator.ap.testmodel; + +import org.hibernate.validator.constraints.IpAddress; + +/** + * @author Ivan Malutin + */ +public class ModelWithIpAddressConstraints { + + @IpAddress + private String string; + + @IpAddress + private CharSequence charSequence; + + @IpAddress + private Integer integer; +} diff --git a/documentation/src/main/asciidoc/ch02.asciidoc b/documentation/src/main/asciidoc/ch02.asciidoc index 24f0987056..633c77e8b0 100644 --- a/documentation/src/main/asciidoc/ch02.asciidoc +++ b/documentation/src/main/asciidoc/ch02.asciidoc @@ -683,6 +683,10 @@ With one exception also these constraints apply to the field/property level, onl Supported data types::: `CharSequence` Hibernate metadata impact::: None +`@IpAddress`:: Checks that the annotated character sequence is a valid https://en.wikipedia.org/wiki/IP_address[IP address]. `type` determines the version of IP address. The default is IPv4. + Supported data types::: `CharSequence` + Hibernate metadata impact::: None + `@ISBN`:: Checks that the annotated character sequence is a valid https://en.wikipedia.org/wiki/International_Standard_Book_Number[ISBN]. `type` determines the type of ISBN. The default is ISBN-13. Supported data types::: `CharSequence` Hibernate metadata impact::: None diff --git a/engine/src/main/java/org/hibernate/validator/cfg/defs/IpAddressDef.java b/engine/src/main/java/org/hibernate/validator/cfg/defs/IpAddressDef.java new file mode 100644 index 0000000000..b9d47d05c4 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/cfg/defs/IpAddressDef.java @@ -0,0 +1,26 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.validator.cfg.defs; + +import org.hibernate.validator.cfg.ConstraintDef; +import org.hibernate.validator.constraints.IpAddress; + +/** + * An {@link IpAddress} constraint definition. + * + * @author Ivan Malutin + * @since 9.1 + */ +public class IpAddressDef extends ConstraintDef { + + public IpAddressDef() { + super(IpAddress.class); + } + + public IpAddressDef type(IpAddress.Type type) { + addParameter("type", type); + return this; + } +} diff --git a/engine/src/main/java/org/hibernate/validator/constraints/IpAddress.java b/engine/src/main/java/org/hibernate/validator/constraints/IpAddress.java new file mode 100644 index 0000000000..cb311777bb --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/constraints/IpAddress.java @@ -0,0 +1,60 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.validator.constraints; + +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.ElementType.TYPE_USE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +/** + * Checks that the annotated character sequence is a valid + * IP address. + * The supported type is {@code CharSequence}. {@code null} is considered valid. + * + * @author Ivan Malutin + * @since 9.1 + */ +@Documented +@Constraint(validatedBy = { }) +@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) +@Retention(RUNTIME) +public @interface IpAddress { + + String message() default "{org.hibernate.validator.constraints.IpAddress.message}"; + + Class[] groups() default { }; + + Class[] payload() default { }; + + Type type() default Type.IPv4; + + /** + * Defines the IP address version. + * Valid IP address versions are: + * + * When using {@code ANY}, an address is considered valid if it passes either + * IPv4 or IPv6 validation. + */ + enum Type { + IPv4, + IPv6, + ANY + } +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/constraintvalidators/hv/IpAddressValidator.java b/engine/src/main/java/org/hibernate/validator/internal/constraintvalidators/hv/IpAddressValidator.java new file mode 100644 index 0000000000..450a2297c0 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/constraintvalidators/hv/IpAddressValidator.java @@ -0,0 +1,168 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.validator.internal.constraintvalidators.hv; + + +import java.util.Arrays; +import java.util.List; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +import org.hibernate.validator.constraints.IpAddress; +import org.hibernate.validator.internal.util.Contracts; + +/** + * Checks that a given character sequence (e.g. string) is a valid IP address. + * + * @author Ivan Malutin + */ +public class IpAddressValidator implements ConstraintValidator { + + private IpAddressValidationAlgorithm ipAddressValidationAlgorithm; + + @Override + public void initialize(IpAddress constraintAnnotation) { + this.ipAddressValidationAlgorithm = IpAddressValidationAlgorithm.from( constraintAnnotation.type() ); + } + + @Override + public boolean isValid(CharSequence charSequence, ConstraintValidatorContext constraintValidatorContext) { + if ( charSequence == null ) { + return true; + } + + return ipAddressValidationAlgorithm.isValid( charSequence ); + } + + private interface IpAddressValidationAlgorithm { + boolean isValid(CharSequence charSequence); + + static IpAddressValidationAlgorithm from(IpAddress.Type type) { + Contracts.assertNotNull( type ); + + return switch ( type ) { + case IPv4 -> IpAddressValidationAlgorithmImpl.IPv4; + case IPv6 -> IpAddressValidationAlgorithmImpl.IPv6; + case ANY -> IpAddressValidationAlgorithmImpl.ANY; + }; + } + } + + private enum IpAddressValidationAlgorithmImpl implements IpAddressValidationAlgorithm { + IPv4 { + @Override + public boolean isValid(CharSequence charSequence) { + String ipAddress = charSequence.toString(); + + String[] parts = ipAddress.split( "\\." ); + + if ( parts.length != 4 ) { + return false; + } + + for ( String part : parts ) { + if ( part.isBlank() || ( part.length() > 1 && part.startsWith( "0" ) ) ) { + return false; + } + + int num; + try { + num = Integer.parseInt( part ); + } + catch (NumberFormatException e) { + return false; + } + if ( num < 0 || 255 < num ) { + return false; + } + + } + + return true; + } + }, + IPv6 { + @Override + public boolean isValid(CharSequence charSequence) { + String ipAddress = charSequence.toString(); + + int compressionIndex = ipAddress.indexOf( "::" ); + if ( compressionIndex != -1 && ipAddress.lastIndexOf( "::" ) != compressionIndex ) { + return false; + } + + boolean startsWithCompression = ipAddress.startsWith( "::" ); + boolean endsWithCompression = ipAddress.endsWith( "::" ); + if ( ( ipAddress.startsWith( ":" ) && !startsWithCompression ) || ( ipAddress.endsWith( ":" ) && !endsWithCompression ) ) { + return false; + } + + String[] parts = ipAddress.split( ":" ); + boolean hasCompression = compressionIndex != -1; + + if ( hasCompression ) { + List partsList = Arrays.asList( parts ); + if ( endsWithCompression ) { + partsList.add( "" ); + } + else if ( startsWithCompression && !partsList.isEmpty() ) { + partsList.remove( 0 ); + } + parts = partsList.toArray( new String[0] ); + } + + if ( parts.length > 8 ) { + return false; + } + + int partsCount = 0; + for ( int i = 0; i < parts.length; i++ ) { + String part = parts[i]; + + if ( part.isBlank() ) { + if ( i > 0 && parts[i - 1].isBlank() ) { + return false; + } + } + else if ( i == parts.length - 1 && part.contains( "." ) ) { + if ( !IPv4.isValid( part ) ) { + return false; + } + partsCount += 2; + } + else { + if ( part.length() > 4 ) { + return false; + } + int num; + try { + num = Integer.parseInt( part, 16 ); + } + catch (NumberFormatException e) { + return false; + } + if ( num < 0 || num > 0xFFFF ) { + return false; + } + partsCount++; + } + } + + if ( partsCount > 8 || ( partsCount < 8 && !hasCompression ) ) { + return false; + } + + return true; + } + }, + ANY { + @Override + public boolean isValid(CharSequence charSequence) { + return IPv4.isValid( charSequence ) || IPv6.isValid( charSequence ); + } + } + } +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/core/BuiltinConstraint.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/core/BuiltinConstraint.java index e2b4b17128..a98f79e214 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/core/BuiltinConstraint.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/core/BuiltinConstraint.java @@ -53,6 +53,7 @@ enum BuiltinConstraint { // Hibernate Validator specific constraints ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_CODE_POINT_LENGTH( "org.hibernate.validator.constraints.CodePointLength" ), ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_CURRENCY( "org.hibernate.validator.constraints.Currency" ), + ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_IP_ADDRESS( "org.hibernate.validator.constraints.IpAddress" ), ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_ISBN( "org.hibernate.validator.constraints.ISBN" ), ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_LENGTH( "org.hibernate.validator.constraints.Length" ), ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_LUHN_CHECK( "org.hibernate.validator.constraints.LuhnCheck" ), diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/core/ConstraintHelper.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/core/ConstraintHelper.java index 67c59f17f9..a24099f3f4 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/core/ConstraintHelper.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/core/ConstraintHelper.java @@ -34,6 +34,7 @@ import static org.hibernate.validator.internal.metadata.core.BuiltinConstraint.ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_CREDIT_CARD_NUMBER; import static org.hibernate.validator.internal.metadata.core.BuiltinConstraint.ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_CURRENCY; import static org.hibernate.validator.internal.metadata.core.BuiltinConstraint.ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_EAN; +import static org.hibernate.validator.internal.metadata.core.BuiltinConstraint.ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_IP_ADDRESS; import static org.hibernate.validator.internal.metadata.core.BuiltinConstraint.ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_ISBN; import static org.hibernate.validator.internal.metadata.core.BuiltinConstraint.ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_KOR_KORRRN; import static org.hibernate.validator.internal.metadata.core.BuiltinConstraint.ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_LENGTH; @@ -107,6 +108,7 @@ import org.hibernate.validator.constraints.Currency; import org.hibernate.validator.constraints.EAN; import org.hibernate.validator.constraints.ISBN; +import org.hibernate.validator.constraints.IpAddress; import org.hibernate.validator.constraints.Length; import org.hibernate.validator.constraints.LuhnCheck; import org.hibernate.validator.constraints.Mod10Check; @@ -327,6 +329,7 @@ import org.hibernate.validator.internal.constraintvalidators.hv.CodePointLengthValidator; import org.hibernate.validator.internal.constraintvalidators.hv.EANValidator; import org.hibernate.validator.internal.constraintvalidators.hv.ISBNValidator; +import org.hibernate.validator.internal.constraintvalidators.hv.IpAddressValidator; import org.hibernate.validator.internal.constraintvalidators.hv.LengthValidator; import org.hibernate.validator.internal.constraintvalidators.hv.LuhnCheckValidator; import org.hibernate.validator.internal.constraintvalidators.hv.Mod10CheckValidator; @@ -754,6 +757,9 @@ protected Map, List> violations = validator.validate( foo ); + assertNoViolations( violations ); + } + + @Test + public void invalidIpAddress() { + Foo foo = new Foo( "256.256.256.256" ); + Set> violations = validator.validate( foo ); + assertThat( violations ).containsOnlyViolations( + violationOf( IpAddress.class ).withMessage( "invalid IP address" ) + ); + } + + private static class Foo { + @IpAddress + private final String ipAddress; + + public Foo(String ipAddress) { + this.ipAddress = ipAddress; + } + } +} diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/constraintvalidators/hv/IpAddressValidatorTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/constraintvalidators/hv/IpAddressValidatorTest.java new file mode 100644 index 0000000000..65c5a84804 --- /dev/null +++ b/engine/src/test/java/org/hibernate/validator/test/internal/constraintvalidators/hv/IpAddressValidatorTest.java @@ -0,0 +1,202 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.validator.test.internal.constraintvalidators.hv; + +import static org.hibernate.validator.testutil.ConstraintViolationAssert.assertNoViolations; +import static org.hibernate.validator.testutil.ConstraintViolationAssert.assertThat; +import static org.hibernate.validator.testutil.ConstraintViolationAssert.violationOf; +import static org.hibernate.validator.testutils.ValidatorUtil.getConfiguration; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import java.util.Set; + +import jakarta.validation.ConstraintViolation; +import jakarta.validation.Validator; + +import org.hibernate.validator.HibernateValidator; +import org.hibernate.validator.HibernateValidatorConfiguration; +import org.hibernate.validator.cfg.ConstraintMapping; +import org.hibernate.validator.cfg.defs.IpAddressDef; +import org.hibernate.validator.constraints.IpAddress; +import org.hibernate.validator.internal.constraintvalidators.hv.IpAddressValidator; +import org.hibernate.validator.internal.util.annotation.ConstraintAnnotationDescriptor; +import org.hibernate.validator.testutil.TestForIssue; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** + * Tests for {@link IpAddress} constraint validator ({@link IpAddress}). + * + * @author Ivan Malutin + */ +@TestForIssue(jiraKey = "HV-2137") +public class IpAddressValidatorTest { + + private IpAddressValidator validator; + + @BeforeMethod + public void setUp() { + validator = new IpAddressValidator(); + } + + @Test + public void validIPv4Addresses() { + validator.initialize( initializeAnnotation( IpAddress.Type.IPv4 ) ); + + String[] validIPv4Addresses = { + "192.168.1.1", + "10.0.0.255", + "172.16.254.1", + "255.255.255.255", + "0.0.0.0", + "127.0.0.1", + "8.8.8.8", + "169.254.1.1", + "192.0.2.1", + "198.51.100.1" + }; + + for ( String validIpAddress : validIPv4Addresses ) { + assertValidIpAddress( validIpAddress ); + } + } + + @Test + public void invalidIPv4Addresses() { + validator.initialize( initializeAnnotation( IpAddress.Type.IPv4 ) ); + + String[] invalidIPv4Addresses = { + "256.1.1.1", + "192.168.1", + "192.168.1.1.1", + "192.168.1.", + "192.168..1", + "192.168.1.01", + "qwe.qwe.qwe.qwe", + "192.168.1.1 ", + "192 .168.1.1", + "192.168.1.-1" + }; + + for ( String invalidIpAddress : invalidIPv4Addresses ) { + assertInvalidIpAddress( invalidIpAddress ); + } + } + + @Test + public void validIPv6Addresses() { + validator.initialize( initializeAnnotation( IpAddress.Type.IPv6 ) ); + + String[] validIPv6Addresses = { + "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "2001:db8:85a3:0:0:8a2e:370:7334", + "2001:db8:85a3::8a2e:370:7334", + "::1", + "::", + "2001:db8::", + "::ffff:192.168.1.1", + "ff02::1", + "2001:0:0:1::1", + "2001:db8:1234:5678:90ab:cdef:1234:5678", + "::ffff:c0a8:101", + "64:ff9b::192.168.1.1", + "2001:20::1", + "fc00::1", + "2001:db8:a::123", + "1:2:3:4:5:6:7:8", + "a:b:c:d:e:f:1:2", + "fe80::215:5dff:fe00:402", + "2001:db8:85a3:8d3:1319:8a2e:370:7348", + "::", + "::1", + "1::", + "::1234:5678", + "1:2:3:4:5:6:7::" + }; + + for ( String validIpAddress : validIPv6Addresses ) { + assertValidIpAddress( validIpAddress ); + } + } + + @Test + public void invalidIPv6Addresses() { + validator.initialize( initializeAnnotation( IpAddress.Type.IPv6 ) ); + + String[] invalidIPv6Addresses = { + "2001:db8:85a3:8d3:1319:8a2e:370", + "2001::85a3::8a2e", + "2001:db8:85a3:8d3:1319:8a2e:370:7334:", + ":2001:db8:85a3:8d3:1319:8a2e:370:7334", + "2001:db8:85a3:8d3:1319:8a2e:370:733g", + "2001:db8:85a3:8d3:1319:8a2e:370:73345", + "2001:db8:85a3:8d3:1319:8a2e:370:", + "2001:db8:::8a2e:370:7334", + "2001:db8:85a3:8d3:1319:8a2e:370:7334:abcd", + "2001:0db8:85a3:00000:0000:8a2e:0370:7334", + "::ffff:192.168.300.1", + "2001:db8:85a3:8d3:1319:8a2e:370:7334 :", + "2001:db8:85a3-8d3:1319:8a2e:370:7334", + "2001::db8::1", + "ff02:::1", + "2001:db8:85a3:0:0:0:0:0:0", + ":1", + "2001:db8:85a3:8d3:1319:8a2e:370:", + "2001:db8:xyz::1", + ":::", + "1:2:3:4:5:6:7:8:9", + "1:2:3:4:5:6:7", + "1::2::3", + "1:2:3:4:5:6:7:8g" + }; + + for ( String invalidIpAddress : invalidIPv6Addresses ) { + assertInvalidIpAddress( invalidIpAddress ); + } + } + + @Test + public void testIpAddressDef() { + HibernateValidatorConfiguration config = getConfiguration( HibernateValidator.class ); + ConstraintMapping mapping = config.createConstraintMapping(); + mapping.type( Foo.class ) + .field( "ipAddress" ) + .constraint( new IpAddressDef().type( IpAddress.Type.IPv4 ) ); + config.addMapping( mapping ); + Validator validator = config.buildValidatorFactory().getValidator(); + + Set> constraintViolations = validator.validate( new Foo( "127.0.0.1" ) ); + assertNoViolations( constraintViolations ); + + constraintViolations = validator.validate( new Foo( "256.256.256.256" ) ); + assertThat( constraintViolations ).containsOnlyViolations( + violationOf( IpAddress.class ) + ); + } + + private void assertValidIpAddress(String ipAddress) { + assertTrue( validator.isValid( ipAddress, null ), ipAddress + " should be a valid IP address" ); + } + + private void assertInvalidIpAddress(String ipAddress) { + assertFalse( validator.isValid( ipAddress, null ), ipAddress + " should be an invalid IP address" ); + } + + private IpAddress initializeAnnotation(IpAddress.Type type) { + ConstraintAnnotationDescriptor.Builder descriptorBuilder = new ConstraintAnnotationDescriptor.Builder<>( IpAddress.class ); + descriptorBuilder.setAttribute( "type", type ); + return descriptorBuilder.build().getAnnotation(); + } + + private static class Foo { + private final String ipAddress; + + public Foo(String ipAddress) { + this.ipAddress = ipAddress; + } + } +}