All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.scalatra.commands.field.scala Maven / Gradle / Ivy

package org.scalatra
package commands

import validation._
import util.conversion._
import scalaz._
import syntax.validation._
import syntax.std.option._
import mojolly.inflector.InflectorImports._
import org.scalatra.util.RicherString._

object DefVal {
  def apply[T:Manifest](prov: => T) = new DefVal(prov)
}
class DefVal[T:Manifest](valueProvider: => T) {
  lazy val value = valueProvider
}
object ValueSource extends Enumeration {
  val Header = Value("header")
  val Body = Value("body")
  val Query = Value("query")
  val Path = Value("path")
}

object FieldDescriptor {
  def apply[T](name: String)(implicit mf: Manifest[T], defV: DefaultValue[T]): FieldDescriptor[T] = 
    new BasicFieldDescriptor[T](name, transformations = identity, defVal = DefVal(defV.default))
}
trait FieldDescriptor[T] {

  def name: String
  def value: FieldValidation[T]
  def validator: Option[Validator[T]]
  def notes: String 
  def notes(note: String): FieldDescriptor[T]
  def description: String 
  def description(desc: String): FieldDescriptor[T]
  def valueManifest: Manifest[T]
  def valueSource: ValueSource.Value
  def sourcedFrom(valueSource: ValueSource.Value): FieldDescriptor[T]
  def allowableValues: List[T]
  def allowableValues(vals: T*): FieldDescriptor[T]
  def displayName: Option[String]
  def displayName(name: String): FieldDescriptor[T]
  
  private[commands] def defVal: DefVal[T]
  def defaultValue: T = defVal.value
  def withDefaultValue(default: => T): FieldDescriptor[T]

  def isValid = value.isSuccess
  def isInvalid = value.isFailure

  private[commands] def isRequired: Boolean
  def required: FieldDescriptor[T]
  def optional: FieldDescriptor[T]

  override def toString() = "FieldDescriptor(name: %s)".format(name)

  def validateWith(validators: BindingValidator[T]*): FieldDescriptor[T]

  def apply[S](original: Either[String, Option[S]])(implicit ms: Manifest[S], df: DefaultValue[S], convert: TypeConverter[S, T]): DataboundFieldDescriptor[S, T]

  override def hashCode() = 41 + 41 * name.hashCode()

  def transform(endo: T => T): FieldDescriptor[T]

  private[commands] def transformations: T => T

  override def equals(obj: Any) = obj match {
    case b : FieldDescriptor[_] => b.name == this.name
    case _ => false
  }

}

class BasicFieldDescriptor[T](
    val name: String, 
    val validator: Option[Validator[T]] = None, 
    private[commands] val transformations: T => T = identity _,
    private[commands] var isRequired: Boolean = false,
    val description: String = "",
    val notes: String = "",
    private[commands] val defVal: DefVal[T],
    val valueSource: ValueSource.Value = ValueSource.Body,
    val allowableValues: List[T] = Nil,
    val displayName: Option[String] = None)(implicit val valueManifest: Manifest[T]) extends FieldDescriptor[T] {

  val value: FieldValidation[T] = defaultValue.success

  def validateWith(bindingValidators: BindingValidator[T]*): FieldDescriptor[T] = {
    val nwValidators: Option[Validator[T]] =
      if(bindingValidators.nonEmpty) Some(bindingValidators.map(_ apply name).reduce(_ andThen _)) else None

    copy(validator = validator.flatMap(v => nwValidators.map(v andThen _)) orElse nwValidators)
  }

  def copy(
      name: String = name, 
      validator: Option[Validator[T]] = validator, 
      transformations: T => T = transformations, 
      isRequired: Boolean = isRequired, 
      description: String = description, 
      notes: String = notes,
      defVal: DefVal[T] = defVal,
      valueSource: ValueSource.Value = valueSource,
      allowableValues: List[T] = allowableValues,
      displayName: Option[String] = displayName): FieldDescriptor[T] = {
    new BasicFieldDescriptor(name, validator, transformations, isRequired, description, notes, defVal, valueSource, allowableValues, displayName)(valueManifest) 
  }

  def apply[S](original: Either[String, Option[S]])(implicit ms: Manifest[S], df: DefaultValue[S], convert: TypeConverter[S, T]): DataboundFieldDescriptor[S, T] = {
    val defValS = df.default
    val conv = original.fold(e => ValidationError(e).fail, o => (convert(o | defValS) | defaultValue).success)
    val o = original.fold(_ => None, identity)
    BoundFieldDescriptor(o, conv, this)
  }

  def transform(endo: T => T): FieldDescriptor[T] = copy(transformations = transformations andThen endo)

  def required = copy(isRequired = true)

  def optional = copy(isRequired = false)
  
  def description(desc: String) = copy(description = desc)
  
  def notes(note: String) = copy(notes = note)
  
  def withDefaultValue(default: => T): FieldDescriptor[T] = copy(defVal = DefVal(default), isRequired = false)
  
  def sourcedFrom(valueSource: ValueSource.Value): FieldDescriptor[T] = copy(valueSource = valueSource)

  def allowableValues(vals: T*): FieldDescriptor[T] = copy(allowableValues = vals.toList).validateWith(BindingValidators.oneOf("%%s must be one of %s.", vals))

  def displayName(name: String): FieldDescriptor[T] = copy(displayName = name.blankOption)
}


trait DataboundFieldDescriptor[S, T] extends FieldDescriptor[T] {
  def field: FieldDescriptor[T]
  def original: Option[S]
  def transform(endo: T => T): DataboundFieldDescriptor[S, T]
  def apply[V](original: Either[String, Option[V]])(implicit mv: Manifest[V], df: DefaultValue[V], convert: TypeConverter[V, T]): DataboundFieldDescriptor[V, T] =
    this.asInstanceOf[DataboundFieldDescriptor[V, T]]

  override def toString() = "FieldDescriptor(name: %s, original: %s, value: %s)".format(name, original, value)
  def validate: ValidatedFieldDescriptor[S, T]
  def validateWith(bindingValidators: BindingValidator[T]*): DataboundFieldDescriptor[S, T]
  def required: DataboundFieldDescriptor[S, T]
  def optional: DataboundFieldDescriptor[S, T]
  def isRequired = field.isRequired
  def description = field.description
  def description(desc: String): DataboundFieldDescriptor[S, T] 
  def notes = field.notes
  def notes(note: String): DataboundFieldDescriptor[S, T]
  def valueManifest = field.valueManifest
  private[commands] def defVal: DefVal[T] = field.defVal
  def withDefaultValue(default: => T): DataboundFieldDescriptor[S, T] 
  def valueSource: ValueSource.Value = field.valueSource
  def sourcedFrom(valueSource: ValueSource.Value): DataboundFieldDescriptor[S, T]
  def allowableValues = field.allowableValues
  def allowableValues(vals: T*): DataboundFieldDescriptor[S, T]
  def displayName: Option[String] = field.displayName
  def displayName(name: String): DataboundFieldDescriptor[S, T]
  
}

trait ValidatedFieldDescriptor[S, T] extends DataboundFieldDescriptor[S, T] {
  def validate: ValidatedFieldDescriptor[S, T] = this
}

object BoundFieldDescriptor {
  def apply[S:DefaultValue, T](original: Option[S], value: FieldValidation[T], binding: FieldDescriptor[T]): DataboundFieldDescriptor[S, T] =
    new BoundFieldDescriptor(original, value, binding, binding.validator)
}


class BoundFieldDescriptor[S:DefaultValue, T](
    val original: Option[S],
    val value: FieldValidation[T], 
    val field: FieldDescriptor[T], 
    val validator: Option[Validator[T]]) extends DataboundFieldDescriptor[S, T] {
  def name: String = field.name
  

  override def hashCode(): Int = field.hashCode()
  override def equals(other: Any) = other match {
    case o: BasicFieldDescriptor[T] => field.equals(o)
    case o: BoundFieldDescriptor[T, S] => field.equals(o.field)
    case _ => false
  }
  override def toString() = "BoundFieldDescriptor(name: %s, original: %s, converted: %s)".format(name, original, value)

  def validateWith(bindingValidators: BindingValidator[T]*): DataboundFieldDescriptor[S, T] = {
    val nwFld = field.validateWith(bindingValidators:_*)
    copy(field = nwFld, validator = nwFld.validator)
  }

  def copy(original: Option[S] = original, value: FieldValidation[T] = value, field: FieldDescriptor[T] = field, validator: Option[Validator[T]] = validator): DataboundFieldDescriptor[S, T] =
    new BoundFieldDescriptor(original, value, field, validator)

  def transform(endo: T => T): DataboundFieldDescriptor[S, T] = copy(value = value map endo)

  def required = copy(field = field.required)

  def optional = copy(field = field.optional)
  
  def description(desc: String) = copy(field = field.description(desc))
  
  def notes(note: String) = copy(field = field.notes(note))

  def validate: ValidatedFieldDescriptor[S, T] = {
    val defaultValidator: Validator[T] = validator getOrElse identity
    if (!isRequired && original.isEmpty) {
      new ValidatedBoundFieldDescriptor(value map transformations, this)
    } else {
      val doValidation: Validator[T] = if (isRequired) {
        (x: FieldValidation[T]) => x flatMap { v =>
          if (original.isDefined) v.success else ValidationError("%s is required." format(name.underscore.humanize), FieldName(name), ValidationFail).fail
        }
      } else identity
      new ValidatedBoundFieldDescriptor((doValidation andThen defaultValidator)(value) map transformations, this)
    }
  }

  private[commands] def transformations: (T) => T = field.transformations
  
  def withDefaultValue(default: => T): DataboundFieldDescriptor[S, T] = copy(field = field.withDefaultValue(default))
  
  def sourcedFrom(valueSource: ValueSource.Value): DataboundFieldDescriptor[S, T] = copy(field = field.sourcedFrom(valueSource))

  def allowableValues(vals: T*): DataboundFieldDescriptor[S, T] = copy(field = field.allowableValues(vals:_*))

  def displayName(name: String): DataboundFieldDescriptor[S, T] = copy(field = field.displayName(name))
}

class ValidatedBoundFieldDescriptor[S, T](val value: FieldValidation[T], val field: DataboundFieldDescriptor[S, T]) extends ValidatedFieldDescriptor[S, T] {
  def name: String = field.name

  override def hashCode(): Int = field.hashCode()
  override def equals(other: Any) = other match {
    case o: BasicFieldDescriptor[T] => field.equals(o)
    case o: BoundFieldDescriptor[T, S] => field.equals(o.field)
    case o: ValidatedBoundFieldDescriptor[S, T] => field.equals(o.field)
    case _ => false
  }
  override def toString() = "BoundFieldDescriptor(name: %s, original: %s, converted: %s)".format(name, original, value)

  def validateWith(bindingValidators: BindingValidator[T]*): DataboundFieldDescriptor[S, T] = {
    copy(field = field.validateWith(bindingValidators:_*))
  }

  def copy(value: FieldValidation[T] = value, field: DataboundFieldDescriptor[S, T] = field): ValidatedFieldDescriptor[S, T] =
    new ValidatedBoundFieldDescriptor(value, field)

  def transform(endo: T => T): DataboundFieldDescriptor[S, T] = copy(value = value map endo)

  def required = copy(field = field.required)

  def optional = copy(field = field.optional)
   
  def description(desc: String) = copy(field = field.description(desc))
  
  def notes(note: String) = copy(field = field.notes(note))

  def validator: Option[Validator[T]] = field.validator

  def original: Option[S] = field.original

  private[commands] def transformations: (T) => T = field.transformations
  
  def withDefaultValue(default: => T): DataboundFieldDescriptor[S, T] = copy(field = field.withDefaultValue(default))
  
  def sourcedFrom(valueSource: ValueSource.Value): DataboundFieldDescriptor[S, T] = copy(field = field.sourcedFrom(valueSource))
  
  def allowableValues(vals: T*): DataboundFieldDescriptor[S, T] = copy(field = field.allowableValues(vals:_*))
  
  def displayName(name: String): DataboundFieldDescriptor[S, T] = copy(field = field.displayName(name))
}

import scala.util.matching.Regex


trait BindingValidatorImplicits {

  import BindingValidators._
  implicit def validatableStringBinding(b: FieldDescriptor[String]) = new ValidatableStringBinding(b)
  implicit def validatableSeqBinding[T <: Seq[_]](b: FieldDescriptor[T]) = new ValidatableSeq(b)
  implicit def validatableGenericBinding[T](b: FieldDescriptor[T]) = new ValidatableGenericBinding(b)
  implicit def validatableOrderedBinding[T <% Ordered[T]](b: FieldDescriptor[T]) = new ValidatableOrdered(b)

}

object BindingValidators {


  class ValidatableSeq[T <: Seq[_]](b: FieldDescriptor[T]) {
    def notEmpty: FieldDescriptor[T] = notEmpty()
    def notEmpty(messageFormat: String = "%s is required."): FieldDescriptor[T] =
      b.required.validateWith(BindingValidators.nonEmptyCollection(messageFormat))
  }


  class ValidatableOrdered[T <% Ordered[T]](b: FieldDescriptor[T]) {
    def greaterThan(min: T, messageFormat: String = "%%s must be greater than %s"): FieldDescriptor[T] =
      b.validateWith(BindingValidators.greaterThan(min, messageFormat))

    def lessThan(max: T, messageFormat: String = "%%s must be less than %s"): FieldDescriptor[T] =
      b.validateWith(BindingValidators.lessThan(max, messageFormat))

    def greaterThanOrEqualTo(min: T, messageFormat: String = "%%s must be greater than or equal to %s"): FieldDescriptor[T] =
      b.validateWith(BindingValidators.greaterThanOrEqualTo(min, messageFormat))

    def lessThanOrEqualTo(max: T, messageFormat: String = "%%s must be greater than or equal to %s"): FieldDescriptor[T] =
      b.validateWith(BindingValidators.lessThanOrEqualTo(max, messageFormat))

  }

  class ValidatableGenericBinding[T](b: FieldDescriptor[T]) {
    def validate(validate: T => Boolean, messageFormat: String = "%s is invalid."): FieldDescriptor[T] = 
      b.validateWith(BindingValidators.validate(validate, messageFormat))
  }

  class ValidatableStringBinding(b: FieldDescriptor[String]) {
    def notBlank: FieldDescriptor[String] = notBlank()
    def notBlank(messageFormat: String = "%s is required"): FieldDescriptor[String] =
      b.required.validateWith(BindingValidators.nonEmptyString(messageFormat))


    def validEmail: FieldDescriptor[String] = validEmail()
    def validEmail(messageFormat: String = "%s must be a valid email address."): FieldDescriptor[String] =
    b.validateWith(BindingValidators.validEmail(messageFormat))

    def validAbsoluteUrl(allowLocalHost: Boolean, messageFormat: String = "%s must be a valid absolute url.", schemes: Seq[String] = Seq("http", "https")): FieldDescriptor[String] =
      b.validateWith(BindingValidators.validAbsoluteUrl(allowLocalHost, messageFormat, schemes))

    def validUrl(allowLocalHost: Boolean, messageFormat: String = "%s must be a valid url.", schemes: Seq[String] = Seq("http", "https")): FieldDescriptor[String] =
      b.validateWith(BindingValidators.validUrl(allowLocalHost, messageFormat, schemes))

    def validForFormat(regex: Regex, messageFormat: String = "%s is invalid."): FieldDescriptor[String] =
      b.validateWith(BindingValidators.validFormat(regex, messageFormat))

    def validForConfirmation(against: Field[String], messageFormat: String = "%%s must match %s."): FieldDescriptor[String] =
      b.validateWith(BindingValidators.validConfirmation(against, messageFormat))


    def minLength(min: Int, messageFormat: String = "%%s must be at least %s characters long."): FieldDescriptor[String] =
      b.validateWith(BindingValidators.minLength(min, messageFormat))


    def enumValue(enum: Enumeration, messageFormat: String = "%%s must be one of %s."): FieldDescriptor[String] =
      b.validateWith(BindingValidators.enumValue(enum, messageFormat))
  }

  import org.scalatra.validation.Validation


  def validate[TValue](validate: TValue => Boolean, messageFormat: String = "%s is invalid."): BindingValidator[TValue] = (s: String) => {
    _ flatMap (Validators.validate(s, messageFormat = messageFormat, validate = validate).validate(_))
  }

  def nonEmptyString: BindingValidator[String] = nonEmptyString()
  def nonEmptyString(messageFormat: String = "%s is required."): BindingValidator[String] = (s: String) => {
    _ flatMap (Validation.nonEmptyString(s, _, messageFormat))
  }

  def notNull: BindingValidator[AnyRef] = notNull()
  def notNull(messageFormat: String = "%s is required."): BindingValidator[AnyRef] = (s: String) => {
    _ flatMap (Validation.notNull(s, _, messageFormat))
  }

  def nonEmptyCollection[TResult <: Traversable[_]]: BindingValidator[TResult] = nonEmptyCollection[TResult]()
  def nonEmptyCollection[TResult <: Traversable[_]](messageFormat: String = "%s must not be empty."): BindingValidator[TResult] = (s: String) =>{
    _ flatMap (Validation.nonEmptyCollection(s, _, messageFormat))
  }

  def validEmail: BindingValidator[String] = validEmail()
  def validEmail(messageFormat: String = "%s must be a valid email address."): BindingValidator[String] = (s: String) =>{
    _ flatMap (Validation.validEmail(s, _, messageFormat))
  }

  def validAbsoluteUrl(allowLocalHost: Boolean, messageFormat: String = "%s must be a absolute valid url.", schemes: Seq[String] = Seq("http", "https")): BindingValidator[String] = (s: String) =>{
    _ flatMap (Validators.validAbsoluteUrl(s, allowLocalHost, messageFormat, schemes).validate(_))
  }

  def validUrl(allowLocalHost: Boolean, messageFormat: String = "%s must be a valid url.", schemes: Seq[String] = Seq("http", "https")): BindingValidator[String] = (s: String) =>{
    _ flatMap (Validators.validUrl(s, allowLocalHost, messageFormat, schemes).validate(_))
  }

  def validFormat(regex: Regex, messageFormat: String = "%s is invalid."): BindingValidator[String] = (s: String) =>{
    _ flatMap (Validators.validFormat(s, regex, messageFormat).validate(_))
  }

  def validConfirmation(against: Field[String], messageFormat: String = "%%s must match %s."): BindingValidator[String] = (s: String) =>{
    _ flatMap { Validators.validConfirmation(s, against.name, against.value | against.defaultValue, messageFormat).validate(_) }
  }

  def greaterThan[T <% Ordered[T]](min: T, messageFormat: String = "%%s must be greater than %s."): BindingValidator[T] = (s: String) =>{
    _ flatMap (Validators.greaterThan(s, min, messageFormat).validate(_))
  }

  def lessThan[T <% Ordered[T]](max: T, messageFormat: String = "%%s must be less than %s."): BindingValidator[T] = (s: String) =>{
    _ flatMap (Validators.lessThan(s, max, messageFormat).validate(_))
  }

  def greaterThanOrEqualTo[T <% Ordered[T]](min: T, messageFormat: String = "%%s must be greater than or equal to %s."): BindingValidator[T] = (s: String) =>{
    _ flatMap (Validators.greaterThanOrEqualTo(s, min, messageFormat).validate(_))
  }

  def lessThanOrEqualTo[T <% Ordered[T]](max: T, messageFormat: String = "%%s must be less than or equal to %s."): BindingValidator[T] = (s: String) =>{
    _ flatMap (Validators.lessThanOrEqualTo(s, max, messageFormat).validate(_))
  }

  def minLength(min: Int, messageFormat: String = "%%s must be at least %s characters long."): BindingValidator[String] = (s: String) =>{
    _ flatMap (Validators.minLength(s, min, messageFormat).validate(_))
  }

  def oneOf[TResult](messageFormat: String = "%%s must be one of %s.", expected: Seq[TResult]): BindingValidator[TResult] = (s: String) => {
    _ flatMap (Validators.oneOf(s, messageFormat, expected).validate(_))
  }

  def enumValue(enum: Enumeration, messageFormat: String = "%%s must be one of %s."): BindingValidator[String] = 
    oneOf(messageFormat, enum.values.map(_.toString).toSeq)
}

class Field[A:Manifest](descr: FieldDescriptor[A], command: Command) {

  val name = descr.name
  def validation: FieldValidation[A] = binding.field.value.asInstanceOf[FieldValidation[A]]
  def value: Option[A] = binding.field.value.toOption.asInstanceOf[Option[A]]
  def defaultValue: A = descr.defaultValue
  def error: Option[ValidationError] = binding.field.value.fold(_.some, _=>None)
  def original = binding.original

  def binding: Binding = command.bindings(name)

  def isValid = validation.isSuccess
  def isInvalid = validation.isFailure
  
	def notes: String = descr.notes
  def description: String = descr.description
  
  def isRequired: Boolean = descr.isRequired
  def valueSource: ValueSource.Value = descr.valueSource
  def allowableValues = descr.allowableValues
  def displayName: Option[String] = descr.displayName
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy