Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I do async form validation in Play Framework 2.x (Scala)?

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 seems to conspire against its use.

The example I have relates to form validation. Play allows for ad-hoc constraints to be defined - see this from the docs:

val loginForm = Form(   tuple(     "email" -> email,     "password" -> text   ) verifying("Invalid user name or password", fields => fields match {        case (e, p) => User.authenticate(e,p).isDefined    }) ) 

Nice and clean. However, if I'm using a fully async data access layer (e.g. ReactiveMongo), such a call to User.authenticate(...) would return a Future and I'm thus in the dark as to how I can utilise the power of both the built in form binding features and the async tools.

It's all well and good to publicise the async approach but I'm getting frustrated that certain parts of the framework don't play so well with it. If the validation has to be done synchronously, it seems to defeat the point of the async approach. I've come across a similar problem when using Action composition - e.g. a security related Action that would make a call to ReactiveMongo.

Can anyone shed any light on where my comprehension is falling short?

like image 741
DrewEaster Avatar asked Feb 16 '13 17:02

DrewEaster


People also ask

Is Play framework asynchronous?

Internally, Play Framework is asynchronous from the bottom up. Play handles every request in an asynchronous, non-blocking way. The default configuration is tuned for asynchronous controllers.

What is form in play framework?

Play's form handling approach is based around the concept of binding data. When data comes in from a POST request, Play will look for formatted values and bind them to a Form object. From there, Play can use the bound form to value a case class with data, call custom validations, and so on.


1 Answers

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 Futures 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.

like image 97
kolen Avatar answered Oct 07 '22 17:10

kolen