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

Http4s todo app #45

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
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
33 changes: 31 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,22 @@ lazy val core = (project in file("core"))
)
)

lazy val catsEffectsIOModule = (project in file("modules/cats-effect-io"))
.settings(commonSettings)
.settings(
name := "query-cats-effect-io",
libraryDependencies ++= Seq(Dependencies.catsEffect)
)
.dependsOn(core % "test->test;compile->compile")

lazy val playSqlModule = (project in file("modules/play-sql"))
.settings(commonSettings)
.settings(
name := "query-play-sql",
libraryDependencies ++= Seq(
jdbc,
evolutions % Test,
logback % Test,
Dependencies.logback % Test,
Dependencies.acolyte % Test,
Dependencies.acolytePlay % Test,
Dependencies.anorm % Test,
Expand Down Expand Up @@ -138,8 +146,29 @@ lazy val todoAppExample = (project in file("examples/todo-app"))
)
.dependsOn(core, playSqlModule)

lazy val todoAppHttp4sExample = (project in file("examples/todo-app-http4s"))
.settings(
name := "todo-app-http4s",
libraryDependencies ++= Seq(
Dependencies.http4sBlazeServer,
Dependencies.http4sCirce,
Dependencies.http4sDsl,
Dependencies.specs2 % Test,
Dependencies.logback
),
addCompilerPlugin("org.spire-math" %% "kind-projector" % "0.9.6"),
addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.2.4")
)

// Aggregate all projects

lazy val root: Project = project
.in(file("."))
.aggregate(core, playSqlModule, sampleAppExample, todoAppExample)
.aggregate(
core,
playSqlModule,
catsEffectsIOModule,
sampleAppExample,
todoAppExample,
todoAppHttp4sExample
)
47 changes: 0 additions & 47 deletions core/src/main/scala/core/database/ComposeWithCompletion.scala

This file was deleted.

22 changes: 22 additions & 0 deletions core/src/main/scala/core/database/LiftAsync.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.zengularity.querymonad.core.database

// import scala.concurrent.{ExecutionContext, Future}
import scala.language.higherKinds

/**
* Heavily inspired from work done by @cchantep in Acolyte (see acolyte.reactivemongo.ComposeWithCompletion)
*/
trait LiftAsync[F[_], M[_], A] {
type Outer

def apply[Resource](
loaner: WithResource[F, Resource],
f: Resource => M[A]
): F[Outer]
}

object LiftAsync {

type Aux[F[_], M[_], A, B] = LiftAsync[F, M, A] { type Outer = B }

}
21 changes: 10 additions & 11 deletions core/src/main/scala/core/database/QueryRunner.scala
Original file line number Diff line number Diff line change
@@ -1,29 +1,28 @@
package com.zengularity.querymonad.core.database

import scala.concurrent.Future
import scala.language.higherKinds

/**
* A class who can run a Query.
*/
sealed trait QueryRunner[Resource] {
sealed trait QueryRunner[F[_], Resource] {
def apply[M[_], T](query: QueryT[M, Resource, T])(
implicit compose: ComposeWithCompletion[M, T]
): Future[compose.Outer]
implicit lift: LiftAsync[F, M, T]
): F[lift.Outer]
}

object QueryRunner {
private class DefaultRunner[Resource](wr: WithResource[Resource])
extends QueryRunner[Resource] {
private class DefaultRunner[F[_], Resource](wr: WithResource[F, Resource])
extends QueryRunner[F, Resource] {
def apply[M[_], T](
query: QueryT[M, Resource, T]
)(implicit compose: ComposeWithCompletion[M, T]): Future[compose.Outer] =
compose(wr, query.run)
)(implicit lift: LiftAsync[F, M, T]): F[lift.Outer] =
lift(wr, query.run)
}

// Default factory
def apply[Resource](
wr: WithResource[Resource]
): QueryRunner[Resource] =
def apply[F[_], Resource](
wr: WithResource[F, Resource]
): QueryRunner[F, Resource] =
new DefaultRunner(wr)
}
7 changes: 4 additions & 3 deletions core/src/main/scala/core/database/WithResource.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package com.zengularity.querymonad.core.database

import scala.concurrent.Future
// import scala.concurrent.Future
import scala.language.higherKinds

trait WithResource[Resource] {
def apply[A](f: Resource => Future[A]): Future[A]
trait WithResource[F[_], Resource] {
def apply[A](f: Resource => F[A]): F[A]
}
38 changes: 38 additions & 0 deletions core/src/main/scala/module/future/LiftAsyncFuture.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.zengularity.querymonad.module.future

import scala.concurrent.{ExecutionContext, Future}
import scala.language.higherKinds

import com.zengularity.querymonad.core.database.LiftAsync

trait LiftAsyncFuture extends LowPriority {

implicit def futureOut[A]: LiftAsync.Aux[Future, Future, A, A] =
new LiftAsync[Future, Future, A] {
type Outer = A

def apply[In](
loaner: WithResourceF[In],
f: In => Future[A]
): Future[Outer] = loaner(f)

override val toString = "futureOut"
}

}

trait LowPriority { _: LiftAsyncFuture =>

implicit def pureOut[F[_], A](
implicit ec: ExecutionContext
): LiftAsync.Aux[Future, F, A, F[A]] =
new LiftAsync[Future, F, A] {
type Outer = F[A]

def apply[In](loaner: WithResourceF[In], f: In => F[A]): Future[Outer] =
loaner(r => Future(f(r)))

override val toString = "pureOut"
}

}
20 changes: 20 additions & 0 deletions core/src/main/scala/module/future/package.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.zengularity.querymonad.module

import scala.concurrent.Future

import com.zengularity.querymonad.core.database.{QueryRunner, WithResource}

package object future {

type WithResourceF[Resource] = WithResource[Future, Resource]

type QueryRunnerF[Resource] = QueryRunner[Future, Resource]

object QueryRunnerF {
def apply[Resource](wc: WithResourceF[Resource]): QueryRunnerF[Resource] =
QueryRunnerF[Resource](wc)
}

object implicits extends LiftAsyncFuture

}
21 changes: 16 additions & 5 deletions core/src/main/scala/module/sql/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package com.zengularity.querymonad.module

import java.sql.Connection

// import scala.concurrent.ExecutionContext
import scala.language.higherKinds
import scala.concurrent.Future

import cats.Applicative

Expand Down Expand Up @@ -60,13 +60,24 @@ package object sql {
type SqlQueryE[A, Err] = QueryE[Connection, A, Err]

// Query runner aliases
type WithSqlConnection = WithResource[Connection]
type WithSqlConnection[F[_]] = WithResource[F, Connection]

type SqlQueryRunner = QueryRunner[Connection]
type SqlQueryRunner[F[_]] = QueryRunner[F, Connection]

object SqlQueryRunner {
def apply(wc: WithSqlConnection): SqlQueryRunner =
QueryRunner[Connection](wc)
def apply[F[_]](wc: WithSqlConnection[F]): SqlQueryRunner[F] =
QueryRunner[F, Connection](wc)
}

object future {
type WithSqlConnectionF = WithSqlConnection[Future]

type SqlQueryRunnerF = SqlQueryRunner[Future]

object SqlQueryRunnerF {
def apply(wc: WithSqlConnectionF): SqlQueryRunnerF =
SqlQueryRunner(wc)
}
}

}
63 changes: 63 additions & 0 deletions core/src/test/scala/module/sql/QueryStackSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.zengularity.querymonad.test.module.sql

import acolyte.jdbc.{
AcolyteDSL,
QueryExecution,
QueryResult => AcolyteQueryResult
}
import acolyte.jdbc.RowLists.rowList1
import anorm.{SQL, SqlParser}
import org.specs2.concurrent.ExecutionEnv
import org.specs2.mutable.Specification

import com.zengularity.querymonad.module.sql.{
SqlQuery,
SqlQueryRunner,
SqlQueryT
}
import com.zengularity.querymonad.module.future.implicits._
import com.zengularity.querymonad.module.sql.future.WithSqlConnectionF
import com.zengularity.querymonad.test.module.sql.utils.SqlConnectionFactory

class QueryStackSpec(implicit ee: ExecutionEnv) extends Specification {

"QueryStackSpec" should {
def error(divisor: Int)(nb: Int): String =
s"$nb cannot be divided by $divisor"

val (divideBy2, divideBy3) = {
def divideBy(divisor: Int)(nb: Int): SqlQuery[Either[String, Int]] =
SqlQuery { implicit c =>
SQL(s"select ($nb / $divisor)").as(
SqlParser.get[Int](1).singleOpt
)
}.map(_.toRight(error(divisor)(nb)))

(divideBy(2) _, divideBy(3) _)
}

def resultSet(result: Int): AcolyteQueryResult = {
val schema = rowList1(classOf[Int])
schema.append(result).asResult
}

"test 1" in {
import cats.instances.either._
val handler = AcolyteDSL.handleQuery {
case QueryExecution("select (6 / 2)", Nil) => resultSet(3)
case QueryExecution("select (3 / 3)", Nil) => resultSet(1)
case _ => AcolyteQueryResult.Nil
}
val withSqlConnection: WithSqlConnectionF =
SqlConnectionFactory.withSqlConnection(handler)
val runner = SqlQueryRunner(withSqlConnection)
val query = for {
three <- SqlQueryT.fromQuery(divideBy2(6))
one <- SqlQueryT.fromQuery(divideBy3(three))
} yield one

runner(query) aka "one" must beTypedEqualTo(Right(1): Either[String, Int]).await
}
}

}
Loading