Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update zio-http to RC4 & Play to 3.0.0 #1968

Merged
merged 18 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
- checkout
- restore_cache:
key: sbtcache
- run: sbt ++2.12.18 core/test http4s/test akkaHttp/test pekkoHttp/test play/test zioHttp/test quickAdapter/test examples/compile catsInterop/test tools/test codegenSbt/test clientJVM/test monixInterop/compile tapirInterop/test federation/test reporting/test tracing/test
- run: sbt ++2.12.18 core/test http4s/test akkaHttp/test pekkoHttp/test zioHttp/test quickAdapter/test catsInterop/test tools/test codegenSbt/test clientJVM/test monixInterop/compile tapirInterop/test federation/test reporting/test tracing/test
- save_cache:
key: sbtcache
paths:
Expand Down Expand Up @@ -98,7 +98,7 @@ jobs:
- checkout
- restore_cache:
key: sbtcache
- run: sbt ++3.3.1 core/test catsInterop/test benchmarks/compile monixInterop/compile clientJVM/test clientJS/compile zioHttp/test quickAdapter/test tapirInterop/test pekkoHttp/test http4s/test federation/test tools/test reporting/test tracing/test
- run: sbt ++3.3.1 core/test catsInterop/test benchmarks/compile monixInterop/compile clientJVM/test clientJS/compile zioHttp/test quickAdapter/test tapirInterop/test pekkoHttp/test http4s/test play/test federation/test tools/test reporting/test tracing/test
- save_cache:
key: sbtcache
paths:
Expand All @@ -113,7 +113,7 @@ jobs:
- checkout
- restore_cache:
key: sbtcache
- run: sbt ++3.3.1 core/test catsInterop/test benchmarks/compile monixInterop/compile clientJVM/test clientJS/compile zioHttp/test quickAdapter/test tapirInterop/test pekkoHttp/test http4s/test federation/test tools/test reporting/test tracing/test
- run: sbt ++3.3.1 core/test catsInterop/test benchmarks/compile monixInterop/compile clientJVM/test clientJS/compile zioHttp/test quickAdapter/test tapirInterop/test pekkoHttp/test http4s/test play/test federation/test tools/test reporting/test tracing/test
- save_cache:
key: sbtcache
paths:
Expand Down Expand Up @@ -177,8 +177,8 @@ jobs:
- checkout
- restore_cache:
key: sbtcache
- run: sbt ++2.13.12 http4s/mimaReportBinaryIssues akkaHttp/mimaReportBinaryIssues pekkoHttp/mimaReportBinaryIssues play/mimaReportBinaryIssues zioHttp/mimaReportBinaryIssues catsInterop/mimaReportBinaryIssues monixInterop/mimaReportBinaryIssues clientJVM/mimaReportBinaryIssues clientJS/mimaReportBinaryIssues clientLaminextJS/mimaReportBinaryIssues federation/mimaReportBinaryIssues reporting/mimaReportBinaryIssues tracing/mimaReportBinaryIssues tools/mimaReportBinaryIssues core/mimaReportBinaryIssues tapirInterop/mimaReportBinaryIssues # quickAdapter/mimaReportBinaryIssues
- run: sbt ++3.3.1 catsInterop/mimaReportBinaryIssues monixInterop/mimaReportBinaryIssues clientJVM/mimaReportBinaryIssues clientJS/mimaReportBinaryIssues clientLaminextJS/mimaReportBinaryIssues zioHttp/mimaReportBinaryIssues http4s/mimaReportBinaryIssues federation/mimaReportBinaryIssues reporting/mimaReportBinaryIssues tracing/mimaReportBinaryIssues tools/mimaReportBinaryIssues core/mimaReportBinaryIssues tapirInterop/mimaReportBinaryIssues pekkoHttp/mimaReportBinaryIssues # quickAdapter/mimaReportBinaryIssues
- run: sbt ++2.13.12 http4s/mimaReportBinaryIssues akkaHttp/mimaReportBinaryIssues pekkoHttp/mimaReportBinaryIssues catsInterop/mimaReportBinaryIssues monixInterop/mimaReportBinaryIssues clientJVM/mimaReportBinaryIssues clientJS/mimaReportBinaryIssues clientLaminextJS/mimaReportBinaryIssues federation/mimaReportBinaryIssues reporting/mimaReportBinaryIssues tracing/mimaReportBinaryIssues tools/mimaReportBinaryIssues core/mimaReportBinaryIssues tapirInterop/mimaReportBinaryIssues # quickAdapter/mimaReportBinaryIssues zioHttp/mimaReportBinaryIssues play/mimaReportBinaryIssues
- run: sbt ++3.3.1 catsInterop/mimaReportBinaryIssues monixInterop/mimaReportBinaryIssues clientJVM/mimaReportBinaryIssues clientJS/mimaReportBinaryIssues clientLaminextJS/mimaReportBinaryIssues http4s/mimaReportBinaryIssues federation/mimaReportBinaryIssues reporting/mimaReportBinaryIssues tracing/mimaReportBinaryIssues tools/mimaReportBinaryIssues core/mimaReportBinaryIssues tapirInterop/mimaReportBinaryIssues pekkoHttp/mimaReportBinaryIssues # quickAdapter/mimaReportBinaryIssues zioHttp/mimaReportBinaryIssues play/mimaReportBinaryIssues
- save_cache:
key: sbtcache
paths:
Expand Down
34 changes: 17 additions & 17 deletions adapters/play/src/main/scala/caliban/PlayAdapter.scala
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package caliban

import akka.stream.scaladsl.{ Flow, Sink, Source }
import akka.stream.{ Materializer, OverflowStrategy }
import akka.util.ByteString
import caliban.PlayAdapter.convertHttpStreamingEndpoint
import caliban.interop.tapir.TapirAdapter._
import caliban.interop.tapir.{ HttpInterpreter, HttpUploadInterpreter, StreamConstructor, WebSocketInterpreter }
import org.apache.pekko.stream.scaladsl.{ Flow, Sink, Source }
import org.apache.pekko.stream.{ Materializer, OverflowStrategy }
import org.apache.pekko.util.ByteString
import play.api.routing.Router.Routes
import sttp.capabilities.{ akka, WebSockets }
import sttp.capabilities.akka.AkkaStreams
import sttp.capabilities.akka.AkkaStreams.Pipe
import sttp.model.{ MediaType, StatusCode }
import sttp.capabilities.WebSockets
import sttp.capabilities.pekko.PekkoStreams
import sttp.capabilities.pekko.PekkoStreams.Pipe
import sttp.model.StatusCode
import sttp.tapir.Codec.JsonCodec
import sttp.tapir.PublicEndpoint
import sttp.tapir.model.ServerRequest
Expand All @@ -30,7 +30,7 @@ class PlayAdapter private (private val options: Option[PlayServerOptions]) {
)(implicit runtime: Runtime[R], materializer: Materializer): Routes =
playInterpreter.toRoutes(
interpreter
.serverEndpoints[R, AkkaStreams](AkkaStreams)
.serverEndpoints[R, PekkoStreams](PekkoStreams)
.map(convertHttpStreamingEndpoint[R, (GraphQLRequest, ServerRequest)])
)

Expand All @@ -40,7 +40,7 @@ class PlayAdapter private (private val options: Option[PlayServerOptions]) {
requestCodec: JsonCodec[GraphQLRequest],
mapCodec: JsonCodec[Map[String, Seq[String]]]
): Routes =
playInterpreter.toRoutes(convertHttpStreamingEndpoint(interpreter.serverEndpoint[R, AkkaStreams](AkkaStreams)))
playInterpreter.toRoutes(convertHttpStreamingEndpoint(interpreter.serverEndpoint[R, PekkoStreams](PekkoStreams)))

def makeWebSocketService[R, E](
interpreter: WebSocketInterpreter[R, E]
Expand All @@ -66,7 +66,7 @@ class PlayAdapter private (private val options: Option[PlayServerOptions]) {
private implicit def streamConstructor(implicit
runtime: Runtime[Any],
mat: Materializer
): StreamConstructor[AkkaStreams.BinaryStream] =
): StreamConstructor[PekkoStreams.BinaryStream] =
new StreamConstructor[Source[ByteString, Any]] {
override def apply(stream: ZStream[Any, Throwable, Byte]): Source[ByteString, Any] =
Unsafe.unsafe(implicit u =>
Expand Down Expand Up @@ -111,18 +111,18 @@ object PlayAdapter extends PlayAdapter(None) {
Unit,
Input,
TapirResponse,
CalibanResponse[AkkaStreams.BinaryStream],
AkkaStreams,
CalibanResponse[PekkoStreams.BinaryStream],
PekkoStreams,
RIO[R, *]
]
)(implicit runtime: Runtime[R], mat: Materializer): ServerEndpoint[AkkaStreams, Future] =
)(implicit runtime: Runtime[R], mat: Materializer): ServerEndpoint[PekkoStreams, Future] =
ServerEndpoint[
Unit,
Unit,
Input,
TapirResponse,
CalibanResponse[akka.AkkaStreams.BinaryStream],
AkkaStreams,
CalibanResponse[PekkoStreams.BinaryStream],
PekkoStreams,
Future
](
endpoint.endpoint,
Expand Down Expand Up @@ -151,14 +151,14 @@ object PlayAdapter extends PlayAdapter(None) {
)(implicit
runtime: Runtime[R],
materializer: Materializer
): ServerEndpoint[AkkaStreams with WebSockets, Future] =
): ServerEndpoint[PekkoStreams with WebSockets, Future] =
ServerEndpoint[
Unit,
Unit,
(ServerRequest, String),
StatusCode,
(String, AkkaPipe),
AkkaStreams with WebSockets,
PekkoStreams with WebSockets,
Future
](
endpoint.endpoint
Expand Down
10 changes: 5 additions & 5 deletions adapters/play/src/test/scala/caliban/PlayAdapterSpec.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package caliban

import akka.actor.ActorSystem
import akka.stream.Materializer
import caliban.interop.tapir.TestData.sampleCharacters
import caliban.interop.tapir.{
FakeAuthorizationInterceptor,
Expand All @@ -13,18 +11,20 @@ import caliban.interop.tapir.{
WebSocketInterpreter
}
import caliban.uploads.Uploads
import org.apache.pekko.actor.ActorSystem
import org.apache.pekko.stream.Materializer
import play.api.Mode
import play.api.routing._
import play.api.routing.sird._
import play.core.server.{ AkkaHttpServer, ServerConfig }
import play.core.server.{ PekkoHttpServer, ServerConfig }
import sttp.client3.UriContext
import zio._
import zio.test.{ Live, ZIOSpecDefault }

import scala.language.postfixOps

object PlayAdapterSpec extends ZIOSpecDefault {
import sttp.tapir.json.circe._
import sttp.tapir.json.play._

private val envLayer = TestService.make(sampleCharacters) ++ Uploads.empty

Expand All @@ -51,7 +51,7 @@ object PlayAdapterSpec extends ZIOSpecDefault {
}
_ <- ZIO
.attempt(
AkkaHttpServer.fromRouterWithComponents(
PekkoHttpServer.fromRouterWithComponents(
ServerConfig(
mode = Mode.Dev,
port = Some(8088),
Expand Down
25 changes: 12 additions & 13 deletions adapters/quick/src/main/scala/caliban/QuickAdapter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,28 @@ import zio.http._
final class QuickAdapter[-R, E] private (requestHandler: QuickRequestHandler[R, E]) {

/**
* Converts this adapter to a [[zio.http.RequestHandler]] which can be used to create zio-http [[zio.http.App]]s
* Converts this adapter to a [[zio.http.RequestHandler]] which can be used to create zio-http `HttpApp`
*/
val handler: RequestHandler[R, Nothing] =
Handler.fromFunctionZIO[Request](requestHandler.handleRequest)

/**
* Converts this adapter to an [[zio.http.App]] serving the GraphQL API at the specified path.
* Converts this adapter to an `HttpApp` serving the GraphQL API at the specified path.
*
* @param apiPath The path where the GraphQL API will be served.
* @param graphiqlPath The path where the GraphiQL UI will be served. If None, GraphiQL will not be served.
*/
def toApp(apiPath: Path, graphiqlPath: Option[Path] = None): App[R] = {
val apiApp = Http.collectHandler[Request] {
case (Method.GET | Method.POST) -> path if path == apiPath => handler
}
graphiqlPath match {
case None => apiApp
case Some(uiPath) =>
val uiHandler = GraphiQLHandler.handler(apiPath.toString(), uiPath.toString)
apiApp ++ Http.collectHandler[Request] {
case Method.GET -> path if path == uiPath => uiHandler
}
def toApp(apiPath: Path, graphiqlPath: Option[Path] = None): HttpApp[R] = {
val apiRoutes = List(
RoutePattern(Method.POST, apiPath) -> handler,
RoutePattern(Method.GET, apiPath) -> handler
)
val graphiqlRoute = graphiqlPath.fold(List.empty[Route[R, Nothing]]) { uiPath =>
val uiHandler = GraphiQLHandler.handler(apiPath.toString(), uiPath.toString)
List(RoutePattern(Method.GET, uiPath) -> uiHandler)
}

Routes.fromIterable(apiRoutes ::: graphiqlRoute).toHttpApp
}

/**
Expand Down
50 changes: 25 additions & 25 deletions adapters/quick/src/main/scala/caliban/QuickRequestHandler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,43 +39,42 @@ final private class QuickRequestHandler[-R, E](interpreter: GraphQLInterpreter[R
val queryParams = httpReq.url.queryParams

def extractFields(key: String): Either[Response, Option[Map[String, InputValue]]] =
try Right(
queryParams
.get(key)
.flatMap(_.headOption)
.map(readFromString[InputValue.ObjectValue](_).fields)
)
try Right(queryParams.get(key).map(readFromString[InputValue.ObjectValue](_).fields))
catch { case NonFatal(_) => Left(badRequest(s"Invalid $key query param")) }

def fromQueryParams: Either[Response, GraphQLRequest] =
for {
vars <- extractFields("variables")
exts <- extractFields("extensions")
} yield GraphQLRequest(
query = queryParams.get("query").flatMap(_.headOption),
operationName = queryParams.get("operationName").flatMap(_.headOption),
query = queryParams.get("query"),
operationName = queryParams.get("operationName"),
variables = vars,
extensions = exts
)

def isApplicationGql =
httpReq.headers.get("content-type").fold(false)(_.startsWith("application/graphql"))
def isGqlJson =
httpReq.header(Header.ContentType).exists { h =>
h.mediaType.subType.equalsIgnoreCase("graphql") &&
h.mediaType.mainType.equalsIgnoreCase("application")
}

def decodeApplicationGql() =
httpReq.body.asString.mapBoth(_ => BodyDecodeErrorResponse, b => GraphQLRequest(Some(b)))

def decodeJson() =
httpReq.body.asArray
.flatMap(arr => ZIO.attempt(readFromArray[GraphQLRequest](arr)))
.orElseFail(BodyDecodeErrorResponse)

val resp = {
if (httpReq.method == Method.GET || queryParams.get("query").isDefined)
ZIO.fromEither(fromQueryParams)
else {
val req =
if (isApplicationGql)
httpReq.body.asString.mapBoth(_ => BodyDecodeErrorResponse, b => GraphQLRequest(Some(b)))
else
httpReq.body.asArray
.flatMap(arr => ZIO.attempt(readFromArray[GraphQLRequest](arr)))
.orElseFail(BodyDecodeErrorResponse)

val req = if (isGqlJson) decodeApplicationGql() else decodeJson()
httpReq.headers
.get(GraphQLRequest.`apollo-federation-include-trace`)
.collect { case GraphQLRequest.ftv1 => req.map(_.withFederatedTracing) }
.collect { case s if s.equalsIgnoreCase(GraphQLRequest.ftv1) => req.map(_.withFederatedTracing) }
.getOrElse(req)
}
}
Expand All @@ -95,15 +94,16 @@ final private class QuickRequestHandler[-R, E](interpreter: GraphQLInterpreter[R

private def transformResponse(httpReq: Request, resp: GraphQLResponse[E])(implicit trace: Trace): Response = {
def acceptsGqlJson: Boolean =
httpReq.header(Header.Accept).fold(false) { h =>
h.mimeTypes.exists(mt =>
mt.mediaType.subType == "graphql-response+json" && mt.mediaType.mainType == "application"
)
httpReq.header(Header.Accept).exists { h =>
h.mimeTypes.exists { mt =>
mt.mediaType.subType.equalsIgnoreCase("graphql-response+json") &&
mt.mediaType.mainType.equalsIgnoreCase("application")
}
}

val cacheDirective = HttpUtils.computeCacheDirective(resp.extensions)

(resp match {
resp match {
case resp @ GraphQLResponse(StreamValue(stream), _, _, _) =>
Response(
Status.Ok,
Expand All @@ -125,7 +125,7 @@ final private class QuickRequestHandler[-R, E](interpreter: GraphQLInterpreter[R
headers = responseHeaders(ContentTypeJson, cacheDirective),
body = encodeSingleResponse(resp, keepDataOnErrors = true, hasCacheDirective = cacheDirective.isDefined)
)
}).withServerTime
}
}

private def encodeSingleResponse(
Expand Down
13 changes: 12 additions & 1 deletion adapters/quick/src/main/scala/caliban/quick/package.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package caliban

import caliban.Configurator.ExecutionConfiguration
import zio.http.{ HttpApp, Path, RequestHandler, Response }
import zio.{ RIO, Trace, ZIO }
import zio.http.{ RequestHandler, Response }

package object quick {

Expand All @@ -21,6 +21,17 @@ package object quick {
): RIO[R, Nothing] =
gql.interpreter.flatMap(QuickAdapter(_).runServer(port, apiPath, graphiqlPath))

/**
* Creates zio-http `HttpApp` from the GraphQL API
*
* @param apiPath The route to serve the API on, e.g., `/api/graphql`
* @param graphiqlPath Optionally define a route to serve the GraphiQL UI on, e.g., `/graphiql`
*/
def toApp(apiPath: String, graphiqlPath: Option[String] = None)(implicit
trace: Trace
): RIO[R, HttpApp[R]] =
gql.interpreter.map(QuickAdapter(_).toApp(Path.decode(apiPath), graphiqlPath.map(Path.decode)))

/**
* Creates a zio-http handler for the GraphQL API
*
Expand Down
11 changes: 4 additions & 7 deletions adapters/quick/src/test/scala/caliban/QuickAdapterSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package caliban

import caliban.interop.tapir.TestData.sampleCharacters
import caliban.interop.tapir.{ TapirAdapterSpec, TestApi, TestService }
import caliban.quick._
import caliban.uploads.Uploads
import sttp.client3.UriContext
import zio._
Expand All @@ -12,23 +11,21 @@ import zio.test.{ Live, ZIOSpecDefault }
import scala.language.postfixOps

object QuickAdapterSpec extends ZIOSpecDefault {
import caliban.quick._
import sttp.tapir.json.jsoniter._

private val envLayer = TestService.make(sampleCharacters) ++ Uploads.empty

private val auth = HttpAppMiddleware.intercept { case (req, resp) =>
private val auth = Middleware.intercept { case (req, resp) =>
if (req.headers.get("X-Invalid").nonEmpty)
Response(Status.Unauthorized, body = Body.fromString("You are unauthorized!"))
else resp
}

private val apiLayer = envLayer >>> ZLayer.fromZIO {
for {
handler <- TestApi.api.handler.map(_ @@ auth)
_ <-
Server
.serve(Http.collectHandler { case _ -> Root / "api" / "graphql" => handler })
.forkScoped
app <- TestApi.api.toApp("/api/graphql").map(_ @@ auth)
_ <- Server.serve(app).forkScoped
_ <- Live.live(Clock.sleep(3 seconds))
service <- ZIO.service[TestService]
} yield service
Expand Down
Loading