Skip to content

Commit

Permalink
Add case insensitive as an option for Play helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
lloydmeta committed Mar 6, 2015
1 parent f1eb490 commit 62d0db3
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 15 deletions.
13 changes: 10 additions & 3 deletions enumeratum-play/src/main/scala/enumeratum/Forms.scala
Expand Up @@ -15,16 +15,23 @@ object Forms {
* {{{
* Form("status" -> maps(Status))
* }}}
*
* @param enum The enum
* @param insensitive bind in a case-insensitive way, defaults to false
*/
def enum[A](enum: Enum[A]): Mapping[A] = PlayForms.of(format(enum))
def enum[A](enum: Enum[A], insensitive: Boolean = false): Mapping[A] = PlayForms.of(format(enum, insensitive))

/**
* Returns a Formatter for [[Enum]]
*
* @param enum The enum
* @param insensitive bind in a case-insensitive way, defaults to false
*/
private[enumeratum] def format[A](enum: Enum[A]): Formatter[A] = new Formatter[A] {
private[enumeratum] def format[A](enum: Enum[A], insensitive: Boolean = false): Formatter[A] = new Formatter[A] {
def bind(key: String, data: Map[String, String]) = {
play.api.data.format.Formats.stringFormat.bind(key, data).right.flatMap { s =>
enum.withNameOption(s) match {
val maybeBound = if (insensitive) enum.withNameInsensitiveOption(s) else enum.withNameOption(s)
maybeBound match {
case Some(obj) => Right(obj)
case None => Left(Seq(FormError(key, "error.enum", Nil)))
}
Expand Down
15 changes: 11 additions & 4 deletions enumeratum-play/src/main/scala/enumeratum/Json.scala
Expand Up @@ -9,11 +9,15 @@ object Json {

/**
* Returns an Json Reads for a given enum [[Enum]]
*
* @param enum The enum
* @param insensitive bind in a case-insensitive way, defaults to false
*/
def reads[A](enum: Enum[A]): Reads[A] = new Reads[A] {
def reads[A](enum: Enum[A], insensitive: Boolean = false): Reads[A] = new Reads[A] {
def reads(json: JsValue): JsResult[A] = json match {
case JsString(s) => {
enum.withNameOption(s) match {
val maybeBound = if (insensitive) enum.withNameInsensitiveOption(s) else enum.withNameOption(s)
maybeBound match {
case Some(obj) => JsSuccess(obj)
case None => JsError(s"Enumeration expected of type: '$enum', but it does not appear to contain the value: '$s'")
}
Expand All @@ -31,9 +35,12 @@ object Json {

/**
* Returns a Json format for a given enum [[Enum]]
*
* @param enum The enum
* @param insensitive bind in a case-insensitive way, defaults to false
*/
def formats[A](enum: Enum[A]): Format[A] = {
Format(reads(enum), writes(enum))
def formats[A](enum: Enum[A], insensitive: Boolean = false): Format[A] = {
Format(reads(enum, insensitive), writes(enum))
}

}
2 changes: 2 additions & 0 deletions enumeratum-play/src/main/scala/enumeratum/PlayEnum.scala
Expand Up @@ -8,6 +8,8 @@ import play.api.mvc.{ QueryStringBindable, PathBindable }
* An Enum that has a lot of the Play-related implicits built-in so you can avoid
* boilerplate.
*
* Note, the binders created here are case-sensitive.
*
* Things included are:
*
* - implicit JSON format
Expand Down
32 changes: 24 additions & 8 deletions enumeratum-play/src/main/scala/enumeratum/UrlBinders.scala
Expand Up @@ -2,7 +2,6 @@ package enumeratum

import play.api.mvc.PathBindable
import play.api.mvc.QueryStringBindable
import play.api.mvc.QueryStringBindable._

/**
* Created by Lloyd on 2/3/15.
Expand All @@ -11,11 +10,15 @@ object UrlBinders {

/**
* Builds a [[PathBindable]] A for a given Enum A
*
* @param enum The enum
* @param insensitive bind in a case-insensitive way, defaults to false
*/
def pathBinder[A](enum: Enum[A]): PathBindable[A] = new PathBindable[A] {
def pathBinder[A](enum: Enum[A], insensitive: Boolean = false): PathBindable[A] = new PathBindable[A] {
def unbind(key: String, value: A): String = value.toString
def bind(key: String, value: String): Either[String, A] = {
enum.withNameOption(value) match {
val maybeBound = if (insensitive) enum.withNameInsensitiveOption(value) else enum.withNameOption(value)
maybeBound match {
case Some(v) => Right(v)
case _ => Left(s"Unknown value supplied for $enum '" + value + "'")
}
Expand All @@ -24,11 +27,24 @@ object UrlBinders {

/**
* Builds a [[QueryStringBindable]] A for a given Enum A
*
* @param enum The enum
* @param insensitive bind in a case-insensitive way, defaults to false
*/
def queryBinder[A](enum: Enum[A]): QueryStringBindable[A] = new Parsing[A](
enum.withName,
_.toString,
(key, exception) => "Cannot parse parameter %s as an Enum: %s".format(key, exception.getMessage)
)
def queryBinder[A](enum: Enum[A], insensitive: Boolean = false): QueryStringBindable[A] =
new QueryStringBindable[A] {

def unbind(key: String, value: A): String = key + "=" + value.toString

def bind(key: String, params: Map[String, Seq[String]]): Option[Either[String, A]] = {
params.get(key).flatMap(_.headOption).map { p =>
val maybeBound = if (insensitive) enum.withNameInsensitiveOption(p) else enum.withNameOption(p)
maybeBound match {
case Some(v) => Right(v)
case _ => Left(s"Cannot parse parameter $key as an Enum: $this")
}
}
}
}

}
49 changes: 49 additions & 0 deletions enumeratum-play/src/test/scala/enumeratum/FormSpec.scala
Expand Up @@ -29,6 +29,28 @@ class FormSpec extends FunSpec with Matchers {

}

describe(".enum insensitive") {

val subject = Form("hello" -> enum(Dummy, true))

it("should bind proper strings into an Enum value disregarding case") {
val r1 = subject.bind(Map("hello" -> "A"))
val r2 = subject.bind(Map("hello" -> "a"))
val r3 = subject.bind(Map("hello" -> "B"))
val r4 = subject.bind(Map("hello" -> "b"))
r1.value.value shouldBe Dummy.A
r2.value.value shouldBe Dummy.A
r3.value.value shouldBe Dummy.B
r4.value.value shouldBe Dummy.B
}

it("should fail to bind random strings") {
val r = subject.bind(Map("hello" -> "AARSE"))
r.value shouldBe None
}

}

describe(".format") {

val subject = format(Dummy)
Expand All @@ -52,4 +74,31 @@ class FormSpec extends FunSpec with Matchers {

}

describe(".format case insensitive") {

val subject = format(Dummy, true)

it("should bind proper strings into an Enum value") {
val r1 = subject.bind("hello", Map("hello" -> "A"))
val r2 = subject.bind("hello", Map("hello" -> "a"))
val r3 = subject.bind("hello", Map("hello" -> "B"))
val r4 = subject.bind("hello", Map("hello" -> "b"))
r1 shouldBe Right(Dummy.A)
r2 shouldBe Right(Dummy.A)
r3 shouldBe Right(Dummy.B)
r4 shouldBe Right(Dummy.B)
}

it("should fail to bind random strings") {
val r = subject.bind("hello", Map("hello" -> "AARSE"))
r should be('left)
}

it("should unbind ") {
val r = subject.unbind("hello", Dummy.A)
r shouldBe Map("hello" -> "A")
}

}

}
14 changes: 14 additions & 0 deletions enumeratum-play/src/test/scala/enumeratum/JsonSpec.scala
Expand Up @@ -19,6 +19,20 @@ class JsonSpec extends FunSpec with Matchers {
}
}

describe("reads insensitive") {
val reads = Json.reads(Dummy, true)

it("should create a reads that works with valid values disregarding case") {
reads.reads(JsString("A")).asOpt.value should be(Dummy.A)
reads.reads(JsString("a")).asOpt.value should be(Dummy.A)
}

it("should create a reads that fails with invalid values") {
reads.reads(JsString("D")).isError should be(true)
reads.reads(JsNumber(2)).isError should be(true)
}
}

describe("writes") {
val writer = Json.writes(Dummy)

Expand Down
43 changes: 43 additions & 0 deletions enumeratum-play/src/test/scala/enumeratum/UrlBindersSpec.scala
Expand Up @@ -29,6 +29,28 @@ class UrlBindersSpec extends FunSpec with Matchers {

}

describe(".pathBinder case insensitive") {

val subject = pathBinder(Dummy, true)

it("should create an enumeration binder that can bind strings corresponding to enum strings, disregarding case") {
subject.bind("hello", "A").right.value shouldBe Dummy.A
subject.bind("hello", "a").right.value shouldBe Dummy.A
subject.bind("hello", "B").right.value shouldBe Dummy.B
subject.bind("hello", "b").right.value shouldBe Dummy.B
}

it("should create an enumeration binder that cannot bind strings not found in the enumeration") {
subject.bind("hello", "Z").isLeft shouldBe true
}

it("should create an enumeration binder that can unbind values") {
subject.unbind("hello", Dummy.A) shouldBe "A"
subject.unbind("hello", Dummy.B) shouldBe "B"
}

}

describe(".queryBinder") {

val subject = queryBinder(Dummy)
Expand All @@ -49,4 +71,25 @@ class UrlBindersSpec extends FunSpec with Matchers {

}

describe(".queryBinder case insensitive") {

val subject = queryBinder(Dummy, true)

it("should create an enumeration binder that can bind strings corresponding to enum strings regardless of case, disregarding case") {
subject.bind("hello", Map("hello" -> Seq("A"))).value.right.value should be(Dummy.A)
subject.bind("hello", Map("hello" -> Seq("a"))).value.right.value should be(Dummy.A)
}

it("should create an enumeration binder that cannot bind strings not found in the enumeration") {
subject.bind("hello", Map("hello" -> Seq("Z"))).value should be('left)
subject.bind("hello", Map("helloz" -> Seq("A"))) shouldBe None
}

it("should create an enumeration binder that can unbind values") {
subject.unbind("hello", Dummy.A) should be("hello=A")
subject.unbind("hello", Dummy.B) should be("hello=B")
}

}

}

0 comments on commit 62d0db3

Please sign in to comment.