Skip to content

Commit

Permalink
Made CsvRuleSet extensible and ensure threadsafety
Browse files Browse the repository at this point in the history
  • Loading branch information
stijn-dejongh committed Nov 22, 2024
1 parent 0baa35d commit 63a282e
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 74 deletions.
5 changes: 3 additions & 2 deletions dsl/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@
<url>https://github.com/sddevelopment-be/modular-validators</url>

<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<maven.compiler.release>21</maven.compiler.release>
<maven.compiler.source>${maven.compiler.release}</maven.compiler.source>
<maven.compiler.target>${maven.compiler.release}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

<sonar.projectKey>sddevelopment-be_coding-utils:${project.artifactId}</sonar.projectKey>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
import be.sddevelopment.commons.annotations.Utility;
import be.sddevelopment.commons.exceptions.WrappedException;
import be.sddevelopment.validation.core.ModularRuleset;
import be.sddevelopment.validation.dsl.rules.CsvValidationRules;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.nio.file.Path;

import static be.sddevelopment.commons.access.AccessProtectionUtils.utilityClassConstructor;
import static be.sddevelopment.commons.exceptions.ExceptionSuppressor.uncheck;
import static be.sddevelopment.validation.dsl.rules.CsvValidationRules.defaultRules;
import static java.nio.file.Files.readAllLines;

@Utility
Expand All @@ -32,8 +32,8 @@ public static ModularRuleset<CsvFile> fromSpecification(Path validationSpec) thr
var ruleSet = ModularRuleset.aValid(CsvFile.class);
readAllLines(validationSpec).stream()
.filter(StringUtils::isNotBlank)
.filter(CsvValidationRules::isKnownSpecification)
.map(uncheck(CsvValidationRules::fromLine))
.filter(defaultRules()::isKnownSpecification)
.map(uncheck(defaultRules()::fromLine))
.forEach(ruleAdder -> ruleAdder.apply(ruleSet));
return ruleSet.iHaveSpoken();
} catch (WrappedException | IOException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ static List<String> parametersFrom(String line) {
String[] rawParameters = line.substring(line.indexOf('(') + 1, line.indexOf(')'))
.trim()
.replace("'", "")
.split(CsvValidationRules.PARAMETER_SEPARATOR);
.split(CsvValidationRules.DEFAULT_PARAMETER_SEPARATOR);
return Stream.of(rawParameters).map(String::trim).toList();
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package be.sddevelopment.validation.dsl.rules;

import be.sddevelopment.commons.annotations.Utility;
import be.sddevelopment.validation.core.Constraint;
import be.sddevelopment.validation.core.ModularRuleset.ModularValidatorBuilder;
import be.sddevelopment.validation.dsl.CsvFile;
Expand All @@ -12,34 +11,50 @@
import java.util.Vector;
import java.util.function.Function;

import static be.sddevelopment.commons.access.AccessProtectionUtils.utilityClassConstructor;
import static be.sddevelopment.commons.exceptions.ExceptionSuppressor.ignore;
import static java.util.Arrays.stream;
import static java.util.stream.Collectors.joining;

@Utility
public final class CsvValidationRules {

public static final String PARAMETER_SEPARATOR = ",";
private static final List<CsvRuleSpec> KNOWN_RULESPECS = List.of(
public static final String DEFAULT_PARAMETER_SEPARATOR = ",";
private static final List<CsvRuleSpec> BASE_RULE_SPECIFICATIONS = List.of(
new CsvRuleSpec("Field", CsvValidationRules::createFieldExistsRule),
new CsvRuleSpec("Distinct", CsvValidationRules::createFieldDistinctnessRule),
new CsvRuleSpec("Unique", CsvValidationRules::createFieldDistinctnessRule),
new CsvRuleSpec("UniqueField", CsvValidationRules::createFieldDistinctnessRule),
new CsvRuleSpec("RecordExists", ignore(CsvValidationRules::containsRecord))
);
private static final CsvValidationRules DEFAULT_RULES = new CsvValidationRules(BASE_RULE_SPECIFICATIONS);

private CsvValidationRules() {
utilityClassConstructor();
private Vector<CsvRuleSpec> knownRuleSpecifications;

public CsvValidationRules(List<CsvRuleSpec> knownRuleSpecifications) {
this.knownRuleSpecifications = new Vector<>(knownRuleSpecifications);
}

public static Function<ModularValidatorBuilder<CsvFile>, ModularValidatorBuilder<CsvFile>> fromLine(String line)
public Function<ModularValidatorBuilder<CsvFile>, ModularValidatorBuilder<CsvFile>> fromLine(String line)
throws SpecificationParserException {
return knownRuleSpecifications().stream()
return knownRuleSpecifications()
.stream().parallel()
.map(spec -> spec.toRule(line.trim()))
.flatMap(Optional::stream)
.findFirst()
.orElseThrow(() -> new SpecificationParserException("Unknown rule specification: %s".formatted(line)));
}

public List<CsvRuleSpec> knownRuleSpecifications() {
return knownRuleSpecifications;
}

public boolean isKnownSpecification(String specificationLine) {
return knownRuleSpecifications().stream().anyMatch(spec -> spec.accepts(specificationLine.trim()));
}

public ModularValidatorBuilder<CsvFile> addToRuleset(ModularValidatorBuilder<CsvFile> ruleSet, String ruleToAdd) throws SpecificationParserException {
return fromLine(ruleToAdd).apply(ruleSet);
}

public static RuleSpecificationAppender<CsvFile> createFieldExistsRule(String line) {
var field = CsvRuleSpec.parametersFrom(line).getFirst();
return ruleset -> ruleset.must(haveField(field));
Expand Down Expand Up @@ -80,7 +95,7 @@ public static Constraint<CsvFile> containRecord(FieldValue identifier, FieldValu
return new Constraint<>(
file -> identifiedBy(identifier).apply(file)
.map(line -> stream(fieldValues).parallel()
.allMatch(expected -> expected.value().equals(line.get(file.fieldIndex(expected.field()))))
.allMatch(expected -> expected.value().equals(line.get(file.fieldIndex(expected.field()))))
)
.orElse(false),
fieldValues.length == 0
Expand All @@ -89,20 +104,18 @@ public static Constraint<CsvFile> containRecord(FieldValue identifier, FieldValu
);
}

public static List<CsvRuleSpec> knownRuleSpecifications() {
return KNOWN_RULESPECS;
}

public static boolean isKnownSpecification(String specificationLine) {
return knownRuleSpecifications().stream().anyMatch(spec -> spec.accepts(specificationLine.trim()));
public static Function<CsvFile, Optional<Vector<String>>> identifiedBy(FieldValue identifier) {
return file -> file.findLineByFieldValue(identifier.field(), identifier.value());
}

public static ModularValidatorBuilder<CsvFile> addToRuleset(ModularValidatorBuilder<CsvFile> ruleSet, String ruleToAdd) throws SpecificationParserException {
return fromLine(ruleToAdd).apply(ruleSet);
public static CsvValidationRules defaultRules() {
return DEFAULT_RULES;
}

public static Function<CsvFile, Optional<Vector<String>>> identifiedBy(FieldValue identifier) {
return file -> file.findLineByFieldValue(identifier.field(), identifier.value());
public void addRuleSpecification(CsvRuleSpec csvRuleSpec) {
if(isKnownSpecification(csvRuleSpec.ruleName())) {
throw new IllegalStateException("Rule specification '%s' is already registered".formatted(csvRuleSpec.ruleName()));
}
this.knownRuleSpecifications.add(csvRuleSpec);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import be.sddevelopment.commons.testing.naming.ReplaceUnderscoredCamelCasing;
import be.sddevelopment.validation.core.Rationale;
import be.sddevelopment.validation.dsl.rules.CsvValidationRules;
import org.assertj.core.api.WithAssertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.DisplayNameGeneration;
Expand All @@ -14,6 +13,7 @@
import java.io.IOException;
import java.nio.file.Paths;

import static be.sddevelopment.validation.dsl.rules.CsvValidationRules.defaultRules;
import static java.nio.file.Files.readAllLines;

@DisplayName("Parsing of validation rules")
Expand Down Expand Up @@ -60,7 +60,7 @@ class parsesSimplesRulesTest {
})
void recognizesSimpleRule(String ruleToParse) {
assertThat(ruleToParse).matches(
CsvValidationRules::isKnownSpecification,
defaultRules()::isKnownSpecification,
"is recognized as a rule specification"
);
}
Expand Down
Loading

0 comments on commit 63a282e

Please sign in to comment.