Skip to content

Commit

Permalink
refactor: using HttpResponse and HttpRequest
Browse files Browse the repository at this point in the history
  • Loading branch information
MoeQuadrat committed Jan 16, 2024
1 parent 535d702 commit 7042031
Show file tree
Hide file tree
Showing 11 changed files with 109 additions and 97 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,21 @@ import cats.implicits.toBifunctorOps
import de.innfactory.smithy4play
import de.innfactory.smithy4play.middleware.MiddlewareBase
import play.api.mvc._
import smithy4s.codecs.{ PayloadError, StringAndBlobCodecs }
import smithy4s.http.{ HttpEndpoint, HttpResponse, Metadata, PathParams }
import smithy4s.json.Json
import smithy4s.codecs.{
BlobDecoder,
BlobEncoder,
PayloadDecoder,
PayloadEncoder,
PayloadError,
StringAndBlobCodecs,
Writer
}
import smithy4s.http.{ HttpEndpoint, HttpRequest, HttpResponse, HttpRestSchema, Metadata, PathParams }
import smithy4s.json.{ Json, JsonPayloadCodecCompiler }
import smithy4s.json.Json.payloadCodecs
import smithy4s.kinds.FunctorInterpreter
import smithy4s.schema.{ CachedSchemaCompiler, Schema }
import smithy4s.xml.Xml.encoders
import smithy4s.xml.{ Xml, XmlDecodeError }
import smithy4s.{ Blob, Endpoint, Service }

Expand Down Expand Up @@ -43,6 +53,15 @@ class SmithyPlayEndpoint[Alg[_[_, _, _, _, _]], F[_] <: ContextRoute[_], Op[
private val outputMetadataEncoder: Metadata.Encoder[O] =
Metadata.Encoder.fromSchema(outputSchema)

private val jsonPayloadCodecs: JsonPayloadCodecCompiler =
Json.payloadCodecs
private val jsonWriters: CachedSchemaCompiler[PayloadEncoder] = jsonPayloadCodecs.encoders
private val jsonDecoders: CachedSchemaCompiler[PayloadDecoder] = jsonPayloadCodecs.decoders
private val xmlEncoder: BlobEncoder.Compiler = Xml.encoders
private val xmlDecoder = Xml.decoders.createCache()
private val stringAndBlobEncoder = StringAndBlobCodecs.decoders
private val stringAndBlobDecoder = StringAndBlobCodecs.encoders

def handler(v1: RequestHeader): Handler =
httpEndpoint.map { httpEp =>
Action.async(parse.raw) { implicit request =>
Expand Down Expand Up @@ -112,21 +131,22 @@ class SmithyPlayEndpoint[Alg[_[_, _, _, _, _]], F[_] <: ContextRoute[_], Op[
EitherT(
Future {
val contentType = request.contentType.getOrElse("application/json")
val x = inputMetadataDecoder.decode(metadata)
val codecs = contentType match {
case "application/json" => (blob: Blob) => Json.read(blob)(inputSchema)
case "application/xml" => (blob: Blob) => Xml.read(blob)(inputSchema)
case _ =>
(blob: Blob) =>
StringAndBlobCodecs.decoders
.fromSchema(inputSchema)
.get
.decode(blob)
}
codecs(Blob(request.body.asBytes().getOrElse(ByteString.empty).toByteBuffer)).leftMap {
case error: PayloadError => Smithy4PlayError(error.expected, smithy4play.Status(Map.empty, 500))
case error: XmlDecodeError => Smithy4PlayError(error.getMessage(), smithy4play.Status(Map.empty, 500))

val codecs = contentType match {
case "application/json" => (blob: Blob) => Some(payloadCodecs.decoders.fromSchema(inputSchema).decode(blob))
case "application/xml" => (blob: Blob) => Some(Xml.decoders.fromSchema(inputSchema).decode(blob))
case _ => (blob: Blob) => StringAndBlobCodecs.decoders.fromSchema(inputSchema).map(_.decode(blob))
}

codecs(request.body.asBytes().map(b => Blob(b.toByteBuffer)).getOrElse(Blob.empty))
.map(_.leftMap { error: PayloadError =>
Smithy4PlayError(error.expected, smithy4play.Status(Map.empty, 500))
})
.getOrElse(
Left[ContextRouteError, I](
Smithy4PlayError("No Codec for InputSchema found", smithy4play.Status(Map.empty, 500))
)
)
}
)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package de.innfactory.smithy4play

import cats.data.{ EitherT, Kleisli }
import cats.implicits.toTraverseOps
import de.innfactory.smithy4play.middleware.MiddlewareBase
import play.api.mvc.{ AbstractController, ControllerComponents, Handler, RequestHeader }
import play.api.routing.Router.Routes
import smithy4s.HintMask
import smithy4s.codecs.{ BlobEncoder, PayloadDecoder, PayloadEncoder }
import smithy4s.http.{ HttpEndpoint, PathSegment }
import smithy4s.internals.InputOutput
import smithy4s.json.{ Json, JsonPayloadCodecCompiler }
import smithy4s.kinds.{ FunctorAlgebra, Kind1, PolyFunction5 }
import smithy4s.schema.CachedSchemaCompiler
import smithy4s.xml.Xml

import scala.concurrent.ExecutionContext

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,15 @@ package de.innfactory.smithy4play.client

import play.api.mvc.Headers
import smithy4s.Blob
import smithy4s.http.HttpResponse

import scala.concurrent.Future

case class SmithyClientResponse(body: Blob, headers: Map[String, Seq[String]], statusCode: Int)

trait RequestClient {
def send(
method: String,
path: String,
headers: Map[String, Seq[String]],
body: Blob
): Future[SmithyClientResponse]
): Future[HttpResponse[Blob]]
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package de.innfactory.smithy4play.client

import cats.implicits.toBifunctorOps
import de.innfactory.smithy4play.ClientResponse
import smithy4s.Blob
import smithy4s.http.HttpEndpoint

import scala.concurrent.{ ExecutionContext, Future }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ package de.innfactory
package smithy4play
package client
import cats.implicits._
import smithy4s.codecs.{PayloadError, StringAndBlobCodecs}
import smithy4s.http.{CaseInsensitive, HttpEndpoint, Metadata, MetadataError}
import smithy4s.codecs.{ PayloadError, StringAndBlobCodecs }
import smithy4s.http.{ CaseInsensitive, HttpEndpoint, HttpResponse, Metadata, MetadataError }
import smithy4s.json.Json
import smithy4s.json.Json.payloadCodecs
import smithy4s.schema.CachedSchemaCompiler
import smithy4s.xml.internals.XmlEncoder
import smithy4s.xml.{Xml, XmlDecodeError, XmlDocument}
import smithy4s.{Blob, Endpoint, Schema}
import smithy4s.xml.{ Xml, XmlDecodeError, XmlDocument }
import smithy4s.{ Blob, Endpoint, Schema }

import scala.concurrent.{ExecutionContext, Future}
import scala.concurrent.{ ExecutionContext, Future }

private[smithy4play] class SmithyPlayClientEndpoint[Op[_, _, _, _, _], I, E, O, SI, SO](
endpoint: Endpoint[Op, I, E, O, SI, SO],
Expand Down Expand Up @@ -49,24 +49,19 @@ private[smithy4play] class SmithyPlayClientEndpoint[Op[_, _, _, _, _], I, E, O,
decodeResponse(response, code)
}

private def writeInputToBlob(input: I, contentType: Seq[String]): Blob = {
//TODO: Use Correct Encoders for Json and Xml and fix StringAndBlobCodecs
contentType match {
case Seq("application/json") => Json.writeBlob(input)
case Seq("application/xml") => Xml.write(input)
private def writeInputToBlob(input: I, contentType: Seq[String]): Blob =
// TODO: Use Correct Encoders for Json and Xml and fix StringAndBlobCodecs
(contentType match {
case Seq("application/json") => Some(payloadCodecs.encoders.fromSchema(inputSchema))
case Seq("application/xml") => Some(Xml.encoders.fromSchema(inputSchema))
case _ =>
StringAndBlobCodecs.encoders
.fromSchema(inputSchema)
.map(_.encode(input))
.getOrElse({
logger.info("sending empty blob")
Blob.empty
})
}
}

}).map(_.encode(input)).getOrElse(Blob.empty)

private def decodeResponse(
response: Future[SmithyClientResponse],
response: Future[HttpResponse[Blob]],
expectedCode: Int
): ClientResponse[O] =
for {
Expand All @@ -76,24 +71,26 @@ private[smithy4play] class SmithyPlayClientEndpoint[Op[_, _, _, _, _], I, E, O,
else handleError(res)
} yield output

def handleSuccess(response: SmithyClientResponse) = {
val headers = response.headers.map(x => (x._1.toLowerCase, x._2))
val contentType = headers.getOrElse("content-type", Seq("application/json"))
def handleSuccess(response: HttpResponse[Blob]): ClientResponse[O] = {
val headers = response.headers.map(x => (x._1, x._2))
val contentType = headers.getOrElse(CaseInsensitive("content-type"), Seq("application/json"))
val codec = contentType match {
case "application/json" :: _ => (o: Blob) => Json.read(o)(outputSchema)
case "application/xml" :: _ => (o: Blob) => Xml.read(o)(outputSchema)
case _ => (o: Blob) => StringAndBlobCodecs.decoders.fromSchema(outputSchema).get.decode(o)
case "application/json" :: _ => Some(payloadCodecs.decoders.fromSchema(outputSchema))
case "application/xml" :: _ => Some(Xml.decoders.fromSchema(outputSchema))
case _ => StringAndBlobCodecs.decoders.fromSchema(outputSchema)
}
Future(
codec(response.body).map(o => SmithyPlayClientEndpointResponse(Some(o), headers, response.statusCode)).leftMap {
case error: PayloadError =>
SmithyPlayClientEndpointErrorResponse(error.expected.getBytes, response.statusCode)
case error: XmlDecodeError =>
SmithyPlayClientEndpointErrorResponse(error.getMessage().getBytes(), response.statusCode)
}
codec
.map(_.decode(response.body))
.map(o =>
o.map(res => HttpResponse(response.statusCode, headers, res)).leftMap { error =>
SmithyPlayClientEndpointErrorResponse(error.expected.getBytes, response.statusCode)
}
)
.getOrElse(Left(SmithyPlayClientEndpointErrorResponse("No Decoder found".getBytes, response.statusCode)))
)
}
private def handleError(response: SmithyClientResponse) = Future(
private def handleError(response: HttpResponse[Blob]): ClientResponse[O] = Future(
Left {
SmithyPlayClientEndpointErrorResponse(
response.body.toArray,
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package de.innfactory.smithy4play.client

import de.innfactory.smithy4play.{ logger, ClientResponse }
import play.api.libs.json.{ Json, Reads }
import smithy4s.http.HttpResponse

import scala.concurrent.duration.{ Duration, DurationInt }
import scala.concurrent.{ Await, ExecutionContext }
Expand All @@ -12,10 +13,13 @@ object SmithyPlayTestUtils {
def awaitRight(implicit
ec: ExecutionContext,
timeout: Duration = 5.seconds
): SmithyPlayClientEndpointResponse[O] =
): HttpResponse[O] =
Await.result(
response.map { res =>
if (res.isLeft) logger.error(s"Expected Right, got Left: ${res.left.toOption.get.toString}")
if (res.isLeft)
logger.error(
s"Expected Right, got Left: ${res.left.toOption.get.toString} Error: ${res.left.toOption.get.error.toErrorString}"
)
res.toOption.get
},
timeout
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package de.innfactory.smithy4play.compliancetests

import de.innfactory.smithy4play.ClientResponse
import de.innfactory.smithy4play.client.{ SmithyPlayClientEndpointErrorResponse, SmithyPlayClientEndpointResponse }
import de.innfactory.smithy4play.client.SmithyPlayClientEndpointErrorResponse
import play.api.libs.json.Json
import smithy4s.http.HttpEndpoint
import smithy4s.http.{ HttpEndpoint, HttpResponse }
import smithy4s.kinds.{ FunctorAlgebra, Kind1 }
import smithy4s.{ Document, Endpoint, Service }
import smithy.test._
Expand Down Expand Up @@ -37,7 +37,7 @@ class ComplianceClient[
}

private def matchResponse[I, E, O, SE, SO](
response: Either[SmithyPlayClientEndpointErrorResponse, SmithyPlayClientEndpointResponse[O]],
response: Either[SmithyPlayClientEndpointErrorResponse, HttpResponse[O]],
endpoint: Endpoint[service.Operation, I, E, O, SE, SO],
responseTestCase: Option[HttpResponseTestCase]
) = {
Expand All @@ -59,7 +59,7 @@ class ComplianceClient[
expectedCode = expectedStatusCode,
receivedCode = responseStatusCode,
expectedBody = expectedOutput,
receivedBody = response.toOption.flatMap(_.body),
receivedBody = response.toOption.map(_.body),
expectedError = responseTestCase match {
case Some(value) => value.body.getOrElse("")
case None => ""
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package de.innfactory

import cats.data.{EitherT, Kleisli}
import de.innfactory.smithy4play.client.{SmithyPlayClientEndpointErrorResponse, SmithyPlayClientEndpointResponse}
import cats.data.{ EitherT, Kleisli }
import de.innfactory.smithy4play.client.SmithyPlayClientEndpointErrorResponse
import org.slf4j
import play.api.Logger
import play.api.libs.json.{JsValue, Json}
import play.api.mvc.{Headers, RequestHeader}
import play.api.libs.json.{ JsValue, Json }
import play.api.mvc.{ Headers, RequestHeader }
import smithy4s.Blob
import smithy4s.http.{CaseInsensitive, HttpEndpoint}
import smithy4s.http.{ CaseInsensitive, HttpEndpoint, HttpResponse }

import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.annotation.{ compileTimeOnly, StaticAnnotation }
import scala.concurrent.Future
import scala.language.experimental.macros

Expand All @@ -20,7 +20,7 @@ package object smithy4play {
def toJson: JsValue
}

type ClientResponse[O] = Future[Either[SmithyPlayClientEndpointErrorResponse, SmithyPlayClientEndpointResponse[O]]]
type ClientResponse[O] = Future[Either[SmithyPlayClientEndpointErrorResponse, HttpResponse[O]]]
type RunnableClientRequest[O] = Kleisli[ClientResponse, Option[Map[String, Seq[String]]], O]
type RouteResult[O] = EitherT[Future, ContextRouteError, O]
type ContextRoute[O] = Kleisli[RouteResult, RoutingContext, O]
Expand Down
8 changes: 4 additions & 4 deletions smithy4playTest/app/controller/TestController.scala
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package controller

import cats.data.{EitherT, Kleisli}
import cats.data.{ EitherT, Kleisli }
import controller.models.TestError
import de.innfactory.smithy4play.{AutoRouting, ContextRoute, ContextRouteError}
import de.innfactory.smithy4play.{ AutoRouting, ContextRoute, ContextRouteError }
import play.api.mvc.ControllerComponents
import smithy4s.Blob
import testDefinitions.test._

import javax.inject.{Inject, Singleton}
import scala.concurrent.{ExecutionContext, Future}
import javax.inject.{ Inject, Singleton }
import scala.concurrent.{ ExecutionContext, Future }

@Singleton
@AutoRouting
Expand Down
Loading

0 comments on commit 7042031

Please sign in to comment.