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

Added Play 2.3 through 2.8 #2

Merged
merged 3 commits into from
Apr 7, 2021
Merged
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
125 changes: 82 additions & 43 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ lazy val scala_2_10Version = "2.10.7"
lazy val scala_2_11Version = "2.11.12"
lazy val scala_2_12Version = "2.12.13"
lazy val scala_2_13Version = "2.13.5"
lazy val scalaVersionsAll = Seq(scala_2_11Version, scala_2_12Version, scala_2_13Version)

lazy val sharedSettings: Seq[Setting[_]] = Seq[Setting[_]](
scalaVersion := scala_2_13Version,
Expand All @@ -38,7 +37,12 @@ lazy val sharedSettings: Seq[Setting[_]] = Seq[Setting[_]](
Test / fork := false,
fork := true,
sonatypeCredentialHost := "s01.oss.sonatype.org",
sonatypeRepository := "https://s01.oss.sonatype.org/service/local"
sonatypeRepository := "https://s01.oss.sonatype.org/service/local",
libraryDependencies ++= Seq(
"org.scalatest" %% "scalatest" % "3.2.7" % "test",
"org.typelevel" %% "cats-core" % theCatsVersion(scalaVersion.value)
),
addCompilerPlugin("org.typelevel" % "kind-projector" % "0.11.3" cross CrossVersion.full)
)

def theScalacOptions(scalaVersion: String): Seq[String] =
Expand All @@ -59,65 +63,100 @@ def theCatsVersion(scalaVersion: String): String =
case _ => throw new IllegalArgumentException(s"Unsupported Scala version $scalaVersion")
}

def projectTemplate(projectName: String, playVersion: String, scalaVersions: List[String]): Seq[Setting[_]] =
sharedSettings ++ Seq[Setting[_]](
name := projectName,
scalaVersion := scalaVersions.head,
crossScalaVersions := scalaVersions,
libraryDependencies += "com.typesafe.play" %% "play" % playVersion
)

def akkaStreamsPlayMonad(projectName: String, playVersion: String, scalaVersions: List[String]): Seq[Setting[_]] =
projectTemplate(projectName, playVersion, scalaVersions) ++ Seq[Setting[_]](
Compile / unmanagedSourceDirectories ++=
Seq(baseDirectory.value / ".." / "play-akka-streams" / "src" / "main" / "scala")
)

def iterateesPlayMonad(projectName: String, playVersion: String, scalaVersions: List[String]): Seq[Setting[_]] =
projectTemplate(projectName, playVersion, scalaVersions) ++ Seq[Setting[_]](
Compile / unmanagedSourceDirectories ++=
Seq(baseDirectory.value / ".." / "play-iteratees" / "src" / "main" / "scala")
)

// Play 2.3
lazy val play23_monad = project
.in(file("play23/play-monad"))
.in(file("play23-monad"))
.settings(
sharedSettings ++ Seq[Setting[_]](
name := "play23-monad",
scalaVersion := scala_2_11Version,
crossScalaVersions := List(scala_2_11Version, scala_2_10Version),
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-core" % theCatsVersion(scalaVersion.value),
"com.typesafe.play" %% "play" % "2.3.10",
"org.scalatest" %% "scalatest" % "3.2.7" % "test"
),
addCompilerPlugin("org.typelevel" % "kind-projector" % "0.11.3" cross CrossVersion.full),
// The Typesafe repository
resolvers += "Typesafe repository" at "https://repo.typesafe.com/typesafe/releases/"
iterateesPlayMonad(
"play23-monad",
"2.3.10",
List(scala_2_11Version, scala_2_10Version)
): _*
)
.settings(
resolvers += "Typesafe repository" at "https://repo.typesafe.com/typesafe/releases/"
)

// Play 2.4
lazy val play24_monad = project
.in(file("play24/play-monad"))
.in(file("play24-monad"))
.settings(
sharedSettings ++ Seq[Setting[_]](
name := "play24-monad",
scalaVersion := scala_2_11Version,
crossScalaVersions := List(scala_2_11Version, scala_2_10Version),
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-core" % theCatsVersion(scalaVersion.value),
"com.typesafe.play" %% "play" % "2.4.11",
"org.scalatest" %% "scalatest" % "3.2.7" % "test"
),
addCompilerPlugin("org.typelevel" % "kind-projector" % "0.11.3" cross CrossVersion.full)
): _*
iterateesPlayMonad(
"play24-monad",
"2.4.11",
List(scala_2_11Version, scala_2_10Version)
)
)

// Play 2.5
lazy val play25_monad = project
.in(file("play25/play-monad"))
.in(file("play25-monad"))
.settings(
sharedSettings ++ Seq[Setting[_]](
name := "play25-monad",
scalaVersion := scala_2_11Version,
crossScalaVersions := List(scala_2_11Version),
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-core" % theCatsVersion(scalaVersion.value),
"com.typesafe.play" %% "play" % "2.5.19",
"org.scalatest" %% "scalatest" % "3.2.7" % "test"
),
addCompilerPlugin("org.typelevel" % "kind-projector" % "0.11.3" cross CrossVersion.full)
): _*
akkaStreamsPlayMonad(
"play25-monad",
"2.5.19",
List(scala_2_11Version)
)
)

// Play 2.6
lazy val play26_monad = project
.in(file("play26-monad"))
.settings(
akkaStreamsPlayMonad(
"play26-monad",
"2.6.25",
List(scala_2_11Version, scala_2_12Version)
)
)

// Play 2.7
lazy val play27_monad = project
.in(file("play27-monad"))
.settings(
akkaStreamsPlayMonad(
"play27-monad",
"2.7.9",
List(scala_2_11Version, scala_2_12Version, scala_2_13Version)
)
)

// Play 2.8
lazy val play28_monad = project
.in(file("play28-monad"))
.settings(
akkaStreamsPlayMonad(
"play28-monad",
"2.8.7",
List(scala_2_12Version, scala_2_13Version)
)
)

// All projects
lazy val root = project
.in(file("."))
.settings(sharedSettings)
.settings(
sharedSettings ++ Seq[Setting[_]](
publish / skip := true
): _*
publish / skip := true
)
.aggregate(play23_monad, play24_monad, play25_monad)
.aggregate(play23_monad, play24_monad, play25_monad, play26_monad, play27_monad, play28_monad)
86 changes: 86 additions & 0 deletions play-akka-streams/src/main/scala/dev/playmonad/MonadicAction.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package dev.playmonad

import akka.stream.Materializer
import akka.util.ByteString
import cats.{CoflatMap, Monad, MonadError}
import cats.data.{EitherT, IndexedStateT}
import play.api.libs.streams.Accumulator
import play.api.mvc.{BodyParser, EssentialAction, RequestHeader, Result}

import scala.concurrent.{ExecutionContext, Future, Promise}

sealed trait RequestReader
case class HeaderReader(requestHeader: RequestHeader) extends RequestReader
case class BodyReader[A](accumulator: Accumulator[ByteString, Either[Result, A]]) extends RequestReader

trait MonadicActionImplicits {
implicit val ec: ExecutionContext = scala.concurrent.ExecutionContext.Implicits.global
implicit val futureInstances: MonadError[Future, Throwable] with CoflatMap[Future] with Monad[Future] =
cats.implicits.catsStdInstancesForFuture
}

object HeaderReader extends MonadicActionImplicits {
type Aux[A] = IndexedStateT[EitherT[Future, Result, *], HeaderReader, HeaderReader, A]

def withHeadersM[A](f: RequestHeader => Future[Either[Result, A]]): HeaderReader.Aux[A] =
IndexedStateT[EitherT[Future, Result, *], HeaderReader, HeaderReader, A] { state =>
EitherT[Future, Result, (HeaderReader, A)](f(state.requestHeader).map(_.right.map(value => (state, value))))
}

def withHeaders[A](f: RequestHeader => Either[Result, A]): HeaderReader.Aux[A] =
IndexedStateT[EitherT[Future, Result, *], HeaderReader, HeaderReader, A] { state =>
EitherT[Future, Result, (HeaderReader, A)](
Future.successful(f(state.requestHeader).right.map(v => (state, v)))
)
}

def withValue[A](value: Future[A]): HeaderReader.Aux[A] =
IndexedStateT[EitherT[Future, Result, *], HeaderReader, HeaderReader, A] { state =>
EitherT[Future, Result, (HeaderReader, A)](value.map(v => Right((state, v))))
}

def withValue[A](value: Either[Result, A]): HeaderReader.Aux[A] =
IndexedStateT[EitherT[Future, Result, *], HeaderReader, HeaderReader, A] { state =>
EitherT[Future, Result, (HeaderReader, A)](Future.successful(value.right.map(v => (state, v))))
}

def withValue[A](value: A): HeaderReader.Aux[A] =
IndexedStateT[EitherT[Future, Result, *], HeaderReader, HeaderReader, A] { state =>
EitherT[Future, Result, (HeaderReader, A)](Future.successful(Right((state, value))))
}

def withResult[A](value: Result): HeaderReader.Aux[A] =
IndexedStateT[EitherT[Future, Result, *], HeaderReader, HeaderReader, A] { _ =>
EitherT[Future, Result, (HeaderReader, A)](Future.successful(Left(value)))
}
}

object BodyReader extends MonadicActionImplicits {
type Aux[Body, A] = IndexedStateT[EitherT[Future, Result, *], HeaderReader, BodyReader[Body], Future[A]]

def apply[A](bodyParser: BodyParser[A]): BodyReader.Aux[A, A] =
IndexedStateT[EitherT[Future, Result, *], HeaderReader, BodyReader[A], Future[A]] { state =>
val promise = Promise[A]()

val bd = BodyReader(bodyParser.apply(state.requestHeader).map {
case Left(errorResult) => Left(errorResult)
case Right(a) =>
promise.success(a) // unblocks `result` for completion
Right(a)
})

EitherT[Future, Result, (BodyReader[A], Future[A])](Future.successful(Right((bd, promise.future))))
}
}

object MonadicAction extends MonadicActionImplicits {
def apply[R <: RequestReader, A](
reader: IndexedStateT[EitherT[Future, Result, *], HeaderReader, R, A]
)(implicit solver: RequestReaderSolver[R, A], mat: Materializer): EssentialAction =
EssentialAction { request =>
Accumulator.flatten(reader.run(HeaderReader(request)).value.map {
case Right((r, a)) => solver.makeResult(r, a)
case Left(result) => Accumulator.done(result)
})
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package dev.playmonad

import akka.util.ByteString
import play.api.libs.concurrent.Execution.Implicits.defaultContext
import play.api.libs.streams.Accumulator
import play.api.mvc.Result

import scala.annotation.implicitNotFound
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future

/** Implicit proof that from RequestReader and a value of type A, an Action result can be built */
Expand All @@ -14,12 +14,14 @@ import scala.concurrent.Future
" This is needed so the MonadicAction can convert this expression to a Play Action." +
" When a MonadicAction ends up with a Result or a Future[Result] in either HeaderReader or BodyReader states, the built in" +
" RequestReaderSolvers will be found automatically, no import is needed." +
"\n - The R type is ${R}:" +
"\n - It should be dev.playmonad.HeaderReader or dev.playmonad.BodyReader[x]." +
"\n - The 'R' type is ${R} and the A type is ${A}:" +
"\n - If 'R' is a dev.playmonad.HeaderReader" +
"\n - 'A' should be play.api.mvc.Result or scala.concurrent.Future[play.api.mvc.Result]." +
"\n - Otherwise you are using a custom extension and should import a RequestReaderSolver for this type." +
"\n - If 'R' is a dev.playmonad.BodyReader[x]" +
"\n - 'A' should be a scala.concurrent.Future[play.api.mvc.Result]. You should map or flatMap on the body." +
"\n - Otherwise you are using a custom extension and should import a RequestReaderSolver for this type." +
"\n - Otherwise it's possible this was built using unsupported states." +
"\n - The A type is ${A}:" +
"\n - It should be play.api.mvc.Result or scala.concurrent.Future[play.api.mvc.Result]." +
"\n - Otherwise you are using a custom extension and should import a RequestReaderSolver for this type." +
"\n\n"
)
trait RequestReaderSolver[R <: RequestReader, A] {
Expand All @@ -34,22 +36,16 @@ object RequestReaderSolver {
Accumulator.done(result)
}

/** A BodyReader of Result can be turned into a Play Action */
implicit def BodyResultSolver[A]: RequestReaderSolver[BodyReader[A], Result] =
new RequestReaderSolver[BodyReader[A], Result] {
override def makeResult(reader: BodyReader[A], result: Result): Accumulator[ByteString, Result] =
reader.accumulator.mapFuture {
case Left(errorResult) => Future.successful(errorResult)
case Right(_) => Future.successful(result)
}
}

/** A HeaderReader of Future[Result] can be turned into a Play Action */
implicit object HeaderFutureResultSolver extends RequestReaderSolver[HeaderReader, Future[Result]] {
override def makeResult(reader: HeaderReader, result: Future[Result]): Accumulator[ByteString, Result] =
Accumulator.done(result)
}

/** A BodyReader of Result could be turned into a Play Action but since body returns Future[A],
* the user should be chaining on it instead of producing a Result. */
// implicit def BodyResultSolver[A]: RequestReaderSolver[BodyReader[A], Result] =

/** A BodyReader of Future[Result] can be turned into a Play Action */
implicit def BodyFutureResultSolver[A]: RequestReaderSolver[BodyReader[A], Future[Result]] =
new RequestReaderSolver[BodyReader[A], Future[Result]] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ import scala.concurrent.Future
" This is needed so the MonadicAction can convert this expression to a Play Action." +
" When a MonadicAction ends up with a Result or a Future[Result] in either HeaderReader or BodyReader states, the built in" +
" RequestReaderSolvers will be found automatically, no import is needed." +
"\n - The R type is ${R} and the A type is ${A}:" +
"\n - If R is a dev.playmonad.HeaderReader" +
"\n - A should be play.api.mvc.Result or scala.concurrent.Future[play.api.mvc.Result]." +
"\n - The 'R' type is ${R} and the A type is ${A}:" +
"\n - If 'R' is a dev.playmonad.HeaderReader" +
"\n - 'A' should be play.api.mvc.Result or scala.concurrent.Future[play.api.mvc.Result]." +
"\n - Otherwise you are using a custom extension and should import a RequestReaderSolver for this type." +
"\n - If R is a dev.playmonad.BodyReader[x]" +
"\n - A should be a scala.concurrent.Future[play.api.mvc.Result]. You should map or flatMap on the body." +
"\n - If 'R' is a dev.playmonad.BodyReader[x]" +
"\n - 'A' should be a scala.concurrent.Future[play.api.mvc.Result]. You should map or flatMap on the body." +
"\n - Otherwise you are using a custom extension and should import a RequestReaderSolver for this type." +
"\n - Otherwise it's possible this was built using unsupported states." +
"\n\n"
Expand Down
53 changes: 0 additions & 53 deletions play24/play-monad/src/main/scala/dev/playmonad/MonadicAction.scala

This file was deleted.

Loading