orePlayCommon/app/controllers/sugar/ActionHelpers.scala
package controllers.sugar
import scala.language.{higherKinds, implicitConversions}
import play.api.data.{Form, FormError}
import play.api.mvc.Results.Redirect
import play.api.mvc._
import controllers.sugar.Requests.OreRequest
import cats.Monad
import cats.data.{EitherT, OptionT}
import cats.effect.syntax.all._
import cats.effect.IO
import com.google.common.base.Preconditions.checkArgument
/**
* A helper class for some common functions of controllers.
*/
trait ActionHelpers {
/**
* Returns a NotFound result with the 404 HTML template.
*
* @return NotFound
*/
def notFound(implicit request: OreRequest[_]): Result
/**
* Redirects to the specified call with the errors of the specified
* Form.
*
* @param call Call to redirect to
* @param form Form with error
* @return Redirect to call
*/
def FormErrorLocalized(call: Call): Form[_] => Result = form => {
checkArgument(form.errors.nonEmpty, "no errors", "")
val errors = form.errors.map(e => s"${e.message}.${e.key}")
Redirect(call).withErrors(errors.toList)
}
/**
* Redirects to the specified call with the errors of the specified
* Form.
*
* @param call Call to redirect to
* @param form Form with error
* @return Redirect to call
*/
def FormError(call: Call): Form[_] => Result = form => {
checkArgument(form.errors.nonEmpty, "no errors", "")
Redirect(call).withFormErrors(form.errors)
}
implicit def toOreResultOps(result: Result): ActionHelpers.OreResultOps =
new ActionHelpers.OreResultOps(result)
implicit def toFormBindOps[A](form: Form[A]): ActionHelpers.FormBindOps[A] =
new ActionHelpers.FormBindOps(form)
implicit def toOreRequestBuilderOps[R[_], B](
builder: ActionBuilder[R, B]
): ActionHelpers.OreActionBuilderOps[R, B] =
new ActionHelpers.OreActionBuilderOps[R, B](builder)
}
object ActionHelpers {
class OreResultOps(private val result: Result) {
/**
* Adds an alert message to the result.
*
* @param tpe Alert type
* @param alert Alert message
* @return Result with error
*/
def withAlert(tpe: String, alert: String): Result = {
val flash = result.newFlash.fold(Flash(Map(tpe -> alert)))(f => Flash(f.data + (tpe -> alert)))
result.flashing(flash)
}
/**
* Adds one or more alerts messages to the result.
*
* @param tpe Alert type
* @param alerts Alert messages
* @return Result with alerts
*/
def withAlerts(tpe: String, alerts: List[String]): Result = alerts match {
case Nil => result
case single :: Nil => withAlert(tpe, single)
case multiple =>
val numPart = s"$tpe-num" -> multiple.size.toString
val newValues = numPart :: multiple.zipWithIndex.map { case (e, i) => s"$tpe-$i" -> e }
val flash = result.newFlash.fold(Flash(newValues.toMap))(f => Flash(f.data ++ newValues))
result.flashing(flash)
}
/**
* Adds an error message to the result.
*
* @param error Error message
* @return Result with error
*/
def withError(error: String): Result = withAlert("error", error)
/**
* Adds one or more error messages to the result.
*
* @param errors Error messages
* @return Result with errors
*/
def withErrors(errors: List[String]): Result = withAlerts("error", errors)
/**
* Adds one or more form error messages to the result.
*
* @param errors Error messages
* @return Result with errors
*/
def withFormErrors(errors: Seq[FormError]): Result = withAlerts("error", errors.flatMap(_.messages).toList)
/**
* Adds a success message to the result.
*
* @param message Success message
* @return Result with message
*/
def withSuccess(message: String): Result = withAlert("success", message)
/**
* Adds one or more success messages to the result.
*
* @param messages Success messages
* @return Result with message
*/
def withSuccesses(messages: List[String]): Result = withAlerts("success", messages)
/**
* Adds a warning info to the result.
*
* @param message Info message
* @return Result with message
*/
def withInfo(message: String): Result = withAlert("info", message)
/**
* Adds one or more info messages to the result.
*
* @param messages Info messages
* @return Result with message
*/
def withInfo(messages: List[String]): Result = withAlerts("info", messages)
/**
* Adds a warning message to the result.
*
* @param message Warning message
* @return Result with message
*/
def withWarning(message: String): Result = withAlert("warning", message)
/**
* Adds one or more warning messages to the result.
*
* @param messages Warning messages
* @return Result with message
*/
def withWarnings(messages: List[String]): Result = withAlerts("warning", messages)
}
class FormBindOps[A](private val form: Form[A]) extends AnyVal {
def bindEitherT[F[_]] = new BindFormEitherTPartiallyApplied[F, A](form)
def bindOptionT[F[_]](implicit F: Monad[F], request: Request[_]): OptionT[F, A] =
form.bindFromRequest().fold(_ => OptionT.none[F, A], OptionT.some[F](_))
}
final class BindFormEitherTPartiallyApplied[F[_], B](private val form: Form[B]) extends AnyVal {
def apply[A](left: Form[B] => A)(implicit F: Monad[F], request: Request[_]): EitherT[F, A, B] =
form.bindFromRequest().fold(left.andThen(EitherT.leftT[F, B](_)), EitherT.rightT[F, A](_))
}
class OreActionBuilderOps[R[_], B](private val action: ActionBuilder[R, B]) extends AnyVal {
def asyncF(fr: IO[Result]): Action[AnyContent] = action.async(fr.unsafeToFuture())
def asyncF(fr: R[B] => IO[Result]): Action[B] =
action.async(r => fr(r).unsafeToFuture())
def asyncF[A](
bodyParser: BodyParser[A]
)(fr: R[A] => IO[Result]): Action[A] =
action.async(bodyParser)(r => fr(r).toIO.unsafeToFuture())
def asyncEitherT(fr: EitherT[IO, _ <: Result, Result]): Action[AnyContent] = asyncF(fr.merge)
def asyncEitherT(fr: R[B] => EitherT[IO, _ <: Result, Result]): Action[B] =
asyncF(r => fr(r).merge)
def asyncEitherT[A](
bodyParser: BodyParser[A]
)(fr: R[A] => EitherT[IO, _ <: Result, Result]): Action[A] =
asyncF(bodyParser)(r => fr(r).merge)
}
}