Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
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.SizeRule;
import it.aboutbits.springboot.testing.validation.rule.ValidBeanRule;
import lombok.AccessLevel;
import lombok.Getter;
Expand Down Expand Up @@ -42,6 +43,7 @@ public abstract class BaseRuleBuilder<R extends BaseRuleBuilder<R>> implements
PositiveOrZeroRule<R>,
PositiveRule<R>,
NotValidatedRule<R>,
SizeRule<R>,
ValidBeanRule<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,47 @@
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.SizeGreaterThanValueSource;
import it.aboutbits.springboot.testing.validation.source.SizeLessThanValueSource;
import lombok.AccessLevel;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;

@SuppressWarnings("unchecked")
public interface SizeRule<V extends BaseRuleBuilder<?>> extends ValidationRulesData {
default Builder<V> size(@NonNull String property) {
return new Builder<>((V) this, property);
}

@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
final class Builder<V extends BaseRuleBuilder<?>> {
private final V parent;
private final String property;

public V min(long minSize) {
parent.addRule(
new Rule(property, SizeLessThanValueSource.class, minSize)
);
return parent;
}

public V max(long maxSize) {
parent.addRule(
new Rule(property, SizeGreaterThanValueSource.class, maxSize)
);
return parent;
}

public V minMax(long minSize, long maxSize) {
parent.addRule(
new Rule(property, SizeLessThanValueSource.class, minSize)
);
parent.addRule(
new Rule(property, SizeGreaterThanValueSource.class, maxSize)
);
return parent;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package it.aboutbits.springboot.testing.validation.source;

import it.aboutbits.springboot.testing.validation.core.ValueSource;
import lombok.NonNull;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.LongFunction;
import java.util.stream.Stream;

public class SizeGreaterThanValueSource implements ValueSource {
private static final Map<Class<?>, LongFunction<Stream<?>>> TYPE_SOURCES = new HashMap<>();
private static final Random RANDOM = new Random();

static {
TYPE_SOURCES.put(String.class, SizeGreaterThanValueSource::getStringStream);
TYPE_SOURCES.put(Collection.class, SizeGreaterThanValueSource::getArrayListStream);
TYPE_SOURCES.put(List.class, SizeGreaterThanValueSource::getArrayListStream);
TYPE_SOURCES.put(ArrayList.class, SizeGreaterThanValueSource::getArrayListStream);
TYPE_SOURCES.put(LinkedList.class, SizeGreaterThanValueSource::getLinkedListStream);
TYPE_SOURCES.put(Set.class, SizeGreaterThanValueSource::getHashSetStream);
TYPE_SOURCES.put(HashSet.class, SizeGreaterThanValueSource::getHashSetStream);
TYPE_SOURCES.put(TreeSet.class, SizeGreaterThanValueSource::getTreeSetStream);
}

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

@Override
@SuppressWarnings("unchecked")
public <T> Stream<T> values(Class<T> propertyClass, Object... args) {
var value = (Long.valueOf((long) args[0]));

var sourceFunction = TYPE_SOURCES.get(propertyClass);
if (sourceFunction != null) {
return (Stream<T>) sourceFunction.apply(value);
}

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

@NonNull
private static Stream<String> getStringStream(long value) {
return getTestSizes(value)
.stream()
.map(size -> generateRandomString(Math.toIntExact(size)));
}

@NonNull
private static Stream<Collection<?>> getHashSetStream(long value) {
return getTestSizes(value)
.stream()
.map(size -> generateCollection(
Math.toIntExact(size),
new HashSet<>()
));
}

@NonNull
private static Stream<Collection<?>> getTreeSetStream(long value) {
return getTestSizes(value)
.stream()
.map(size -> generateCollection(
Math.toIntExact(size),
new TreeSet<>()
));
}

@NonNull
private static Stream<Collection<?>> getArrayListStream(long value) {
return getTestSizes(value)
.stream()
.map(size -> generateCollection(
Math.toIntExact(size),
new ArrayList<>()
));
}

@NonNull
private static Stream<Collection<?>> getLinkedListStream(long value) {
return getTestSizes(value)
.stream()
.map(size -> generateCollection(
Math.toIntExact(size),
new LinkedList<>()
));
}

private static List<Long> getTestSizes(long value) {
var sizes = new ArrayList<Long>();

sizes.add(value + 1);
sizes.add(value + 2);
sizes.add(value + 6);

return sizes;
}

private static String generateRandomString(int length) {
// Include printable ASCII characters (32-126) which includes space and common characters
return RANDOM.ints(length, 32, 127)
.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
.toString();
}

private static Collection<Object> generateCollection(int size, Collection<Object> collection) {
for (int i = 0; i < size; i++) {
collection.add("dummy_" + i); // Add dummy elements, content doesn't matter
}
return collection;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package it.aboutbits.springboot.testing.validation.source;

import it.aboutbits.springboot.testing.validation.core.ValueSource;
import lombok.NonNull;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.LongFunction;
import java.util.stream.Stream;

public class SizeLessThanValueSource implements ValueSource {
private static final Map<Class<?>, LongFunction<Stream<?>>> TYPE_SOURCES = new HashMap<>();
private static final Random RANDOM = new Random();

static {
TYPE_SOURCES.put(String.class, SizeLessThanValueSource::getStringStream);
TYPE_SOURCES.put(Collection.class, SizeLessThanValueSource::getArrayListStream);
TYPE_SOURCES.put(List.class, SizeLessThanValueSource::getArrayListStream);
TYPE_SOURCES.put(ArrayList.class, SizeLessThanValueSource::getArrayListStream);
TYPE_SOURCES.put(LinkedList.class, SizeLessThanValueSource::getLinkedListStream);
TYPE_SOURCES.put(Set.class, SizeLessThanValueSource::getHashSetStream);
TYPE_SOURCES.put(HashSet.class, SizeLessThanValueSource::getHashSetStream);
TYPE_SOURCES.put(TreeSet.class, SizeLessThanValueSource::getTreeSetStream);
}

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

@Override
@SuppressWarnings("unchecked")
public <T> Stream<T> values(Class<T> propertyClass, Object... args) {
var value = (Long.valueOf((long) args[0]));

if (value == 0) {
return Stream.empty();
}

if (value < 0) {
throw new IllegalArgumentException("Value must be positive or zero. A size cannot be less than empty.");
}

var sourceFunction = TYPE_SOURCES.get(propertyClass);
if (sourceFunction != null) {
return (Stream<T>) sourceFunction.apply(value);
}

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

@NonNull
private static Stream<String> getStringStream(long value) {
return Stream.concat(
Stream.of(""),
Stream.iterate(1L, i -> i < value, i -> i + 1)
.map(length -> generateRandomString(Math.toIntExact(length)))
);
}

@NonNull
private static Stream<Collection<?>> getArrayListStream(long value) {
return Stream.concat(
Stream.of(new ArrayList<>()),
Stream.iterate(1L, i -> i < value, i -> i + 1)
.map(size -> generateCollection(
Math.toIntExact(size),
new ArrayList<>()
))
);
}

@NonNull
private static Stream<Collection<?>> getLinkedListStream(long value) {
return Stream.concat(
Stream.of(new LinkedList<>()),
Stream.iterate(1L, i -> i < value, i -> i + 1)
.map(size -> generateCollection(
Math.toIntExact(size),
new LinkedList<>()
))
);
}

@NonNull
private static Stream<Collection<?>> getHashSetStream(long value) {
return Stream.concat(
Stream.of(new HashSet<>()),
Stream.iterate(1L, i -> i < value, i -> i + 1)
.map(size -> generateCollection(
Math.toIntExact(size),
new HashSet<>()
))
);
}

@NonNull
private static Stream<Collection<?>> getTreeSetStream(long value) {
return Stream.concat(
Stream.of(new TreeSet<>()),
Stream.iterate(1L, i -> i < value, i -> i + 1)
.map(size -> generateCollection(
Math.toIntExact(size),
new TreeSet<>()
))
);
}

private static String generateRandomString(int length) {
// Include printable ASCII characters (32-126) which includes space and common characters
return RANDOM.ints(length, 32, 127)
.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
.toString();
}

private static Collection<Object> generateCollection(int size, Collection<Object> collection) {
for (int i = 0; i < size; i++) {
collection.add("dummy_" + i); // Add dummy elements, content doesn't matter
}
return collection;
}
}
Loading