diff --git a/build.sbt b/build.sbt index 7d02a33..ce7f971 100644 --- a/build.sbt +++ b/build.sbt @@ -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, @@ -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] = @@ -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) diff --git a/play-akka-streams/src/main/scala/dev/playmonad/MonadicAction.scala b/play-akka-streams/src/main/scala/dev/playmonad/MonadicAction.scala new file mode 100644 index 0000000..faebd0a --- /dev/null +++ b/play-akka-streams/src/main/scala/dev/playmonad/MonadicAction.scala @@ -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) + }) + } +} diff --git a/play25/play-monad/src/main/scala/dev/playmonad/RequestReaderSolver.scala b/play-akka-streams/src/main/scala/dev/playmonad/RequestReaderSolver.scala similarity index 69% rename from play25/play-monad/src/main/scala/dev/playmonad/RequestReaderSolver.scala rename to play-akka-streams/src/main/scala/dev/playmonad/RequestReaderSolver.scala index 8512a02..7cc0730 100644 --- a/play25/play-monad/src/main/scala/dev/playmonad/RequestReaderSolver.scala +++ b/play-akka-streams/src/main/scala/dev/playmonad/RequestReaderSolver.scala @@ -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 */ @@ -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] { @@ -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]] { diff --git a/play23/play-monad/src/main/scala/dev/playmonad/MonadicAction.scala b/play-iteratees/src/main/scala/dev/playmonad/MonadicAction.scala similarity index 100% rename from play23/play-monad/src/main/scala/dev/playmonad/MonadicAction.scala rename to play-iteratees/src/main/scala/dev/playmonad/MonadicAction.scala diff --git a/play23/play-monad/src/main/scala/dev/playmonad/RequestReaderSolver.scala b/play-iteratees/src/main/scala/dev/playmonad/RequestReaderSolver.scala similarity index 87% rename from play23/play-monad/src/main/scala/dev/playmonad/RequestReaderSolver.scala rename to play-iteratees/src/main/scala/dev/playmonad/RequestReaderSolver.scala index 5cce82d..f39bc2a 100644 --- a/play23/play-monad/src/main/scala/dev/playmonad/RequestReaderSolver.scala +++ b/play-iteratees/src/main/scala/dev/playmonad/RequestReaderSolver.scala @@ -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" diff --git a/play24/play-monad/src/main/scala/dev/playmonad/MonadicAction.scala b/play24/play-monad/src/main/scala/dev/playmonad/MonadicAction.scala deleted file mode 100644 index bcb4089..0000000 --- a/play24/play-monad/src/main/scala/dev/playmonad/MonadicAction.scala +++ /dev/null @@ -1,53 +0,0 @@ -package dev.playmonad - -import cats.data.{EitherT, IndexedStateT} -import cats.instances.future._ -import play.api.libs.concurrent.Execution.Implicits.defaultContext -import play.api.libs.iteratee.{Done, Input, Iteratee} -import play.api.mvc.{BodyParser, EssentialAction, RequestHeader, Result} - -import scala.concurrent.{Future, Promise} - -sealed trait RequestReader -case class HeaderReader(requestHeader: RequestHeader) extends RequestReader -case class HeaderReaderHeaderReader(requestHeader: RequestHeader) extends RequestReader -case class BodyReader[A](accumulator: Iteratee[Array[Byte], Either[Result, A]]) extends RequestReader - -object HeaderReader { - type Aux[A] = IndexedStateT[EitherT[Future, Result, *], HeaderReader, HeaderReader, A] - - def apply[A](f: RequestHeader => Future[Either[Result, A]]): HeaderReader.Aux[A] = - IndexedStateT[EitherT[Future, Result, *], HeaderReader, HeaderReader, A] { state => - EitherT[Future, Result, A](f(state.requestHeader)).map(value => (state, value)) - } -} - -object BodyReader { - 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 { - def apply[R <: RequestReader, A]( - reader: IndexedStateT[EitherT[Future, Result, *], HeaderReader, R, A] - )(implicit solver: RequestReaderSolver[R, A]): EssentialAction = - EssentialAction { request => - Iteratee.flatten(reader.run(HeaderReader(request)).value.map { - case Right((r, a)) => solver.makeResult(r, a) - case Left(result) => Done[Array[Byte], Result](result, Input.EOF) - }) - } -} diff --git a/play24/play-monad/src/main/scala/dev/playmonad/RequestReaderSolver.scala b/play24/play-monad/src/main/scala/dev/playmonad/RequestReaderSolver.scala deleted file mode 100644 index a9326b0..0000000 --- a/play24/play-monad/src/main/scala/dev/playmonad/RequestReaderSolver.scala +++ /dev/null @@ -1,61 +0,0 @@ -package dev.playmonad - -import play.api.libs.concurrent.Execution.Implicits.defaultContext -import play.api.libs.iteratee.{Done, Input, Iteratee} -import play.api.mvc.Result - -import scala.annotation.implicitNotFound -import scala.concurrent.Future - -/** Implicit proof that from RequestReader and a value of type A, an Action result can be built */ -@implicitNotFound( - "\n\nA RequestReaderSolver implicit for state ${R} and value ${A} could not be found." + - " 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 - 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] { - def makeResult(reader: R, result: A): Iteratee[Array[Byte], Result] -} - -object RequestReaderSolver { - - /** A HeaderReader of Result can be turned into a Play Action */ - implicit object HeaderResultSolver extends RequestReaderSolver[HeaderReader, Result] { - override def makeResult(reader: HeaderReader, result: Result): Iteratee[Array[Byte], Result] = - Done(result, Input.EOF) - } - - /** 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): Iteratee[Array[Byte], Result] = - reader.accumulator.mapM { - 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]): Iteratee[Array[Byte], Result] = - Iteratee.flatten(result.map(Done(_, Input.EOF))) - } - - /** 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]] { - override def makeResult(reader: BodyReader[A], result: Future[Result]): Iteratee[Array[Byte], Result] = - reader.accumulator.mapM { - case Left(errorResult) => Future.successful(errorResult) - case Right(_) => result - } - } -} diff --git a/play24/play-monad/src/main/scala/dev/playmonad/package.scala b/play24/play-monad/src/main/scala/dev/playmonad/package.scala deleted file mode 100644 index 68ad58f..0000000 --- a/play24/play-monad/src/main/scala/dev/playmonad/package.scala +++ /dev/null @@ -1,17 +0,0 @@ -package dev - -import controllers.Assets.BadRequest -import play.api.mvc.BodyParser - -import scala.concurrent.Future - -package object playmonad { - def header(name: String): HeaderReader.Aux[String] = HeaderReader { request => - request.headers.get(name) match { - case Some(value) => Future.successful(Right(value)) - case None => Future.successful(Left(BadRequest(s"Header $name must be provided"))) - } - } - - def body[A](bodyParser: BodyParser[A]): BodyReader.Aux[A, A] = BodyReader(bodyParser) -} diff --git a/play25/play-monad/src/main/scala/dev/playmonad/MonadicAction.scala b/play25/play-monad/src/main/scala/dev/playmonad/MonadicAction.scala deleted file mode 100644 index 0592fa2..0000000 --- a/play25/play-monad/src/main/scala/dev/playmonad/MonadicAction.scala +++ /dev/null @@ -1,54 +0,0 @@ -package dev.playmonad - -import akka.stream.Materializer -import akka.util.ByteString -import cats.data.{EitherT, IndexedStateT} -import cats.instances.future._ -import play.api.libs.concurrent.Execution.Implicits.defaultContext -import play.api.libs.streams.Accumulator -import play.api.mvc.{BodyParser, EssentialAction, RequestHeader, Result} - -import scala.concurrent.{Future, Promise} - -sealed trait RequestReader -case class HeaderReader(requestHeader: RequestHeader) extends RequestReader -case class BodyReader[A](accumulator: Accumulator[ByteString, Either[Result, A]]) extends RequestReader - -object HeaderReader { - type Aux[A] = IndexedStateT[EitherT[Future, Result, *], HeaderReader, HeaderReader, A] - - def apply[A](f: RequestHeader => Future[Either[Result, A]]): HeaderReader.Aux[A] = - IndexedStateT[EitherT[Future, Result, *], HeaderReader, HeaderReader, A] { state => - EitherT[Future, Result, A](f(state.requestHeader)).map(value => (state, value)) - } -} - -object BodyReader { - 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 { - 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) - }) - } -} diff --git a/play25/play-monad/src/main/scala/dev/playmonad/package.scala b/play25/play-monad/src/main/scala/dev/playmonad/package.scala deleted file mode 100644 index 68ad58f..0000000 --- a/play25/play-monad/src/main/scala/dev/playmonad/package.scala +++ /dev/null @@ -1,17 +0,0 @@ -package dev - -import controllers.Assets.BadRequest -import play.api.mvc.BodyParser - -import scala.concurrent.Future - -package object playmonad { - def header(name: String): HeaderReader.Aux[String] = HeaderReader { request => - request.headers.get(name) match { - case Some(value) => Future.successful(Right(value)) - case None => Future.successful(Left(BadRequest(s"Header $name must be provided"))) - } - } - - def body[A](bodyParser: BodyParser[A]): BodyReader.Aux[A, A] = BodyReader(bodyParser) -} diff --git a/project/ScalacOptions.scala b/project/ScalacOptions.scala index 02a9064..beb7194 100644 --- a/project/ScalacOptions.scala +++ b/project/ScalacOptions.scala @@ -158,7 +158,7 @@ object ScalacOptions { "-language:implicitConversions", // Allow definition of implicit functions called views "-unchecked", // Enable additional warnings where generated code depends on assumptions. "-Xcheckinit", // Wrap field accessors to throw an exception on uninitialized access. - "-Xfatal-warnings", // Fail the compilation if there are any warnings. +// "-Xfatal-warnings", // Fail the compilation if there are any warnings. // "-Xfuture", // Turn on future language features. "-Xlint:adapted-args", // Warn if an argument list is modified to match the receiver. // "-Xlint:by-name-right-associative", // By-name parameter of right associative operator. diff --git a/play25/play-sample/app/controllers/HelloController.scala b/sample-play25/app/controllers/HelloController.scala similarity index 100% rename from play25/play-sample/app/controllers/HelloController.scala rename to sample-play25/app/controllers/HelloController.scala diff --git a/play25/play-sample/build.sbt b/sample-play25/build.sbt similarity index 91% rename from play25/play-sample/build.sbt rename to sample-play25/build.sbt index 4f3938f..1892102 100644 --- a/play25/play-sample/build.sbt +++ b/sample-play25/build.sbt @@ -1,5 +1,5 @@ name := "Play 2.5 Monadic Sample" -organization := "io.playmonad" +organization := "dev.playmonad" version := "0.1-SNAPSHOT" diff --git a/play25/play-sample/conf/application.conf b/sample-play25/conf/application.conf similarity index 100% rename from play25/play-sample/conf/application.conf rename to sample-play25/conf/application.conf diff --git a/play25/play-sample/conf/routes b/sample-play25/conf/routes similarity index 100% rename from play25/play-sample/conf/routes rename to sample-play25/conf/routes diff --git a/play25/play-sample/project/build.properties b/sample-play25/project/build.properties similarity index 100% rename from play25/play-sample/project/build.properties rename to sample-play25/project/build.properties diff --git a/play25/play-sample/project/plugins.sbt b/sample-play25/project/plugins.sbt similarity index 100% rename from play25/play-sample/project/plugins.sbt rename to sample-play25/project/plugins.sbt diff --git a/play25/play-sample/test/HelloSpec.scala b/sample-play25/test/HelloSpec.scala similarity index 100% rename from play25/play-sample/test/HelloSpec.scala rename to sample-play25/test/HelloSpec.scala