Skip to content

Commit

Permalink
errors as JSON (#41)
Browse files Browse the repository at this point in the history
* errors as JSON

* errors as JSON, Akka
  • Loading branch information
sideeffffect authored Jul 19, 2019
1 parent 6483d16 commit e940bd1
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 31 deletions.
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
package com.avast.grpc.jsonbridge.akkahttp

import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import akka.http.scaladsl.model.StatusCodes.ClientError
import cats.effect.implicits._
import akka.http.scaladsl.model._
import akka.http.scaladsl.model.headers.`Content-Type`
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.{PathMatcher, Route}
import cats.data.NonEmptyList
import cats.effect.Effect
import com.avast.grpc.jsonbridge.GrpcJsonBridge
import cats.effect.implicits._
import com.avast.grpc.jsonbridge.GrpcJsonBridge.GrpcMethodName
import com.avast.grpc.jsonbridge.{GrpcJsonBridge, GrpcStatusJson}
import io.grpc.Status.Code
import spray.json._

import scala.concurrent.ExecutionContext
import scala.language.higherKinds
import scala.util.control.NonFatal
import scala.util.{Failure, Success}

object AkkaHttp {
object AkkaHttp extends SprayJsonSupport with DefaultJsonProtocol {

private implicit val grpcStatusJsonFormat: RootJsonFormat[GrpcStatusJson] = jsonFormat3(GrpcStatusJson.apply)

private[akkahttp] final val JsonContentType: `Content-Type` = `Content-Type` {
ContentType.WithMissingCharset(MediaType.applicationWithOpenCharset("json"))
Expand Down Expand Up @@ -52,7 +56,9 @@ object AkkaHttp {
respondWithHeader(JsonContentType) {
complete(r)
}
case Success(Left(status)) => complete(mapStatus(status))
case Success(Left(status)) =>
val (s, body) = mapStatus(status)
complete(s, body)
case Failure(NonFatal(_)) => complete(StatusCodes.InternalServerError)
}
}
Expand Down Expand Up @@ -81,24 +87,30 @@ object AkkaHttp {
private def mapHeaders(headers: Seq[HttpHeader]): Map[String, String] = headers.toList.map(h => (h.name(), h.value())).toMap

// https://github.com/grpc/grpc/blob/master/doc/statuscodes.md
private def mapStatus(s: io.grpc.Status): StatusCode = s.getCode match {
case Code.OK => StatusCodes.OK
case Code.CANCELLED => ClientError(499)("Client Closed Request", "The operation was cancelled, typically by the caller.")
case Code.UNKNOWN => StatusCodes.InternalServerError
case Code.INVALID_ARGUMENT => StatusCodes.BadRequest
case Code.DEADLINE_EXCEEDED => StatusCodes.GatewayTimeout
case Code.NOT_FOUND => StatusCodes.NotFound
case Code.ALREADY_EXISTS => StatusCodes.Conflict
case Code.PERMISSION_DENIED => StatusCodes.Forbidden
case Code.RESOURCE_EXHAUSTED => StatusCodes.TooManyRequests
case Code.FAILED_PRECONDITION => StatusCodes.BadRequest
case Code.ABORTED => StatusCodes.Conflict
case Code.OUT_OF_RANGE => StatusCodes.BadRequest
case Code.UNIMPLEMENTED => StatusCodes.NotImplemented
case Code.INTERNAL => StatusCodes.InternalServerError
case Code.UNAVAILABLE => StatusCodes.ServiceUnavailable
case Code.DATA_LOSS => StatusCodes.InternalServerError
case Code.UNAUTHENTICATED => StatusCodes.Unauthorized
private def mapStatus(s: io.grpc.Status): (StatusCode, GrpcStatusJson) = {

val description = GrpcStatusJson.fromGrpcStatus(s)

s.getCode match {
case Code.OK => (StatusCodes.OK, description)
case Code.CANCELLED =>
(ClientError(499)("Client Closed Request", "The operation was cancelled, typically by the caller."), description)
case Code.UNKNOWN => (StatusCodes.InternalServerError, description)
case Code.INVALID_ARGUMENT => (StatusCodes.BadRequest, description)
case Code.DEADLINE_EXCEEDED => (StatusCodes.GatewayTimeout, description)
case Code.NOT_FOUND => (StatusCodes.NotFound, description)
case Code.ALREADY_EXISTS => (StatusCodes.Conflict, description)
case Code.PERMISSION_DENIED => (StatusCodes.Forbidden, description)
case Code.RESOURCE_EXHAUSTED => (StatusCodes.TooManyRequests, description)
case Code.FAILED_PRECONDITION => (StatusCodes.BadRequest, description)
case Code.ABORTED => (StatusCodes.Conflict, description)
case Code.OUT_OF_RANGE => (StatusCodes.BadRequest, description)
case Code.UNIMPLEMENTED => (StatusCodes.NotImplemented, description)
case Code.INTERNAL => (StatusCodes.InternalServerError, description)
case Code.UNAVAILABLE => (StatusCodes.ServiceUnavailable, description)
case Code.DATA_LOSS => (StatusCodes.InternalServerError, description)
case Code.UNAUTHENTICATED => (StatusCodes.Unauthorized, description)
}
}
}

Expand Down
11 changes: 8 additions & 3 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ crossScalaVersions := Seq("2.12.8")
lazy val Versions = new {
val gpb3Version = "3.8.0"
val grpcVersion = "1.22.1"

val circeVersion = "0.11.1"
val http4sVersion = "0.20.6"
val akkaHttp = "10.1.5" // DO NOT upgrade to 10.1.[67] - will cause https://github.com/scala/community-builds/issues/825
}

Expand Down Expand Up @@ -119,8 +120,11 @@ lazy val http4s = (project in file("http4s")).settings(
grpcTestGenSettings,
name := "grpc-json-bridge-http4s",
libraryDependencies ++= Seq(
"org.http4s" %% "http4s-dsl" % "0.20.0",
"org.http4s" %% "http4s-blaze-server" % "0.20.0"
"org.http4s" %% "http4s-dsl" % Versions.http4sVersion,
"org.http4s" %% "http4s-blaze-server" % Versions.http4sVersion,
"org.http4s" %% "http4s-circe" % Versions.http4sVersion,
"io.circe" %% "circe-core" % Versions.circeVersion,
"io.circe" %% "circe-generic" % Versions.circeVersion
),
scalacOptions += "-Ypartial-unification"
).dependsOn(core)
Expand All @@ -132,6 +136,7 @@ lazy val akkaHttp = (project in file("akka-http")).settings(
name := "grpc-json-bridge-akkahttp",
libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-http" % Versions.akkaHttp,
"com.typesafe.akka" %% "akka-http-spray-json" % Versions.akkaHttp,
"com.typesafe.akka" %% "akka-stream" % "2.5.21",
"com.typesafe.akka" %% "akka-http-testkit" % Versions.akkaHttp % "test"
),
Expand Down
13 changes: 13 additions & 0 deletions core/src/main/scala/com/avast/grpc/jsonbridge/GrpcStatusJson.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.avast.grpc.jsonbridge

import io.grpc.Status

final case class GrpcStatusJson(description: Option[String], exception: Option[String], message: Option[String])

object GrpcStatusJson {
def fromGrpcStatus(s: Status): GrpcStatusJson = GrpcStatusJson(
Option(s.getDescription),
Option(s.getCause).flatMap(e => Option(e.getClass.getCanonicalName)),
Option(s.getCause).flatMap(e => Option(e.getMessage))
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package com.avast.grpc.jsonbrige.http4s
import cats.data.NonEmptyList
import cats.effect._
import cats.syntax.all._
import com.avast.grpc.jsonbridge.GrpcJsonBridge
import com.avast.grpc.jsonbridge.{GrpcJsonBridge, GrpcStatusJson}
import com.avast.grpc.jsonbridge.GrpcJsonBridge.GrpcMethodName
import com.typesafe.scalalogging.StrictLogging
import io.grpc.Status.Code
Expand Down Expand Up @@ -85,12 +85,10 @@ object Http4s extends StrictLogging {

private def mapStatus[F[_]: Sync](s: GrpcStatus, configuration: Configuration)(implicit h: Http4sDsl[F]): F[Response[F]] = {
import h._
import io.circe.generic.auto._
import org.http4s.circe.CirceEntityEncoder._

val description = List(
Option(s.getDescription),
Option(s.getCause).flatMap(e => Option(e.getClass.getCanonicalName)),
Option(s.getCause).flatMap(e => Option(e.getMessage))
).flatten.mkString(", ")
val description = GrpcStatusJson.fromGrpcStatus(s)

// https://github.com/grpc/grpc/blob/master/doc/statuscodes.md
s.getCode match {
Expand Down

0 comments on commit e940bd1

Please sign in to comment.