Skip to content

Commit

Permalink
Refinement and implementation of DSL ruleset specifications
Browse files Browse the repository at this point in the history
  • Loading branch information
stijn-dejongh committed Nov 20, 2024
1 parent 1f1e1fb commit 5531835
Show file tree
Hide file tree
Showing 11 changed files with 231 additions and 59 deletions.
11 changes: 11 additions & 0 deletions dsl/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@
</properties>

<dependencies>
<dependency>
<groupId>com.opencsv</groupId>
<artifactId>opencsv</artifactId>
<version>5.9</version>
</dependency>

<dependency>
<groupId>be.sddevelopment.validation</groupId>
<artifactId>core</artifactId>
Expand Down Expand Up @@ -69,6 +75,11 @@
<artifactId>archunit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.tngtech.archunit</groupId>
<artifactId>archunit-junit5</artifactId>
Expand Down
29 changes: 29 additions & 0 deletions dsl/src/main/java/be/sddevelopment/validation/dsl/CsvFile.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package be.sddevelopment.validation.dsl;

import java.nio.file.Path;
import java.util.Vector;
import java.util.stream.Stream;

public record CsvFile (
String fileIdentifier,
Vector<String> headerFields,
Vector<Vector<String>> lines
) {
public CsvFile {
if (headerFields == null || lines == null) {
throw new IllegalArgumentException("Header fields and lines must not be null");
}
}

public Vector<String> line(int lineNumber) {
return lines.get(lineNumber);
}

public static CsvFile fromLines(Stream<String> lines) {
return null;
}

public static CsvFile fromFile(Path dataFile) {
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package be.sddevelopment.validation.dsl;

import be.sddevelopment.commons.annotations.Utility;
import be.sddevelopment.validation.core.ModularRuleset;
import be.sddevelopment.validation.core.ModularRuleset.ModularValidatorBuilder;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.function.Function;

import static be.sddevelopment.commons.access.AccessProtectionUtils.utilityClassConstructor;

@Utility
public final class FileValidatorParser {

private FileValidatorParser() {
utilityClassConstructor();
}

public static ModularRuleset<CsvFile> fromSpecification(Path validationSpec) throws SpecificationParserException {
try {
var lines = Files.readAllLines(validationSpec);
var ruleSet = ModularRuleset.aValid(CsvFile.class);
lines.stream()
.filter(StringUtils::isNotBlank)
.filter(FileValidatorParser::isRuleSpecification)
.map(FileValidatorParser::<CsvFile>toRuleAdder)
.forEach(ruleAdder -> ruleAdder.apply(ruleSet));
return ruleSet.iHaveSpoken();
} catch (IOException e) {
throw new SpecificationParserException("Error processing validation specification file", e);
}
}

private static final List<String> KNOWN_RULESPECS = List.of(
"RecordIdentifier",
"FieldExists",
"FieldPopulated",
"RecordExists"
);

static boolean isRuleSpecification(String specificationLine) {
return KNOWN_RULESPECS.stream().anyMatch(specificationLine::contains);
}

static <T> Function<ModularValidatorBuilder<T>, ModularValidatorBuilder<T>> toRuleAdder(String line) {
return ruleset -> ruleset;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package be.sddevelopment.validation.dsl;

/**
* Exception thrown when an error occurs while parsing a validation specification.
* To be used by the {@link FileValidatorParser} or related specification file to {@link be.sddevelopment.validation.core.ModularRuleset} parsers.
*
* @since 1.1.0
*/
public class SpecificationParserException extends Exception {

public SpecificationParserException(String message) {
super(message);
}

public SpecificationParserException(String message, Throwable cause) {
super(message, cause);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* <p>
* Contains the domain-specific language classes (DSL) for the validation framework. These classes are used to create the validation rules, based on the DSL specifications provided to it.
* The most notable entries are {@link be.sddevelopment.validation.dsl.ValidatorParser}.
* The most notable entries are {@link be.sddevelopment.validation.dsl.FileValidatorParser}.
* </p>
*
* @since 1.1.0-SNAPSHOT
Expand Down
34 changes: 34 additions & 0 deletions dsl/src/test/java/be/sddevelopment/validation/dsl/CsvFileTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package be.sddevelopment.validation.dsl;

import be.sddevelopment.commons.testing.naming.ReplaceUnderscoredCamelCasing;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.assertj.core.api.WithAssertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.InstanceOfAssertFactories.*;

@DisplayName("Comma Separated Values File")
@DisplayNameGeneration(ReplaceUnderscoredCamelCasing.class)
class CsvFileTest implements WithAssertions {

@Test
void canBeCreatedFromLines() {
var dataWithHeader = """
NAME,HEIGHT,SPECIES
Luke Skywalker,172,Human
C-3PO,167,Droid
R2-D2,96,Droid
Boba Fett,183, Human
""";

var csvFile = CsvFile.fromLines(dataWithHeader.lines());

assertThat(csvFile).isNotNull()
.extracting(CsvFile::headerFields)
.asInstanceOf(LIST)
.containsExactly("NAME", "HEIGHT", "SPECIES");
assertThat(csvFile.line(0)).containsExactly("Luke Skywalker", "172", "Human");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package be.sddevelopment.validation.dsl;

import be.sddevelopment.commons.testing.naming.ReplaceUnderscoredCamelCasing;
import be.sddevelopment.validation.core.ModularRuleset;
import be.sddevelopment.validation.core.Rationale;
import com.opencsv.CSVParser;
import com.opencsv.bean.util.OpencsvUtils;
import org.assertj.core.api.WithAssertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

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

@DisplayName("Parsing of validation rules")
@DisplayNameGeneration(ReplaceUnderscoredCamelCasing.class)
class FileValidatorParserTest implements WithAssertions {

@Test
void createsAValidatorBasedOnSpecifications() throws IOException, SpecificationParserException {
var validationSpec = Paths.get("src/test/resources/parsing/star_wars/STARWARS_VALIDATOR.puml");
var dataFile = Paths.get("src/test/resources/parsing/star_wars/STARWARS_INPUT_DATA.csv");
assertThat(validationSpec).exists();
assertThat(validationSpec).isRegularFile();

var validator = FileValidatorParser.fromSpecification(validationSpec);
assertThat(validator).isNotNull();

var result = validator.check(CsvFile.fromFile(dataFile));

assertThat(result).isNotNull()
.matches(Rationale::isPassing);
}

@Nested
class parsesSimplesRulesTest {

@ParameterizedTest
@ValueSource(strings = {
"RecordIdentifier('NAME')",
"FieldPopulated('SPECIES')",
"FieldExists('HOMEWORLD')",
"RecordExists('C-3PO')"
})
void recognizesSimpleRule(String ruleToParse) {
assertThat(ruleToParse).matches(
FileValidatorParser::isRuleSpecification,
"is recognized as a rule specification"
);
}

@Test
void canCheckFieldExistence() {
var dataFile = CsvFile.fromLines(
"""
NAME,HEIGHT,SPECIES
Luke Skywalker,172,Human
C-3PO,167,Droid
R2-D2,96,Droid
Boba Fett,183, Human
""".lines()
);
var rule = "FieldExists('HOMEWORLD')";
var ruleAdder = FileValidatorParser.<CsvFile>toRuleAdder(rule);

var ruleset = ModularRuleset.aValid(CsvFile.class);
ruleAdder.apply(ruleset);

var result = ruleset.iHaveSpoken().check(dataFile);

assertThat(result).isNotNull().matches(Rationale::isFailing, "fails because the field does not exist");
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
ValidatorFor(".*STARWARS.*\\.csv") {
Header{
Header {
Field('NAME')
Field('HEIGHT')
Field('MASS')
Expand All @@ -11,6 +11,7 @@ ValidatorFor(".*STARWARS.*\\.csv") {
Field('HOMEWORLD')
Field('SPECIES')
}

RecordIdentifier('NAME')
FieldPopulated('SPECIES')
FieldPopulated('HOMEWORLD')
Expand Down
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,12 @@
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
Expand Down

0 comments on commit 5531835

Please sign in to comment.