diff --git a/common/src/main/java/com/scottlogic/deg/common/profile/ConstraintType.java b/common/src/main/java/com/scottlogic/deg/common/profile/ConstraintType.java new file mode 100644 index 000000000..bbaa6148a --- /dev/null +++ b/common/src/main/java/com/scottlogic/deg/common/profile/ConstraintType.java @@ -0,0 +1,69 @@ +package com.scottlogic.deg.common.profile; + +public enum ConstraintType +{ + EQUAL_TO("equalTo"), + IN_SET("inSet"), + IN_MAP("inMap"), + NULL("null"), + GRANULAR_TO("granularTo"), + MATCHES_REGEX("matchingRegex"), + CONTAINS_REGEX("containingRegex"), + OF_LENGTH("ofLength"), + LONGER_THAN("longerThan"), + SHORTER_THAN("shorterThan"), + GREATER_THAN("greaterThan"), + GREATER_THAN_OR_EQUAL_TO("greaterThanOrEqualTo"), + LESS_THAN("lessThan"), + LESS_THAN_OR_EQUAL_TO("lessThanOrEqualTo"), + AFTER("after"), + AFTER_OR_AT("afterOrAt"), + BEFORE("before"), + BEFORE_OR_AT("beforeOrAt"), + NOT("not"), + ANY_OF("anyOf"), + ALL_OF("allOf"), + CONDITION("condition"); + + private final String type; + + ConstraintType(String name) + { + this.type = name; + } + + public String getType() + { + return type; + } + + public static ConstraintType fromString(String type){ + switch (type) + { + case "equalTo": return EQUAL_TO; + case "inSet": return IN_SET; + case "inMap": return IN_MAP; + case "null": return NULL; + case "granularTo": return GRANULAR_TO; + case "matchingRegex": return MATCHES_REGEX; + case "containingRegex": return CONTAINS_REGEX; + case "ofLength": return OF_LENGTH; + case "longerThan": return LONGER_THAN; + case "shorterThan": return SHORTER_THAN; + case "greaterThan": return GREATER_THAN; + case "greaterThanOrEqualTo": return GREATER_THAN_OR_EQUAL_TO; + case "lessThan": return LESS_THAN; + case "lessThanOrEqualTo": return LESS_THAN_OR_EQUAL_TO; + case "after": return AFTER; + case "afterOrAt": return AFTER_OR_AT; + case "before": return BEFORE; + case "beforeOrAt": return BEFORE_OR_AT; + case "not": return NOT; + case "anyOf": return ANY_OF; + case "allOf": return ALL_OF; + case "condition": return CONDITION; + default: + throw new IllegalStateException("No constraint types with name " + type); + } + } +} diff --git a/common/src/main/java/com/scottlogic/deg/common/profile/SpecificFieldType.java b/common/src/main/java/com/scottlogic/deg/common/profile/SpecificFieldType.java new file mode 100644 index 000000000..f4a6ab66f --- /dev/null +++ b/common/src/main/java/com/scottlogic/deg/common/profile/SpecificFieldType.java @@ -0,0 +1,56 @@ +package com.scottlogic.deg.common.profile; + +import com.fasterxml.jackson.annotation.JsonValue; + +public enum SpecificFieldType +{ + DECIMAL("decimal", FieldType.NUMERIC), + INTEGER( "integer", FieldType.NUMERIC), + ISIN("ISIN", FieldType.STRING), + SEDOL("SEDOL", FieldType.STRING), + CUSIP("CUSIP", FieldType.STRING), + RIC("RIC", FieldType.STRING), + FIRST_NAME("firstname", FieldType.STRING), + LAST_NAME("lastname", FieldType.STRING), + FULL_NAME("fullname", FieldType.STRING), + STRING("string", FieldType.STRING), + DATETIME("datetime", FieldType.DATETIME); + + @JsonValue + private final String type; + private final FieldType fieldType; + + SpecificFieldType(String type, FieldType fieldType) + { + this.type = type; + this.fieldType = fieldType; + } + + public String getType() { + return type; + } + + public FieldType getFieldType() + { + return fieldType; + } + + public static SpecificFieldType from(String type){ + switch (type) + { + case "decimal": return DECIMAL; + case "integer": return INTEGER; + case "ISIN": return ISIN; + case "SEDOL": return SEDOL; + case "CUSIP": return CUSIP; + case "RIC": return RIC; + case "firstname": return FIRST_NAME; + case "lastname": return LAST_NAME; + case "fullname": return FULL_NAME; + case "string": return STRING; + case "datetime": return DATETIME; + default: + throw new IllegalStateException("No data types with type " + type); + } + } +} diff --git a/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/features/operators/general/OfType.feature b/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/features/operators/general/OfType.feature index d0432feaa..3e43c13c3 100644 --- a/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/features/operators/general/OfType.feature +++ b/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/features/operators/general/OfType.feature @@ -121,8 +121,4 @@ Feature: User can specify that a field is of a specific type (string, integer, d And the generator can generate at most 20 rows Then foo contains strings of length between 1 and 1000 inclusively - Scenario: Running a 'ofType' request that specifies null should be unsuccessful - Given there is a field foo - Then the profile is invalid because "Field \[foo\]: is not typed; add its type to the field definition" - And no data is created diff --git a/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/features/operators/numeric/GreaterThan.feature b/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/features/operators/numeric/GreaterThan.feature index b2ef70d1f..b689a5383 100644 --- a/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/features/operators/numeric/GreaterThan.feature +++ b/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/features/operators/numeric/GreaterThan.feature @@ -58,21 +58,6 @@ Feature: User can specify that a numeric value is higher than, but not equal to, | 100.00000000000000000004 | | 100.00000000000000000005 | - Scenario: Running a 'greaterThan' request that specifies a string should be unsuccessful - Given foo is greater than "bar" - Then the profile is invalid because "Field \[foo\]: Couldn't recognise 'value' property, it must be an Number but was a String with value `bar`" - And no data is created - - Scenario: Running a 'greaterThan' request that specifies an empty string should be unsuccessful - Given foo is greater than "" - Then the profile is invalid because "Field \[foo\]: Couldn't recognise 'value' property, it must be an Number but was a String with value ``" - And no data is created - - Scenario: Running a 'greaterThan' request that specifies null should be unsuccessful - Given foo is greater than null - Then the profile is invalid because "Field \[foo\]: Couldn't recognise 'value' property, it must be set to a value" - And no data is created - Scenario: Running a 'greaterThan' request that specifies a negative should be successful for type integer Given foo is greater than -100 And foo has type "integer" diff --git a/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/features/operators/numeric/GreaterThanOrEqualTo.feature b/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/features/operators/numeric/GreaterThanOrEqualTo.feature index 82f510e9c..0f7ff665a 100644 --- a/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/features/operators/numeric/GreaterThanOrEqualTo.feature +++ b/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/features/operators/numeric/GreaterThanOrEqualTo.feature @@ -57,21 +57,6 @@ Feature: User can specify that a numeric value is higher than, or equal to, a sp | 3 | | 4 | - Scenario: Running a 'greaterThanOrEqualTo' request that includes a string should fail - Given foo is greater than or equal to "Zero" - Then the profile is invalid because "Field \[foo\]: Couldn't recognise 'value' property, it must be an Number but was a String with value `Zero`" - And no data is created - - Scenario: Running a 'greaterThanOrEqualTo' request that includes an empty string should fail - Given foo is greater than or equal to "" - Then the profile is invalid because "Field \[foo\]: Couldn't recognise 'value' property, it must be an Number but was a String with value ``" - And no data is created - - Scenario: Running a 'greaterThanOrEqualTo' request that specifies null should be unsuccessful - Given foo is greater than or equal to null - Then the profile is invalid because "Field \[foo\]: Couldn't recognise 'value' property, it must be set to a value" - And no data is created - Scenario: greaterThanOrEqualTo run against a non contradicting greaterThanOrEqualTo should be successful Given foo is greater than or equal to 5 And foo is greater than or equal to 5 diff --git a/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/features/operators/string/MatchingRegex.feature b/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/features/operators/string/MatchingRegex.feature index 67315728f..601ac42ab 100644 --- a/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/features/operators/string/MatchingRegex.feature +++ b/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/features/operators/string/MatchingRegex.feature @@ -204,12 +204,6 @@ Feature: User can specify that a value either matches or contains a specified re Then the profile is invalid because "Field \[foo\]: Unclosed character class near index 3\r?\n\[\]\{\}\r?\n \^" And no data is created - Scenario: Running a 'matchingRegex' request with the value property set to a null entry (null) should throw an error - Given there is a field foo - And foo is matching regex null - Then the profile is invalid because "Field \[foo\]: Couldn't recognise 'value' property, it must be set to a value" - And no data is created - Scenario: User using matchingRegex operator to provide an exact set of values Given foo is matching regex /[a]{1,3}/ And foo is anything but null diff --git a/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/features/validation/TypeMandation.feature b/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/features/validation/TypeMandation.feature index 469b3b4bf..cfc9f3c22 100644 --- a/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/features/validation/TypeMandation.feature +++ b/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/features/validation/TypeMandation.feature @@ -1,14 +1,6 @@ Feature: Type mandation validation Profiles should be rejected if they don't positively specify (to a certain standard) the types of all their fields. - Scenario: A field with no relevant constraints should fail type mandation - Given there is a field user_id - And user_id is greater than 3 - And user_id is less than 10 - And user_id is granular to 1 - # ideally I guess we'd have more here - what's a sensible amount? maybe we should use scenario outlines? - Then the profile is invalid because "Field \[user_id\]: is not typed; add its type to the field definition" - Scenario: An explicit type constraint should satisfy type mandation Given there is a field user_id And user_id has type "string" @@ -17,14 +9,16 @@ Feature: Type mandation validation Scenario: An equalTo constraint should satisfy type mandation Given there is a field user_id And user_id is equal to "banana" - Then the profile is invalid because "Field \[user_id\]: is not typed; add its type to the field definition" + And user_id has type "string" + Then the profile should be considered valid Scenario: An inSet constraint should satisfy type mandation Given there is a field user_id + And user_id has type "string" And user_id is in set: | "banana" | | "cactus" | - Then the profile is invalid because "Field \[user_id\]: is not typed; add its type to the field definition" + Then the profile should be considered valid Scenario: An ISIN constraint should satisfy type mandation Given there is a field foo @@ -40,16 +34,3 @@ Feature: Type mandation validation Given there is a field foo And foo has type "CUSIP" Then the profile should be considered valid - - Scenario: A mandatorily absent field should satisfy type mandation - Given there is a field user_id - And user_id is null - Then the profile is invalid because "Field \[user_id\]: is not typed; add its type to the field definition" - - Scenario: When only some fields fail type mandation, the errors should be specific to which - Given there is a field user_id - And user_id has type "string" - And there is a field price - And there is a field purchase_time - Then the profile is invalid because "Fields price, purchase_time are not typed; add their type to the field definition" - diff --git a/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/testframework/steps/GeneralTestStep.java b/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/testframework/steps/GeneralTestStep.java index c2ac646ef..03abd9063 100644 --- a/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/testframework/steps/GeneralTestStep.java +++ b/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/testframework/steps/GeneralTestStep.java @@ -31,7 +31,6 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; - import static org.hamcrest.Matchers.*; public class GeneralTestStep { diff --git a/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/testframework/utils/CucumberTestState.java b/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/testframework/utils/CucumberTestState.java index bd0d9bcb3..bcd19141e 100644 --- a/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/testframework/utils/CucumberTestState.java +++ b/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/testframework/utils/CucumberTestState.java @@ -18,17 +18,16 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import com.scottlogic.deg.common.profile.SpecificFieldType; +import com.scottlogic.deg.common.profile.constraintdetail.AtomicConstraintType; import com.scottlogic.deg.generator.config.detail.CombinationStrategyType; import com.scottlogic.deg.generator.config.detail.DataGenerationType; -import com.scottlogic.deg.common.profile.constraintdetail.AtomicConstraintType; import com.scottlogic.deg.profile.dto.ConstraintDTO; import com.scottlogic.deg.profile.dto.FieldDTO; import java.io.IOException; import java.util.*; import java.util.stream.Collectors; -import static com.scottlogic.deg.common.profile.FieldBuilder.createField; -import static com.scottlogic.deg.common.profile.FieldBuilder.createInternalField; /** * Class to represent the state during cucumber test running and execution @@ -247,11 +246,11 @@ public void setFieldUnique(String fieldName) { }).collect(Collectors.toList()); } - public void setFieldType(String fieldName, String types) { + public void setFieldType(String fieldName, String type) { profileFields = profileFields.stream() .map(fieldDTO -> { if (fieldDTO.name.equals(fieldName)) { - fieldDTO.type = types; + fieldDTO.type = SpecificFieldType.from(type); } return fieldDTO; }).collect(Collectors.toList()); diff --git a/profile/src/main/java/com/scottlogic/deg/profile/dto/FieldDTO.java b/profile/src/main/java/com/scottlogic/deg/profile/dto/FieldDTO.java index 37c36dd46..ed73ff345 100644 --- a/profile/src/main/java/com/scottlogic/deg/profile/dto/FieldDTO.java +++ b/profile/src/main/java/com/scottlogic/deg/profile/dto/FieldDTO.java @@ -16,9 +16,11 @@ package com.scottlogic.deg.profile.dto; +import com.scottlogic.deg.common.profile.SpecificFieldType; + public class FieldDTO { public String name; - public String type; + public SpecificFieldType type; public String formatting; public boolean unique; public boolean nullable = true; diff --git a/profile/src/main/java/com/scottlogic/deg/profile/reader/JsonProfileReader.java b/profile/src/main/java/com/scottlogic/deg/profile/reader/JsonProfileReader.java index 20f342e60..aafe45b65 100644 --- a/profile/src/main/java/com/scottlogic/deg/profile/reader/JsonProfileReader.java +++ b/profile/src/main/java/com/scottlogic/deg/profile/reader/JsonProfileReader.java @@ -18,28 +18,31 @@ import com.google.inject.Inject; import com.google.inject.name.Named; -import com.scottlogic.deg.common.profile.*; +import com.scottlogic.deg.common.profile.Field; +import com.scottlogic.deg.common.profile.ProfileFields; +import com.scottlogic.deg.common.profile.SpecificFieldType; import com.scottlogic.deg.common.profile.constraintdetail.AtomicConstraintType; -import com.scottlogic.deg.generator.profile.constraints.Constraint; import com.scottlogic.deg.generator.profile.Profile; import com.scottlogic.deg.generator.profile.Rule; import com.scottlogic.deg.generator.profile.RuleInformation; +import com.scottlogic.deg.generator.profile.constraints.Constraint; import com.scottlogic.deg.profile.dto.ConstraintDTO; -import com.scottlogic.deg.profile.reader.atomic.ConstraintReaderHelpers; +import com.scottlogic.deg.profile.dto.ProfileDTO; import com.scottlogic.deg.profile.reader.atomic.OfTypeConstraintFactory; import com.scottlogic.deg.profile.serialisation.ProfileDeserialiser; -import com.scottlogic.deg.profile.dto.ProfileDTO; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; -import java.util.*; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import static com.scottlogic.deg.profile.reader.atomic.AtomicConstraintFactory.create; -import static com.scottlogic.deg.profile.reader.atomic.ConstraintReaderHelpers.getFieldType; /** * JsonProfileReader is responsible for reading and validating a profile from a path to a profile JSON file. @@ -74,18 +77,12 @@ public Profile read(String profileJson) throws IOException { } List inMapFields = getInMapConstraints(profileDto).stream() - .map(file -> - new Field( - file, - getFieldType("integer"), - false, - null, - true) + .map(file -> new Field(file, SpecificFieldType.from("integer").getFieldType(),false,null, true) ).collect(Collectors.toList()); List fields = profileDto.fields.stream() - .map(fDto -> new Field(fDto.name, ConstraintReaderHelpers.getFieldType(fDto.type), fDto.unique, fDto.formatting,false)) + .map(fDto -> new Field(fDto.name, fDto.type.getFieldType(), fDto.unique, fDto.formatting,false)) .collect(Collectors.toList()); fields.addAll(inMapFields); diff --git a/profile/src/main/java/com/scottlogic/deg/profile/reader/atomic/ConstraintReaderHelpers.java b/profile/src/main/java/com/scottlogic/deg/profile/reader/atomic/ConstraintReaderHelpers.java index f0db010b1..9175ef258 100644 --- a/profile/src/main/java/com/scottlogic/deg/profile/reader/atomic/ConstraintReaderHelpers.java +++ b/profile/src/main/java/com/scottlogic/deg/profile/reader/atomic/ConstraintReaderHelpers.java @@ -89,7 +89,7 @@ public static FieldType getFieldType(String type) { throw new InvalidProfileException("Profile is invalid: no type known for " + type); } - + public static DateTimeGranularity getDateTimeGranularity(String granularity) { String offsetUnitUpperCase = granularity.toUpperCase(); boolean workingDay = offsetUnitUpperCase.equals("WORKING DAYS"); diff --git a/profile/src/main/java/com/scottlogic/deg/profile/reader/atomic/OfTypeConstraintFactory.java b/profile/src/main/java/com/scottlogic/deg/profile/reader/atomic/OfTypeConstraintFactory.java index 17df39685..59d2d979e 100644 --- a/profile/src/main/java/com/scottlogic/deg/profile/reader/atomic/OfTypeConstraintFactory.java +++ b/profile/src/main/java/com/scottlogic/deg/profile/reader/atomic/OfTypeConstraintFactory.java @@ -1,44 +1,32 @@ package com.scottlogic.deg.profile.reader.atomic; import com.scottlogic.deg.common.profile.Field; +import com.scottlogic.deg.common.profile.SpecificFieldType; import com.scottlogic.deg.common.profile.constraintdetail.NumericGranularityFactory; import com.scottlogic.deg.generator.profile.constraints.Constraint; import com.scottlogic.deg.generator.profile.constraints.atomic.*; -import com.scottlogic.deg.profile.reader.InvalidProfileException; import com.scottlogic.deg.profile.reader.file.names.NameRetriever; import java.math.BigDecimal; import java.util.Optional; - public class OfTypeConstraintFactory { - public static Optional create(Field field, String value){ - switch (value) { - case "decimal": - case "string": - case "datetime": + public static Optional create(Field field, SpecificFieldType specificFieldType){ + String type = specificFieldType.getType(); + switch (specificFieldType) { + case INTEGER: + return Optional.of(new IsGranularToNumericConstraint(field, NumericGranularityFactory.create(BigDecimal.ONE))); + case ISIN: + case SEDOL: + case CUSIP: + case RIC: + return Optional.of(new MatchesStandardConstraint(field, StandardConstraintTypes.valueOf(type.toUpperCase()))); + case FIRST_NAME: + case LAST_NAME: + case FULL_NAME: + return Optional.of(new IsInSetConstraint(field, NameRetriever.loadNamesFromFile(NameConstraintTypes.lookupProfileText(type.toLowerCase())))); + default: return Optional.empty(); - case "integer": - return Optional.of(new IsGranularToNumericConstraint(field, - NumericGranularityFactory.create(BigDecimal.ONE)) - ); - case "ISIN": - case "SEDOL": - case "CUSIP": - case "RIC": - return Optional.of(new MatchesStandardConstraint( - field, - StandardConstraintTypes.valueOf(value.toUpperCase())) - ); - case "firstname": - case "lastname": - case "fullname": - return Optional.of(new IsInSetConstraint( - field, - NameRetriever.loadNamesFromFile(NameConstraintTypes.lookupProfileText(value.toLowerCase()))) - ); } - - throw new InvalidProfileException("Profile is invalid: no constraints known for \"is\": \"ofType\", \"value\": \"" + value + "\""); } } diff --git a/profile/src/test/java/com/scottlogic/deg/profile/reader/atomic/OfTypeConstraintFactoryTest.java b/profile/src/test/java/com/scottlogic/deg/profile/reader/atomic/OfTypeConstraintFactoryTest.java index 6c413fd1a..9deb0f555 100644 --- a/profile/src/test/java/com/scottlogic/deg/profile/reader/atomic/OfTypeConstraintFactoryTest.java +++ b/profile/src/test/java/com/scottlogic/deg/profile/reader/atomic/OfTypeConstraintFactoryTest.java @@ -19,6 +19,7 @@ import com.scottlogic.deg.common.profile.Field; import com.scottlogic.deg.common.profile.FieldBuilder; +import com.scottlogic.deg.common.profile.SpecificFieldType; import com.scottlogic.deg.generator.profile.constraints.Constraint; import com.scottlogic.deg.generator.profile.constraints.atomic.*; import com.scottlogic.deg.common.profile.constraintdetail.NumericGranularity; @@ -35,27 +36,27 @@ class OfTypeConstraintFactoryTest { @Test void returnsNullWhenPassedDecimalLowerCase() { - Optional constraint = OfTypeConstraintFactory.create(field,"decimal"); + Optional constraint = OfTypeConstraintFactory.create(field, SpecificFieldType.DECIMAL); assertFalse(constraint.isPresent()); } @Test void returnsGranularToOneWhenPassedInteger() { - Optional constraint = OfTypeConstraintFactory.create(field,"integer"); + Optional constraint = OfTypeConstraintFactory.create(field,SpecificFieldType.INTEGER); assertTrue(constraint.isPresent()); assertEquals(((IsGranularToNumericConstraint) constraint.get()).granularity, new NumericGranularity(0)); } @Test void returnsStandardRICConstraintWhenPassedUpperCaseRIC() { - Optional constraint = OfTypeConstraintFactory.create(field,"RIC"); + Optional constraint = OfTypeConstraintFactory.create(field,SpecificFieldType.RIC); assertTrue(constraint.isPresent()); assertEquals(((MatchesStandardConstraint) constraint.get()).standard, StandardConstraintTypes.RIC); } @Test void returnsInSetConstraintWhenPassedLowerCaseFullName() { - Optional constraint = OfTypeConstraintFactory.create(field,"fullname"); + Optional constraint = OfTypeConstraintFactory.create(field,SpecificFieldType.FULL_NAME); IsInSetConstraint isInSetConstraint = new IsInSetConstraint( field, NameRetriever.loadNamesFromFile(NameConstraintTypes.lookupProfileText("fullname"))