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 6 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
57 changes: 39 additions & 18 deletions akka-http/src/main/scala/caliban/AkkaHttpAdapter.scala
Original file line number Diff line number Diff line change
@@ -1,39 +1,60 @@
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.{ 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 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 =
ghostdogpr marked this conversation as resolved.
Show resolved Hide resolved
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)
ghostdogpr marked this conversation as resolved.
Show resolved Hide resolved
}
} ~
post {
entity(as[GraphQLRequest]) { completeRequest }
}
}
}
}
26 changes: 25 additions & 1 deletion 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 @@ -32,6 +33,23 @@ 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.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")
)

def makeHttpService[R, E](interpreter: GraphQLInterpreter[R, E]): HttpRoutes[RIO[R, *]] = {
object dsl extends Http4sDsl[RIO[R, *]]
import dsl._
Expand All @@ -43,6 +61,12 @@ object Http4sAdapter {
result <- executeToJson(interpreter, query)
response <- Ok(result)
} yield response
case req @ GET -> Root =>
for {
query <- Task(getGraphQLRequest(req.params)).absolve
ghostdogpr marked this conversation as resolved.
Show resolved Hide resolved
result <- executeToJson(interpreter, query)
response <- Ok(result)
} yield response
}
}

Expand Down