pepegar / hammock
1
package hammock
2
package akka
3

4
import _root_.akka.http.scaladsl.HttpExt
5
import _root_.akka.http.scaladsl.client.RequestBuilding.RequestBuilder
6
import _root_.akka.http.scaladsl.model.{
7
  HttpMethods,
8
  ContentType => AkkaContentType,
9
  HttpRequest => AkkaRequest,
10
  HttpResponse => AkkaResponse,
11
  StatusCode => AkkaStatus,
12
  _
13
}
14
import _root_.akka.http.scaladsl.model.headers.RawHeader
15
import _root_.akka.stream.ActorMaterializer
16
import _root_.akka.util.ByteString
17
import cats._
18
import cats.data.Kleisli
19
import cats.effect.{Async, ContextShift, Sync}
20
import cats.implicits._
21
import scala.concurrent.{ExecutionContext, Future}
22

23
object AkkaInterpreter {
24

25
  def apply[F[_]](implicit F: InterpTrans[F]): InterpTrans[F] = F
26

27
  implicit def instance[F[_]: Async: ContextShift](
28
      implicit
29
      client: HttpExt,
30
      materializer: ActorMaterializer,
31
      executionContext: ExecutionContext) =
32 1
    new InterpTrans[F] {
33 1
      override def trans: HttpF ~> F = transK andThen λ[Kleisli[F, HttpExt, *] ~> F](_.run(client))
34
    }
35

36
  def transK[F[_]: Async: ContextShift](
37
      implicit materializer: ActorMaterializer,
38
      executionContext: ExecutionContext): HttpF ~> Kleisli[F, HttpExt, *] = {
39

40 1
    def doReq(req: HttpF[HttpResponse]): Kleisli[F, HttpExt, HttpResponse] = Kleisli { http =>
41
      for {
42 1
        akkaRequest    <- mapRequest(req)
43 1
        responseFuture <- Sync[F].delay(http.singleRequest(akkaRequest).flatMap(mapResponse))
44 1
        responseF      <- Async.fromFuture(Async[F].delay(responseFuture))
45
      } yield responseF
46
    }
47

48
    def mapResponse(akkaResp: AkkaResponse): Future[HttpResponse] = {
49 1
      val status  = mapStatus(akkaResp.status)
50 1
      val headers = akkaResp.headers.map(h => (h.name, h.value)).toMap
51
      akkaResp.entity.dataBytes
52
        .runFold(ByteString(""))(_ ++ _)
53
        .map(bs => Entity.StringEntity(bs.utf8String))
54 1
        .map(entity => HttpResponse(status, headers, entity))
55
    }
56

57
    def mapStatus(st: AkkaStatus): Status = st match {
58 0
      case StatusCodes.Continue                      => Status.Continue
59 0
      case StatusCodes.SwitchingProtocols            => Status.SwitchingProtocols
60 0
      case StatusCodes.Processing                    => Status.Processing
61 1
      case StatusCodes.OK                            => Status.OK
62 0
      case StatusCodes.Created                       => Status.Created
63 0
      case StatusCodes.Accepted                      => Status.Accepted
64 0
      case StatusCodes.NonAuthoritativeInformation   => Status.NonAuthoritativeInformation
65 0
      case StatusCodes.NoContent                     => Status.NoContent
66 0
      case StatusCodes.ResetContent                  => Status.ResetContent
67 0
      case StatusCodes.PartialContent                => Status.PartialContent
68 0
      case StatusCodes.MultiStatus                   => Status.MultiStatus
69 0
      case StatusCodes.AlreadyReported               => Status.AlreadyReported
70 0
      case StatusCodes.IMUsed                        => Status.IMUsed
71 0
      case StatusCodes.MultipleChoices               => Status.MultipleChoices
72 0
      case StatusCodes.MovedPermanently              => Status.MovedPermanently
73 0
      case StatusCodes.Found                         => Status.Found
74 0
      case StatusCodes.SeeOther                      => Status.SeeOther
75 0
      case StatusCodes.NotModified                   => Status.NotModified
76 0
      case StatusCodes.UseProxy                      => Status.UseProxy
77 0
      case StatusCodes.TemporaryRedirect             => Status.TemporaryRedirect
78 0
      case StatusCodes.PermanentRedirect             => Status.PermanentRedirect
79 0
      case StatusCodes.BadRequest                    => Status.BadRequest
80 0
      case StatusCodes.Unauthorized                  => Status.Unauthorized
81 0
      case StatusCodes.PaymentRequired               => Status.PaymentRequired
82 0
      case StatusCodes.Forbidden                     => Status.Forbidden
83 0
      case StatusCodes.NotFound                      => Status.NotFound
84 0
      case StatusCodes.MethodNotAllowed              => Status.MethodNotAllowed
85 0
      case StatusCodes.NotAcceptable                 => Status.NotAcceptable
86 0
      case StatusCodes.ProxyAuthenticationRequired   => Status.ProxyAuthenticationRequired
87 0
      case StatusCodes.RequestTimeout                => Status.RequestTimeout
88 0
      case StatusCodes.Conflict                      => Status.Conflict
89 0
      case StatusCodes.Gone                          => Status.Gone
90 0
      case StatusCodes.LengthRequired                => Status.LengthRequired
91 0
      case StatusCodes.PreconditionFailed            => Status.PreconditionFailed
92 0
      case StatusCodes.RequestEntityTooLarge         => Status.RequestEntityTooLarge
93 0
      case StatusCodes.RequestUriTooLong             => Status.RequestUriTooLong
94 0
      case StatusCodes.UnsupportedMediaType          => Status.UnsupportedMediaType
95 0
      case StatusCodes.RequestedRangeNotSatisfiable  => Status.RequestedRangeNotSatisfiable
96 0
      case StatusCodes.ExpectationFailed             => Status.ExpectationFailed
97 0
      case StatusCodes.EnhanceYourCalm               => Status.EnhanceYourCalm
98 0
      case StatusCodes.UnprocessableEntity           => Status.UnprocessableEntity
99 0
      case StatusCodes.Locked                        => Status.Locked
100 0
      case StatusCodes.FailedDependency              => Status.FailedDependency
101 0
      case StatusCodes.TooEarly                      => Status.TooEarly
102 0
      case StatusCodes.UpgradeRequired               => Status.UpgradeRequired
103 0
      case StatusCodes.PreconditionRequired          => Status.PreconditionRequired
104 0
      case StatusCodes.TooManyRequests               => Status.TooManyRequests
105 0
      case StatusCodes.RequestHeaderFieldsTooLarge   => Status.RequestHeaderFieldsTooLarge
106 0
      case StatusCodes.RetryWith                     => Status.RetryWith
107 0
      case StatusCodes.BlockedByParentalControls     => Status.BlockedByParentalControls
108 0
      case StatusCodes.UnavailableForLegalReasons    => Status.UnavailableForLegalReasons
109 0
      case StatusCodes.InternalServerError           => Status.InternalServerError
110 0
      case StatusCodes.NotImplemented                => Status.NotImplemented
111 0
      case StatusCodes.BadGateway                    => Status.BadGateway
112 0
      case StatusCodes.ServiceUnavailable            => Status.ServiceUnavailable
113 0
      case StatusCodes.GatewayTimeout                => Status.GatewayTimeout
114 0
      case StatusCodes.HTTPVersionNotSupported       => Status.HTTPVersionNotSupported
115 0
      case StatusCodes.VariantAlsoNegotiates         => Status.VariantAlsoNegotiates
116 0
      case StatusCodes.InsufficientStorage           => Status.InsufficientStorage
117 0
      case StatusCodes.LoopDetected                  => Status.LoopDetected
118 0
      case StatusCodes.BandwidthLimitExceeded        => Status.BandwidthLimitExceeded
119 0
      case StatusCodes.NotExtended                   => Status.NotExtended
120 0
      case StatusCodes.NetworkAuthenticationRequired => Status.NetworkAuthenticationRequired
121 0
      case StatusCodes.NetworkReadTimeout            => Status.NetworkReadTimeout
122 0
      case StatusCodes.NetworkConnectTimeout         => Status.NetworkConnectTimeout
123 0
      case StatusCodes.ClientError(x)                => Status.custom(x)
124 0
      case StatusCodes.CustomStatusCode(x)           => Status.custom(x)
125 0
      case StatusCodes.Informational(x)              => Status.custom(x)
126 0
      case StatusCodes.Redirection(x)                => Status.custom(x)
127 0
      case StatusCodes.ServerError(x)                => Status.custom(x)
128 0
      case StatusCodes.Success(x)                    => Status.custom(x)
129
    }
130

131 1
    λ[HttpF ~> Kleisli[F, HttpExt, *]] {
132 1
      case req @ (Options(_) | Get(_) | Head(_) | Post(_) | Put(_) | Delete(_) | Trace(_) | Patch(_)) => doReq(req)
133
    }
134
  }
135

136
  def mapRequest[F[_]: Async](reqF: HttpF[HttpResponse]): F[AkkaRequest] = {
137

138 1
    def mapContentType(ct: ContentType): F[AkkaContentType] = AkkaContentType.parse(ct.name) match {
139 0
      case Left(errors)       => Async[F].raiseError(new Exception(s"Unable to parse content type ${ct.name}, $errors"))
140 1
      case Right(contentType) => Async[F].pure(contentType)
141
    }
142

143 1
    def mapHeaders: F[List[RawHeader]] = reqF.req.headers.map { case (k, v) => RawHeader(k, v) }.toList.pure[F]
144

145
    def mapMethod(reqF: HttpF[HttpResponse]): F[HttpMethod] =
146
      (reqF match {
147
        case Options(_) => HttpMethods.OPTIONS
148
        case Get(_)     => HttpMethods.GET
149
        case Head(_)    => HttpMethods.HEAD
150
        case Post(_)    => HttpMethods.POST
151
        case Put(_)     => HttpMethods.PUT
152
        case Delete(_)  => HttpMethods.DELETE
153
        case Trace(_)   => HttpMethods.TRACE
154
        case Patch(_)   => HttpMethods.PATCH
155 1
      }).pure[F]
156

157
    def mapAkkaRequest(method: HttpMethod, akkaHeaders: List[RawHeader]): F[AkkaRequest] = {
158 1
      (reqF.req.entity match {
159
        case Some(Entity.StringEntity(body, contentType)) =>
160 1
          mapContentType(contentType) >>= { ct =>
161
            new RequestBuilder(method)(Uri(reqF.req.uri.show), HttpEntity.Strict(ct, ByteString.fromString(body)))
162
              .pure[F]
163
          }
164
        case Some(Entity.ByteArrayEntity(body, contentType)) =>
165 1
          mapContentType(contentType) >>= { ct =>
166
            new RequestBuilder(method)(Uri(reqF.req.uri.show), HttpEntity.Strict(ct, ByteString(body)))
167
              .pure[F]
168
          }
169 1
        case _ => new RequestBuilder(method)(Uri(reqF.req.uri.show)).pure[F]
170 1
      }).map(_.withHeaders(akkaHeaders))
171
    }
172

173
    for {
174 1
      method      <- mapMethod(reqF)
175 1
      akkaHeaders <- mapHeaders
176 1
      req         <- mapAkkaRequest(method, akkaHeaders)
177
    } yield req
178
  }
179
}

Read our documentation on viewing source code .

Loading