diff --git a/akka-http/src/main/scala/caliban/AkkaHttpAdapter.scala b/akka-http/src/main/scala/caliban/AkkaHttpAdapter.scala index 6abf23fad..2b2ae8851 100644 --- a/akka-http/src/main/scala/caliban/AkkaHttpAdapter.scala +++ b/akka-http/src/main/scala/caliban/AkkaHttpAdapter.scala @@ -1,10 +1,13 @@ package caliban import akka.http.scaladsl.model.MediaTypes.`application/json` -import akka.http.scaladsl.model.{ HttpEntity, HttpResponse } -import akka.http.scaladsl.server.Route +import akka.http.scaladsl.model.{ HttpEntity, HttpResponse, StatusCodes } +import akka.http.scaladsl.server.Directives.complete +import akka.http.scaladsl.server.{ Route, StandardRoute } import caliban.Value.NullValue import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport +import io.circe.Decoder.Result +import io.circe.Json import io.circe.syntax._ import zio.{ Runtime, URIO } @@ -12,28 +15,49 @@ import scala.concurrent.ExecutionContext object AkkaHttpAdapter extends FailFastCirceSupport { - private def execute[R, E]( + private def executeHttpResponse[R, E]( interpreter: GraphQLInterpreter[R, E], - query: GraphQLRequest - ): URIO[R, GraphQLResponse[E]] = - interpreter.execute(query.query, query.operationName, query.variables.getOrElse(Map())) + request: GraphQLRequest + ): URIO[R, HttpResponse] = + interpreter + .execute(request.query, request.operationName, request.variables.getOrElse(Map())) + .foldCause(cause => GraphQLResponse(NullValue, cause.defects).asJson, _.asJson) + .map(gqlResult => HttpResponse(StatusCodes.OK, entity = HttpEntity(`application/json`, gqlResult.toString()))) + + def getGraphQLRequest(query: String, op: Option[String], vars: Option[String]): Result[GraphQLRequest] = { + import io.circe.parser._ + val variablesJs = vars.flatMap(parse(_).toOption) + val fields = List("query" -> Json.fromString(query)) ++ + op.map(o => "operationName" -> Json.fromString(o)) ++ + variablesJs.map(js => "variables" -> js) + Json + .fromFields(fields) + .as[GraphQLRequest] + } + + def completeRequest[R, E]( + interpreter: GraphQLInterpreter[R, E] + )(request: GraphQLRequest)(implicit ec: ExecutionContext, runtime: Runtime[R]): StandardRoute = + complete( + runtime + .unsafeRunToFuture(executeHttpResponse(interpreter, request)) + .future + ) def makeHttpService[R, E]( interpreter: GraphQLInterpreter[R, E] )(implicit ec: ExecutionContext, runtime: Runtime[R]): Route = { import akka.http.scaladsl.server.Directives._ - post { - entity(as[GraphQLRequest]) { request => - complete({ - runtime - .unsafeRunToFuture( - execute(interpreter, request) - .foldCause(cause => GraphQLResponse(NullValue, cause.defects).asJson, _.asJson) - .map(gqlResult => HttpResponse(200, entity = HttpEntity(`application/json`, gqlResult.toString()))) - ) - .future - }) + + get { + parameters((Symbol("query").as[String], Symbol("operationName").?, Symbol("variables").?)) { + case (query, op, vars) => + getGraphQLRequest(query, op, vars) + .fold(failWith, completeRequest(interpreter)) + } + } ~ + post { + entity(as[GraphQLRequest]) { completeRequest(interpreter) } } - } } } diff --git a/build.sbt b/build.sbt index 7684fdbdd..e7a47ef06 100644 --- a/build.sbt +++ b/build.sbt @@ -1,9 +1,9 @@ import sbtcrossproject.CrossPlugin.autoImport.{ crossProject, CrossType } -val mainScala = "2.12.10" -val allScala = Seq("2.13.1", mainScala) -val http4sVersion = "0.21.0-M5" - +val mainScala = "2.12.10" +val allScala = Seq("2.13.1", mainScala) +val http4sVersion = "0.21.0-M5" +val silencerVersion = "1.4.4" inThisBuild( List( organization := "com.github.ghostdogpr", @@ -108,7 +108,9 @@ lazy val http4s = project compilerPlugin( ("org.typelevel" %% "kind-projector" % "0.11.0") .cross(CrossVersion.full) - ) + ), + compilerPlugin("com.github.ghik" % "silencer-plugin" % silencerVersion cross CrossVersion.full), + "com.github.ghik" % "silencer-lib" % silencerVersion % Provided cross CrossVersion.full ) ) .dependsOn(coreJVM) diff --git a/http4s/src/main/scala/caliban/Http4sAdapter.scala b/http4s/src/main/scala/caliban/Http4sAdapter.scala index 74cbdb5f3..492b48373 100644 --- a/http4s/src/main/scala/caliban/Http4sAdapter.scala +++ b/http4s/src/main/scala/caliban/Http4sAdapter.scala @@ -7,7 +7,8 @@ import cats.effect.Effect import cats.effect.syntax.all._ import cats.~> import fs2.{ Pipe, Stream } -import io.circe._ +import io.circe.Decoder.Result +import io.circe.Json import io.circe.parser._ import io.circe.syntax._ import org.http4s._ @@ -18,6 +19,7 @@ import org.http4s.websocket.WebSocketFrame import org.http4s.websocket.WebSocketFrame.Text import zio._ import zio.interop.catz._ +import com.github.ghik.silencer.silent object Http4sAdapter { @@ -32,7 +34,24 @@ object Http4sAdapter { def makeRestService[R, E](interpreter: GraphQLInterpreter[R, E]): HttpRoutes[RIO[R, *]] = makeHttpService(interpreter) - def makeHttpService[R, E](interpreter: GraphQLInterpreter[R, E]): HttpRoutes[RIO[R, *]] = { + private def getGraphQLRequest(query: String, op: Option[String], vars: Option[String]): Result[GraphQLRequest] = { + val variablesJs = vars.flatMap(parse(_).toOption) + val fields = List("query" -> Json.fromString(query)) ++ + op.map(o => "operationName" -> Json.fromString(o)) ++ + variablesJs.map(js => "variables" -> js) + Json + .fromFields(fields) + .as[GraphQLRequest] + } + + private def getGraphQLRequest(params: Map[String, String]): Result[GraphQLRequest] = + getGraphQLRequest( + params.getOrElse("query", ""), + params.get("operationName"), + params.get("variables") + ) + + @silent def makeHttpService[R, E](interpreter: GraphQLInterpreter[R, E]): HttpRoutes[RIO[R, *]] = { object dsl extends Http4sDsl[RIO[R, *]] import dsl._ @@ -43,6 +62,12 @@ object Http4sAdapter { result <- executeToJson(interpreter, query) response <- Ok(result) } yield response + case req @ GET -> Root => + for { + query <- Task.fromEither(getGraphQLRequest(req.params)) + result <- executeToJson(interpreter, query) + response <- Ok(result) + } yield response } }