Skip to content

Commit

Permalink
Merge pull request #85 from niqdev/caliban-pagination
Browse files Browse the repository at this point in the history
[ecosystem] Add Caliban pagination
  • Loading branch information
niqdev authored Jul 21, 2020
2 parents 790e50b + 9e51437 commit 0199f43
Show file tree
Hide file tree
Showing 21 changed files with 1,167 additions and 43 deletions.
11 changes: 11 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,18 @@ lazy val versions = new {
val catsCore = "2.1.1"
val catsEffect = "2.1.4"
val catsRetry = "1.1.1"
val log4cats = "1.1.1"
val zio = "1.0.0-RC21-2"
val zioInteropCats = "2.1.3.0-RC16"
val circe = "0.13.0"
val newtype = "0.4.4"
val refined = "0.9.15"
val squants = "1.6.0"
val enumeratum = "1.6.1"
val fs2 = "2.4.2"
val http4s = "0.21.6"
val doobie = "0.9.0"
val flyway = "6.5.0"
val caliban = "0.9.0"
val magnolia = "0.16.0"
val droste = "0.8.0"
Expand All @@ -39,13 +42,16 @@ lazy val dependencies = new {
"org.typelevel" %% "cats-core" % versions.catsCore,
"org.typelevel" %% "cats-effect" % versions.catsEffect,
"com.github.cb372" %% "cats-retry" % versions.catsRetry,
"io.chrisdavenport" %% "log4cats-core" % versions.log4cats,
"io.chrisdavenport" %% "log4cats-slf4j" % versions.log4cats,
"dev.zio" %% "zio" % versions.zio,
"dev.zio" %% "zio-streams" % versions.zio,
"dev.zio" %% "zio-interop-cats" % versions.zioInteropCats,
"io.circe" %% "circe-core" % versions.circe,
"io.circe" %% "circe-generic" % versions.circe,
"io.circe" %% "circe-parser" % versions.circe,
"io.circe" %% "circe-refined" % versions.circe,
"io.estatico" %% "newtype" % versions.newtype,
"eu.timepit" %% "refined" % versions.refined,
"org.typelevel" %% "squants" % versions.squants,
"com.beachape" %% "enumeratum" % versions.enumeratum,
Expand All @@ -58,7 +64,9 @@ lazy val dependencies = new {
"org.http4s" %% "http4s-blaze-client" % versions.http4s,
"org.http4s" %% "http4s-prometheus-metrics" % versions.http4s,
"org.tpolecat" %% "doobie-core" % versions.doobie,
"org.tpolecat" %% "doobie-refined" % versions.doobie,
"org.tpolecat" %% "doobie-h2" % versions.doobie,
"org.flywaydb" % "flyway-core" % versions.flyway,
"com.github.ghostdogpr" %% "caliban" % versions.caliban,
"com.github.ghostdogpr" %% "caliban-http4s" % versions.caliban,
"com.github.ghostdogpr" %% "caliban-cats" % versions.caliban,
Expand All @@ -83,6 +91,8 @@ lazy val commonSettings = Seq(
// https://tpolecat.github.io/2017/04/25/scalac-flags.html
// https://github.com/DavidGregory084/sbt-tpolecat
// https://nathankleyn.com/2019/05/13/recommended-scalac-flags-for-2-13
// http://eed3si9n.com/stricter-scala-with-xlint-xfatal-warnings-and-scalafix
// https://alexn.org/blog/2020/05/26/scala-fatal-warnings.html
scalacOptions ++= Seq(
"-encoding",
"UTF-8", // source files are in UTF-8
Expand All @@ -93,6 +103,7 @@ lazy val commonSettings = Seq(
"-language:implicitConversions", // allow definition of implicit functions called views
"-Xlint", // enable handy linter warnings
"-Wconf:any:error", // configurable warnings see https://github.com/scala/scala/pull/8373
"-Ymacro-annotations", // required by newtype
"-Xsource:2.13"
),
addCompilerPlugin("org.typelevel" %% "kind-projector" % versions.kindProjector cross CrossVersion.full)
Expand Down
7 changes: 7 additions & 0 deletions docs/fp-ecosystem.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ sbt "test:testOnly *fs2*"
* [doobie](https://tpolecat.github.io/doobie) (Documentation)
* [Pure Functional Database Programming with Fixpoint Types](https://www.youtube.com/watch?v=7xSfLPD6tiQ) by Rob Norris (video)

### Examples

```bash
# run
sbt "ecosystem/runMain com.github.niqdev.doobie.ExampleH2"
```

## shapeless

### Resources
Expand Down
81 changes: 81 additions & 0 deletions docs/graphql-error.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Since Node2 is also a root node, it needs to be of higher-kinded i.e. Node2[F[_]]

---
# how to reproduce the error

final case class Queries[F[_]](
example: Int => F[Example[F]],
node2: Node2[F]
)
object Queries {
private[this] def resolver[F[_]: Effect](services: Services[F]): Queries[F] =
Queries(
example = a => Effect[F].pure(Example[F](NodeId(Base64String.unsafeFrom("aGVsbG8K")), b => Effect[F].pure(b))),
node2 = Example[F](NodeId(Base64String.unsafeFrom("aGVsbG8K")), b => Effect[F].pure(b))
)
}
}

# if Node2[F[_]] is not higher-kinded too it cause the error below ONLY because it's also a root node
@GQLInterface
sealed trait Node2 {
def id: NodeId
}

case class Example[F[_]](id: NodeId, f: Int => F[Int]) extends Node2

---
# error (not sure why it's not always printed)
[error] /PATH/REDACTED/scala-fp/modules/ecosystem/src/main/scala/com/github/niqdev/caliban/pagination/schema.scala:64:14: illegal cyclic reference involving type F
[error] case class Example[F[_]](id: NodeId, f: Int => F[Int]) extends Node2

---
# error

[info] welcome to sbt 1.3.13 (AdoptOpenJDK Java 1.8.0_242)
[info] loading settings for project scala-fp-build from plugins.sbt ...
[info] loading project definition from /PATH/REDACTED/scala-fp/project
[info] loading settings for project root from build.sbt ...
[info] set current project to scala-fp (in build file:/PATH/REDACTED/scala-fp/)
[success] Total time: 0 s, completed 16-Jul-2020 19:40:18
[info] Compiling 4 Scala sources to /PATH/REDACTED/scala-fp/modules/common/target/scala-2.13/classes ...
[warn] There may be incompatibilities among your library dependencies; run 'evicted' to see detailed eviction warnings.
[warn] There may be incompatibilities among your library dependencies; run 'evicted' to see detailed eviction warnings.
[info] Compiling 26 Scala sources to /PATH/REDACTED/scala-fp/modules/ecosystem/target/scala-2.13/classes ...
[error]
[error] no-symbol does not have a type constructor (this may indicate scalac cannot find fundamental classes)
[error] while compiling: /PATH/REDACTED/scala-fp/modules/ecosystem/src/main/scala/com/github/niqdev/caliban/pagination/queries.scala
[error] during phase: typer
[error] library version: version 2.13.3
[error] compiler version: version 2.13.3
[error] reconstructed args: -bootclasspath REDACTED
[error]
[error] last tree to typer: Ident(Base64String)
[error] tree position: line 48 of /PATH/REDACTED/scala-fp/modules/ecosystem/src/main/scala/com/github/niqdev/caliban/pagination/schema.scala
[error] symbol: <none>
[error] symbol definition: <none> (a NoSymbol)
[error] symbol package: <none>
[error] symbol owners:
[error] call site: object Cursor in object schema in package pagination
[error]
[error] == Source file context for tree position ==
[error]
[error] 45
[error] 46 @newtype case class Offset(value: NonNegLong)
[error] 47 @newtype case class NodeId(value: Base64String)
[error] 48 @newtype case class Cursor(value: Base64String)
[error] 49 object Cursor {
[error] 50 final val prefix = "cursor:v1:"
[error] 51 }
[info] Compiling 21 Scala sources to /PATH/REDACTED/scala-fp/modules/fp/target/scala-2.13/classes ...
[info] Compiling 1 Scala source to /PATH/REDACTED/scala-fp/modules/common/target/scala-2.13/test-classes ...
[error] /PATH/REDACTED/scala-fp/modules/ecosystem/src/main/scala/com/github/niqdev/caliban/pagination/queries.scala:43:22: Cannot find a Schema for type com.github.niqdev.caliban.pagination.queries.Queries[F].
[error]
[error] Caliban derives a Schema automatically for basic Scala types, case classes and sealed traits, but
[error] you need to manually provide an implicit Schema for other types that could be nested in com.github.niqdev.caliban.pagination.queries.Queries[F].
[error] If you use a custom type as an argument, you also need to provide an implicit ArgBuilder for that type.
[error] See https://ghostdogpr.github.io/caliban/docs/schema.html for more information.
[error]
[error] Error occurred in an application involving default arguments.
[error] GraphQL.graphQL(RootResolver(resolver[F](services)))
[error]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CREATE SCHEMA IF NOT EXISTS example;
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
CREATE TABLE IF NOT EXISTS example.user (
id UUID NOT NULL DEFAULT RANDOM_UUID(),
name VARCHAR(250) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP(),
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP(),
CONSTRAINT user_pkey PRIMARY KEY (id)
);

-- index on TEXT not supported by H2
CREATE INDEX IF NOT EXISTS user_name_idx ON example.user (name);
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
INSERT INTO example.user (id, name) VALUES ('e1fba794-54f5-4231-a448-f464aac512e5', 'typelevel');
INSERT INTO example.user (id, name) VALUES ('f0fbe131-3f65-4145-b373-5bbfc1c9a1ae', 'zio');
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
CREATE TABLE IF NOT EXISTS example.repository (
id UUID NOT NULL DEFAULT RANDOM_UUID(),
user_id UUID NOT NULL,
name VARCHAR(250) NOT NULL,
url VARCHAR(250) NOT NULL,
is_fork BOOLEAN NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP(),
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP(),
CONSTRAINT repository_pkey PRIMARY KEY (id)
);

CREATE INDEX IF NOT EXISTS repository_user_id_idx ON example.repository (user_id);
CREATE INDEX IF NOT EXISTS repository_name_idx ON example.repository (name);

ALTER TABLE example.repository ADD FOREIGN KEY (user_id) REFERENCES example.user (id);
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
-- typelevel
INSERT INTO example.repository (user_id, name, url, is_fork) VALUES ('e1fba794-54f5-4231-a448-f464aac512e5', 'cats', 'https://github.com/typelevel/cats', FALSE);
INSERT INTO example.repository (user_id, name, url, is_fork) VALUES ('e1fba794-54f5-4231-a448-f464aac512e5', 'cats-effect', 'https://github.com/typelevel/cats-effect', FALSE);
INSERT INTO example.repository (user_id, name, url, is_fork) VALUES ('e1fba794-54f5-4231-a448-f464aac512e5', 'cats-mtl', 'https://github.com/typelevel/cats-mtl', FALSE);
INSERT INTO example.repository (user_id, name, url, is_fork) VALUES ('e1fba794-54f5-4231-a448-f464aac512e5', 'scalacheck', 'https://github.com/typelevel/scalacheck', FALSE);
INSERT INTO example.repository (user_id, name, url, is_fork) VALUES ('e1fba794-54f5-4231-a448-f464aac512e5', 'kind-projector', 'https://github.com/typelevel/kind-projector', FALSE);
INSERT INTO example.repository (user_id, name, url, is_fork) VALUES ('e1fba794-54f5-4231-a448-f464aac512e5', 'algebra', 'https://github.com/typelevel/algebra', FALSE);
INSERT INTO example.repository (user_id, name, url, is_fork) VALUES ('e1fba794-54f5-4231-a448-f464aac512e5', 'discipline', 'https://github.com/typelevel/discipline', FALSE);
INSERT INTO example.repository (user_id, name, url, is_fork) VALUES ('e1fba794-54f5-4231-a448-f464aac512e5', 'squants', 'https://github.com/typelevel/squants', FALSE);
INSERT INTO example.repository (user_id, name, url, is_fork) VALUES ('e1fba794-54f5-4231-a448-f464aac512e5', 'simulacrum', 'https://github.com/typelevel/simulacrum', FALSE);
INSERT INTO example.repository (user_id, name, url, is_fork) VALUES ('e1fba794-54f5-4231-a448-f464aac512e5', 'kittens', 'https://github.com/typelevel/kittens', FALSE);
INSERT INTO example.repository (user_id, name, url, is_fork) VALUES ('e1fba794-54f5-4231-a448-f464aac512e5', 'jawn', 'https://github.com/typelevel/jawn', FALSE);
INSERT INTO example.repository (user_id, name, url, is_fork) VALUES ('e1fba794-54f5-4231-a448-f464aac512e5', 'shapeless', 'https://github.com/milessabin/shapeless', FALSE);
INSERT INTO example.repository (user_id, name, url, is_fork) VALUES ('e1fba794-54f5-4231-a448-f464aac512e5', 'refined', 'https://github.com/fthomas/refined', FALSE);
INSERT INTO example.repository (user_id, name, url, is_fork) VALUES ('e1fba794-54f5-4231-a448-f464aac512e5', 'scala-steward', 'https://github.com/fthomas/scala-steward', FALSE);
INSERT INTO example.repository (user_id, name, url, is_fork) VALUES ('e1fba794-54f5-4231-a448-f464aac512e5', 'scodec', 'https://github.com/scodec/scodec', FALSE);
INSERT INTO example.repository (user_id, name, url, is_fork) VALUES ('e1fba794-54f5-4231-a448-f464aac512e5', 'ciris', 'https://github.com/vlovgr/ciris', FALSE);
INSERT INTO example.repository (user_id, name, url, is_fork) VALUES ('e1fba794-54f5-4231-a448-f464aac512e5', 'doobie', 'https://github.com/tpolecat/doobie', FALSE);
INSERT INTO example.repository (user_id, name, url, is_fork) VALUES ('e1fba794-54f5-4231-a448-f464aac512e5', 'fs2', 'https://github.com/functional-streams-for-scala/fs2', FALSE);
INSERT INTO example.repository (user_id, name, url, is_fork) VALUES ('e1fba794-54f5-4231-a448-f464aac512e5', 'monix', 'https://github.com/monix/monix', FALSE);
INSERT INTO example.repository (user_id, name, url, is_fork) VALUES ('e1fba794-54f5-4231-a448-f464aac512e5', 'Monocle', 'https://github.com/optics-dev/Monocle', FALSE);
INSERT INTO example.repository (user_id, name, url, is_fork) VALUES ('e1fba794-54f5-4231-a448-f464aac512e5', 'scala', 'https://github.com/typelevel/scala', TRUE);

-- zio
INSERT INTO example.repository (user_id, name, url, is_fork) VALUES ('f0fbe131-3f65-4145-b373-5bbfc1c9a1ae', 'zio', 'https://github.com/zio/zio', FALSE);
INSERT INTO example.repository (user_id, name, url, is_fork) VALUES ('f0fbe131-3f65-4145-b373-5bbfc1c9a1ae', 'zio-config', 'https://github.com/zio/zio-config', FALSE);
INSERT INTO example.repository (user_id, name, url, is_fork) VALUES ('f0fbe131-3f65-4145-b373-5bbfc1c9a1ae', 'zio-keeper', 'https://github.com/zio/zio-keeper', FALSE);
INSERT INTO example.repository (user_id, name, url, is_fork) VALUES ('f0fbe131-3f65-4145-b373-5bbfc1c9a1ae', 'zio-nio', 'https://github.com/zio/zio-nio', FALSE);
INSERT INTO example.repository (user_id, name, url, is_fork) VALUES ('f0fbe131-3f65-4145-b373-5bbfc1c9a1ae', 'zio-redis', 'https://github.com/zio/zio-redis', FALSE);
INSERT INTO example.repository (user_id, name, url, is_fork) VALUES ('f0fbe131-3f65-4145-b373-5bbfc1c9a1ae', 'zio-sqs', 'https://github.com/zio/zio-sqs', FALSE);
INSERT INTO example.repository (user_id, name, url, is_fork) VALUES ('f0fbe131-3f65-4145-b373-5bbfc1c9a1ae', 'zio-s3', 'https://github.com/zio/zio-s3', FALSE);
INSERT INTO example.repository (user_id, name, url, is_fork) VALUES ('f0fbe131-3f65-4145-b373-5bbfc1c9a1ae', 'zio-logging', 'https://github.com/zio/zio-logging', FALSE);
INSERT INTO example.repository (user_id, name, url, is_fork) VALUES ('f0fbe131-3f65-4145-b373-5bbfc1c9a1ae', 'zio-kafka', 'https://github.com/zio/zio-kafka', FALSE);
INSERT INTO example.repository (user_id, name, url, is_fork) VALUES ('f0fbe131-3f65-4145-b373-5bbfc1c9a1ae', 'zio-query', 'https://github.com/zio/zio-query', FALSE);
INSERT INTO example.repository (user_id, name, url, is_fork) VALUES ('f0fbe131-3f65-4145-b373-5bbfc1c9a1ae', 'zio-actors', 'https://github.com/zio/zio-actors', FALSE);
INSERT INTO example.repository (user_id, name, url, is_fork) VALUES ('f0fbe131-3f65-4145-b373-5bbfc1c9a1ae', 'zio-lambda', 'https://github.com/zio/zio-lambda', FALSE);
INSERT INTO example.repository (user_id, name, url, is_fork) VALUES ('f0fbe131-3f65-4145-b373-5bbfc1c9a1ae', 'zio-ftp', 'https://github.com/zio/zio-ftp', FALSE);
INSERT INTO example.repository (user_id, name, url, is_fork) VALUES ('f0fbe131-3f65-4145-b373-5bbfc1c9a1ae', 'zio-web', 'https://github.com/zio/zio-web', FALSE);
INSERT INTO example.repository (user_id, name, url, is_fork) VALUES ('f0fbe131-3f65-4145-b373-5bbfc1c9a1ae', 'zio-sql', 'https://github.com/zio/zio-sql', FALSE);
INSERT INTO example.repository (user_id, name, url, is_fork) VALUES ('f0fbe131-3f65-4145-b373-5bbfc1c9a1ae', 'zio-metrics', 'https://github.com/zio/zio-metrics', FALSE);
INSERT INTO example.repository (user_id, name, url, is_fork) VALUES ('f0fbe131-3f65-4145-b373-5bbfc1c9a1ae', 'zio-codec', 'https://github.com/zio/zio-codec', FALSE);
INSERT INTO example.repository (user_id, name, url, is_fork) VALUES ('f0fbe131-3f65-4145-b373-5bbfc1c9a1ae', 'zio-cli', 'https://github.com/zio/zio-cli', FALSE);
INSERT INTO example.repository (user_id, name, url, is_fork) VALUES ('f0fbe131-3f65-4145-b373-5bbfc1c9a1ae', 'zio-concurrent', 'https://github.com/zio/zio-concurrent', FALSE);
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,43 @@ package com.github.niqdev.caliban

import caliban.Http4sAdapter
import caliban.interop.cats.implicits.CatsEffectGraphQL
import cats.effect.{ ConcurrentEffect, ExitCode, IO, IOApp, Resource, Timer }
import cats.effect.{ ConcurrentEffect, ContextShift, ExitCode, IO, IOApp, Resource, Timer }
import com.github.niqdev.caliban.pagination.queries.Queries
import com.github.niqdev.caliban.pagination.repositories.Repositories
import com.github.niqdev.caliban.pagination.services.Services
import com.github.niqdev.doobie.Database
import io.chrisdavenport.log4cats.Logger
import io.chrisdavenport.log4cats.slf4j.Slf4jLogger
import org.http4s.server.Router
import org.http4s.server.blaze.BlazeServerBuilder
import org.http4s.syntax.kleisli.http4sKleisliResponseSyntaxOptionT
import zio.Runtime

import scala.concurrent.ExecutionContext

// sbt -jvm-debug 5005 "ecosystem/runMain com.github.niqdev.caliban.CalibanCatsHttp4sApp"
object CalibanCatsHttp4sApp extends IOApp {

implicit val runtime: Runtime[Any] = Runtime.default
private[this] implicit val runtime: Runtime[Any] = Runtime.default

override def run(args: List[String]): IO[ExitCode] =
server[IO]
.use(_ => IO.never)
.as(ExitCode.Success)
Slf4jLogger
.create[IO]
.flatMap(implicit logger =>
server[IO]
.use(_ => IO.never)
.as(ExitCode.Success)
)

def server[F[_]: ConcurrentEffect: Timer]: Resource[F, Unit] =
private[caliban] def server[F[_]: ConcurrentEffect: ContextShift: Timer: Logger]: Resource[F, Unit] =
for {
interpreter <- Resource.liftF(ExampleApi.api.interpreterAsync)
_ <- Resource.liftF(Logger[F].info("Start server..."))
xa <- Database.initInMemory[F]
repositories <- Repositories.make[F](xa)
services <- Services.make[F](repositories)
api = ExampleApi.api |+| Queries.api[F](services)
_ <- Resource.liftF(Logger[F].info(s"GraphQL Schema:\n${api.render}"))
interpreter <- Resource.liftF(api.interpreterAsync)
httpApp = Router(
"/api/graphql" -> Http4sAdapter.makeHttpServiceF(interpreter)
).orNotFound
Expand Down
Loading

0 comments on commit 0199f43

Please sign in to comment.