I\'m making a real push to understand the async powers of Play but finding a lot of conflict with regard to places where async invocation fits and places where the framework see
Yes, validation in Play is designed synchronously. I think it's because assumed that most of time there is no I/O in form validation: field values are just checked for size, length, matching against regexp, etc.
Validation is built over play.api.data.validation.Constraint that store function from validated value to ValidationResult
(either Valid
or Invalid
, there is no place to put Future
here).
/**
* A form constraint.
*
* @tparam T type of values handled by this constraint
* @param name the constraint name, to be displayed to final user
* @param args the message arguments, to format the constraint name
* @param f the validation function
*/
case class Constraint[-T](name: Option[String], args: Seq[Any])(f: (T => ValidationResult)) {
/**
* Run the constraint validation.
*
* @param t the value to validate
* @return the validation result
*/
def apply(t: T): ValidationResult = f(t)
}
verifying
just adds another constraint with user-defined function.
So I think Data Binding in Play just isn't designed for doing I/O while validation. Making it asynchronous would make it more complex and harder to use, so it kept simple. Making every piece of code in framework to work on data wrapped in Future
s is overkill.
If you need to use validation with ReactiveMongo, you can use Await.result
. ReactiveMongo returns Futures everywhere, and you can block until completion of these Futures to get result inside verifying
function. Yes, it will waste a thread while MongoDB query runs.
object Application extends Controller {
def checkUser(e:String, p:String):Boolean = {
// ... construct cursor, etc
val result = cursor.toList().map( _.length != 0)
Await.result(result, 5 seconds)
}
val loginForm = Form(
tuple(
"email" -> email,
"password" -> text
) verifying("Invalid user name or password", fields => fields match {
case (e, p) => checkUser(e, p)
})
)
def index = Action { implicit request =>
if (loginForm.bindFromRequest.hasErrors)
Ok("Invalid user name")
else
Ok("Login ok")
}
}
Maybe there's way to not waste thread by using continuations, not tried it.
I think it's good to discuss this in Play mailing list, maybe many people want to do asynchronous I/O in Play data binding (for example, for checking values against database), so someone may implement it for future versions of Play.