Skip to content

Commit 8a2b7ec

Browse files
committed
Refinement and implementation of DSL ruleset specifications
1 parent 1f1e1fb commit 8a2b7ec

File tree

11 files changed

+228
-59
lines changed

11 files changed

+228
-59
lines changed

dsl/pom.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@
2525
</properties>
2626

2727
<dependencies>
28+
<dependency>
29+
<groupId>com.opencsv</groupId>
30+
<artifactId>opencsv</artifactId>
31+
<version>5.9</version>
32+
</dependency>
33+
2834
<dependency>
2935
<groupId>be.sddevelopment.validation</groupId>
3036
<artifactId>core</artifactId>
@@ -69,6 +75,11 @@
6975
<artifactId>archunit</artifactId>
7076
<scope>test</scope>
7177
</dependency>
78+
<dependency>
79+
<groupId>org.junit.jupiter</groupId>
80+
<artifactId>junit-jupiter-params</artifactId>
81+
<scope>test</scope>
82+
</dependency>
7283
<dependency>
7384
<groupId>com.tngtech.archunit</groupId>
7485
<artifactId>archunit-junit5</artifactId>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package be.sddevelopment.validation.dsl;
2+
3+
import java.nio.file.Path;
4+
import java.util.Vector;
5+
import java.util.stream.Stream;
6+
7+
public record CsvFile (
8+
String fileIdentifier,
9+
Vector<String> headerFields,
10+
Vector<Vector<String>> lines
11+
) {
12+
public CsvFile {
13+
if (headerFields == null || lines == null) {
14+
throw new IllegalArgumentException("Header fields and lines must not be null");
15+
}
16+
}
17+
18+
public Vector<String> line(int lineNumber) {
19+
return lines.get(lineNumber);
20+
}
21+
22+
public static CsvFile fromLines(Stream<String> lines) {
23+
return null;
24+
}
25+
26+
public static CsvFile fromFile(Path dataFile) {
27+
return null;
28+
}
29+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package be.sddevelopment.validation.dsl;
2+
3+
import be.sddevelopment.commons.annotations.Utility;
4+
import be.sddevelopment.validation.core.ModularRuleset;
5+
import be.sddevelopment.validation.core.ModularRuleset.ModularValidatorBuilder;
6+
import org.apache.commons.lang3.StringUtils;
7+
8+
import java.io.IOException;
9+
import java.nio.file.Files;
10+
import java.nio.file.Path;
11+
import java.util.List;
12+
import java.util.function.Function;
13+
14+
import static be.sddevelopment.commons.access.AccessProtectionUtils.utilityClassConstructor;
15+
16+
@Utility
17+
public final class FileValidatorParser {
18+
19+
private FileValidatorParser() {
20+
utilityClassConstructor();
21+
}
22+
23+
public static ModularRuleset<CsvFile> fromSpecification(Path validationSpec) throws SpecificationParserException {
24+
try {
25+
var lines = Files.readAllLines(validationSpec);
26+
var ruleSet = ModularRuleset.aValid(CsvFile.class);
27+
lines.stream()
28+
.filter(StringUtils::isNotBlank)
29+
.filter(FileValidatorParser::isRuleSpecification)
30+
.map(FileValidatorParser::<CsvFile>toRuleAdder)
31+
.forEach(ruleAdder -> ruleAdder.apply(ruleSet));
32+
return ruleSet.iHaveSpoken();
33+
} catch (IOException e) {
34+
throw new SpecificationParserException("Error processing validation specification file", e);
35+
}
36+
}
37+
38+
private static final List<String> KNOWN_RULESPECS = List.of(
39+
"RecordIdentifier",
40+
"FieldExists",
41+
"FieldPopulated",
42+
"RecordExists"
43+
);
44+
45+
static boolean isRuleSpecification(String specificationLine) {
46+
return KNOWN_RULESPECS.stream().anyMatch(specificationLine::contains);
47+
}
48+
49+
static <T> Function<ModularValidatorBuilder<T>, ModularValidatorBuilder<T>> toRuleAdder(String line) {
50+
return ruleset -> ruleset;
51+
}
52+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package be.sddevelopment.validation.dsl;
2+
3+
/**
4+
* Exception thrown when an error occurs while parsing a validation specification.
5+
* To be used by the {@link FileValidatorParser} or related specification file to {@link be.sddevelopment.validation.core.ModularRuleset} parsers.
6+
*
7+
* @since 1.1.0
8+
*/
9+
public class SpecificationParserException extends Exception {
10+
11+
public SpecificationParserException(String message) {
12+
super(message);
13+
}
14+
15+
public SpecificationParserException(String message, Throwable cause) {
16+
super(message, cause);
17+
}
18+
}

dsl/src/main/java/be/sddevelopment/validation/dsl/ValidatorParser.java

Lines changed: 0 additions & 21 deletions
This file was deleted.

dsl/src/main/java/be/sddevelopment/validation/dsl/package-info.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/**
22
* <p>
33
* 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.
4-
* The most notable entries are {@link be.sddevelopment.validation.dsl.ValidatorParser}.
4+
* The most notable entries are {@link be.sddevelopment.validation.dsl.FileValidatorParser}.
55
* </p>
66
*
77
* @since 1.1.0-SNAPSHOT
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package be.sddevelopment.validation.dsl;
2+
3+
import be.sddevelopment.commons.testing.naming.ReplaceUnderscoredCamelCasing;
4+
import org.assertj.core.api.WithAssertions;
5+
import org.junit.jupiter.api.DisplayName;
6+
import org.junit.jupiter.api.DisplayNameGeneration;
7+
import org.junit.jupiter.api.Test;
8+
9+
@DisplayName("Comma Separated Values File")
10+
@DisplayNameGeneration(ReplaceUnderscoredCamelCasing.class)
11+
class CsvFileTest implements WithAssertions {
12+
13+
@Test
14+
void canBeCreatedFromLines() {
15+
var dataWithHeader = """
16+
NAME,HEIGHT,SPECIES
17+
Luke Skywalker,172,Human
18+
C-3PO,167,Droid
19+
R2-D2,96,Droid
20+
Boba Fett,183, Human
21+
""";
22+
23+
var csvFile = CsvFile.fromLines(dataWithHeader.lines());
24+
25+
assertThat(csvFile).isNotNull()
26+
.extracting(CsvFile::headerFields)
27+
.asInstanceOf(LIST)
28+
.containsExactly("NAME", "HEIGHT", "SPECIES");
29+
assertThat(csvFile.line(0)).containsExactly("Luke Skywalker", "172", "Human");
30+
}
31+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package be.sddevelopment.validation.dsl;
2+
3+
import be.sddevelopment.commons.testing.naming.ReplaceUnderscoredCamelCasing;
4+
import be.sddevelopment.validation.core.ModularRuleset;
5+
import be.sddevelopment.validation.core.Rationale;
6+
import com.opencsv.CSVParser;
7+
import com.opencsv.bean.util.OpencsvUtils;
8+
import org.assertj.core.api.WithAssertions;
9+
import org.junit.jupiter.api.DisplayName;
10+
import org.junit.jupiter.api.DisplayNameGeneration;
11+
import org.junit.jupiter.api.Nested;
12+
import org.junit.jupiter.api.Test;
13+
import org.junit.jupiter.params.ParameterizedTest;
14+
import org.junit.jupiter.params.provider.ValueSource;
15+
16+
import java.io.IOException;
17+
import java.nio.file.Paths;
18+
19+
@DisplayName("Parsing of validation rules")
20+
@DisplayNameGeneration(ReplaceUnderscoredCamelCasing.class)
21+
class FileValidatorParserTest implements WithAssertions {
22+
23+
@Test
24+
void createsAValidatorBasedOnSpecifications() throws IOException, SpecificationParserException {
25+
var validationSpec = Paths.get("src/test/resources/parsing/star_wars/STARWARS_VALIDATOR.puml");
26+
var dataFile = Paths.get("src/test/resources/parsing/star_wars/STARWARS_INPUT_DATA.csv");
27+
assertThat(validationSpec).exists();
28+
assertThat(validationSpec).isRegularFile();
29+
30+
var validator = FileValidatorParser.fromSpecification(validationSpec);
31+
assertThat(validator).isNotNull();
32+
33+
var result = validator.check(CsvFile.fromFile(dataFile));
34+
35+
assertThat(result).isNotNull()
36+
.matches(Rationale::isPassing);
37+
}
38+
39+
@Nested
40+
class parsesSimplesRulesTest {
41+
42+
@ParameterizedTest
43+
@ValueSource(strings = {
44+
"RecordIdentifier('NAME')",
45+
"FieldPopulated('SPECIES')",
46+
"FieldExists('HOMEWORLD')",
47+
"RecordExists('C-3PO')"
48+
})
49+
void recognizesSimpleRule(String ruleToParse) {
50+
assertThat(ruleToParse).matches(
51+
FileValidatorParser::isRuleSpecification,
52+
"is recognized as a rule specification"
53+
);
54+
}
55+
56+
@Test
57+
void canCheckFieldExistence() {
58+
var dataFile = CsvFile.fromLines(
59+
"""
60+
NAME,HEIGHT,SPECIES
61+
Luke Skywalker,172,Human
62+
C-3PO,167,Droid
63+
R2-D2,96,Droid
64+
Boba Fett,183, Human
65+
""".lines()
66+
);
67+
var rule = "FieldExists('HOMEWORLD')";
68+
var ruleAdder = FileValidatorParser.<CsvFile>toRuleAdder(rule);
69+
70+
var ruleset = ModularRuleset.aValid(CsvFile.class);
71+
ruleAdder.apply(ruleset);
72+
73+
var result = ruleset.iHaveSpoken().check(dataFile);
74+
75+
assertThat(result).isNotNull().matches(Rationale::isFailing, "fails because the field does not exist");
76+
}
77+
}
78+
}

dsl/src/test/java/be/sddevelopment/validation/dsl/ValidatorParserTest.java

Lines changed: 0 additions & 36 deletions
This file was deleted.

dsl/src/test/resources/parsing/star_wars/STARWARS_VALIDATOR.puml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
ValidatorFor(".*STARWARS.*\\.csv") {
2-
Header{
2+
Header {
33
Field('NAME')
44
Field('HEIGHT')
55
Field('MASS')
@@ -11,6 +11,7 @@ ValidatorFor(".*STARWARS.*\\.csv") {
1111
Field('HOMEWORLD')
1212
Field('SPECIES')
1313
}
14+
1415
RecordIdentifier('NAME')
1516
FieldPopulated('SPECIES')
1617
FieldPopulated('HOMEWORLD')

0 commit comments

Comments
 (0)