Created
February 19, 2013 02:38
-
-
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).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class RichFieldDescriptor[T](fd: FieldDescriptor[T]) { | |
def optionalWithoutDefault = new OptionalAndEmptyFieldDescriptor(fd.optional) | |
} | |
object RichFieldDescriptor { | |
implicit def fieldDescriptor2GravityFieldDescriptor[T](fd: FieldDescriptor[T]) = new RichFieldDescriptor(fd) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
hey would you care to contribute this back as a pull request. This is an often requested feature