Skip to content
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
55 changes: 25 additions & 30 deletions elastic4play/app/org/elastic4play/JsonFormat.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import java.util.Date

import scala.util.{Failure, Success, Try}

import scala.reflect.ClassTag

import play.api.libs.json._

import org.elastic4play.controllers.JsonFormat.inputValueFormat
Expand All @@ -14,38 +16,31 @@ object JsonFormat {
private val dateWrites: Writes[Date] = Writes[Date](d => JsNumber(d.getTime))
implicit val dateFormat: Format[Date] = Format(dateReads, dateWrites)

private val invalidFormatAttributeErrorWrites = Writes[InvalidFormatAttributeError] { ifae =>
Json.writes[InvalidFormatAttributeError].writes(ifae) +
("type" -> JsString("InvalidFormatAttributeError")) +
("message" -> JsString(ifae.toString))
}
private val unknownAttributeErrorWrites = Writes[UnknownAttributeError] { uae =>
Json.writes[UnknownAttributeError].writes(uae) +
("type" -> JsString("UnknownAttributeError")) +
("message" -> JsString(uae.toString))
}
private val updateReadOnlyAttributeErrorWrites = Writes[UpdateReadOnlyAttributeError] { uroae =>
Json.writes[UpdateReadOnlyAttributeError].writes(uroae) +
("type" -> JsString("UpdateReadOnlyAttributeError")) +
("message" -> JsString(uroae.toString))
}
private val missingAttributeErrorWrites = Writes[MissingAttributeError] { mae =>
Json.writes[MissingAttributeError].writes(mae) +
("type" -> JsString("MissingAttributeError")) +
("message" -> JsString(mae.toString))
private def attributeErrorWrites[E <: AttributeError](underlying: OWrites[E]): OWrites[E] = {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current invalidFormatAttributeErrorWrites/unknownAttributeErrorWrites/updateReadOnlyAttributeErrorWrites/missingAttributeErrorWrites are private, only for attributeCheckingExceptionWrites to use them, so change there (such as removing the type field) do not introduce regression, as long as there is no regression in attributeCheckingExceptionWrites.

OWrites.transform(underlying) { (origErr, obj) =>
obj + ("message" -> JsString(origErr.toString))
}
}

implicit val attributeCheckingExceptionWrites: OWrites[AttributeCheckingError] = OWrites[AttributeCheckingError] { ace =>
Json.obj(
"tableName" -> ace.tableName,
"type" -> "AttributeCheckingError",
"errors" -> JsArray(ace.errors.map {
case e: InvalidFormatAttributeError => invalidFormatAttributeErrorWrites.writes(e)
case e: UnknownAttributeError => unknownAttributeErrorWrites.writes(e)
case e: UpdateReadOnlyAttributeError => updateReadOnlyAttributeErrorWrites.writes(e)
case e: MissingAttributeError => missingAttributeErrorWrites.writes(e)
})
)
private[elastic4play] implicit val invalidFormatAttributeErrorWrites: OWrites[InvalidFormatAttributeError] = attributeErrorWrites[InvalidFormatAttributeError](Json.writes[InvalidFormatAttributeError])

private[elastic4play] implicit val unknownAttributeErrorWrites: OWrites[UnknownAttributeError] = attributeErrorWrites[UnknownAttributeError](Json.writes[UnknownAttributeError])

private[elastic4play] implicit val updateReadOnlyAttributeErrorWrites: OWrites[UpdateReadOnlyAttributeError] = attributeErrorWrites[UpdateReadOnlyAttributeError](Json.writes[UpdateReadOnlyAttributeError])

private[elastic4play] implicit val missingAttributeErrorWrites: OWrites[MissingAttributeError] = attributeErrorWrites(Json.writes[MissingAttributeError])

implicit val attributeCheckingExceptionWrites: OWrites[AttributeCheckingError] = {
val pkgName = classOf[AttributeError].getPackage.getName + '.'

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As runtime invariant, it allows to move the *AttributeError to a different without change there.


implicit def errWrites: OWrites[AttributeError] = Json.configured(JsonConfiguration(

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Custom discriminator configuration (Play JSON default is "_json": "full.qualified.type.ClassName", the current Elastic4Play is "type": "ClassName") to avoid regression.

discriminator = "type",
typeNaming = JsonNaming(_.stripPrefix(pkgName))
)).writes

val discriminator = "type" -> JsString("AttributeCheckingError")

Json.writes[AttributeCheckingError].transform(_ + discriminator)
}

implicit def tryWrites[A](implicit aWrites: Writes[A]): Writes[Try[A]] = Writes[Try[A]] {
Expand Down
98 changes: 98 additions & 0 deletions elastic4play/test/org/elastic4play/JsonFormatSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package org.elastic4play

import play.api.libs.json._

import org.elastic4play.controllers.JsonInputValue

final class JsonFormatSpec extends org.specs2.mutable.Specification {
"Play JSON format for Elastic".title

"Format for attribute errors" should {
val invalidFormatAttrErr = InvalidFormatAttributeError(
name = "Test 1",
format = "Foo",
value = JsonInputValue(JsString("Input")))

val invalidFormatAttrErrJson = Json.obj(
"name" -> "Test 1",
"format" -> "Foo",
"value" -> Json.obj("type" -> "JsonInputValue", "value" -> "Input"),
"message" -> "Invalid format for Test 1: JsonInputValue(\"Input\"), expected Foo")

"support InvalidFormatAttributeError" in {
implicit def w: OWrites[InvalidFormatAttributeError] =
JsonFormat.invalidFormatAttributeErrorWrites

Json.toJsObject(invalidFormatAttrErr) must_=== invalidFormatAttrErrJson
}

val unknownAttrErr = UnknownAttributeError(
name = "Test 2",
value = JsString("Bar"))

val unknownAttrErrJson = Json.obj(
"name" -> "Test 2",
"value" -> "Bar",
"message" -> "Unknown attribute Test 2: \"Bar\"")

"support UnknownAttributeError" in {
implicit def w: OWrites[UnknownAttributeError] =
JsonFormat.unknownAttributeErrorWrites

Json.toJsObject(unknownAttrErr) must_=== unknownAttrErrJson
}

val updateRoAttrErr = UpdateReadOnlyAttributeError("Lorem")

val updateRoAttrErrJson = Json.obj(
"name" -> "Lorem",
"message" -> "Attribute Lorem is read-only")

"support UpdateReadOnlyAttributeError" in {
implicit def w: OWrites[UpdateReadOnlyAttributeError] =
JsonFormat.updateReadOnlyAttributeErrorWrites

Json.toJsObject(updateRoAttrErr) must_=== updateRoAttrErrJson
}

val missingAttrErr = MissingAttributeError("Ipsum")

val missingAttrErrJson = Json.obj(
"name" -> "Ipsum",
"message" -> "Attribute Ipsum is missing")

"support MissingAttributeError" in {
implicit def w: OWrites[MissingAttributeError] =
JsonFormat.missingAttributeErrorWrites

Json.toJsObject(missingAttrErr) must_=== missingAttrErrJson
}

"support AttributeCheckingError" in {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No regression test

import JsonFormat.attributeCheckingExceptionWrites

val errors = Seq(invalidFormatAttrErr, unknownAttrErr, updateRoAttrErr, missingAttrErr)

val errorsJson = Seq(
(invalidFormatAttrErrJson + ("type" -> JsString("InvalidFormatAttributeError"))),
(unknownAttrErrJson + ("type" -> JsString("UnknownAttributeError"))),
(updateRoAttrErrJson + ("type" -> JsString("UpdateReadOnlyAttributeError"))),
(missingAttrErrJson + ("type" -> JsString("MissingAttributeError"))))

Json.toJsObject(AttributeCheckingError(
tableName = "Table 1",
errors = errors)) must_=== Json.obj(
"tableName" -> "Table 1",
"type" -> "AttributeCheckingError",
"errors" -> errorsJson) and {
// Reverse errors to make sure the order is preserved
Json.toJsObject(AttributeCheckingError(
tableName = "Table 2",
errors = errors.reverse)) must_=== Json.obj(
"tableName" -> "Table 2",
"type" -> "AttributeCheckingError",
"errors" -> errorsJson.reverse)
}
}
}
}