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

HTTP GET support for Akka and http4s #177

Merged
merged 9 commits into from
Jan 28, 2020
Merged
Show file tree
Hide file tree
Changes from all 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
60 changes: 42 additions & 18 deletions akka-http/src/main/scala/caliban/AkkaHttpAdapter.scala
Original file line number Diff line number Diff line change
@@ -1,39 +1,63 @@
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 }

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) }
}
}
}
}
12 changes: 7 additions & 5 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -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",
Expand Down Expand Up @@ -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)
Expand Down
29 changes: 27 additions & 2 deletions http4s/src/main/scala/caliban/Http4sAdapter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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._
Expand All @@ -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 {

Expand All @@ -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._

Expand All @@ -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
}
}

Expand Down