Skip to content
This repository
file 210 lines (183 sloc) 7.306 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
/*
* Copyright (C) 2009-2013 Typesafe Inc. <http://www.typesafe.com>
*/
package play.api.data.validation

import play.api.data._

/**
* A form constraint.
*
* @tparam T type of values handled by this constraint
* @param name the constraint name, to be displayed to final user
* @param args the message arguments, to format the constraint name
* @param f the validation function
*/
case class Constraint[-T](name: Option[String], args: Seq[Any])(f: (T => ValidationResult)) {

  /**
* Run the constraint validation.
*
* @param t the value to validate
* @return the validation result
*/
  def apply(t: T): ValidationResult = f(t)
}

/**
* This object provides helpers for creating `Constraint` values.
*
* For example:
* {{{
* val negative = Constraint[Int] {
* case i if i < 0 => Valid
* case _ => Invalid("Must be a negative number.")
* }
* }}}
*/
object Constraint {

  /**
* Creates a new anonymous constraint from a validation function.
*
* @param f the validation function
* @return a constraint
*/
  def apply[T](f: (T => ValidationResult)): Constraint[T] = apply(None, Nil)(f)

  /**
* Creates a new named constraint from a validation function.
*
* @param name the constraint name
* @param args the constraint arguments, used to format the constraint name
* @param f the validation function
* @return a constraint
*/
  def apply[T](name: String, args: Any*)(f: (T => ValidationResult)): Constraint[T] = apply(Some(name), args.toSeq)(f)

}

/**
* Defines a set of built-in constraints.
*/
object Constraints extends Constraints

/**
* Defines a set of built-in constraints.
*/
trait Constraints {

  /**
* Defines an ‘emailAddress’ constraint for `String` values which will validate email addresses.
*
* '''name'''[constraint.email]
* '''error'''[error.email]
*/
  private val emailRegex = """^(?!\.)("([^"\r\\]|\\["\r\\])*"|([-a-zA-Z0-9!#$%&'*+/=?^_`{|}~]|(?<!\.)\.)*)(?<!\.)@[a-zA-Z0-9][\w\.-]*[a-zA-Z0-9]\.[a-zA-Z][a-zA-Z\.]*[a-zA-Z]$""".r
  def emailAddress: Constraint[String] = Constraint[String]("constraint.email") { e =>
    if (e == null) Invalid(ValidationError("error.email"))
    else if (e.trim.isEmpty) Invalid(ValidationError("error.email"))
    else emailRegex.findFirstMatchIn(e)
      .map(_ => Valid)
      .getOrElse(Invalid(ValidationError("error.email")))
  }

  /**
* Defines a ‘required’ constraint for `String` values, i.e. one in which empty strings are invalid.
*
* '''name'''[constraint.required]
* '''error'''[error.required]
*/
  def nonEmpty: Constraint[String] = Constraint[String]("constraint.required") { o =>
    if (o == null) Invalid(ValidationError("error.required")) else if (o.trim.isEmpty) Invalid(ValidationError("error.required")) else Valid
  }

  /**
* Defines a minimum value for `Ordered` values, by default the value must be greater than or equal to the constraint parameter
*
* '''name'''[constraint.min(minValue)]
* '''error'''[error.min(minValue)] or [error.min.strict(minValue)]
*/
  def min[T](minValue: T, strict: Boolean = false)(implicit ordering: scala.math.Ordering[T]): Constraint[T] = Constraint[T]("constraint.min", minValue) { o =>
    (ordering.compare(o, minValue).signum, strict) match {
      case (1, _) | (0, false) => Valid
      case (_, false) => Invalid(ValidationError("error.min", minValue))
      case (_, true) => Invalid(ValidationError("error.min.strict", minValue))
    }
  }

  /**
* Defines a maximum value for `Ordered` values, by default the value must be less than or equal to the constraint parameter
*
* '''name'''[constraint.max(maxValue)]
* '''error'''[error.max(maxValue)] or [error.max.strict(maxValue)]
*/
  def max[T](maxValue: T, strict: Boolean = false)(implicit ordering: scala.math.Ordering[T]): Constraint[T] = Constraint[T]("constraint.max", maxValue) { o =>
    (ordering.compare(o, maxValue).signum, strict) match {
      case (-1, _) | (0, false) => Valid
      case (_, false) => Invalid(ValidationError("error.max", maxValue))
      case (_, true) => Invalid(ValidationError("error.max.strict", maxValue))
    }
  }

  /**
* Defines a minimum length constraint for `String` values, i.e. the string’s length must be greater than or equal to the constraint parameter
*
* '''name'''[constraint.minLength(length)]
* '''error'''[error.minLength(length)]
*/
  def minLength(length: Int): Constraint[String] = Constraint[String]("constraint.minLength", length) { o =>
    require(length >= 0, "string minLength must not be negative")
    if (o == null) Invalid(ValidationError("error.minLength", length)) else if (o.size >= length) Valid else Invalid(ValidationError("error.minLength", length))
  }

  /**
* Defines a maximum length constraint for `String` values, i.e. the string’s length must be less than or equal to the constraint parameter
*
* '''name'''[constraint.maxLength(length)]
* '''error'''[error.maxLength(length)]
*/
  def maxLength(length: Int): Constraint[String] = Constraint[String]("constraint.maxLength", length) { o =>
    require(length >= 0, "string maxLength must not be negative")
    if (o == null) Invalid(ValidationError("error.maxLength", length)) else if (o.size <= length) Valid else Invalid(ValidationError("error.maxLength", length))
  }

  /**
* Defines a regular expression constraint for `String` values, i.e. the string must match the regular expression pattern
*
* '''name'''[constraint.pattern(regex)] or defined by the name parameter.
* '''error'''[error.pattern(regex)] or defined by the error parameter.
*/
  def pattern(regex: => scala.util.matching.Regex, name: String = "constraint.pattern", error: String = "error.pattern"): Constraint[String] = Constraint[String](name, () => regex) { o =>
    require(regex != null, "regex must not be null")
    require(name != null, "name must not be null")
    require(error != null, "error must not be null")

    if (o == null) Invalid(ValidationError(error, regex)) else regex.unapplySeq(o).map(_ => Valid).getOrElse(Invalid(ValidationError(error, regex)))
  }

}

/**
* A validation result.
*/
sealed trait ValidationResult

/**
* Validation was a success.
*/
case object Valid extends ValidationResult

/**
* Validation was a failure.
*
* @param errors the resulting errors
*/
case class Invalid(errors: Seq[ValidationError]) extends ValidationResult {

  /**
* Combines these validation errors with another validation failure.
*
* @param another validation failure
* @return a new merged `Invalid`
*/
  def ++(other: Invalid): Invalid = Invalid(this.errors ++ other.errors)
}

/**
* This object provides helper methods to construct `Invalid` values.
*/
object Invalid {

  /**
* Creates an `Invalid` value with a single error.
*
* @param error the validation error
* @return an `Invalid` value
*/
  def apply(error: ValidationError): Invalid = Invalid(Seq(error))

  /**
* Creates an `Invalid` value with a single error.
*
* @param error the validation error message
* @param args the validation error message arguments
* @return an `Invalid` value
*/
  def apply(error: String, args: Any*): Invalid = Invalid(Seq(ValidationError(error, args: _*)))
}

Something went wrong with that request. Please try again.