Skip to content

Instantly share code, notes, and snippets.

@john-kurkowski
Created February 19, 2013 02:38
Show Gist options
  • Save john-kurkowski/4982642 to your computer and use it in GitHub Desktop.
Save john-kurkowski/4982642 to your computer and use it in GitHub Desktop.
Scalatra FieldDescriptor that shows .value and .validation as empty for optional, default-less fields. Useful when you want to distinguish these fields being provided or not, and when they have no convenient DefaultValue (zero).
package org.scalatra.commands
import org.scalatra.DefaultValue
import org.scalatra.util.conversion.TypeConverter
abstract class ForwardingFieldDescriptor[T](delegate: FieldDescriptor[T]) extends FieldDescriptor[T] {
protected def copy(newDelegate: FieldDescriptor[T]): ForwardingFieldDescriptor[T]
override def name = delegate.name
override def value = delegate.value
override def validator = delegate.validator
override def notes = delegate.notes
override def notes(note: String) = copy(delegate.notes(note))
override def description = delegate.description
override def description(desc: String) = copy(delegate.description(desc))
override def valueManifest = delegate.valueManifest
override def valueSource = delegate.valueSource
override def sourcedFrom(valueSource: ValueSource.Value): FieldDescriptor[T] = copy(delegate.sourcedFrom(valueSource))
override def allowableValues = delegate.allowableValues
override def allowableValues(vals: T*): FieldDescriptor[T] = copy(delegate.allowableValues(vals: _*))
override def displayName = delegate.displayName
override def displayName(name: String): FieldDescriptor[T] = copy(delegate.displayName(name))
override private[commands] def defVal = delegate.defVal
override def defaultValue = delegate.defaultValue
override def withDefaultValue(default: => T): FieldDescriptor[T] = copy(delegate.withDefaultValue(default))
override private[commands] def isRequired = delegate.isRequired
override def required = copy(delegate.required)
override def optional = copy(delegate.optional)
override def toString() = getClass.getSimpleName + "(" + delegate + ")"
override def validateWith(bindingValidators: BindingValidator[T]*): FieldDescriptor[T] = copy(delegate.validateWith(bindingValidators: _*))
override def apply[S](original: Either[String, Option[S]])(implicit ms: Manifest[S], df: DefaultValue[S], convert: TypeConverter[S, T]) = delegate(original)
override def hashCode() = delegate.hashCode()
override private[commands] def transformations = delegate.transformations
override def transform(endo: T => T): FieldDescriptor[T] = copy(delegate.transform(endo))
override def equals(obj: Any) = delegate.equals(obj)
}
package org.scalatra.commands
import scalaz._
import Scalaz._
import org.scalatra.DefaultValue
import org.scalatra.util.conversion.TypeConverter
import org.scalatra.validation.{ValidationFail, FieldName, ValidationError}
import java.lang.reflect.{Method, InvocationHandler}
/**
* Allows uniform access to Field.validation and Field.value across empty, default, and
* validated cases.
*
* Overrides .validation and .value to provide truly empty values if a parameter was not
* specified, rather than an ambiguous org.scalatra.DefaultValue. If .withDefaultValue
* is called, .validation and .value still provide that default, however.
*/
class OptionalAndEmptyFieldDescriptor[T](delegate: FieldDescriptor[T]) extends ForwardingFieldDescriptor[T](delegate) {
override protected def copy(newDelegate: FieldDescriptor[T]) = new OptionalAndEmptyFieldDescriptor[T](newDelegate)
/**
* Copied from BasicFieldDescriptor.apply, with one key difference: Validation failures do NOT default to df.default.
*/
override def apply[S](original: Either[String, Option[S]])(implicit ms: Manifest[S], df: DefaultValue[S], convert: TypeConverter[S, T]): DataboundFieldDescriptor[S, T] = {
val conv = original match {
case Left(e) => ValidationError(e).fail
case Right(None) if isRequired => ValidationError(name + " is required.", FieldName(name).some, ValidationFail.some, Nil).fail
case Right(None) => defaultValue.success
case Right(Some(s)) => convert(s) toSuccess (ValidationError("Couldn't parse " + name + " " + s + ".", FieldName(name).some, ValidationFail.some, Nil))
}
val neverEqualInstance = java.lang.reflect.Proxy.newProxyInstance(ms.erasure.getClassLoader, ms.erasure.getInterfaces, new InvocationHandler {
override def invoke(proxy: Any, method: Method, args: Array[AnyRef]) = method.getName match {
case "equals" => false: java.lang.Boolean // the key, so binding always thinks conversion failed
case "hashCode" => scala.util.Random.nextInt(): java.lang.Integer // shrug
case _ => null
}
}).asInstanceOf[S]
val o = original match {
case Left(_) => neverEqualInstance
case Right(og) => og | neverEqualInstance
}
BoundFieldDescriptor(o, conv, this)
}
}
class RichFieldDescriptor[T](fd: FieldDescriptor[T]) {
def optionalWithoutDefault = new OptionalAndEmptyFieldDescriptor(fd.optional)
}
object RichFieldDescriptor {
implicit def fieldDescriptor2GravityFieldDescriptor[T](fd: FieldDescriptor[T]) = new RichFieldDescriptor(fd)
}
@casualjim
Copy link

hey would you care to contribute this back as a pull request. This is an often requested feature

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment