Question

My goal is to validate User's fields within the object's applymethod before creating one effective User instance:

case class User(String userName, String password)

object User {

  def apply(userValidator: UserValidator): ValidationNel[UserCreationFailure, User] = {
    //call UserValidator's validate() method here and initialize effective User instance.
  }

}

I chose to use Validation from Scalaz7 to accumulate potential illegal arguments / errors.

One drawback in the following code is that Scalaz7 API force me to make the validator creates itself the instance. However, by following Single-Responsibility principle, it's clearly not its role. Its role would be to just validate fields and to return some errors list.

Let's first present my actual code (for information, Empty**** objects are just some case object extending UserCreationFailure):

class UserValidator(val userName: String, val password: String)
  extends CommonValidator[UserCreationFailure] {

  def validate(): ValidationNel[UserCreationFailure, User] = {
    (checkForUserName ⊛
      checkForPassword)((userName, password) => new User(userName, password)
  }

  private def checkForUserName: ValidationNel[UserCreationFailure, String] = {
    checkForNonEmptyString(userName) {
      EmptyUserName
    }
  }

  def checkForPassword: ValidationNel[UserCreationFailure, String] = {
    checkForNonEmptyString(password) {
      EmptyPassword
    }
  }
}

What I would expect is to merely return this snippet code:

(checkForUserName ⊛ checkForPassword)

and bring the appropriate result into my User class, allowing to create the effective instance by doing:

def apply(userValidator: UserValidator): ValidationNel[UserCreationFailure, User] = {
        userValidator(username, password).validate()((userName, password)(new User(userName, password))
 }

Indeed, it would be more friendly with SRP.

But (checkForUserName ⊛ checkForPassword) returns a totally private type type:

private[scalaz] trait ApplicativeBuilder[M[_], A, B],

thus I don't have the hand on the type of class returned.

Therefore, I am forced to directly associate User's creation with it.

How could I keep SRP and keep this validation mechanism?

-----UPDATE----

As @Travis Brown mentioned, the intent to use an external class for my UserValidator may seem weird. Actually, I expect the validator to be mockable and thus, I'm forced to use composition over trait/abstract class.

Was it helpful?

Solution

I'm not sure I understand why you need a dedicated UserValidator class in the first place. In a case like this I'd be more likely to bundle all of my generic validation code into a separate trait, and to have my User companion object (or whatever other piece I want to be responsible for creating User instances) extend that trait. Here's a quick sketch:

import scalaz._, Scalaz._

trait Validator[E] {
  def checkNonEmpty(error: E)(s: String): ValidationNel[E, String] =
    if (s.isEmpty) error.failNel else s.successNel
}

sealed trait UserCreationFailure
case object EmptyPassword extends UserCreationFailure
case object EmptyUsername extends UserCreationFailure

case class User(name: String, pass: String)

object User extends Validator[UserCreationFailure] {
  def validated(
    name: String,
    pass: String
  ): ValidationNel[UserCreationFailure, User] = (
    checkNonEmpty(EmptyUsername)(name) |@| checkNonEmpty(EmptyPassword)(pass)
  )(apply)
}

And then:

scala> println(User.validated("", ""))
Failure(NonEmptyList(EmptyUsername, EmptyPassword))

scala> println(User.validated("a", ""))
Failure(NonEmptyList(EmptyPassword))

scala> println(User.validated("", "b"))
Failure(NonEmptyList(EmptyUsername))

scala> println(User.validated("a", "b"))
Success(User(a,b))

If you have a huge amount of User-specific validation logic that you don't want polluting your User object, I suppose you could factor it out into a UserValidator trait that would extend your generic Validator and be extended by User.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top