11package com .avast .grpc .jsonbridge .akkahttp
22
33import akka .http .scaladsl .marshallers .sprayjson .SprayJsonSupport
4- import akka .http .scaladsl .model . StatusCodes . ClientError
4+ import akka .http .scaladsl .marshalling .{ Marshaller , ToEntityMarshaller , ToResponseMarshallable }
55import akka .http .scaladsl .model ._
66import akka .http .scaladsl .model .headers .`Content-Type`
77import akka .http .scaladsl .server .Directives ._
88import akka .http .scaladsl .server .{PathMatcher , Route }
99import cats .data .NonEmptyList
10- import cats .effect .Effect
11- import cats .effect . implicits ._
10+ import cats .effect .Sync
11+ import cats .implicits ._
1212import com .avast .grpc .jsonbridge .GrpcJsonBridge .GrpcMethodName
1313import com .avast .grpc .jsonbridge .{BridgeError , BridgeErrorResponse , GrpcJsonBridge }
1414import com .typesafe .scalalogging .LazyLogging
@@ -22,11 +22,10 @@ object AkkaHttp extends SprayJsonSupport with DefaultJsonProtocol with LazyLoggi
2222
2323 private implicit val grpcStatusJsonFormat : RootJsonFormat [BridgeErrorResponse ] = jsonFormat3(BridgeErrorResponse .apply)
2424
25- private [akkahttp] final val JsonContentType : `Content-Type` = `Content-Type` {
26- ContentType .WithMissingCharset (MediaType .applicationWithOpenCharset(" json" ))
27- }
25+ private val jsonStringMarshaller : ToEntityMarshaller [String ] =
26+ Marshaller .stringMarshaller(MediaTypes .`application/json`)
2827
29- def apply [F [_]: Effect ](configuration : Configuration )(bridge : GrpcJsonBridge [F ]): Route = {
28+ def apply [F [_]: Sync : LiftToFuture ](configuration : Configuration )(bridge : GrpcJsonBridge [F ]): Route = {
3029
3130 val pathPattern = configuration.pathPrefix
3231 .map { case NonEmptyList (head, tail) =>
@@ -44,71 +43,61 @@ object AkkaHttp extends SprayJsonSupport with DefaultJsonProtocol with LazyLoggi
4443 post {
4544 path(pathPattern) { (serviceName, methodName) =>
4645 extractRequest { request =>
47- val headers = request.headers
4846 request.header[`Content-Type`] match {
49- case Some (`JsonContentType`) =>
47+ case Some (ct) if ct.contentType.mediaType == MediaTypes .`application/json` =>
5048 entity(as[String ]) { body =>
5149 val methodNameString = GrpcMethodName (serviceName, methodName)
52- val headersString = mapHeaders(headers)
53- val methodCall = bridge.invoke(methodNameString, body, headersString).toIO.unsafeToFuture()
50+ val headersString = mapHeaders(request.headers)
51+ val methodCall = LiftToFuture [F ].liftF {
52+ bridge
53+ .invoke(methodNameString, body, headersString)
54+ .flatMap(Sync [F ].fromEither)
55+ }
56+
5457 onComplete(methodCall) {
55- case Success (result) =>
56- result match {
57- case Right (resp) =>
58- logger.trace(" Request successful: {}" , resp.substring(0 , 100 ))
59- respondWithHeader(JsonContentType ) {
60- complete(resp)
61- }
62- case Left (er) =>
63- er match {
64- case BridgeError .GrpcMethodNotFound =>
65- val message = s " Method ' ${methodNameString.fullName}' not found "
66- logger.debug(message)
67- respondWithHeader(JsonContentType ) {
68- complete(StatusCodes .NotFound , BridgeErrorResponse .fromMessage(message))
69- }
70- case er : BridgeError .Json =>
71- val message = " Wrong JSON"
72- logger.debug(message, er.t)
73- respondWithHeader(JsonContentType ) {
74- complete(StatusCodes .BadRequest , BridgeErrorResponse .fromException(message, er.t))
75- }
76- case er : BridgeError .Grpc =>
77- val message = " gRPC error" + Option (er.s.getDescription).map(" : " + _).getOrElse(" " )
78- logger.trace(message, er.s.getCause)
79- val (s, body) = mapStatus(er.s)
80- respondWithHeader(JsonContentType ) {
81- complete(s, body)
82- }
83- case er : BridgeError .Unknown =>
84- val message = " Unknown error"
85- logger.warn(message, er.t)
86- respondWithHeader(JsonContentType ) {
87- complete(StatusCodes .InternalServerError , BridgeErrorResponse .fromException(message, er.t))
88- }
89- }
90- }
91- case Failure (NonFatal (er)) =>
58+ case Success (resp) =>
59+ logger.trace(" Request successful: {}" , resp.substring(0 , 100 ))
60+
61+ complete(ToResponseMarshallable (resp)(jsonStringMarshaller))
62+ case Failure (BridgeError .GrpcMethodNotFound ) =>
63+ val message = s " Method ' ${methodNameString.fullName}' not found "
64+ logger.debug(message)
65+
66+ complete(StatusCodes .NotFound , BridgeErrorResponse .fromMessage(message))
67+ case Failure (er : BridgeError .Json ) =>
68+ val message = " Wrong JSON"
69+ logger.debug(message, er.t)
70+
71+ complete(StatusCodes .BadRequest , BridgeErrorResponse .fromException(message, er.t))
72+ case Failure (er : BridgeError .Grpc ) =>
73+ val message = " gRPC error" + Option (er.s.getDescription).map(" : " + _).getOrElse(" " )
74+ logger.trace(message, er.s.getCause)
75+ val (s, body) = mapStatus(er.s)
76+
77+ complete(s, body)
78+ case Failure (er : BridgeError .Unknown ) =>
79+ val message = " Unknown error"
80+ logger.warn(message, er.t)
81+
82+ complete(StatusCodes .InternalServerError , BridgeErrorResponse .fromException(message, er.t))
83+ case Failure (NonFatal (ex)) =>
9284 val message = " Unknown exception"
93- logger.debug(message, er)
94- respondWithHeader(JsonContentType ) {
95- complete(StatusCodes .InternalServerError , BridgeErrorResponse .fromException(message, er))
96- }
85+ logger.debug(message, ex)
86+
87+ complete(StatusCodes .InternalServerError , BridgeErrorResponse .fromException(message, ex))
9788 case Failure (e) => throw e // scalafix:ok
9889 }
9990 }
10091 case Some (c) =>
101- val message = s " Content-Type must be ' $JsonContentType ', it is ' $c' "
92+ val message = s " Content-Type must be 'application/json ', it is ' $c' "
10293 logger.debug(message)
103- respondWithHeader(JsonContentType ) {
104- complete(StatusCodes .BadRequest , BridgeErrorResponse .fromMessage(message))
105- }
94+
95+ complete(StatusCodes .BadRequest , BridgeErrorResponse .fromMessage(message))
10696 case None =>
107- val message = s " Content-Type must be ' $JsonContentType ' "
97+ val message = " Content-Type must be 'application/json '"
10898 logger.debug(message)
109- respondWithHeader(JsonContentType ) {
110- complete(StatusCodes .BadRequest , BridgeErrorResponse .fromMessage(message))
111- }
99+
100+ complete(StatusCodes .BadRequest , BridgeErrorResponse .fromMessage(message))
112101 }
113102 }
114103 }
@@ -118,9 +107,8 @@ object AkkaHttp extends SprayJsonSupport with DefaultJsonProtocol with LazyLoggi
118107 case None =>
119108 val message = s " Service ' $serviceName' not found "
120109 logger.debug(message)
121- respondWithHeader(JsonContentType ) {
122- complete(StatusCodes .NotFound , BridgeErrorResponse .fromMessage(message))
123- }
110+
111+ complete(StatusCodes .NotFound , BridgeErrorResponse .fromMessage(message))
124112 case Some (methods) =>
125113 complete(methods.map(_.fullName).toList.mkString(" \n " ))
126114 }
@@ -142,7 +130,7 @@ object AkkaHttp extends SprayJsonSupport with DefaultJsonProtocol with LazyLoggi
142130 s.getCode match {
143131 case Code .OK => (StatusCodes .OK , description)
144132 case Code .CANCELLED =>
145- (ClientError (499 )( " Client Closed Request" , " The operation was cancelled, typically by the caller." ), description)
133+ (StatusCodes .custom (499 , " Client Closed Request" , " The operation was cancelled, typically by the caller." ), description)
146134 case Code .UNKNOWN => (StatusCodes .InternalServerError , description)
147135 case Code .INVALID_ARGUMENT => (StatusCodes .BadRequest , description)
148136 case Code .DEADLINE_EXCEEDED => (StatusCodes .GatewayTimeout , description)
@@ -162,7 +150,7 @@ object AkkaHttp extends SprayJsonSupport with DefaultJsonProtocol with LazyLoggi
162150 }
163151}
164152
165- final case class Configuration private (pathPrefix : Option [NonEmptyList [String ]])
153+ final case class Configuration (pathPrefix : Option [NonEmptyList [String ]])
166154
167155object Configuration {
168156 val Default : Configuration = Configuration (
0 commit comments