Skip to content

Commit

Permalink
upgrade to cats effect 3
Browse files Browse the repository at this point in the history
  • Loading branch information
mariolyon committed Apr 29, 2022
1 parent 29d7e31 commit ed54bae
Show file tree
Hide file tree
Showing 22 changed files with 491 additions and 465 deletions.
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,8 @@ out/

# Bloop/Metals
.bloop
.metals
.metals
.bsp

# VSCode
.vscode
59 changes: 59 additions & 0 deletions .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// mostly based on the config from Cats effect 3
// https://raw.githubusercontent.com/typelevel/cats-effect/4711d7915544c177c4fb64c079a73461fe83f708/.scalafmt.conf
version = 3.5.2
maxColumn = 110 # changed from 120 in cats effect 3 repo
align.preset = most
align.multiline = false
continuationIndent.defnSite = 2
assumeStandardLibraryStripMargin = true
docstrings.style = Asterisk
docstrings.wrapMaxColumn = 80
lineEndings = preserve
includeCurlyBraceInSelectChains = false
danglingParentheses.preset = true
optIn.annotationNewlines = true
newlines.alwaysBeforeMultilineDef = false
runner.dialect = scala213
rewrite.rules = [RedundantBraces]

project.excludeFilters = [
"core/shared/src/main/scala/zio/Has.scala",
"core/shared/src/main/scala/zio/ZLayer.scala",
"core/shared/src/main/scala-2.x/zio/VersionSpecific.scala"
]

rewrite.redundantBraces.generalExpressions = false
rewriteTokens = {
"⇒": "=>"
"→": "->"
"←": "<-"
}

# customisations below
align.tokens = [
{
code = ":"
owners = [
{ regex = "Term\\.Param", parents = [ "Ctor\\.Primary" ] },
{ parents = [ "Defn\\." ]}
]
},
{code = "("},
{code = "{"},
{code = "->"},
{code = "<-"},
{code = "="},
{code = "%%"},
{code = "%"},
{code = ":="},
{code = "=>", owner = "Case"}
]

verticalMultiline.atDefnSite = true
verticalMultiline.newlineAfterOpenParen = true
verticalMultiline.arityThreshold = 2

align.closeParenSite = true
align.openParenCallSite = true
danglingParentheses.callSite = true
danglingParentheses.defnSite = true
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
## doobie-http4s-sangria-grapgql-example

Example app that uses doobie, http4s, and Sangria to serve GraphQL.
Example app that uses doobie, http4s, Cats effect 3 and Sangria to serve GraphQL.

This was bootstrapped from an [example](https://github.com/OlegIlyenko/sangria-http4s-graalvm-example) by @OlegIlyenko. Thanks!

Expand All @@ -19,6 +19,13 @@ or if you have bloop set up you can do
bloop run core

The go to http://localhost:8080 and play around.
For example try this query, to see a list of cities starting with "Bei":

query {
cities(namePattern: "Bei%") {
name
}
}

When you're done, ^C to kill the Scala server and

Expand Down
22 changes: 9 additions & 13 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
lazy val catsEffectVersion = "2.5.4"
lazy val catsVersion = "2.6.1"
lazy val catsEffectVersion = "3.3.11"
lazy val catsVersion = "2.7.0"
lazy val circeVersion = "0.14.1"
lazy val doobieVersion = "0.13.4"
lazy val fs2Version = "2.5.10"
lazy val doobieVersion = "1.0.0-RC2"
lazy val fs2Version = "3.2.5"
lazy val kindProjectorVersion = "0.13.2"
lazy val log4catsVersion = "1.1.1"
lazy val sangriaCirceVersion = "1.3.0"
lazy val sangriaVersion = "2.1.6"
lazy val log4catsVersion = "2.2.0"
lazy val sangriaCirceVersion = "1.3.2"
lazy val sangriaVersion = "3.0.0"
lazy val scala13Version = "2.13.8"
lazy val http4sVersion = "0.21.33"
lazy val slf4jVersion = "1.7.30"
lazy val http4sVersion = "0.23.11"

ThisBuild / scalaVersion := scala13Version
//ThisBuild / scalacOptions += "-P:semanticdb:synthetics:on"
Expand Down Expand Up @@ -49,10 +48,8 @@ lazy val scalacSettings = Seq(
// "-Ywarn-unused:params", // Warn if a value parameter is unused.
"-Ywarn-unused:privates", // Warn if a private member is unused.
"-Ywarn-value-discard", // Warn when non-Unit expression results are unused.
// "-Ywarn-macros:before", // via som
"-Ymacro-annotations",
"-Yrangepos" // for longer squiggles
// "-Ypartial-unification"
)
,
(Compile / console / scalacOptions) --= Seq("-Xfatal-warnings", "-Ywarn-unused:imports", "-Yno-imports"),
Expand Down Expand Up @@ -98,7 +95,6 @@ lazy val core = project
"org.http4s" %% "http4s-blaze-server" % http4sVersion,
"org.http4s" %% "http4s-circe" % http4sVersion,
"io.circe" %% "circe-optics" % circeVersion,
"io.chrisdavenport" %% "log4cats-slf4j" % log4catsVersion,
"org.slf4j" % "slf4j-simple" % slf4jVersion,
"org.typelevel" %% "log4cats-slf4j" % log4catsVersion
)
)
40 changes: 24 additions & 16 deletions modules/core/src/main/scala/GraphQL.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,41 @@

package demo

import io.circe.{ Json, JsonObject }
import io.circe.Json
import io.circe.JsonObject

/** An algebra of operations in F that evaluate GraphQL requests. */
/**
* An algebra of operations in F that evaluate GraphQL requests.
*/
trait GraphQL[F[_]] {

/**
* Executes a JSON-encoded request in the standard POST encoding, described thus in the spec:
* Executes a JSON-encoded request in the standard POST encoding, described
* thus in the spec:
*
* A standard GraphQL POST request should use the application/json content type, and include a
* JSON-encoded body of the following form:
* A standard GraphQL POST request should use the application/json content
* type, and include a JSON-encoded body of the following form:
*
* {
* "query": "...",
* "operationName": "...",
* "variables": { "myVariable": "someValue", ... }
* }
* { "query": "...", "operationName": "...", "variables": { "myVariable":
* "someValue", ... } }
*
* `operationName` and `variables` are optional fields. `operationName` is only required if
* multiple operations are present in the query.
* @return either an error Json or result Json
* `operationName` and `variables` are optional fields. `operationName` is
* only required if multiple operations are present in the query.
* @return
* either an error Json or result Json
*/
def query(request: Json): F[Either[Json, Json]]

/**
* Executes a request given a `query`, optional `operationName`, and `varianbles`.
* @return either an error Json or result Json
* Executes a request given a `query`, optional `operationName`, and
* `varianbles`.
* @return
* either an error Json or result Json
*/
def query(query: String, operationName: Option[String], variables: JsonObject): F[Either[Json, Json]]
def query(
query: String,
operationName: Option[String],
variables: JsonObject
): F[Either[Json, Json]]

}
20 changes: 10 additions & 10 deletions modules/core/src/main/scala/GraphQLRoutes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@ import org.http4s.dsl._

object GraphQLRoutes {

/** An `HttpRoutes` that maps the standard `/graphql` path to a `GraphQL` instace. */
def apply[F[_]: Sync](
graphQL: GraphQL[F]
): HttpRoutes[F] = {
/**
* An `HttpRoutes` that maps the standard `/graphql` path to a `GraphQL`
* instance.
*/
def apply[F[_]: Async](graphQL: GraphQL[F]): HttpRoutes[F] = {
object dsl extends Http4sDsl[F]; import dsl._
HttpRoutes.of[F] {
case req @ POST -> Root / "graphql" =>
req.as[Json].flatMap(graphQL.query).flatMap {
case Right(json) => Ok(json)
case Left(json) => BadRequest(json)
}
HttpRoutes.of[F] { case req @ POST -> Root / "graphql" =>
req.as[Json].flatMap(graphQL.query).flatMap {
case Right(json) => Ok(json)
case Left(json) => BadRequest(json)
}
}
}

Expand Down
104 changes: 47 additions & 57 deletions modules/core/src/main/scala/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,96 +10,86 @@ import demo.sangria.SangriaGraphQL
import demo.schema._
import doobie._
import doobie.hikari._
import doobie.util.ExecutionContexts
import io.chrisdavenport.log4cats.Logger
import io.chrisdavenport.log4cats.slf4j.Slf4jLogger
import repo._
import _root_.sangria.schema._
import cats.effect.std.Dispatcher
import org.http4s._
import org.http4s.blaze.server.BlazeServerBuilder
import org.http4s.dsl._
import org.http4s.headers.Location
import org.http4s.implicits._
import org.http4s.server.Server
import org.http4s.server.blaze._
import scala.concurrent.ExecutionContext
import scala.concurrent.ExecutionContext.global
import cats.effect.unsafe
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import org.http4s.implicits._

object Main extends IOApp {
implicit val ioRuntime = unsafe.IORuntime
implicit val ec = ioRuntime.global.compute

// Construct a transactor for connecting to the database.
def transactor[F[_]: Async: ContextShift](
blocker: Blocker
): Resource[F, HikariTransactor[F]] =
ExecutionContexts.fixedThreadPool[F](10).flatMap { ce =>
HikariTransactor.newHikariTransactor(
"org.postgresql.Driver",
"jdbc:postgresql:world",
"user",
"password",
ce,
blocker
)
}
val hikariDataSourceConfig = {
val config = new HikariConfig()
config.setDriverClassName("org.postgresql.Driver")
config.setJdbcUrl("jdbc:postgresql:world")
config.setUsername("user")
config.setPassword("password")
config.setMaximumPoolSize(5)
config
}

def transactor[F[_]: Async]: IO[Transactor[F]] =
IO.pure(
HikariTransactor
.apply[F](new HikariDataSource(hikariDataSourceConfig), ioRuntime.global.compute)
)

// Construct a GraphQL implementation based on our Sangria definitions.
def graphQL[F[_]: Effect: ContextShift: Logger](
transactor: Transactor[F],
blockingContext: ExecutionContext
def graphQLServerFor[F[_]: Async](
dispatcher: Dispatcher[F],
transactor: Transactor[F]
): GraphQL[F] =
SangriaGraphQL[F](
Schema(
query = QueryType[F],
mutation = Some(MutationType[F])
),
Schema(query = QueryType.apply[F](dispatcher), mutation = Some(MutationType[F](dispatcher))),
WorldDeferredResolver[F],
MasterRepo.fromTransactor(transactor).pure[F],
blockingContext
ec
)

// Playground or else redirect to playground
def playgroundOrElse[F[_]: Sync: ContextShift](
blocker: Blocker
): HttpRoutes[F] = {
object dsl extends Http4sDsl[F]; import dsl._
def playgroundOrElse[F[_]: Async]: HttpRoutes[F] = {
object dsl extends Http4sDsl[F];
import dsl._
HttpRoutes.of[F] {

case GET -> Root / "playground.html" =>
StaticFile
.fromResource[F]("/assets/playground.html", blocker)
.getOrElseF(NotFound())
StaticFile.fromResource[F]("/assets/playground.html").getOrElseF(NotFound())

case _ =>
PermanentRedirect(Location(Uri.uri("/playground.html")))
PermanentRedirect(Location(uri"/playground.html"))

}
}

// Resource that mounts the given `routes` and starts a server.
def server[F[_]: ConcurrentEffect: ContextShift: Timer](
routes: HttpRoutes[F]
): Resource[F, Server[F]] =
BlazeServerBuilder[F](global)
.bindHttp(8080, "localhost")
def server[F[_]: Async](routes: HttpRoutes[F]): Resource[F, Server] =
BlazeServerBuilder[F]
.withExecutionContext(ioRuntime.global.compute)
.bindHttp(8080, "0.0.0.0")
.withHttpApp(routes.orNotFound)
.resource

// Resource that constructs our final server.
def resource[F[_]: ConcurrentEffect: ContextShift: Timer](
implicit L: Logger[F]
): Resource[F, Server[F]] =
for {
b <- Blocker[F]
xa <- transactor[F](b)
gql = graphQL[F](xa, b.blockingContext)
rts = GraphQLRoutes[F](gql) <+> playgroundOrElse(b)
svr <- server[F](rts)
} yield svr

// Our entry point starts the server and blocks forever.
def run(args: List[String]): IO[ExitCode] = {
implicit val log = Slf4jLogger.getLogger[IO]
resource[IO].use(_ => IO.never.as(ExitCode.Success))
val resource =
for {
xa <- Resource.eval(transactor[IO])
dispatcher <- Dispatcher[IO]
graphQlServer = graphQLServerFor[IO](dispatcher, xa)
routes = GraphQLRoutes[IO](graphQlServer) <+> playgroundOrElse
server <- server[IO](routes)
} yield server

resource.use(_ => IO.never.as(ExitCode.Success))
}

}

11 changes: 5 additions & 6 deletions modules/core/src/main/scala/model/City.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@
package demo.model

final case class City(
id: Int,
name: String,
countryCode: String,
district: String,
population: Int,
)
id: Int,
name: String,
countryCode: String,
district: String,
population: Int)
Loading

0 comments on commit ed54bae

Please sign in to comment.