Skip to content

Commit

Permalink
Validation on input types for rules with overriden attributes patch (f…
Browse files Browse the repository at this point in the history
…inos#600)

* Patches

* Fixed comment

* Cleaned

* Cleaned
  • Loading branch information
SimonCockx authored Jul 24, 2023
1 parent 18561e3 commit e402ade
Show file tree
Hide file tree
Showing 8 changed files with 221 additions and 64 deletions.
6 changes: 3 additions & 3 deletions documentation/rosetta-modelling-component.md
Original file line number Diff line number Diff line change
Expand Up @@ -1007,9 +1007,9 @@ enum Foo:
```
- `Foo -> VALUE1 to-string` will result in the string `"VALUE1"`,
- `Foo -> VALUE2 to-string` will result in the string `"Value 2"`, (note that the display name is used if present)
- `"VALUE1" to-enum Foo" will result in the enum value `Foo -> VALUE1`,
- `"Value 2" to-enum Foo" will result in the enum value `Foo -> VALUE2`, (again, the display name is used if present)
- `"-3.14" to-number` will result in the number 3.14,
- `"VALUE1" to-enum Foo` will result in the enum value `Foo -> VALUE1`,
- `"Value 2" to-enum Foo` will result in the enum value `Foo -> VALUE2`, (again, the display name is used if present)
- `"-3.14" to-number` will result in the number -3.14,
- `"17:05:33" to-time` will result in a value representing the local time 17 hours, 5 minutes and 33 seconds.

If the conversion fails, the result is an empty value. For example,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ class RosettaExtensions {
/**
* Recursively collects all reporting rules for all attributes
*/
private def void collectReportingRules(Data dataType, RosettaPath path, RosettaExternalRuleSource ruleSource, Map<PathAttribute, RosettaBlueprint> visitor, Set<Data> collectedTypes, boolean allLeafNodes) {
def void collectReportingRules(Data dataType, RosettaPath path, RosettaExternalRuleSource ruleSource, Map<PathAttribute, RosettaBlueprint> visitor, Set<Data> collectedTypes, boolean allLeafNodes) {
val attrRules = externalAnn.getAllRuleReferencesForType(ruleSource, dataType)

dataType.allNonOverridesAttributes.forEach[attr |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class ValidatorsGenerator {

@Override
public «ValidationResult»<«t.toJavaType»> validate(«RosettaPath» path, «t.toJavaType» o) {
/* Casting is required to ensure types are output to ensure code generation in Rosetta */
/* Casting is required to ensure types are output to ensure recompilation in Rosetta */
String error =
«Lists».<«ComparisonResult»>newArrayList(
«FOR attrCheck : attributes.map[checkCardinality(toExpandedAttribute)].filter[it !== null] SEPARATOR ", "»
Expand Down Expand Up @@ -123,7 +123,7 @@ class ValidatorsGenerator {
def private StringConcatenationClient onlyExistsClassBody(RDataType t, String version, Iterable<Attribute> attributes) '''
public class «t.toOnlyExistsValidatorClass» implements «ValidatorWithArg»<«t.toJavaType», «Set»<String>> {

/* Casting is required to ensure types are output to ensure code generation in Rosetta */
/* Casting is required to ensure types are output to ensure recompilation in Rosetta */
@Override
public <T2 extends «t.toJavaType»> «ValidationResult»<«t.toJavaType»> validate(«RosettaPath» path, T2 o, «Set»<String> fields) {
«Map»<String, Boolean> fieldExistenceMap = «ImmutableMap».<String, Boolean>builder()
Expand Down Expand Up @@ -151,7 +151,7 @@ class ValidatorsGenerator {
if (attr.inf === 0 && attr.isUnbound) {
null
} else {
/* Casting is required to ensure types are output to ensure code generation in Rosetta */
/* Casting is required to ensure types are output to ensure recompilation in Rosetta */
'''
«IF attr.isMultiple»
«method(ExpressionOperators, "checkCardinality")»("«attr.name»", («attr.toMultiMetaOrRegularJavaType») o.get«attr.name?.toFirstUpper»() == null ? 0 : ((«attr.toMultiMetaOrRegularJavaType») o.get«attr.name?.toFirstUpper»()).size(), «attr.inf», «attr.sup»)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
package com.regnosys.rosetta.types;

import java.util.List;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.stream.Collectors;

import org.apache.commons.lang3.Validate;

import com.google.inject.Inject;
import com.regnosys.rosetta.interpreter.RosettaInterpreterContext;
import com.regnosys.rosetta.rosetta.RosettaExternalRuleSource;
import com.regnosys.rosetta.rosetta.RosettaFeature;
import com.regnosys.rosetta.rosetta.TypeCall;
import com.regnosys.rosetta.rosetta.expression.RosettaExpression;
import com.regnosys.rosetta.rosetta.simple.Attribute;
import com.regnosys.rosetta.rosetta.simple.Data;
import com.regnosys.rosetta.rosetta.simple.RosettaRuleReference;
import com.regnosys.rosetta.types.builtin.RBuiltinTypeService;
import com.regnosys.rosetta.typing.RosettaTyping;
import com.regnosys.rosetta.utils.ExternalAnnotationUtil;
Expand All @@ -31,16 +35,32 @@ public RListType inferType(RosettaExpression expr) {
return typing.inferType(expr).getValue();
}

public RType getRulesInputType(Data data) {
return getRulesInputType(data, null);
}
public RType getRulesInputType(Data data, RosettaExternalRuleSource source) {
return getRulesInputType(data, source, new HashSet<>());
}
private RType getRulesInputType(Data data, RosettaExternalRuleSource source, Set<Data> visited) {
Validate.notNull(data);
if (!visited.add(data)) {
return builtins.ANY;
}

List<RType> reportTypeInputTypes = annotationUtil.getAllRuleReferencesForType(source, data).values().stream()
.map(ruleRef -> typeCallToRType(ruleRef.getReportingRule().getInput()))
.collect(Collectors.toList());
return meet(reportTypeInputTypes);
Map<RosettaFeature, RosettaRuleReference> ruleReferences = annotationUtil.getAllRuleReferencesForType(source, data);
RType result = builtins.ANY;
for (Attribute attr: data.getAttributes()) {
RosettaRuleReference ref = ruleReferences.get(attr);
if (ref != null) {
RType inputType = typeCallToRType(ref.getReportingRule().getInput());
result = meet(result, inputType);
} else {
RType attrType = stripFromTypeAliases(typeCallToRType(attr.getTypeCall()));
if (attrType instanceof RDataType) {
Data attrData = ((RDataType)attrType).getData();
RType inputType = getRulesInputType(attrData, source, visited);
result = meet(result, inputType);
}
}
}
return result;
}

public RType join(RType t1, RType t2) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1007,10 +1007,13 @@ class RosettaSimpleValidator extends AbstractDeclarativeValidator {
}
else if (filter.filterBP !== null) {
val targetRule = filter.filterBP.blueprint
val node = buildTypeGraph(targetRule)
if (!checkBPNodeSingle(node, false)) {
error('''The expression for Filter must return a single value but the rule «filter.filterBP.blueprint.name» can return multiple values''',
filter, BLUEPRINT_FILTER__FILTER_BP)
try {
val node = buildTypeGraph(targetRule)
if (!checkBPNodeSingle(node, false)) {
error('''The expression for Filter must return a single value but the rule «filter.filterBP.blueprint.name» can return multiple values''',
filter, BLUEPRINT_FILTER__FILTER_BP)
}
} catch (BlueprintUnresolvedTypeException e) {
}
}
}
Expand Down Expand Up @@ -1074,22 +1077,25 @@ class RosettaSimpleValidator extends AbstractDeclarativeValidator {
val attrType = attr.typeCall.typeCallToRType
if (bp.isLegacy) {
if (bp.nodes !== null) {
val node = buildTypeGraph(bp)

val ruleSingle = checkBPNodeSingle(node, false)
try {
val node = buildTypeGraph(bp)

// check cardinality
if (attrSingle != ruleSingle) {
val cardWarning = '''Cardinality mismatch - report field «attr.name» has «IF attrSingle»single«ELSE»multiple«ENDIF» cardinality ''' +
'''whereas the reporting rule «bp.name» has «IF ruleSingle»single«ELSE»multiple«ENDIF» cardinality.'''
warning(cardWarning, ruleRef, ROSETTA_RULE_REFERENCE__REPORTING_RULE)
}
// check type
val bpType = node.output.type
if (!node.repeatable && bpType.map[!isSubtypeOf(attrType)].orElse(false)) {
val typeError = '''Type mismatch - report field «attr.name» has type «attrType.name» ''' +
'''whereas the reporting rule «bp.name» has type «bpType.get».'''
error(typeError, ruleRef, ROSETTA_RULE_REFERENCE__REPORTING_RULE)
val ruleSingle = checkBPNodeSingle(node, false)

// check cardinality
if (attrSingle != ruleSingle) {
val cardWarning = '''Cardinality mismatch - report field «attr.name» has «IF attrSingle»single«ELSE»multiple«ENDIF» cardinality ''' +
'''whereas the reporting rule «bp.name» has «IF ruleSingle»single«ELSE»multiple«ENDIF» cardinality.'''
warning(cardWarning, ruleRef, ROSETTA_RULE_REFERENCE__REPORTING_RULE)
}
// check type
val bpType = node.output.type
if (!node.repeatable && bpType.map[!isSubtypeOf(attrType)].orElse(false)) {
val typeError = '''Type mismatch - report field «attr.name» has type «attrType.name» ''' +
'''whereas the reporting rule «bp.name» has type «bpType.get».'''
error(typeError, ruleRef, ROSETTA_RULE_REFERENCE__REPORTING_RULE)
}
} catch (BlueprintUnresolvedTypeException e) {
}
}
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import org.eclipse.emf.ecore.EPackage;
import org.eclipse.xtext.validation.Check;
Expand All @@ -15,18 +17,21 @@
import com.regnosys.rosetta.rosetta.RosettaExternalClass;
import com.regnosys.rosetta.rosetta.RosettaExternalRegularAttribute;
import com.regnosys.rosetta.rosetta.RosettaExternalRuleSource;
import com.regnosys.rosetta.rosetta.RosettaFeature;
import com.regnosys.rosetta.rosetta.expression.ChoiceOperation;
import com.regnosys.rosetta.rosetta.expression.RosettaOnlyElement;
import com.regnosys.rosetta.rosetta.simple.Attribute;
import com.regnosys.rosetta.rosetta.simple.Data;
import com.regnosys.rosetta.rosetta.simple.RosettaRuleReference;
import com.regnosys.rosetta.types.RType;
import com.regnosys.rosetta.types.RDataType;
import com.regnosys.rosetta.types.RListType;
import com.regnosys.rosetta.types.TypeFactory;
import com.regnosys.rosetta.types.TypeSystem;
import com.regnosys.rosetta.types.TypeValidationUtil;
import com.regnosys.rosetta.types.builtin.RBuiltinTypeService;
import com.regnosys.rosetta.typing.validation.RosettaTypingCheckingValidator;
import com.regnosys.rosetta.utils.ExternalAnnotationUtil;

import static com.regnosys.rosetta.rosetta.expression.ExpressionPackage.Literals.*;
import static com.regnosys.rosetta.rosetta.RosettaPackage.Literals.*;
Expand All @@ -45,6 +50,9 @@ public class StandaloneRosettaTypingValidator extends RosettaTypingCheckingValid
@Inject
private RBuiltinTypeService builtins;

@Inject
private ExternalAnnotationUtil annotationUtil;

@Override
protected List<EPackage> getEPackages() {
List<EPackage> result = new ArrayList<EPackage>();
Expand Down Expand Up @@ -117,7 +125,7 @@ public void checkReportType(Data data) {
RType current;
if (data.getSuperType() != null) {
current = ts.getRulesInputType(data.getSuperType(), null);
if (current == builtins.NOTHING) {
if (current.equals(builtins.NOTHING)) {
return;
}
} else {
Expand All @@ -129,11 +137,25 @@ public void checkReportType(Data data) {
RosettaBlueprint rule = ref.getReportingRule();
RType inputType = ts.typeCallToRType(rule.getInput());
RType newCurrent = ts.meet(current, inputType);
if (newCurrent == builtins.NOTHING) {
if (newCurrent.equals(builtins.NOTHING)) {
error("Rule `" + rule.getName() + "` expects an input of type `" + inputType + "`, while previous rules expect an input of type `" + current + "`.", ref, ROSETTA_RULE_REFERENCE__REPORTING_RULE);
} else {
current = newCurrent;
}
} else {
RType attrType = ts.stripFromTypeAliases(ts.typeCallToRType(attr.getTypeCall()));
if (attrType instanceof RDataType) {
Data attrData = ((RDataType)attrType).getData();
RType inputType = ts.getRulesInputType(attrData, null);
if (!inputType.equals(builtins.NOTHING)) {
RType newCurrent = ts.meet(current, inputType);
if (newCurrent.equals(builtins.NOTHING)) {
error("Attribute `" + attr.getName() + "` contains rules that expect an input of type `" + inputType + "`, while previous rules expect an input of type `" + current + "`.", attr, null);
} else {
current = newCurrent;
}
}
}
}
}
}
Expand All @@ -142,27 +164,44 @@ public void checkReportType(Data data) {
public void checkExternalRuleSource(RosettaExternalRuleSource source) {
for (RosettaExternalClass externalClass: source.getExternalClasses()) {
Data data = externalClass.getData();
Map<RosettaFeature, RosettaRuleReference> ruleReferences = annotationUtil.getAllRuleReferencesForType(source, data);

RType current;
if (source.getSuperRuleSource() != null) {
current = ts.getRulesInputType(data, source.getSuperRuleSource());
if (current == builtins.NOTHING) {
continue;
}
} else {
current = builtins.ANY;
}
for (RosettaExternalRegularAttribute attr: externalClass.getRegularAttributes()) {
if (attr.getOperator() == ExternalValueOperator.PLUS) {
RosettaRuleReference ref = attr.getExternalRuleReference();
if (ref != null) {
RosettaBlueprint rule = ref.getReportingRule();
RType inputType = ts.typeCallToRType(rule.getInput());
RType newCurrent = ts.meet(current, inputType);
if (newCurrent == builtins.NOTHING) {
error("Rule `" + rule.getName() + "` expects an input of type `" + inputType + "`, while previous rules expect an input of type `" + current + "`.", ref, ROSETTA_RULE_REFERENCE__REPORTING_RULE);
} else {
current = newCurrent;
RType current = builtins.ANY;
for (Attribute attr: data.getAttributes()) {
Optional<RosettaExternalRegularAttribute> maybeExtAttr = externalClass.getRegularAttributes().stream()
.filter(ext -> ext.getOperator() == ExternalValueOperator.PLUS)
.filter(ext -> ext.getAttributeRef().equals(attr))
.findAny();
RosettaRuleReference ref = ruleReferences.get(attr);
if (ref != null) {
RosettaBlueprint rule = ref.getReportingRule();
RType inputType = ts.typeCallToRType(rule.getInput());
RType newCurrent = ts.meet(current, inputType);
if (newCurrent.equals(builtins.NOTHING)) {
if (maybeExtAttr.isPresent()) {
RosettaExternalRegularAttribute extAttr = maybeExtAttr.get();
error("Attribute `" + attr.getName() + "` has a rule that expects an input of type `" + inputType + "`, while other rules expect an input of type `" + current + "`.", extAttr, ROSETTA_EXTERNAL_REGULAR_ATTRIBUTE__ATTRIBUTE_REF);
}
} else {
current = newCurrent;
}
} else {
RType attrType = ts.stripFromTypeAliases(ts.typeCallToRType(attr.getTypeCall()));
if (attrType instanceof RDataType) {
Data attrData = ((RDataType)attrType).getData();
RType inputType = ts.getRulesInputType(attrData, source);
if (!inputType.equals(builtins.NOTHING)) {
RType newCurrent = ts.meet(current, inputType);
if (newCurrent.equals(builtins.NOTHING)) {
if (maybeExtAttr.isPresent()) {
RosettaExternalRegularAttribute extAttr = maybeExtAttr.get();
error("Attribute `" + attr.getName() + "` contains rules that expect an input of type `" + inputType + "`, while other rules expect an input of type `" + current + "`.", extAttr, ROSETTA_EXTERNAL_REGULAR_ATTRIBUTE__ATTRIBUTE_REF);
} else {
error("Attribute `" + attr.getName() + "` contains rules that expect an input of type `" + inputType + "`, while other rules expect an input of type `" + current + "`.", externalClass, ROSETTA_EXTERNAL_CLASS__DATA);
}
} else {
current = newCurrent;
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ class ModelMetaGeneratorTest {
@Override
public ValidationResult<Foo> validate(RosettaPath path, Foo o) {
/* Casting is required to ensure types are output to ensure code generation in Rosetta */
/* Casting is required to ensure types are output to ensure recompilation in Rosetta */
String error =
Lists.<ComparisonResult>newArrayList(
checkCardinality("a", (List<String>) o.getA() == null ? 0 : ((List<String>) o.getA()).size(), 1, 2),
Expand Down
Loading

0 comments on commit e402ade

Please sign in to comment.