The goal of play-stringent is to bring stricter typing and enhanced clarity to Play Framework applications written in Scala.
Specifically, play-stringent gives you compile time result checks for actions:
def myAction = Action.stringent[OkResult, BadRequest] = {
if(requestIsValid()) {
Ok
} else {
BadRequest
}
}
If you tried to return a response you didn't specify, play-stringent will return a compile time error:
def myAction = Action.stringent[OkResult, BadRequest] = {
if(requestIsValid()) {
Ok
}
if(userIsAuthorized()) {
Unauthorized // compile time error
} else {
BadRequest
}
}
This gives you self describing actions and compile time safety, so you know the exact response types each action can return.
resolvers += "Sonatype snapshots" at "http://oss.sonatype.org/content/repositories/snapshots/"
"xyz.mattclifton" %% "play-stringent" % "2.5.3-SNAPSHOT"
Simply mix in the StringentActions trait into your controller:
class Application extends Controller with StringentActions
Many actions are supported, though some have slightly different semantics:
def asyncBodyContent = Action.stringent.withContentAsync[TestContent, OkWithContent[TestResponse], BadRequestResult](parse.json[TestContent]){ request =>
if(!requestIsValid(request)) {
Future(BadRequest)
} else {
Future(Ok.withContent(TestResponse(1, "test")))
}
}
Note that the method name to return content is withContent in play-stringent, rather than apply. (also make sure you define a Writeable)
def block = Action {
Ok
}
def block = Action.stringent[OkResult]{
Ok
}
def block = Action {
Ok(Json.toJson(TestResponse(1, "test")))
}
def block = Action.stringent[OkWithContent[TestResponse]{
Ok.withContent(TestResponse(1, "test"))
}
Make sure you define a Writeable
def anyContent = Action { request =>
Ok
}
def anyContent = Action.stringent.anyContent[OkResult]{ request =>
Ok
}
def withBodyParser = Action(parse.json[TestContent]){ request =>
Ok
}
def withBodyParser = Action.stringent.withContent[TestContent, OkResult](parse.json[TestContent]){ request =>
Ok
}
def asyncBlock = Action.async {
Future(Ok)
}
def asyncBlock = Action.stringent.async[OkResult]{
Future(Ok)
}
def asyncAnyContent = Action.async{ request =>
Future(Ok)
}
def asyncAnyContent = Action.stringent.anyContentAsync[OkResult]{ request =>
Future(Ok)
}
def asyncBodyContent = Action.async(parse.json[TestContent]){ request =>
Future(Ok)
}
def asyncBodyContent = Action.stringent.withContentAsync[TestContent, OkResult](parse.json[TestContent]){ request =>
Future(Ok)
}
Result types are simply the name of the Status/Result code with a Result suffix. For example:
// Ok + Result = OkResult
def myAction = Action.stringent[OkResult] = {
Ok
}
If the result is a Status, then it can return a body. To ensure you are returning a result with the specified body, use the status name with a WithContent suffix. For Example:
// Ok + WithContent + [Content Entity] = OkWithContent[TestResponse]
def blockWithContent = Action.stringent[OkWithContent[TestResponse]]{
Ok.withContent(TestResponse(1, "test"))
}
Make sure you define a Writeable to use WithContent statuses.
Since there are many types of redirects, SeeOther was chosen as the default (as Play Framework uses this as it's default redirect).
RedirectTo is the play-stringent equivalent of Redirect, due to typing collisions:
def redirect = Action.stringent[SeeOtherResult]{
RedirectTo("https://mattclifton.xyz")
}
Note that to use the withContent method, you must define a Writeable[T] rather than using something like Json.toJson. A simple generic JSON solution would look like this:
implicit def jsonWriteable[A](implicit jsonWriter: Writes[A]): Writeable[A] = new Writeable[A](b => ByteString(Json.toJson(b).toString()), Some("application/json"))
While actions cannot return unspecified result types, they can return less than what is specified in the return type list.
In this example, no error will be returned at compile time due to a lack of a BadRequest return path:
def myAction = Action.stringent[OkResult, BadRequest] = {
Ok
}
Standard Play Framework | Stringent Equivalent |
---|---|
apply[A](bodyParser: BodyParser[A])(block: R[A] => Result): Action[A] | withContent[A, S1 <: StringentResult](bodyParser: BodyParser[A])(block: R[A] => StatusOf1.AnyOf[S1]) |
apply(block: => Result): Action[AnyContent] | apply[S1 <: StringentResult](block: => StatusOf1.AnyOf[S1]) |
apply(block: => Result): Action[AnyContent] | apply[S1 <: StringentResult](block: => StatusOf1.AnyOf[S1]) |
apply(block: R[AnyContent] => Result): Action[AnyContent] | anyContent[S1 <: StringentResult](block: R[AnyContent] => StatusOf1.AnyOf[S1]) |
async(block: => Future[Result]): Action[AnyContent] | async[S1 <: StringentResult](block: => Future[StatusOf1.AnyOf[S1]]) |
async(block: R[AnyContent] => Future[Result]): Action[AnyContent] | anyContentAsync[S1 <: StringentResult](block: R[AnyContent] => Future[StatusOf1.AnyOf[S1]]) |
async[A](bodyParser: BodyParser[A])(block: R[A] => Future[Result]): Action[A] | withContentAsync[A, S1 <: StringentResult](bodyParser: BodyParser[A])(block: R[A] => Future[StatusOf1.AnyOf[S1]]) |