I was recently reading an article by Martin Fowler where he was using JSON to describe validation rules for your model.

It reminded me of an approach we tested on a previous project, but ended up never using. Our project was a web application where he web UI was entirely written in Javascript, and the server written in Scala. That implicated on having two equal implementations for validating models: one server side, and the second in JS for the interface. We were looking for ideas on how to avoid this duplication, and remembered that the JVM now provides a Javascript implementation through Nashorn, thus giving us the idea to use it for implementing the validation logic on both sides.

I created an small example to demonstrate that (it requires Java 8). On it, our model is composed of a single class, called City. It contains the city’s name, plus the population. In order to be valid it must follow these simple rules:

  1. None of the fields can be null;
  2. The name cannot contain numbers;
  3. The population must be a natural number (n >= 0).

Here you can see the model and the interface of our Validator. It returns an instance of Validation, that can either be a Valid or Invalid object, representing the validation result.

case class City(name: String, population: Long)

trait Validator {
  def apply(city: City): Validation
}

sealed trait Validation {
  def city: City
  def valid: Boolean
}

case class Valid(city: City) extends Validation {
  final def valid = true
}

case class Invalid(city: City, errors: Map[String, String] = Map.empty) extends Validation {
  final def valid = false
}

With that we can create an implementation of the Validator that will internally use the Javascript engine. The function is embedded in the class through a string (script), but it could as well be coming from a file.

import java.io._
import javax.script._

import scala.collection.JavaConversions._

class JavascriptValidator extends Validator {

  val engine = {
    val factory = new ScriptEngineManager
    factory.getEngineByName("JavaScript")
  }

  val (bindings, eval) = {
    val ctx = new SimpleScriptContext
    engine.setContext(ctx)
    engine.eval(script)
    val bindings = ctx.getBindings(ScriptContext.ENGINE_SCOPE)
    (bindings, (cmd: String) => engine.eval(cmd, bindings))
  }

  def apply(city: City) : Validation = {

    bindings.put("city", toMap(city))

    eval("validate(city);") match {
      case b: java.lang.Boolean =>
        if (b.booleanValue) Valid(city)
        else Invalid(city)
      case errors: java.util.Map[_, _] =>
        val asScala = errors.map { case (k, v) => k.toString -> v.toString }.toMap
        Invalid(city, asScala)
      case other =>
        println(other)
        Invalid(city, Map("err" -> s"Unknown return type: $other"))
    }
  }

  //TODO Can be generated by a macro or reflection
  private def toMap(city: City): java.util.Map[String, Any] = {
    val map = new java.util.HashMap[String, Any]()
    map.put("name", city.name)
    map.put("population", city.population)
    map
  }

  private def script: Reader = new StringReader("""
    |function validate(city) {
    |
    |  var errors = {};
    |
    |  if (!city.name || city.name.match(/\d/)) {
    |    errors.name = 'Invalid city name: ' + city.name;
    |  }
    |
    |  if (!city.population || city.population < 0) {
    |    errors.population = 'Population must be > 0';
    |  }
    |
    |  return (Object.keys(errors).length) ? errors : true;
    |}
    |""".stripMargin)

}

Finally, we can get a few examples running and check the results:

object Validate extends App {

  val validate = new JavascriptValidator

  println(validate(City("Berlin", 3000000)))
  //  Valid(City(Berlin,3000000))

  println(validate(City("Berlin 123", 3000000)))
  //  Invalid(City(Berlin 123,3000000),Map(name -> Invalid city name: Berlin 123))

  println(validate(City("Berlin", -3)))
  //  Invalid(City(Berlin,-3),Map(population -> Population must be > 0))

  println(validate(City("Berlin 123", -3)))
  //  Invalid(City(Berlin 123,-3),Map(population -> Population must be > 0, name -> Invalid city name: Berlin 123))

}

In this example we are passing the Scala object to the Javascript engine by converting it to a Map. Since our backend web API received JSON objects, we could instead pass the JSON object directly to the Validator, avoiding translating JSON to Scala, and from Scala to JS, in order to validate it.

Having your model as JSON is also interesting because the approach allows the use of JSON Schema. Assuming your have your model object converted to JSON, you can use json-schema-validator to do the following:

val mapper = new ObjectMapper()
val rawSchema = mapper.readTree(/* stream for JSON Schema in file, ... */)
val schema = JsonSchemaFactory.byDefault().getJsonSchema(rawSchema)

schema.validate(myModelObjectAsJson)

Furthermore, another option is to use Scala.js, which might provide a cleaner solution.