Runtime Validation Using Mathematical Expression
JANUARY 11, 2021
Coming up with comprehensive validation cases is challenging at development time, especially for configurable applications(e.g. custom data collectors). A potential solution could be treating the validation logics as mathematical expressions. A system for evaluating such mathematical expressions will enable users to add validation rules during the run time without needing an application rebuild.
For example, if we consider a scenario with two inputs: start and end having a defined range, we can specify a rule as:
The nature of these rules is generic for most input types and can be added on-demand on runtime.
Here is a sample implementation(functional for only types: numerical and dates) using
mXparser :
Class ExpressionValidator.java
public class ExpressionValidator {
private static final String FUNCTION_PREFIX_DAY = "day";
private static final String FUNCTION_PREFIX_DATE = "date";
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd/MM/yyyy");
private static final Pattern PATTERN_FUNCTION_VALUE = Pattern.compile("[(][0-9/]*[)]");
private String expression;
public ExpressionValidator(String expression) {
this.expression = expression;
}
public boolean isValid(Map params) {
Constant[] constants = params.entrySet().stream()
.map(entry -> getConstant(entry.getKey(), entry.getValue()))
.collect(Collectors.toList()).toArray(new Constant[0]);
return new Expression(this.expression, constants).calculate() > 0;
}
private Constant getConstant(String key, String value) {
try {
return new Constant(key + " = " + getEvaluatedValue(value));
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
private String getEvaluatedValue(String subExpression) throws ParseException {
Matcher matcher = PATTERN_FUNCTION_VALUE.matcher(subExpression);
if (!matcher.find()) {
return subExpression;
}
String givenValue = subExpression.substring(matcher.start() + 1, matcher.end() - 1);
return subExpression.startsWith(FUNCTION_PREFIX_DATE) ? Long.toString(DATE_FORMAT.parse(givenValue).getTime())
: subExpression.startsWith(FUNCTION_PREFIX_DAY) ? Long.toString(Long.parseLong(givenValue) * 86400000)
: subExpression;
}
}
Rules like and can be added without changing system at runtime.
Sample Run
Map params = new HashMap<>();
ExpressionValidator expressionValidator = new ExpressionValidator("end - start <= range");
params.put("start", "55");
params.put("end", "100");
params.put("range", "50");
System.out.println(expressionValidator.isValid(params)); // true
params.put("start", "45");
params.put("end", "100");
params.put("range", "50");
System.out.println(expressionValidator.isValid(params)); // false
params.put("start", "date(06/01/2000)");
params.put("end", "date(15/01/2000)");
params.put("range", "day(10)");
System.out.println(expressionValidator.isValid(params)); // true
params.put("start", "date(02/01/2000)");
params.put("end", "date(15/01/2000)");
params.put("range", "day(10)");
System.out.println(expressionValidator.isValid(params)); // false