From cf11549594a50db70441e5bf7254fcd2cdd04ed8 Mon Sep 17 00:00:00 2001 From: phderome Date: Sun, 26 Jan 2020 13:41:51 -0500 Subject: [PATCH 1/9] tentative GET solution for Akka --- .../main/scala/caliban/AkkaHttpAdapter.scala | 55 ++++++++++++++----- 1 file changed, 42 insertions(+), 13 deletions(-) diff --git a/akka-http/src/main/scala/caliban/AkkaHttpAdapter.scala b/akka-http/src/main/scala/caliban/AkkaHttpAdapter.scala index 6abf23fad..f04722fd4 100644 --- a/akka-http/src/main/scala/caliban/AkkaHttpAdapter.scala +++ b/akka-http/src/main/scala/caliban/AkkaHttpAdapter.scala @@ -2,9 +2,11 @@ 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.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 } @@ -18,22 +20,49 @@ object AkkaHttpAdapter extends FailFastCirceSupport { ): URIO[R, GraphQLResponse[E]] = interpreter.execute(query.query, query.operationName, query.variables.getOrElse(Map())) + private def executeHttpResponse[R, E]( + interpreter: GraphQLInterpreter[R, E], + request: GraphQLRequest + ): URIO[R, HttpResponse] = + execute(interpreter, request) + .foldCause(cause => GraphQLResponse(NullValue, cause.defects).asJson, _.asJson) + .map(gqlResult => HttpResponse(200, entity = HttpEntity(`application/json`, gqlResult.toString()))) + + private def getGraphQLRequest(query: String, op: Option[String], vars: Option[String]): Result[GraphQLRequest] = { + val variablesJs = vars + .map(Json.fromString(_)) + .filterNot { _.asObject.isEmpty } // we don't want to keep in variables='{}' + // as building the GraphQLRequest would fail with DecodingFailure([K, V]Map[K, V], List(DownField(variables))) + 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 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 - }) + + def completeRequest(request: GraphQLRequest)(implicit ec: ExecutionContext, runtime: Runtime[R]): StandardRoute = + complete( + runtime + .unsafeRunToFuture(executeHttpResponse(interpreter, request)) + .future + ) + + concat( + get { + parameters(('query.as[String], 'operationName.?, 'variables.?)) { (query, op, vars) => + val request = getGraphQLRequest(query, op, vars) + request.fold(decodingFail => failWith(decodingFail), completeRequest) + } + }, + post { + entity(as[GraphQLRequest]) { completeRequest } } - } + ) } } From 366202f32662b0751a1d606ed9c0ab54507e79ae Mon Sep 17 00:00:00 2001 From: phderome Date: Sun, 26 Jan 2020 17:11:56 -0500 Subject: [PATCH 2/9] use Akka HTTP Status code OK. --- akka-http/src/main/scala/caliban/AkkaHttpAdapter.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/akka-http/src/main/scala/caliban/AkkaHttpAdapter.scala b/akka-http/src/main/scala/caliban/AkkaHttpAdapter.scala index f04722fd4..a5ec0750b 100644 --- a/akka-http/src/main/scala/caliban/AkkaHttpAdapter.scala +++ b/akka-http/src/main/scala/caliban/AkkaHttpAdapter.scala @@ -1,7 +1,7 @@ package caliban import akka.http.scaladsl.model.MediaTypes.`application/json` -import akka.http.scaladsl.model.{ HttpEntity, HttpResponse } +import akka.http.scaladsl.model.{ HttpEntity, HttpResponse, StatusCodes } import akka.http.scaladsl.server.{ Route, StandardRoute } import caliban.Value.NullValue import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport @@ -26,7 +26,7 @@ object AkkaHttpAdapter extends FailFastCirceSupport { ): URIO[R, HttpResponse] = execute(interpreter, request) .foldCause(cause => GraphQLResponse(NullValue, cause.defects).asJson, _.asJson) - .map(gqlResult => HttpResponse(200, entity = HttpEntity(`application/json`, gqlResult.toString()))) + .map(gqlResult => HttpResponse(StatusCodes.OK, entity = HttpEntity(`application/json`, gqlResult.toString()))) private def getGraphQLRequest(query: String, op: Option[String], vars: Option[String]): Result[GraphQLRequest] = { val variablesJs = vars From 906df766a0bb0c3e54f402f3e07e1a1f80805ca0 Mon Sep 17 00:00:00 2001 From: phderome Date: Sun, 26 Jan 2020 18:51:42 -0500 Subject: [PATCH 3/9] wip for Http4sAdapter --- .../main/scala/caliban/Http4sAdapter.scala | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/http4s/src/main/scala/caliban/Http4sAdapter.scala b/http4s/src/main/scala/caliban/Http4sAdapter.scala index 74cbdb5f3..293fc0aa0 100644 --- a/http4s/src/main/scala/caliban/Http4sAdapter.scala +++ b/http4s/src/main/scala/caliban/Http4sAdapter.scala @@ -7,6 +7,7 @@ import cats.effect.Effect import cats.effect.syntax.all._ import cats.~> import fs2.{ Pipe, Stream } +import io.circe.Decoder.Result import io.circe._ import io.circe.parser._ import io.circe.syntax._ @@ -32,6 +33,26 @@ object Http4sAdapter { def makeRestService[R, E](interpreter: GraphQLInterpreter[R, E]): HttpRoutes[RIO[R, *]] = makeHttpService(interpreter) + private def getGraphQLRequest(query: String, op: Option[String], vars: Option[String]): Result[GraphQLRequest] = { + val variablesJs = vars + .map(Json.fromString(_)) + .filterNot { _.asObject.isEmpty } // we don't want to keep in variables='{}' + // as building the GraphQLRequest would fail with DecodingFailure([K, V]Map[K, V], List(DownField(variables))) + 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] = { + val query = params.get("query").getOrElse("") + val variables = params.get("variables") + val op = params.get("operationName") + getGraphQLRequest(query, op, variables) + } + def makeHttpService[R, E](interpreter: GraphQLInterpreter[R, E]): HttpRoutes[RIO[R, *]] = { object dsl extends Http4sDsl[RIO[R, *]] import dsl._ @@ -43,6 +64,12 @@ object Http4sAdapter { result <- executeToJson(interpreter, query) response <- Ok(result) } yield response + case req @ GET -> Root => + for { + query <- Task(getGraphQLRequest(req.params)).absolve + result <- executeToJson(interpreter, query) + response <- Ok(result) + } yield response } } @@ -122,7 +149,7 @@ object Http4sAdapter { ) .noSpaces ) - ) + ) ) } case "stop" => From 86afd4423a00bc40f53219a5297bdd5354f0cb5c Mon Sep 17 00:00:00 2001 From: phderome Date: Sun, 26 Jan 2020 18:53:35 -0500 Subject: [PATCH 4/9] wip for Http4sAdapter --- http4s/src/main/scala/caliban/Http4sAdapter.scala | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/http4s/src/main/scala/caliban/Http4sAdapter.scala b/http4s/src/main/scala/caliban/Http4sAdapter.scala index 293fc0aa0..ec5587cde 100644 --- a/http4s/src/main/scala/caliban/Http4sAdapter.scala +++ b/http4s/src/main/scala/caliban/Http4sAdapter.scala @@ -33,6 +33,7 @@ object Http4sAdapter { def makeRestService[R, E](interpreter: GraphQLInterpreter[R, E]): HttpRoutes[RIO[R, *]] = makeHttpService(interpreter) + // to consolidate with the Akka piece. private def getGraphQLRequest(query: String, op: Option[String], vars: Option[String]): Result[GraphQLRequest] = { val variablesJs = vars .map(Json.fromString(_)) @@ -46,12 +47,10 @@ object Http4sAdapter { .as[GraphQLRequest] } - private def getGraphQLRequest(params: Map[String, String]): Result[GraphQLRequest] = { - val query = params.get("query").getOrElse("") - val variables = params.get("variables") - val op = params.get("operationName") - getGraphQLRequest(query, op, variables) - } + private def getGraphQLRequest(params: Map[String, String]): Result[GraphQLRequest] = + getGraphQLRequest(query = params.get("query").getOrElse(""), + op = params.get("operationName"), + vars = params.get("variables")) def makeHttpService[R, E](interpreter: GraphQLInterpreter[R, E]): HttpRoutes[RIO[R, *]] = { object dsl extends Http4sDsl[RIO[R, *]] From 520b9cb165e510b498fd16d8f29324eb651cf1bd Mon Sep 17 00:00:00 2001 From: phderome Date: Sun, 26 Jan 2020 19:01:42 -0500 Subject: [PATCH 5/9] sbt fmt --- http4s/src/main/scala/caliban/Http4sAdapter.scala | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/http4s/src/main/scala/caliban/Http4sAdapter.scala b/http4s/src/main/scala/caliban/Http4sAdapter.scala index ec5587cde..e89dd6120 100644 --- a/http4s/src/main/scala/caliban/Http4sAdapter.scala +++ b/http4s/src/main/scala/caliban/Http4sAdapter.scala @@ -48,9 +48,11 @@ object Http4sAdapter { } private def getGraphQLRequest(params: Map[String, String]): Result[GraphQLRequest] = - getGraphQLRequest(query = params.get("query").getOrElse(""), - op = params.get("operationName"), - vars = params.get("variables")) + getGraphQLRequest( + query = params.get("query").getOrElse(""), + op = params.get("operationName"), + vars = params.get("variables") + ) def makeHttpService[R, E](interpreter: GraphQLInterpreter[R, E]): HttpRoutes[RIO[R, *]] = { object dsl extends Http4sDsl[RIO[R, *]] @@ -148,7 +150,7 @@ object Http4sAdapter { ) .noSpaces ) - ) + ) ) } case "stop" => From 26ac48d4b45a9d70442ece7944e81df064d6e70b Mon Sep 17 00:00:00 2001 From: phderome Date: Sun, 26 Jan 2020 22:59:14 -0500 Subject: [PATCH 6/9] handle variables as a JSON object use AkkaHttp DSL combinator ~ simplified a bit --- .../main/scala/caliban/AkkaHttpAdapter.scala | 32 +++++++------------ .../main/scala/caliban/Http4sAdapter.scala | 14 +++----- 2 files changed, 17 insertions(+), 29 deletions(-) diff --git a/akka-http/src/main/scala/caliban/AkkaHttpAdapter.scala b/akka-http/src/main/scala/caliban/AkkaHttpAdapter.scala index a5ec0750b..2fa63e1e6 100644 --- a/akka-http/src/main/scala/caliban/AkkaHttpAdapter.scala +++ b/akka-http/src/main/scala/caliban/AkkaHttpAdapter.scala @@ -14,25 +14,18 @@ import scala.concurrent.ExecutionContext object AkkaHttpAdapter extends FailFastCirceSupport { - private def execute[R, E]( - interpreter: GraphQLInterpreter[R, E], - query: GraphQLRequest - ): URIO[R, GraphQLResponse[E]] = - interpreter.execute(query.query, query.operationName, query.variables.getOrElse(Map())) - private def executeHttpResponse[R, E]( interpreter: GraphQLInterpreter[R, E], request: GraphQLRequest ): URIO[R, HttpResponse] = - execute(interpreter, request) + 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()))) - private def getGraphQLRequest(query: String, op: Option[String], vars: Option[String]): Result[GraphQLRequest] = { - val variablesJs = vars - .map(Json.fromString(_)) - .filterNot { _.asObject.isEmpty } // we don't want to keep in variables='{}' - // as building the GraphQLRequest would fail with DecodingFailure([K, V]Map[K, V], List(DownField(variables))) + 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) @@ -53,16 +46,15 @@ object AkkaHttpAdapter extends FailFastCirceSupport { .future ) - concat( - get { - parameters(('query.as[String], 'operationName.?, 'variables.?)) { (query, op, vars) => - val request = getGraphQLRequest(query, op, vars) - request.fold(decodingFail => failWith(decodingFail), completeRequest) - } - }, + get { + parameters(('query.as[String], 'operationName.?, 'variables.?)) { + case (query, op, vars) => + getGraphQLRequest(query, op, vars) + .fold(decodingFail => failWith(decodingFail), completeRequest) + } + } ~ post { entity(as[GraphQLRequest]) { completeRequest } } - ) } } diff --git a/http4s/src/main/scala/caliban/Http4sAdapter.scala b/http4s/src/main/scala/caliban/Http4sAdapter.scala index e89dd6120..2cde572f0 100644 --- a/http4s/src/main/scala/caliban/Http4sAdapter.scala +++ b/http4s/src/main/scala/caliban/Http4sAdapter.scala @@ -8,7 +8,7 @@ import cats.effect.syntax.all._ import cats.~> import fs2.{ Pipe, Stream } import io.circe.Decoder.Result -import io.circe._ +import io.circe.Json import io.circe.parser._ import io.circe.syntax._ import org.http4s._ @@ -33,12 +33,8 @@ object Http4sAdapter { def makeRestService[R, E](interpreter: GraphQLInterpreter[R, E]): HttpRoutes[RIO[R, *]] = makeHttpService(interpreter) - // to consolidate with the Akka piece. private def getGraphQLRequest(query: String, op: Option[String], vars: Option[String]): Result[GraphQLRequest] = { - val variablesJs = vars - .map(Json.fromString(_)) - .filterNot { _.asObject.isEmpty } // we don't want to keep in variables='{}' - // as building the GraphQLRequest would fail with DecodingFailure([K, V]Map[K, V], List(DownField(variables))) + 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) @@ -49,9 +45,9 @@ object Http4sAdapter { private def getGraphQLRequest(params: Map[String, String]): Result[GraphQLRequest] = getGraphQLRequest( - query = params.get("query").getOrElse(""), - op = params.get("operationName"), - vars = params.get("variables") + params.getOrElse("query", ""), + params.get("operationName"), + params.get("variables") ) def makeHttpService[R, E](interpreter: GraphQLInterpreter[R, E]): HttpRoutes[RIO[R, *]] = { From 1a3f73ea46480e578f566495290b6c1a4c7fa6ed Mon Sep 17 00:00:00 2001 From: phderome Date: Mon, 27 Jan 2020 08:43:50 -0500 Subject: [PATCH 7/9] PR feedback --- .../main/scala/caliban/AkkaHttpAdapter.scala | 21 +++++++++++-------- .../main/scala/caliban/Http4sAdapter.scala | 2 +- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/akka-http/src/main/scala/caliban/AkkaHttpAdapter.scala b/akka-http/src/main/scala/caliban/AkkaHttpAdapter.scala index 2fa63e1e6..dde9d0074 100644 --- a/akka-http/src/main/scala/caliban/AkkaHttpAdapter.scala +++ b/akka-http/src/main/scala/caliban/AkkaHttpAdapter.scala @@ -2,6 +2,7 @@ package caliban import akka.http.scaladsl.model.MediaTypes.`application/json` 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 @@ -34,27 +35,29 @@ object AkkaHttpAdapter extends FailFastCirceSupport { .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._ - def completeRequest(request: GraphQLRequest)(implicit ec: ExecutionContext, runtime: Runtime[R]): StandardRoute = - complete( - runtime - .unsafeRunToFuture(executeHttpResponse(interpreter, request)) - .future - ) - get { parameters(('query.as[String], 'operationName.?, 'variables.?)) { case (query, op, vars) => getGraphQLRequest(query, op, vars) - .fold(decodingFail => failWith(decodingFail), completeRequest) + .fold(failWith, completeRequest(interpreter)) } } ~ post { - entity(as[GraphQLRequest]) { completeRequest } + entity(as[GraphQLRequest]) { completeRequest(interpreter) } } } } diff --git a/http4s/src/main/scala/caliban/Http4sAdapter.scala b/http4s/src/main/scala/caliban/Http4sAdapter.scala index 2cde572f0..5e991012f 100644 --- a/http4s/src/main/scala/caliban/Http4sAdapter.scala +++ b/http4s/src/main/scala/caliban/Http4sAdapter.scala @@ -63,7 +63,7 @@ object Http4sAdapter { } yield response case req @ GET -> Root => for { - query <- Task(getGraphQLRequest(req.params)).absolve + query <- Task.fromEither(getGraphQLRequest(req.params)) result <- executeToJson(interpreter, query) response <- Ok(result) } yield response From f7d12b04e2d9c869ad1758043c64569def0b33d9 Mon Sep 17 00:00:00 2001 From: phderome Date: Mon, 27 Jan 2020 19:47:52 -0500 Subject: [PATCH 8/9] silencer for scalac 2.13 on http4s HttpRoutes.of with partial functions --- build.sbt | 12 +++++++----- http4s/src/main/scala/caliban/Http4sAdapter.scala | 3 ++- 2 files changed, 9 insertions(+), 6 deletions(-) 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 5e991012f..492b48373 100644 --- a/http4s/src/main/scala/caliban/Http4sAdapter.scala +++ b/http4s/src/main/scala/caliban/Http4sAdapter.scala @@ -19,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 { @@ -50,7 +51,7 @@ object Http4sAdapter { params.get("variables") ) - def makeHttpService[R, E](interpreter: GraphQLInterpreter[R, E]): HttpRoutes[RIO[R, *]] = { + @silent def makeHttpService[R, E](interpreter: GraphQLInterpreter[R, E]): HttpRoutes[RIO[R, *]] = { object dsl extends Http4sDsl[RIO[R, *]] import dsl._ From 769abd10fa899ad9db159eedb1c537c39c510c4c Mon Sep 17 00:00:00 2001 From: phderome Date: Mon, 27 Jan 2020 19:54:15 -0500 Subject: [PATCH 9/9] fixing Symbol deprecation to satisfy CI and 2.13. --- akka-http/src/main/scala/caliban/AkkaHttpAdapter.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/akka-http/src/main/scala/caliban/AkkaHttpAdapter.scala b/akka-http/src/main/scala/caliban/AkkaHttpAdapter.scala index dde9d0074..2b2ae8851 100644 --- a/akka-http/src/main/scala/caliban/AkkaHttpAdapter.scala +++ b/akka-http/src/main/scala/caliban/AkkaHttpAdapter.scala @@ -50,7 +50,7 @@ object AkkaHttpAdapter extends FailFastCirceSupport { import akka.http.scaladsl.server.Directives._ get { - parameters(('query.as[String], 'operationName.?, 'variables.?)) { + parameters((Symbol("query").as[String], Symbol("operationName").?, Symbol("variables").?)) { case (query, op, vars) => getGraphQLRequest(query, op, vars) .fold(failWith, completeRequest(interpreter))