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

后端 未结 5 730
执笔经年
执笔经年 2021-02-01 03:14

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

5条回答
  •  Happy的楠姐
    2021-02-01 04:05

    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.

提交回复
热议问题