From c0071260b3c9921fbcb9d0a145a479233de77297 Mon Sep 17 00:00:00 2001 From: Tobias Jonas Date: Fri, 20 Dec 2019 01:02:23 +0100 Subject: [PATCH] #72 akka http without WS (#109) * Add akka-http adapter * Add akka-http rest sample * Remove ws related stuff * Cleanup akkahttp dependencies * Move http4s Example * Reuse example data from package * Update CI and compile new akkahttp module * Make function private * Rename * Fix Example Apps --- .circleci/config.yml | 4 +- .../main/scala/caliban/AkkaHttpAdapter.scala | 36 +++++++++ build.sbt | 20 ++++- .../scala/caliban/akkahttp/ExampleApp.scala | 75 +++++++++++++++++++ .../caliban/{ => http4s}/ExampleApp.scala | 7 +- 5 files changed, 136 insertions(+), 6 deletions(-) create mode 100644 akka-http/src/main/scala/caliban/AkkaHttpAdapter.scala create mode 100644 examples/src/main/scala/caliban/akkahttp/ExampleApp.scala rename examples/src/main/scala/caliban/{ => http4s}/ExampleApp.scala (92%) diff --git a/.circleci/config.yml b/.circleci/config.yml index 98938ebc8..ce82e0efc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -21,7 +21,7 @@ jobs: - checkout - restore_cache: key: sbtcache - - run: sbt ++2.12.10! coreJVM/test http4s/compile examples/compile catsInteropJVM/compile benchmarks/compile + - run: sbt ++2.12.10! coreJVM/test http4s/compile akkaHttp/compile examples/compile catsInteropJVM/compile benchmarks/compile - save_cache: key: sbtcache paths: @@ -35,7 +35,7 @@ jobs: - checkout - restore_cache: key: sbtcache - - run: sbt ++2.13.1! coreJVM/test http4s/compile examples/compile catsInteropJVM/compile + - run: sbt ++2.13.1! coreJVM/test http4s/compile akkaHttp/compile examples/compile catsInteropJVM/compile - save_cache: key: sbtcache paths: diff --git a/akka-http/src/main/scala/caliban/AkkaHttpAdapter.scala b/akka-http/src/main/scala/caliban/AkkaHttpAdapter.scala new file mode 100644 index 000000000..17eb9ba85 --- /dev/null +++ b/akka-http/src/main/scala/caliban/AkkaHttpAdapter.scala @@ -0,0 +1,36 @@ +package caliban + +import akka.http.scaladsl.model.MediaTypes.`application/json` +import akka.http.scaladsl.model.{HttpEntity, HttpResponse} +import akka.http.scaladsl.server.Route +import caliban.Value.NullValue +import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport +import io.circe.syntax._ +import zio.{Runtime, URIO} + +import scala.concurrent.ExecutionContext + +object AkkaHttpAdapter extends FailFastCirceSupport { + + + private def execute[R, Q, M, S, E]( + interpreter: GraphQL[R, Q, M, S, E], + query: GraphQLRequest + ): URIO[R, GraphQLResponse[E]] = + interpreter.execute(query.query, query.operationName, query.variables.getOrElse(Map())) + + def makeHttpService[R, Q, M, S, E](interpreter: GraphQL[R, Q, M, S, 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 + }) + } + } + } +} diff --git a/build.sbt b/build.sbt index f8d012ef1..1842e2f05 100644 --- a/build.sbt +++ b/build.sbt @@ -112,11 +112,29 @@ lazy val http4s = project ) .dependsOn(coreJVM) +lazy val akkaHttp = project + .in(file("akka-http")) + .settings(name := "caliban-akka-http") + .settings(commonSettings) + .settings( + libraryDependencies ++= Seq( + "com.typesafe.akka" %% "akka-http" % "10.1.11", + "com.typesafe.akka" %% "akka-stream" % "2.5.26", + "de.heikoseeberger" %% "akka-http-circe" % "1.29.1", + "io.circe" %% "circe-parser" % "0.12.3", + compilerPlugin( + ("org.typelevel" %% "kind-projector" % "0.11.0") + .cross(CrossVersion.full) + ) + ) + ) + .dependsOn(coreJVM) + lazy val examples = project .in(file("examples")) .settings(commonSettings) .settings(skip in publish := true) - .dependsOn(http4s, catsInteropJVM) + .dependsOn(akkaHttp, http4s, catsInteropJVM) lazy val benchmarks = project .in(file("benchmarks")) diff --git a/examples/src/main/scala/caliban/akkahttp/ExampleApp.scala b/examples/src/main/scala/caliban/akkahttp/ExampleApp.scala new file mode 100644 index 000000000..00f5e4a7b --- /dev/null +++ b/examples/src/main/scala/caliban/akkahttp/ExampleApp.scala @@ -0,0 +1,75 @@ +package caliban.akkahttp + +import akka.actor.ActorSystem +import akka.http.scaladsl.Http +import akka.http.scaladsl.server.Directives._ +import akka.stream.ActorMaterializer +import caliban.ExampleData.{Character, CharacterArgs, CharactersArgs, Role, sampleCharacters} +import caliban.GraphQL.graphQL +import caliban.schema.Annotations.{GQLDeprecated, GQLDescription} +import caliban.schema.GenericSchema +import caliban.{AkkaHttpAdapter, ExampleService, RootResolver} +import zio.clock.Clock +import zio.console.Console +import zio.stream.ZStream +import zio.{DefaultRuntime, URIO} + +import scala.io.StdIn + +object ExampleApp extends App with GenericSchema[Console with Clock] { + + implicit val system = ActorSystem() + implicit val materializer = ActorMaterializer() + implicit val executionContext = system.dispatcher + implicit val defaultRuntime = new DefaultRuntime {} + + implicit val roleSchema = gen[Role] + implicit val characterSchema = gen[Character] + implicit val characterArgsSchema = gen[CharacterArgs] + implicit val charactersArgsSchema = gen[CharactersArgs] + + case class Queries( + @GQLDescription("Return all characters from a given origin") + characters: CharactersArgs => URIO[Console, List[Character]], + @GQLDeprecated("Use `characters`") + character: CharacterArgs => URIO[Console, Option[Character]] + ) + case class Mutations(deleteCharacter: CharacterArgs => URIO[Console, Boolean]) + case class Subscriptions(characterDeleted: ZStream[Console, Nothing, String]) + + val interpreter = defaultRuntime.unsafeRun(ExampleService.make(sampleCharacters).map(service => { + graphQL( + RootResolver( + Queries( + args => service.getCharacters(args.origin), + args => service.findCharacter(args.name) + ), + Mutations(args => service.deleteCharacter(args.name)), + Subscriptions(service.deletedEvents))) + })) + + /** + * curl -X POST \ + * http://localhost:8080/api/graphql \ + * -H 'Host: localhost:8080' \ + * -H 'Content-Type: application/json' \ + * -d '{ + * "query": "query { characters { name }}" + * }' + */ + + val route = + path("api" / "graphql") { + AkkaHttpAdapter.makeHttpService(interpreter) + } ~ path("graphiql") { + getFromResource("graphiql.html") + } + + val bindingFuture = Http().bindAndHandle(route, "localhost", 8080) + println(s"Server online at http://localhost:8080/\nPress RETURN to stop...") + StdIn.readLine() + bindingFuture + .flatMap(_.unbind()) + .onComplete(_ => system.terminate()) + +} diff --git a/examples/src/main/scala/caliban/ExampleApp.scala b/examples/src/main/scala/caliban/http4s/ExampleApp.scala similarity index 92% rename from examples/src/main/scala/caliban/ExampleApp.scala rename to examples/src/main/scala/caliban/http4s/ExampleApp.scala index c7b2cdd54..41114da4e 100644 --- a/examples/src/main/scala/caliban/ExampleApp.scala +++ b/examples/src/main/scala/caliban/http4s/ExampleApp.scala @@ -1,10 +1,11 @@ -package caliban +package caliban.http4s import caliban.ExampleData._ import caliban.GraphQL._ import caliban.execution.QueryAnalyzer._ -import caliban.schema.Annotations.{ GQLDeprecated, GQLDescription } +import caliban.schema.Annotations.{GQLDeprecated, GQLDescription} import caliban.schema.GenericSchema +import caliban.{CalibanError, ExampleService, GraphQL, Http4sAdapter, RootResolver} import cats.data.Kleisli import cats.effect.Blocker import org.http4s.StaticFile @@ -15,7 +16,7 @@ import org.http4s.server.middleware.CORS import zio._ import zio.blocking.Blocking import zio.clock.Clock -import zio.console.{ putStrLn, Console } +import zio.console.{Console, putStrLn} import zio.interop.catz._ import zio.stream.ZStream