Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 4 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
<?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">
<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>4.0.1</version>
<version>4.0.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

Expand All @@ -25,7 +26,7 @@
<dependency>
<groupId>it.aboutbits</groupId>
<artifactId>spring-boot-toolbox</artifactId>
<version>2.1.0-RC1</version>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>it.aboutbits</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,12 @@
import it.aboutbits.springboot.testing.validation.rule.PastRule;
import it.aboutbits.springboot.testing.validation.rule.PositiveOrZeroRule;
import it.aboutbits.springboot.testing.validation.rule.PositiveRule;
import it.aboutbits.springboot.testing.validation.rule.RepeatedFieldRule;
import it.aboutbits.springboot.testing.validation.rule.SizeRule;
import it.aboutbits.springboot.testing.validation.rule.ValidBeanRule;
import it.aboutbits.springboot.testing.validation.rule.ValidDateRangeRule;
import it.aboutbits.springboot.testing.validation.rule.ValidNumericRangeRule;
import it.aboutbits.springboot.testing.validation.rule.ValidPasswordRule;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
Expand All @@ -39,13 +43,17 @@ public abstract class BaseRuleBuilder<R extends BaseRuleBuilder<R>> implements
NotBlankRule<R>,
NotEmptyRule<R>,
NotNullRule<R>,
NotValidatedRule<R>,
NullableRule<R>,
PastRule<R>,
PositiveOrZeroRule<R>,
PositiveRule<R>,
NotValidatedRule<R>,
RepeatedFieldRule<R>,
SizeRule<R>,
ValidBeanRule<R> {
ValidBeanRule<R>,
ValidDateRangeRule<R>,
ValidNumericRangeRule<R>,
ValidPasswordRule<R> {
@Getter(AccessLevel.PACKAGE)
private final List<Rule> rules = new ArrayList<>();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package it.aboutbits.springboot.testing.validation.rule;

import it.aboutbits.springboot.testing.validation.core.BaseRuleBuilder;
import it.aboutbits.springboot.testing.validation.core.CustomValidationFunction;
import it.aboutbits.springboot.testing.validation.core.ValidationRulesData;
import it.aboutbits.springboot.toolbox.validation.annotation.RepeatedField;
import org.jspecify.annotations.NullMarked;

import java.util.Arrays;

@SuppressWarnings("unchecked")
@NullMarked
public interface RepeatedFieldRule<V extends BaseRuleBuilder<?>> extends ValidationRulesData {
default V repeatedField(String originalField, String repeatedField) {
addValidationFunction(
(Object o) -> {
var isValid = false;
var currentClass = o.getClass();

while (currentClass != null) {
var annotations = currentClass.getDeclaredAnnotationsByType(RepeatedField.class);

isValid = Arrays.stream(annotations).anyMatch(
a -> a.originalField().equals(originalField) && a.repeatedField()
.equals(repeatedField)
);

if (isValid) {
break;
}
currentClass = currentClass.getSuperclass();
}

return new CustomValidationFunction.Result(
isValid,
"@RepeatedField(originalField = \"%s\", repeatedField = \"%s\") is missing.".formatted(
originalField,
repeatedField
)
);
}
);

return (V) this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package it.aboutbits.springboot.testing.validation.rule;

import it.aboutbits.springboot.testing.validation.core.BaseRuleBuilder;
import it.aboutbits.springboot.testing.validation.core.CustomValidationFunction;
import it.aboutbits.springboot.testing.validation.core.ValidationRulesData;
import it.aboutbits.springboot.toolbox.validation.annotation.ValidDateRange;
import org.jspecify.annotations.NullMarked;

import java.util.Arrays;

@SuppressWarnings("unchecked")
@NullMarked
public interface ValidDateRangeRule<V extends BaseRuleBuilder<?>> extends ValidationRulesData {
default V validDateRange(String fromDateField, String toDateField) {
addValidationFunction(
(Object o) -> {
var isValid = false;
var currentClass = o.getClass();

while (currentClass != null) {
var annotations = currentClass.getDeclaredAnnotationsByType(ValidDateRange.class);

isValid = Arrays.stream(annotations).anyMatch(
a -> a.fromDateField().equals(fromDateField) && a.toDateField()
.equals(toDateField)
);

if (isValid) {
break;
}
currentClass = currentClass.getSuperclass();
}

return new CustomValidationFunction.Result(
isValid,
"@ValidDateRange(fromDateField = \"%s\", toDateField = \"%s\") is missing.".formatted(
fromDateField,
toDateField
)
);
}
);

return (V) this;
}

default V validDateRange(
String fromDateField,
String toDateField,
boolean allowEmptyRange
) {
addValidationFunction(
(Object o) -> {
var isValid = false;
var currentClass = o.getClass();

while (currentClass != null) {
var annotations = currentClass.getDeclaredAnnotationsByType(ValidDateRange.class);

isValid = Arrays.stream(annotations).anyMatch(
a -> a.fromDateField().equals(fromDateField)
&& a.toDateField().equals(toDateField)
&& a.allowEmptyRange() == allowEmptyRange
);

if (isValid) {
break;
}
currentClass = currentClass.getSuperclass();
}

return new CustomValidationFunction.Result(
isValid,
"@ValidDateRange(fromDateField = \"%s\", toDateField = \"%s\", allowEmptyRange = %s) is missing."
.formatted(fromDateField, toDateField, allowEmptyRange)
);
}
);

return (V) this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package it.aboutbits.springboot.testing.validation.rule;

import it.aboutbits.springboot.testing.validation.core.BaseRuleBuilder;
import it.aboutbits.springboot.testing.validation.core.CustomValidationFunction;
import it.aboutbits.springboot.testing.validation.core.ValidationRulesData;
import it.aboutbits.springboot.toolbox.validation.annotation.ValidNumericRange;
import org.jspecify.annotations.NullMarked;

import java.util.Arrays;

@SuppressWarnings("unchecked")
@NullMarked
public interface ValidNumericRangeRule<V extends BaseRuleBuilder<?>> extends ValidationRulesData {
default V validNumericRange(String lowerBoundField, String upperBoundField) {
addValidationFunction(
(Object o) -> {
var isValid = false;
var currentClass = o.getClass();

while (currentClass != null) {
var annotations = currentClass.getDeclaredAnnotationsByType(ValidNumericRange.class);

isValid = Arrays.stream(annotations).anyMatch(
a -> a.lowerBoundField().equals(lowerBoundField) && a.upperBoundField()
.equals(upperBoundField)
);

if (isValid) {
break;
}
currentClass = currentClass.getSuperclass();
}

return new CustomValidationFunction.Result(
isValid,
"@ValidNumericRange(lowerBoundField = \"%s\", upperBoundField = \"%s\") is missing.".formatted(
lowerBoundField,
upperBoundField
)
);
}
);

return (V) this;
}

default V validNumericRange(
String lowerBoundField,
String upperBoundField,
boolean allowEqualValues
) {
addValidationFunction(
(Object o) -> {
var isValid = false;
var currentClass = o.getClass();

while (currentClass != null) {
var annotations = currentClass.getDeclaredAnnotationsByType(ValidNumericRange.class);

isValid = Arrays.stream(annotations).anyMatch(
a -> a.lowerBoundField().equals(lowerBoundField)
&& a.upperBoundField().equals(upperBoundField)
&& a.allowEqualValues() == allowEqualValues
);

if (isValid) {
break;
}
currentClass = currentClass.getSuperclass();
}

return new CustomValidationFunction.Result(
isValid,
"@ValidNumericRange(lowerBoundField = \"%s\", upperBoundField = \"%s\", allowEqualValues = %s) is missing."
.formatted(lowerBoundField, upperBoundField, allowEqualValues)
);
}
);

return (V) this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package it.aboutbits.springboot.testing.validation.rule;

import it.aboutbits.springboot.testing.validation.core.BaseRuleBuilder;
import it.aboutbits.springboot.testing.validation.core.Rule;
import it.aboutbits.springboot.testing.validation.core.ValidationRulesData;
import it.aboutbits.springboot.testing.validation.source.LongerThanValueSource;
import it.aboutbits.springboot.testing.validation.source.ShorterThanValueSource;
import org.jspecify.annotations.NullMarked;

@SuppressWarnings("unchecked")
@NullMarked
public interface ValidPasswordRule<V extends BaseRuleBuilder<?>> extends ValidationRulesData {
default V validPassword(String property, long minLength, long maxLength) {
addRule(
new Rule(property, LongerThanValueSource.class, maxLength)
);
addRule(
new Rule(property, ShorterThanValueSource.class, minLength)
);
return (V) this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package it.aboutbits.springboot.testing.validation.source;

import it.aboutbits.springboot.testing.validation.core.ValueSource;
import org.jspecify.annotations.NullMarked;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.function.Function;
import java.util.stream.Stream;

@NullMarked
public class LongerThanValueSource implements ValueSource {
private static final Map<Class<?>, Function<Object[], Stream<?>>> TYPE_SOURCES = new HashMap<>();
private static final Random RANDOM = new Random();

static {
TYPE_SOURCES.put(String.class, LongerThanValueSource::getStream);
}

@SuppressWarnings("unused")
public static void registerType(Class<?> type, Function<Object[], Stream<?>> source) {
TYPE_SOURCES.put(type, source);
}

@Override
@SuppressWarnings("unchecked")
public <T> Stream<T> values(Class<T> propertyClass, Object... args) {
var sourceFunction = TYPE_SOURCES.get(propertyClass);
if (sourceFunction != null) {
return (Stream<T>) sourceFunction.apply(args);
}

throw new IllegalArgumentException("Property class not supported!");
}

private static Stream<String> getStream(Object[] args) {
var length = Long.valueOf((long) args[0]).intValue();
var minLength = length + 1;

var maxLength = length + 1024;

if (minLength == maxLength) {
return Stream.of(generateRandomString(minLength));
}

var randomLength = RANDOM.nextInt(minLength, maxLength);

return Stream.of(
generateRandomString(minLength),
generateRandomString(maxLength),
generateRandomString(randomLength)
).distinct();
}

private static String generateRandomString(int length) {
return RANDOM.ints(length, 32, 127)
.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
.toString();
}
}
Loading