Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HV-1973 - Bitcoin address validator #1343

Closed
wants to merge 23 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
909723f
HV-1973 - Bitcoin address validator (alpha version)
jyoshiriro Apr 13, 2024
73411f4
HV-1973 - Bitcoin address validator (alpha version)
jyoshiriro Apr 13, 2024
0a4993b
HV-1973 - Unit tests for invalid scenarios
jyoshiriro Apr 13, 2024
404f529
message() fixed
jyoshiriro Apr 15, 2024
e7d6234
HV-1973 - Validation file for BitcoinAddressValidator
jyoshiriro Apr 15, 2024
59bc4ee
HV-1973 - 'validatedBy' clean and main javadoc improved
jyoshiriro Apr 15, 2024
329599a
HV-1973 - BitcoinAddressType refactored - pre-compiled pattern
jyoshiriro Apr 15, 2024
83783db
HV-1973 - BitcoinAddressValidator refactored
jyoshiriro Apr 15, 2024
5ab28ef
HV-1973 - Validation messages fixed
jyoshiriro Apr 15, 2024
83255e5
HV-1973 - BitcoinValidatorTest refactored
jyoshiriro Apr 15, 2024
7c40599
HV-1973 - BitcoinAddressValidator added to configuration
jyoshiriro Apr 15, 2024
0339faf
HV-1973 - toLowerCase() fixed in BitcoinAddressValidator
jyoshiriro Apr 15, 2024
56d84ce
HV-1973 - BitcoinConstrainedTest added
jyoshiriro Apr 15, 2024
2941e94
HV-1973 - sintaxe issue fixed
jyoshiriro Apr 15, 2024
47e3708
HV-1973 - programmatic definition added
jyoshiriro Apr 15, 2024
2f2ed7f
HV-1973 - Message key refactored
jyoshiriro Apr 15, 2024
df28c8d
HV-1973 - tests refactored
jyoshiriro Apr 15, 2024
07f362d
HV-1973 - annotation processor support added
jyoshiriro Apr 15, 2024
4c24a61
HV-1973 - annotation processor support added
jyoshiriro Apr 15, 2024
f461881
HV-1973 - import issues fixed
jyoshiriro Apr 15, 2024
50a2175
HV-1973 - import issues fixed
jyoshiriro Apr 15, 2024
cac4ef0
HV-1973 - @ISBN -> @BitcoinAddress
jyoshiriro Apr 16, 2024
73844e6
HV-1973 - @ISBN -> @BitcoinAddress
jyoshiriro Apr 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ public ConstraintHelper(Types typeUtils, AnnotationApiHelper annotationApiHelper
registerAllowedTypesForBuiltInConstraint( BeanValidationTypes.SIZE, TYPES_SUPPORTED_BY_SIZE_AND_NOT_EMPTY_ANNOTATIONS );

//register HV-specific constraints
registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.BITCOIN_ADDRESS, CharSequence.class );
registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.CODE_POINT_LENGTH, CharSequence.class );
registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.CURRENCY, JavaMoneyTypes.MONETARY_AMOUNT );
registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.DURATION_MAX, Duration.class );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ public static class HibernateValidatorTypes {

private static final String ORG_HIBERNATE_VALIDATOR_CONSTRAINTS = "org.hibernate.validator.constraints";

public static final String BITCOIN_ADDRESS = ORG_HIBERNATE_VALIDATOR_CONSTRAINTS + ".BitcoinAddress";

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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import org.hibernate.validator.ap.testmodel.FieldLevelValidationUsingBuiltInConstraints;
import org.hibernate.validator.ap.testmodel.MethodLevelValidationUsingBuiltInConstraints;
import org.hibernate.validator.ap.testmodel.ModelWithBitcoinAddressConstraints;
import org.hibernate.validator.ap.testmodel.ModelWithCodePointLengthConstraints;
import org.hibernate.validator.ap.testmodel.ModelWithDateConstraints;
import org.hibernate.validator.ap.testmodel.ModelWithISBNConstraints;
Expand Down Expand Up @@ -780,4 +781,21 @@ public void isbnConstraints() {
new DiagnosticExpectation( Kind.ERROR, 22 )
);
}

@Test
public void bitcoinAddressConstraints() {
File[] sourceFiles = new File[] {
compilerHelper.getSourceFile( ModelWithBitcoinAddressConstraints.class )
};

boolean compilationResult =
compilerHelper.compile( new ConstraintValidationProcessor(), diagnostics, false, true, sourceFiles );

assertFalse( compilationResult );
assertThatDiagnosticsMatch(
diagnostics,
new DiagnosticExpectation( Kind.ERROR, 19 ),
new DiagnosticExpectation( Kind.ERROR, 22 )
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Hibernate Validator, declare and validate application constraints
*
* License: Apache License, Version 2.0
* See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
*/
package org.hibernate.validator.ap.testmodel;

import org.hibernate.validator.constraints.BitcoinAddress;

/**
* @author José Yoshiriro
*/
public class ModelWithBitcoinAddressConstraints {

@BitcoinAddress
private String string;

@BitcoinAddress
private Integer integer;

@BitcoinAddress
private Boolean aBoolean;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Hibernate Validator, declare and validate application constraints
*
* License: Apache License, Version 2.0
* See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
*/
package org.hibernate.validator.cfg.defs;

import org.hibernate.validator.cfg.ConstraintDef;
import org.hibernate.validator.constraints.BitcoinAddress;
import org.hibernate.validator.constraints.BitcoinAddressType;

/**
* @author José Yoshiriro
* @since 8.0.2
*/
public class BitcoinAddressDef extends ConstraintDef<BitcoinAddressDef, BitcoinAddress> {

public BitcoinAddressDef() {
super( BitcoinAddress.class );
}

public BitcoinAddressDef value(BitcoinAddressType type) {
addParameter( "value", new BitcoinAddressType[] { type } );
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Hibernate Validator, declare and validate application constraints
*
* License: Apache License, Version 2.0
* See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
*/
package org.hibernate.validator.constraints;

import jakarta.validation.Constraint;
import jakarta.validation.Payload;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

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;


/**
* The string has to be a well-formed BTC (Bitcoin) Mainnet address. Accepts {@code CharSequence}.
* P2PK, P2MS and Nested SegWit (P2SH-P2WPKH and P2SH-P2WSH) addresses are not valid.
* <p>
* {@code null} elements are considered valid.
*
* @author José Yoshiriro
*
* @since 8.0.2
*/
@Documented
@Constraint(validatedBy = { })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface BitcoinAddress {

String message() default "{org.hibernate.validator.constraints.BitcoinAddress.message}";

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

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

BitcoinAddressType[] value() default BitcoinAddressType.ANY ;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Hibernate Validator, declare and validate application constraints
*
* License: Apache License, Version 2.0
* See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
*/
package org.hibernate.validator.constraints;

import java.util.regex.Pattern;

public enum BitcoinAddressType {
ANY,
P2PKH( "^(1)[a-zA-HJ-NP-Z0-9]{25,61}$"),
P2SH( "^(3)[a-zA-HJ-NP-Z0-9]{33}$"),
BECH32( "^(bc1)[a-zA-HJ-NP-Z0-9]{39,59}$"),
P2WSH( "^(bc1q)[a-zA-HJ-NP-Z0-9]{58}$"),
P2WPKH( "^(bc1q)[a-zA-HJ-NP-Z0-9]{38}$"),
P2TR("^(bc1p)[a-zA-HJ-NP-Z0-9]{58}$");

private final Pattern pattern;

BitcoinAddressType(String pattern) {
this.pattern = pattern != null ? Pattern.compile( pattern ) : null;
}
BitcoinAddressType() {
this( null );
}

public Pattern getPattern() {
return pattern;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Hibernate Validator, declare and validate application constraints
*
* License: Apache License, Version 2.0
* See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
*/
package org.hibernate.validator.internal.constraintvalidators.hv;

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import org.hibernate.validator.constraints.BitcoinAddress;
import org.hibernate.validator.constraints.BitcoinAddressType;
import org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorContext;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
* Checks that a given character sequence (e.g. string) is a well-formed BTC (Bitcoin) address.
*
* @author José Yoshiriro
*/
public class BitcoinAddressValidator implements ConstraintValidator<BitcoinAddress, CharSequence> {

static final String HIBERNATE_VALIDATION_MESSAGES = "org.hibernate.validator.ValidationMessages";
static final String ADDRESS_TYPE_VALIDATION_MESSAGE_PREFIX = "org.hibernate.validator.constraints.BitcoinAddress.type.";
private final List<BitcoinAddressType> addressTypes = new ArrayList<>();

@Override
public void initialize(BitcoinAddress bitcoinAddress) {
BitcoinAddressType[] types = bitcoinAddress.value();

if ( Arrays.stream( types ).anyMatch( type -> type == BitcoinAddressType.ANY ) ) {
this.addressTypes.addAll(
Arrays.stream( BitcoinAddressType.values() )
.filter( type -> type != BitcoinAddressType.ANY )
.collect( Collectors.toList() ) );
return;
}

Collections.addAll( this.addressTypes, types );
}

/**
* Checks that the character sequence is a valid BTC (Bitcoin) address
*
* @param charSequence the character sequence to validate
* @param context context in which the constraint is evaluated
* @return returns {@code true} if the string is a valid BTC (Bitcoin) address
*/
@Override
public boolean isValid(CharSequence charSequence, ConstraintValidatorContext context) {

if ( charSequence == null ) {
return true;
}

for ( BitcoinAddressType type : this.addressTypes ) {
Pattern pattern = type.getPattern();
Matcher matcher = pattern.matcher( charSequence );

if ( matcher.matches() ) {
return true;
}
}


context.unwrap( HibernateConstraintValidatorContext.class )
.addExpressionVariable( "singleType", isSingleType() )
.addExpressionVariable( "typesDescription", getTypesDescription() );

return false;
}

boolean isSingleType() {
return addressTypes.size() == 1
|| addressTypes.containsAll( Arrays.stream( BitcoinAddressType.values() )
.filter( type -> type != BitcoinAddressType.ANY ).collect( Collectors.toList() ) );
}

String getTypesDescription() {
if ( isSingleType() ) {
return getAddressTypeName( BitcoinAddressType.ANY );
}

return this.addressTypes.stream().map( this::getAddressTypeName ).collect( Collectors.joining( "; " ) );
}

String getAddressTypeName(BitcoinAddressType bitcoinAddressType) {
ResourceBundle resourceBundle = ResourceBundle.getBundle( HIBERNATE_VALIDATION_MESSAGES, Locale.getDefault() );
return resourceBundle.getString(
ADDRESS_TYPE_VALIDATION_MESSAGE_PREFIX + bitcoinAddressType.name().toLowerCase( Locale.getDefault() ) );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ enum BuiltinConstraint {
ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_RU_INN("org.hibernate.validator.constraints.ru.INN"),
ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_TIME_DURATION_MAX("org.hibernate.validator.constraints.time.DurationMax"),
ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_TIME_DURATION_MIN("org.hibernate.validator.constraints.time.DurationMin"),
ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_UUID("org.hibernate.validator.constraints.UUID");
ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_UUID("org.hibernate.validator.constraints.UUID"),
ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_BITCOIN_ADDRESS("org.hibernate.validator.constraints.BitcoinAddress");

private static final Map<String, Set<BuiltinConstraint>> CONSTRAINT_MAPPING;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
import static org.hibernate.validator.internal.metadata.core.BuiltinConstraint.ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_UNIQUE_ELEMENTS;
import static org.hibernate.validator.internal.metadata.core.BuiltinConstraint.ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_URL;
import static org.hibernate.validator.internal.metadata.core.BuiltinConstraint.ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_UUID;
import static org.hibernate.validator.internal.metadata.core.BuiltinConstraint.ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_BITCOIN_ADDRESS;
import static org.hibernate.validator.internal.util.logging.Messages.MESSAGES;

import java.lang.annotation.Annotation;
Expand Down Expand Up @@ -95,6 +96,7 @@
import org.hibernate.validator.constraints.ScriptAssert;
import org.hibernate.validator.constraints.URL;
import org.hibernate.validator.constraints.UUID;
import org.hibernate.validator.constraints.BitcoinAddress;
import org.hibernate.validator.constraints.UniqueElements;
import org.hibernate.validator.constraints.br.CNPJ;
import org.hibernate.validator.constraints.br.CPF;
Expand Down Expand Up @@ -314,6 +316,7 @@
import org.hibernate.validator.internal.constraintvalidators.hv.ScriptAssertValidator;
import org.hibernate.validator.internal.constraintvalidators.hv.URLValidator;
import org.hibernate.validator.internal.constraintvalidators.hv.UUIDValidator;
import org.hibernate.validator.internal.constraintvalidators.hv.BitcoinAddressValidator;
import org.hibernate.validator.internal.constraintvalidators.hv.UniqueElementsValidator;
import org.hibernate.validator.internal.constraintvalidators.hv.br.CNPJValidator;
import org.hibernate.validator.internal.constraintvalidators.hv.br.CPFValidator;
Expand Down Expand Up @@ -822,6 +825,9 @@ private ConstraintHelper(Set<BuiltinConstraint> enabledBuiltinConstraints) {
if ( enabledBuiltinConstraints.contains( ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_UUID ) ) {
putBuiltinConstraint( tmpConstraints, UUID.class, UUIDValidator.class );
}
if ( enabledBuiltinConstraints.contains( ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_BITCOIN_ADDRESS ) ) {
putBuiltinConstraint( tmpConstraints, BitcoinAddress.class, BitcoinAddressValidator.class );
}

this.enabledBuiltinConstraints = Collections.unmodifiableMap( tmpConstraints );
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,14 @@ org.hibernate.validator.constraints.ru.INN.message = invalid Ru

org.hibernate.validator.constraints.time.DurationMax.message = must be shorter than${inclusive == true ? ' or equal to' : ''}${days == 0 ? '' : days == 1 ? ' 1 day' : ' ' += days += ' days'}${hours == 0 ? '' : hours == 1 ? ' 1 hour' : ' ' += hours += ' hours'}${minutes == 0 ? '' : minutes == 1 ? ' 1 minute' : ' ' += minutes += ' minutes'}${seconds == 0 ? '' : seconds == 1 ? ' 1 second' : ' ' += seconds += ' seconds'}${millis == 0 ? '' : millis == 1 ? ' 1 milli' : ' ' += millis += ' millis'}${nanos == 0 ? '' : nanos == 1 ? ' 1 nano' : ' ' += nanos += ' nanos'}
org.hibernate.validator.constraints.time.DurationMin.message = must be longer than${inclusive == true ? ' or equal to' : ''}${days == 0 ? '' : days == 1 ? ' 1 day' : ' ' += days += ' days'}${hours == 0 ? '' : hours == 1 ? ' 1 hour' : ' ' += hours += ' hours'}${minutes == 0 ? '' : minutes == 1 ? ' 1 minute' : ' ' += minutes += ' minutes'}${seconds == 0 ? '' : seconds == 1 ? ' 1 second' : ' ' += seconds += ' seconds'}${millis == 0 ? '' : millis == 1 ? ' 1 milli' : ' ' += millis += ' millis'}${nanos == 0 ? '' : nanos == 1 ? ' 1 nano' : ' ' += nanos += ' nanos'}


org.hibernate.validator.constraints.BitcoinAddress.message = must be a valid ${singleType ? typesDescription += ' address' : 'address for one of these types: ' += typesDescription}

org.hibernate.validator.constraints.BitcoinAddress.type.any = Bitcoin
org.hibernate.validator.constraints.BitcoinAddress.type.p2pkh = Legacy (P2PKH)
org.hibernate.validator.constraints.BitcoinAddress.type.p2sh = Nested SegWit (P2SH)
org.hibernate.validator.constraints.BitcoinAddress.type.bech32 = Native SegWit (Bech32)
org.hibernate.validator.constraints.BitcoinAddress.type.p2wsh = SegWit variant of P2SH (P2WSH)
org.hibernate.validator.constraints.BitcoinAddress.type.p2wpkh = SegWit variant of P2PKH (P2WPKH)
org.hibernate.validator.constraints.BitcoinAddress.type.p2tr = Taproot (P2TR)
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,11 @@ org.hibernate.validator.constraints.ru.INN.message = n\u00famer

org.hibernate.validator.constraints.time.DurationMax.message = deve ser menor que${inclusive == true ? ' ou igual a' : ''}${days == 0 ? '' : days == 1 ? ' 1 dia' : ' ' += days += ' dias'}${hours == 0 ? '' : hours == 1 ? ' 1 hora' : ' ' += hours += ' horas'}${minutes == 0 ? '' : minutes == 1 ? ' 1 minuto' : ' ' += minutes += ' minutos'}${seconds == 0 ? '' : seconds == 1 ? ' 1 segundo' : ' ' += seconds += ' segundos'}${millis == 0 ? '' : millis == 1 ? ' 1 mili' : ' ' += millis += ' miliss'}${nanos == 0 ? '' : nanos == 1 ? ' 1 nano' : ' ' += nanos += ' nanos'}
org.hibernate.validator.constraints.time.DurationMin.message = deve ser maior que${inclusive == true ? ' ou igual a' : ''}${days == 0 ? '' : days == 1 ? ' 1 dia' : ' ' += days += ' dias'}${hours == 0 ? '' : hours == 1 ? ' 1 hora' : ' ' += hours += ' horas'}${minutes == 0 ? '' : minutes == 1 ? ' 1 minuto' : ' ' += minutes += ' minutos'}${seconds == 0 ? '' : seconds == 1 ? ' 1 segundo' : ' ' += seconds += ' segundos'}${millis == 0 ? '' : millis == 1 ? ' 1 mili' : ' ' += millis += ' miliss'}${nanos == 0 ? '' : nanos == 1 ? ' 1 nano' : ' ' += nanos += ' nanos'}

org.hibernate.validator.constraints.BitcoinAddress.message = deve ser um endere\u00e7o ${singleType ? typesDescription += ' v\u00e1lido' : 'v\u00e1lido para algum desses tipos: ' += typesDescription}

org.hibernate.validator.constraints.BitcoinAddress.type.p2pkh = Legado (P2PKH)
org.hibernate.validator.constraints.BitcoinAddress.type.p2sh = SegWit aninhado (P2SH)
org.hibernate.validator.constraints.BitcoinAddress.type.bech32 = SegWit nativo (Bech32)
org.hibernate.validator.constraints.BitcoinAddress.type.p2wsh = Variante SegWit de P2SH (P2WSH)
org.hibernate.validator.constraints.BitcoinAddress.type.p2wpkh = Variante SegWit de P2PKH (P2WPKH)
Loading