Skip to content

Commit

Permalink
Improve client-side error response messages
Browse files Browse the repository at this point in the history
- add custom exception handler + exception case classes
- propagate error messages back to the client with 400s if they're known
  errors & 500 if they're unknown
  • Loading branch information
Kelly Innes committed Sep 18, 2017
1 parent 570810c commit cb5cc54
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 25 deletions.
42 changes: 42 additions & 0 deletions api/src/main/scala/ErrorHandler.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.wikiwatershed.mmw.geoprocessing

import akka.http.scaladsl.model._
import akka.http.scaladsl.server._
import StatusCodes.{BadRequest,InternalServerError}
import Directives._

sealed trait GeoprocessingException extends Exception
case class MissingTargetRasterException() extends GeoprocessingException()
case class MissingVectorCRSException() extends GeoprocessingException()
case class MissingVectorException() extends GeoprocessingException()
case class InvalidOperationException(val message: String) extends GeoprocessingException()
case class UnknownCRSException(val crs: String) extends GeoprocessingException()

trait ErrorHandler {
val geoprocessingExceptionHandler = ExceptionHandler {
case InvalidOperationException(ex) => {
println(s"Invalid operation type: $ex")
complete(HttpResponse(BadRequest, entity = ex))
}
case MissingTargetRasterException() => {
println("Input error: Missing required targetRaster")
complete(HttpResponse(BadRequest, entity = "Missing required targetRaster"))
}
case ex: MissingVectorCRSException => {
println("Input error: Missing required vectorCRS")
complete(HttpResponse(BadRequest, entity = "Missing required vectorCRS"))
}
case ex: MissingVectorException => {
println(s"Input error: Missing required vector")
complete(HttpResponse(BadRequest, entity = "Missing required vector"))
}
case UnknownCRSException(crs) => {
println(s"Unknown CRS error: $crs")
complete(HttpResponse(BadRequest, entity = "Unknown CRS"))
}
case e: Exception => {
println(s"Exception: $e")
complete(HttpResponse(InternalServerError, entity = s"$e"))
}
}
}
12 changes: 6 additions & 6 deletions api/src/main/scala/Geoprocessing.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@ trait Geoprocessing extends Utils {
* @param input The InputData
* @return A histogram of results
*/
@throws(classOf[MissingTargetRasterException])
def getRasterGroupedAverage(input: InputData): Future[ResultDouble] = {
val aoi = createAOIFromInput(input)
val futureLayers = cropRastersToAOI(input.rasters, input.zoom, aoi)
val targetLayer = input.targetRaster match {
case Some(targetRaster) =>
cropSingleRasterToAOI(targetRaster, input.zoom, aoi)
case None =>
throw new Exception("Request data missing required 'targetRaster'.")
case None => throw new MissingTargetRasterException
}
val opts = getRasterizerOptions(input.pixelIsArea)

Expand All @@ -62,6 +62,8 @@ trait Geoprocessing extends Utils {
* @param input The InputData
* @return A histogram of results
*/
@throws(classOf[MissingVectorException])
@throws(classOf[MissingVectorCRSException])
def getRasterLinesJoin(input: InputData): Future[ResultInt] = {
val aoi = createAOIFromInput(input)
val futureLayers = cropRastersToAOI(input.rasters, input.zoom, aoi)
Expand All @@ -70,11 +72,9 @@ trait Geoprocessing extends Utils {
input.vectorCRS match {
case Some(crs) =>
createMultiLineFromInput(vector, crs, input.rasterCRS)
case None =>
throw new Exception("Request data missing required 'vectorCRS'.")
case None => throw new MissingVectorCRSException
}
case None =>
throw new Exception("Request data missing required 'vector'.")
case None => throw new MissingVectorException
}

futureLayers.map { rasterLayers =>
Expand Down
3 changes: 2 additions & 1 deletion api/src/main/scala/Utils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -153,11 +153,12 @@ trait Utils {
* @param crs The key (e.g. "input.rasterCRS")
* @return A CRS
*/
@throws(classOf[UnknownCRSException])
def getCRS(crs: String): CRS = crs match {
case "LatLng" => LatLng
case "WebMercator" => WebMercator
case "ConusAlbers" => ConusAlbers
case s: String => throw new Exception(s"Unknown CRS: $s")
case s: String => throw new UnknownCRSException(s)
}

/**
Expand Down
41 changes: 23 additions & 18 deletions api/src/main/scala/WebServer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,27 +32,32 @@ object PostRequestProtocol extends DefaultJsonProtocol {
implicit val resultDoubleFormat = jsonFormat1(ResultDouble)
}

object WebServer extends HttpApp with App with LazyLogging with Geoprocessing {
object WebServer extends HttpApp with App with LazyLogging with Geoprocessing with ErrorHandler {
import PostRequestProtocol._

@throws(classOf[InvalidOperationException])
def routes: Route =
get {
path("ping") {
complete("pong")
}
} ~
post {
path("run") {
entity(as[PostRequest]) { data =>
data.input.operationType match {
case "RasterGroupedCount" =>
complete(getRasterGroupedCount(data.input))
case "RasterGroupedAverage" =>
complete(getRasterGroupedAverage(data.input))
case "RasterLinesJoin" =>
complete(getRasterLinesJoin(data.input))
case _ =>
throw new Exception(s"Unknown operationType: ${data.input.operationType}")
handleExceptions(geoprocessingExceptionHandler) {
get {
path("ping") {
complete("pong")
}
} ~
post {
path("run") {
entity(as[PostRequest]) { data =>
data.input.operationType match {
case "RasterGroupedCount" =>
complete(getRasterGroupedCount(data.input))
case "RasterGroupedAverage" =>
complete(getRasterGroupedAverage(data.input))
case "RasterLinesJoin" =>
complete(getRasterLinesJoin(data.input))
case _ => {
val message = s"Unknown operationType: ${data.input.operationType}"
throw new InvalidOperationException(message)
}
}
}
}
}
Expand Down

0 comments on commit cb5cc54

Please sign in to comment.