Skip to content

Commit

Permalink
#6 Add error handler
Browse files Browse the repository at this point in the history
  • Loading branch information
To-om committed Dec 16, 2016
1 parent e4e8e93 commit e398db7
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 5 deletions.
68 changes: 68 additions & 0 deletions app/org/elastic4play/ErrorHandler.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package org.elastic4play

import scala.annotation.implicitNotFound
import scala.concurrent.Future

import play.api.Logger
import play.api.http.HttpErrorHandler
import play.api.libs.json.Json
import play.api.mvc.{ RequestHeader, Result, Results }

import org.elasticsearch.client.transport.NoNodeAvailableException
import org.elasticsearch.index.IndexNotFoundException
import org.elasticsearch.transport.RemoteTransportException

import org.elastic4play.JsonFormat.attributeCheckingExceptionWrites
import play.api.libs.json.JsValue
import play.api.http.Status
import play.api.libs.json.JsNull
import play.api.mvc.ResponseHeader
import play.api.http.Writeable

/**
* This class handles errors. It traverses all causes of exception to find known error and shows the appropriate message
*/
class ErrorHandler extends HttpErrorHandler {

def onClientError(request: RequestHeader, statusCode: Int, message: String) = Future.successful {
Results.Status(statusCode)(s"A client error occurred on ${request.method} ${request.uri} : $message")
}

private def getRootCauseError(ex: Throwable): Option[(Int, JsValue)] = {
ex match {
case AuthenticationError(message) Some(Status.UNAUTHORIZED Json.obj("type" "AuthenticationError", "error" message))
case AuthorizationError(message) Some(Status.FORBIDDEN Json.obj("type" "AuthorizationError", "error" message))
case UpdateError(status, message, attributes) Some(Status.INTERNAL_SERVER_ERROR Json.obj("type" "UpdateError", "error" message, "obj" attributes))
case InternalError(message) Some(Status.INTERNAL_SERVER_ERROR Json.obj("type" "InternalError", "error" message))
case nfe: NumberFormatException Some(Status.BAD_REQUEST Json.obj("type" "NumberFormatException", "error" ("Invalid format " + nfe.getMessage)))
case NotFoundError(message) Some(Status.NOT_FOUND Json.obj("type" "NotFoundError", "message" message))
case BadRequestError(message) Some(Status.BAD_REQUEST Json.obj("type" "BadRequest", "error" message))
case SearchError(message, cause) Some(Status.BAD_REQUEST Json.obj("type" "SearchError", "error" s"$message (${cause.getMessage})"))
case ace: AttributeCheckingError Some(Status.BAD_REQUEST Json.toJson(ace))
case iae: IllegalArgumentException Some(Status.BAD_REQUEST Json.obj("type" "IllegalArgument", "error" iae.getMessage))
case nnae: NoNodeAvailableException Some(Status.INTERNAL_SERVER_ERROR Json.obj("type" "NoNodeAvailable", "error" "ElasticSearch cluster is unreachable"))
case CreateError(status, message, attributes) Some(Status.INTERNAL_SERVER_ERROR Json.obj("type" "CreateError", "error" message, "obj" attributes))
case ConflictError(message, attributes) Some(Status.BAD_REQUEST Json.obj("type" "ConflictError", "error" message, "obj" attributes))
case GetError(message) Some(Status.INTERNAL_SERVER_ERROR Json.obj("type" "GetError", "error" message))
case MultiError(message, exceptions)
val suberrors = exceptions.map(e getRootCauseError(e)).collect {
case Some((s, j)) j
}
Some(Status.MULTI_STATUS Json.obj("type" "MultiError", "error" message, "suberrors" suberrors))
case rte: RemoteTransportException
rte.getCause match {
case infe: IndexNotFoundException Some(520 JsNull)
case t: Throwable Some(Status.INTERNAL_SERVER_ERROR Json.obj("type" t.getClass.getName, "error" s"Database error : ${t.getMessage}"))
}
case t: Throwable Option(t.getCause).flatMap(getRootCauseError)
}
}

def toResult[C](status: Int, c: C)(implicit writeable: Writeable[C]) = Result(header = ResponseHeader(status), body = writeable.toEntity(c))

def onServerError(request: RequestHeader, exception: Throwable) = {
val (status, body) = getRootCauseError(exception).getOrElse(Status.INTERNAL_SERVER_ERROR Json.obj("type" exception.getClass.getName, "error" exception.getMessage))
Logger.info(s"${request.method} ${request.uri} returned ${status}", exception)
Future.successful(toResult(status, body))
}
}
1 change: 1 addition & 0 deletions app/org/elastic4play/Errors.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import org.elastic4play.controllers.InputValue

case class BadRequestError(message: String) extends Exception(message)
case class CreateError(status: Option[String], message: String, attributes: JsObject) extends Exception(message)
case class ConflictError(message: String, attributes: JsObject) extends Exception(message)
case class NotFoundError(message: String) extends Exception(message)
case class GetError(message: String) extends Exception(message)
case class UpdateError(status: Option[String], message: String, attributes: JsObject) extends Exception(message)
Expand Down
2 changes: 1 addition & 1 deletion app/org/elastic4play/JsonFormat.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ object JsonFormat {

implicit val attributeCheckingExceptionWrites = Writes[AttributeCheckingError]((ace: AttributeCheckingError) JsObject(Seq(
"tableName" JsString(ace.tableName),
"message" JsString(ace.toString),
"type" JsString("AttributeCheckingError"),
"errors" JsArray(ace.errors.map {
case e: InvalidFormatAttributeError invalidFormatAttributeErrorWrites.writes(e)
case e: UnknownAttributeError unknownAttributeErrorWrites.writes(e)
Expand Down
15 changes: 11 additions & 4 deletions app/org/elastic4play/database/DBCreate.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import com.sksamuel.elastic4s.streams.RequestBuilder

import org.elastic4play.{ CreateError, Timed }
import org.elastic4play.models.BaseEntity
import org.elasticsearch.index.engine.DocumentAlreadyExistsException
import org.elastic4play.ConflictError

/**
* Service lass responsible for entity creation
Expand Down Expand Up @@ -71,6 +73,7 @@ class DBCreate @Inject() (
* @return for each requested entity creation, a try of its attributes
* attributes contain _id and _routing (and _parent if entity is a child)
*/
@deprecated("Bulk creation is deprecated. Use single creation in a loop", "1.1.2")
private[database] def create(params: Seq[CreateParams]): Future[Seq[Try[JsObject]]] = {
if (params.isEmpty)
return Future.successful(Nil)
Expand All @@ -87,6 +90,12 @@ class DBCreate @Inject() (
}
}

private[database] def convertError(params: CreateParams, error: Throwable): Throwable = error match {
case rte: RemoteTransportException convertError(params, rte.getCause)
case daee: DocumentAlreadyExistsException ConflictError(daee.getMessage, params.attributes)
case other CreateError(None, other.getMessage, params.attributes)
}

/**
* Create an entity using CreateParams
*
Expand All @@ -96,10 +105,8 @@ class DBCreate @Inject() (
*/
private[database] def create(params: CreateParams): Future[JsObject] = {
db.execute(params.indexDef refresh true).transform(
indexResponse params.result(indexResponse.getId), {
case t: RemoteTransportException CreateError(None, t.getCause.getMessage, params.attributes)
case t CreateError(None, t.getMessage, params.attributes)
})
indexResponse params.result(indexResponse.getId),
convertError(params, _))
}

/**
Expand Down
2 changes: 2 additions & 0 deletions conf/application.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# handler for errors (transform exception to related http status code
play.http.errorHandler = org.elastic4play.ErrorHandler

0 comments on commit e398db7

Please sign in to comment.