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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With