[scala]50行实现web表单验证器


思路是这样,每个表单实现Validatable特质。这个特质要求实现返回一个属性名到验证器列表的映射。

表单验证器执行时首先获取表单所有的属性(除去class),然后遍历这个映射,运行字段对应的验证器变成响应的验证错误(如果有的话),否则最后是个空集合。

FormValidatorRunner(代码最下方)是测试类,执行结果是

Map(name -> List(ValidateError(default.notBlank,List())))

代码如下:

import scala.collection.Traversable

case class ValidateError(messageCode: String, 
  messageArguments: Traversable[Any] = List[Any]())

trait FieldRule {
  def apply(form: Validatable, propertyValue: Option[Any]): Option[ValidateError]
}

object NotBlankFieldRule extends FieldRule {
  def apply(form: Validatable, propertyValue: Option[Any]) = propertyValue match {
    case Some(value) if value.toString.trim.length > 0 => None
    case _ => Some(ValidateError("default.notBlank"))
  }
}

trait Validatable {
  def getRules(): Map[String, Traversable[FieldRule]]
}

class FormError(fieldErrors: Map[String, Traversable[ValidateError]]) {
  def hasError(): Boolean = fieldErrors.isEmpty
  override def toString = fieldErrors.toString
}

object FormValidator {
  def apply(form: Validatable): FormError = {
    val props = getProps(form)
    new FormError(form.getRules.map{
      case (fieldName, rules) => (fieldName, 
        rules.flatMap(_.apply(form, props.get(fieldName))))
    })
  }

  private def getProps(obj: Any): Map[String, Any] = {
    java.beans.Introspector.getBeanInfo(
        obj.getClass).getPropertyDescriptors.flatMap{ d => 
      Option(d.getReadMethod).map(getter => (d.getName, getter.invoke(obj)))
    }.toMap - "class"
  }
}

object FormValidatorRunner {

  class Person(id: Long, name: String) extends Validatable {
    def getId(): Long = id
    def getName(): String = name
    def getRules = Map("name" -> List(NotBlankFieldRule))
  }

  def main(args: Array[String]): Unit = {
    println(FormValidator.apply(new Person(1L, "")))
  }
}