PageRenderTime 25ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/framework/src/play/src/main/scala/play/api/data/validation/Validation.scala

http://github.com/playframework/Play20
Scala | 225 lines | 77 code | 25 blank | 123 comment | 26 complexity | ce7c74c9eb1b03aa3eab2d8116d76b0d MD5 | raw file
Possible License(s): Apache-2.0
  1. /*
  2. * Copyright (C) 2009-2016 Lightbend Inc. <https://www.lightbend.com>
  3. */
  4. package play.api.data.validation
  5. /**
  6. * A form constraint.
  7. *
  8. * @tparam T type of values handled by this constraint
  9. * @param name the constraint name, to be displayed to final user
  10. * @param args the message arguments, to format the constraint name
  11. * @param f the validation function
  12. */
  13. case class Constraint[-T](name: Option[String], args: Seq[Any])(f: (T => ValidationResult)) {
  14. /**
  15. * Run the constraint validation.
  16. *
  17. * @param t the value to validate
  18. * @return the validation result
  19. */
  20. def apply(t: T): ValidationResult = f(t)
  21. }
  22. /**
  23. * This object provides helpers for creating `Constraint` values.
  24. *
  25. * For example:
  26. * {{{
  27. * val negative = Constraint[Int] {
  28. * case i if i < 0 => Valid
  29. * case _ => Invalid("Must be a negative number.")
  30. * }
  31. * }}}
  32. */
  33. object Constraint {
  34. /**
  35. * Creates a new anonymous constraint from a validation function.
  36. *
  37. * @param f the validation function
  38. * @return a constraint
  39. */
  40. def apply[T](f: (T => ValidationResult)): Constraint[T] = apply(None, Nil)(f)
  41. /**
  42. * Creates a new named constraint from a validation function.
  43. *
  44. * @param name the constraint name
  45. * @param args the constraint arguments, used to format the constraint name
  46. * @param f the validation function
  47. * @return a constraint
  48. */
  49. def apply[T](name: String, args: Any*)(f: (T => ValidationResult)): Constraint[T] = apply(Some(name), args.toSeq)(f)
  50. }
  51. /**
  52. * Defines a set of built-in constraints.
  53. */
  54. object Constraints extends Constraints
  55. /**
  56. * Defines a set of built-in constraints.
  57. */
  58. trait Constraints {
  59. /**
  60. * Defines an ‘emailAddress’ constraint for `String` values which will validate email addresses.
  61. *
  62. * '''name'''[constraint.email]
  63. * '''error'''[error.email]
  64. */
  65. private val emailRegex = """^[a-zA-Z0-9\.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$""".r
  66. def emailAddress: Constraint[String] = Constraint[String]("constraint.email") { e =>
  67. if (e == null) Invalid(ValidationError("error.email"))
  68. else if (e.trim.isEmpty) Invalid(ValidationError("error.email"))
  69. else emailRegex.findFirstMatchIn(e)
  70. .map(_ => Valid)
  71. .getOrElse(Invalid(ValidationError("error.email")))
  72. }
  73. /**
  74. * Defines a ‘required’ constraint for `String` values, i.e. one in which empty strings are invalid.
  75. *
  76. * '''name'''[constraint.required]
  77. * '''error'''[error.required]
  78. */
  79. def nonEmpty: Constraint[String] = Constraint[String]("constraint.required") { o =>
  80. if (o == null) Invalid(ValidationError("error.required")) else if (o.trim.isEmpty) Invalid(ValidationError("error.required")) else Valid
  81. }
  82. /**
  83. * Defines a minimum value for `Ordered` values, by default the value must be greater than or equal to the constraint parameter
  84. *
  85. * '''name'''[constraint.min(minValue)]
  86. * '''error'''[error.min(minValue)] or [error.min.strict(minValue)]
  87. */
  88. def min[T](minValue: T, strict: Boolean = false)(implicit ordering: scala.math.Ordering[T]): Constraint[T] = Constraint[T]("constraint.min", minValue) { o =>
  89. (ordering.compare(o, minValue).signum, strict) match {
  90. case (1, _) | (0, false) => Valid
  91. case (_, false) => Invalid(ValidationError("error.min", minValue))
  92. case (_, true) => Invalid(ValidationError("error.min.strict", minValue))
  93. }
  94. }
  95. /**
  96. * Defines a maximum value for `Ordered` values, by default the value must be less than or equal to the constraint parameter
  97. *
  98. * '''name'''[constraint.max(maxValue)]
  99. * '''error'''[error.max(maxValue)] or [error.max.strict(maxValue)]
  100. */
  101. def max[T](maxValue: T, strict: Boolean = false)(implicit ordering: scala.math.Ordering[T]): Constraint[T] = Constraint[T]("constraint.max", maxValue) { o =>
  102. (ordering.compare(o, maxValue).signum, strict) match {
  103. case (-1, _) | (0, false) => Valid
  104. case (_, false) => Invalid(ValidationError("error.max", maxValue))
  105. case (_, true) => Invalid(ValidationError("error.max.strict", maxValue))
  106. }
  107. }
  108. /**
  109. * Defines a minimum length constraint for `String` values, i.e. the string’s length must be greater than or equal to the constraint parameter
  110. *
  111. * '''name'''[constraint.minLength(length)]
  112. * '''error'''[error.minLength(length)]
  113. */
  114. def minLength(length: Int): Constraint[String] = Constraint[String]("constraint.minLength", length) { o =>
  115. require(length >= 0, "string minLength must not be negative")
  116. if (o == null) Invalid(ValidationError("error.minLength", length)) else if (o.size >= length) Valid else Invalid(ValidationError("error.minLength", length))
  117. }
  118. /**
  119. * Defines a maximum length constraint for `String` values, i.e. the string’s length must be less than or equal to the constraint parameter
  120. *
  121. * '''name'''[constraint.maxLength(length)]
  122. * '''error'''[error.maxLength(length)]
  123. */
  124. def maxLength(length: Int): Constraint[String] = Constraint[String]("constraint.maxLength", length) { o =>
  125. require(length >= 0, "string maxLength must not be negative")
  126. if (o == null) Invalid(ValidationError("error.maxLength", length)) else if (o.size <= length) Valid else Invalid(ValidationError("error.maxLength", length))
  127. }
  128. /**
  129. * Defines a regular expression constraint for `String` values, i.e. the string must match the regular expression pattern
  130. *
  131. * '''name'''[constraint.pattern(regex)] or defined by the name parameter.
  132. * '''error'''[error.pattern(regex)] or defined by the error parameter.
  133. */
  134. def pattern(regex: => scala.util.matching.Regex, name: String = "constraint.pattern", error: String = "error.pattern"): Constraint[String] = Constraint[String](name, () => regex) { o =>
  135. require(regex != null, "regex must not be null")
  136. require(name != null, "name must not be null")
  137. require(error != null, "error must not be null")
  138. if (o == null) Invalid(ValidationError(error, regex)) else regex.unapplySeq(o).map(_ => Valid).getOrElse(Invalid(ValidationError(error, regex)))
  139. }
  140. }
  141. /**
  142. * A validation result.
  143. */
  144. sealed trait ValidationResult
  145. /**
  146. * Validation was a success.
  147. */
  148. case object Valid extends ValidationResult
  149. /**
  150. * Validation was a failure.
  151. *
  152. * @param errors the resulting errors
  153. */
  154. case class Invalid(errors: Seq[ValidationError]) extends ValidationResult {
  155. /**
  156. * Combines these validation errors with another validation failure.
  157. *
  158. * @param other validation failure
  159. * @return a new merged `Invalid`
  160. */
  161. def ++(other: Invalid): Invalid = Invalid(this.errors ++ other.errors)
  162. }
  163. /**
  164. * This object provides helper methods to construct `Invalid` values.
  165. */
  166. object Invalid {
  167. /**
  168. * Creates an `Invalid` value with a single error.
  169. *
  170. * @param error the validation error
  171. * @return an `Invalid` value
  172. */
  173. def apply(error: ValidationError): Invalid = Invalid(Seq(error))
  174. /**
  175. * Creates an `Invalid` value with a single error.
  176. *
  177. * @param error the validation error message
  178. * @param args the validation error message arguments
  179. * @return an `Invalid` value
  180. */
  181. def apply(error: String, args: Any*): Invalid = Invalid(Seq(ValidationError(error, args: _*)))
  182. }
  183. object ParameterValidator {
  184. def apply[T](constraints: Iterable[Constraint[T]], optionalParam: Option[T]*) =
  185. optionalParam.flatMap {
  186. _.map { param =>
  187. constraints.flatMap {
  188. _(param) match {
  189. case i: Invalid => Some(i)
  190. case _ => None
  191. }
  192. }
  193. }
  194. }.flatten match {
  195. case Nil => Valid
  196. case invalids => invalids.reduceLeft {
  197. (a, b) => a ++ b
  198. }
  199. }
  200. }