Skip to content

Detailed error on Enum reads in enumeratum-play-json #411

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ node_modules

# Ignore compiled assets
/public/assets
.java-version
73 changes: 51 additions & 22 deletions enumeratum-play-json/src/main/scala/enumeratum/EnumFormats.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,37 +15,47 @@ object EnumFormats {
*/
def reads[A <: EnumEntry](
@deprecatedName(Symbol("enum")) e: Enum[A],
insensitive: Boolean = false
insensitive: Boolean = false,
detailedError: Boolean = false
): Reads[A] =
readsAndExtracts[A](e) { s =>
readsAndExtracts[A](e, detailedError) { s =>
if (insensitive) e.withNameInsensitiveOption(s)
else e.withNameOption(s)
}

def readsLowercaseOnly[A <: EnumEntry](@deprecatedName(Symbol("enum")) e: Enum[A]): Reads[A] =
readsAndExtracts[A](e)(e.withNameLowercaseOnlyOption)
def readsLowercaseOnly[A <: EnumEntry](
@deprecatedName(Symbol("enum")) e: Enum[A],
detailedError: Boolean = false
): Reads[A] =
readsAndExtracts[A](e, detailedError)(e.withNameLowercaseOnlyOption)

def readsUppercaseOnly[A <: EnumEntry](@deprecatedName(Symbol("enum")) e: Enum[A]): Reads[A] =
readsAndExtracts[A](e)(e.withNameUppercaseOnlyOption)
def readsUppercaseOnly[A <: EnumEntry](
@deprecatedName(Symbol("enum")) e: Enum[A],
detailedError: Boolean = false
): Reads[A] =
readsAndExtracts[A](e, detailedError)(e.withNameUppercaseOnlyOption)

def keyReads[A <: EnumEntry](
@deprecatedName(Symbol("enum")) e: Enum[A],
insensitive: Boolean = false
insensitive: Boolean = false,
detailedError: Boolean = false
): KeyReads[A] =
readsKeyAndExtracts[A](e) { s =>
readsKeyAndExtracts[A](e, detailedError) { s =>
if (insensitive) e.withNameInsensitiveOption(s)
else e.withNameOption(s)
}

def keyReadsLowercaseOnly[A <: EnumEntry](
@deprecatedName(Symbol("enum")) e: Enum[A]
@deprecatedName(Symbol("enum")) e: Enum[A],
detailedError: Boolean = false
): KeyReads[A] =
readsKeyAndExtracts[A](e)(e.withNameLowercaseOnlyOption)
readsKeyAndExtracts[A](e, detailedError)(e.withNameLowercaseOnlyOption)

def keyReadsUppercaseOnly[A <: EnumEntry](
@deprecatedName(Symbol("enum")) e: Enum[A]
@deprecatedName(Symbol("enum")) e: Enum[A],
detailedError: Boolean = false
): KeyReads[A] =
readsKeyAndExtracts[A](e)(e.withNameUppercaseOnlyOption)
readsKeyAndExtracts[A](e, detailedError)(e.withNameUppercaseOnlyOption)

/** Returns a Json writes for a given enum [[Enum]]
*/
Expand Down Expand Up @@ -102,9 +112,10 @@ object EnumFormats {
*/
def formats[A <: EnumEntry](
@deprecatedName(Symbol("enum")) e: Enum[A],
insensitive: Boolean = false
insensitive: Boolean = false,
detailedError: Boolean = false
): Format[A] = {
Format(reads(e, insensitive), writes(e))
Format(reads(e, insensitive, detailedError), writes(e))
}

/** Returns a Json format for a given enum [[Enum]] for handling lower case transformations
Expand All @@ -113,9 +124,10 @@ object EnumFormats {
* The enum
*/
def formatsLowerCaseOnly[A <: EnumEntry](
@deprecatedName(Symbol("enum")) e: Enum[A]
@deprecatedName(Symbol("enum")) e: Enum[A],
detailedError: Boolean = false
): Format[A] = {
Format(readsLowercaseOnly(e), writesLowercaseOnly(e))
Format(readsLowercaseOnly(e, detailedError), writesLowercaseOnly(e))
}

/** Returns a Json format for a given enum [[Enum]] for handling upper case transformations
Expand All @@ -124,31 +136,48 @@ object EnumFormats {
* The enum
*/
def formatsUppercaseOnly[A <: EnumEntry](
@deprecatedName(Symbol("enum")) e: Enum[A]
@deprecatedName(Symbol("enum")) e: Enum[A],
detailedError: Boolean = false
): Format[A] = {
Format(readsUppercaseOnly(e), writesUppercaseOnly(e))
Format(readsUppercaseOnly(e, detailedError), writesUppercaseOnly(e))
}

// ---

private def readsAndExtracts[A <: EnumEntry](
@deprecatedName(Symbol("enum")) e: Enum[A]
@deprecatedName(Symbol("enum")) e: Enum[A],
detailedError: Boolean
)(extract: String => Option[A]): Reads[A] = Reads[A] {
case JsString(s) =>
extract(s) match {
case Some(obj) => JsSuccess(obj)
case None => JsError("error.expected.validenumvalue")
case None if detailedError =>
JsError(
JsonValidationError(
"error.expected.validenumvalue",
s"valid enum values are: (${e.values.map(_.entryName).mkString(", ")}), but provided: $s"
)
)
case None => JsError("error.expected.validenumvalue")
}

case _ => JsError("error.expected.enumstring")
}

private def readsKeyAndExtracts[A <: EnumEntry](
@deprecatedName(Symbol("enum")) e: Enum[A]
@deprecatedName(Symbol("enum")) e: Enum[A],
detailedError: Boolean
)(extract: String => Option[A]): KeyReads[A] = new KeyReads[A] {
def readKey(s: String): JsResult[A] = extract(s) match {
case Some(obj) => JsSuccess(obj)
case None => JsError("error.expected.validenumvalue")
case None if detailedError =>
JsError(
JsonValidationError(
"error.expected.validenumvalue",
s"valid enum values are: (${e.values.map(_.entryName).mkString(", ")}), but provided: $s"
)
)
case None => JsError("error.expected.validenumvalue")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package enumeratum

import play.api.libs.json._

trait PlayDetailedErrorJsonEnum[A <: EnumEntry] { self: Enum[A] =>
implicit val keyWrites: KeyWrites[A] = EnumFormats.keyWrites(this)

implicit def contraKeyWrites[K <: A]: KeyWrites[K] = {
val w = this.keyWrites

new KeyWrites[K] {
def writeKey(k: K) = w.writeKey(k)
}
}

implicit val keyReads: KeyReads[A] = EnumFormats.keyReads(this, detailedError = true)

implicit val jsonFormat: Format[A] = EnumFormats.formats(this, detailedError = true)
implicit def contraJsonWrites[B <: A]: Writes[B] = jsonFormat.contramap[B](b => b: A)
}
8 changes: 8 additions & 0 deletions enumeratum-play-json/src/test/scala/enumeratum/Dummy.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,11 @@ object UppercaseDummy extends Enum[UppercaseDummy] with PlayUppercaseJsonEnum[Up
case object Cherry extends UppercaseDummy
val values = findValues
}

sealed trait Operation extends EnumEntry
object Operation extends Enum[Operation] with PlayDetailedErrorJsonEnum[Operation] {
val values = findValues
case object Eq extends Operation
case object Add extends Operation
case object Not extends Operation
}
Loading