Skip to content

Commit

Permalink
Merge pull request #33 from rallyhealth/better-enum-format-error
Browse files Browse the repository at this point in the history
Better Format.enumValueString error messaging
  • Loading branch information
jeffmay authored Mar 19, 2020
2 parents 019c475 + db5da3c commit 94b02f4
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,24 @@ import scala.util.control.NonFatal

object FormatOps {

/**
* Extracts the class name of the [[Enumeration]] removing any `$` symbols without throwing an exception.
*
* @note this will include any outer object or class names separated by `.`s
*/
def enumClassName(o: Enumeration): String = {
// This logic is designed to be robust without much noise
// 1. use getName to avoid runtime exceptions from getSimpleName
// 2. filter out '$' anonymous class / method separators
// 3. start the full class name from the first upper-cased outer class name
// (to avoid picking up unnecessary package names)
o.getClass.getName
.split('.')
.last // safe because Class names will never be empty in any realistic scenario
.split('$')
.mkString(".")
}

/**
* Creates a Format for the given enumeration's values by converting it to and from a string.
*
Expand All @@ -29,7 +47,9 @@ object FormatOps {
override def reads(json: JsValue): JsResult[E#Value] = json.validate[String].flatMap { s =>
try JsSuccess(o.withName(s))
catch {
case NonFatal(e) => JsError(e.getMessage)
case NonFatal(_) =>
val lowerCaseClassName = enumClassName(o).toLowerCase
JsError(s"error.expected.$lowerCaseClassName: No value found for '$s'")
}
}
override def writes(o: E#Value): JsValue = JsString(o.toString)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class FormatOpsSpec extends WordSpec {
}

"fails to read an invalid value" in {
assertResult(JsError("No value found for 'ERROR'")) {
assertResult(JsError("error.expected.enumexample: No value found for 'ERROR'")) {
formatEnumString.reads(JsString("ERROR"))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,29 @@ package play.api.libs.json.ops

import play.api.libs.json._

import scala.language.higherKinds
import scala.reflect._
import scala.util.control.NonFatal

object FormatOps {

/**
* Extracts the class name of the [[Enumeration]] removing any `$` symbols without throwing an exception.
*
* @note this will include any outer object or class names separated by `.`s
*/
def enumClassName(o: Enumeration): String = {
// This logic is designed to be robust without much noise
// 1. use getName to avoid runtime exceptions from getSimpleName
// 2. filter out '$' anonymous class / method separators
// 3. start the full class name from the first upper-cased outer class name
// (to avoid picking up unnecessary package names)
o.getClass.getName
.split('.')
.last // safe because Class names will never be empty in any realistic scenario
.split('$')
.mkString(".")
}

/**
* Creates a Format for the given enumeration's values by converting it to and from a string.
*
Expand All @@ -29,7 +46,9 @@ object FormatOps {
override def reads(json: JsValue): JsResult[E#Value] = json.validate[String].flatMap { s =>
try JsSuccess(o.withName(s))
catch {
case NonFatal(e) => JsError(e.getMessage)
case NonFatal(_) =>
val lowerCaseClassName = enumClassName(o).toLowerCase
JsError(s"error.expected.$lowerCaseClassName: No value found for '$s'")
}
}
override def writes(o: E#Value): JsValue = JsString(o.toString)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class FormatOpsSpec extends AnyWordSpec {
}

"fails to read an invalid value" in {
assertResult(JsError("No value found for 'ERROR'")) {
assertResult(JsError("error.expected.enumexample: No value found for 'ERROR'")) {
formatEnumString.reads(JsString("ERROR"))
}
}
Expand Down

0 comments on commit 94b02f4

Please sign in to comment.