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:

end - start range

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 start min and end max 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