In the doc about Validation, when speaking of Smart constructor, it's suggested to use them on data class, but making this possible:
object EmptyAuthorName
data class Author private constructor(val name: String) {
companion object {
operator fun invoke(name: String): Either<EmptyAuthorName, Author> = either {
ensure(name.isNotEmpty()) { EmptyAuthorName }
Author(name)
}
}
}
Author("fo")
.map { it.copy(name = "") }
.run { println(this) } // Either.Right(Author(name=))
The EmptyAuthorName constraint wasn't respected. Really bad isn't it?
Redefining copy for the data class isn't possible...
Currently my only way out is to not use data class.
Am i missing something?
The primary constructor of a data class is always available via the copy method. You either have to make sure that the result of the primary constructor is valid, or you must not use a data class.
In this case, the validation logic seems to be in a wrapper around the data class, not in the data class itself. That means you can do the validation on an instance of
Author
, instead of "faking" a constructor call. Then you can call it after operations likemap
, and you'll always get a properly validatedEither
.Another upside of doing it this way is that your code becomes easier to understand. In your example, you have a function that looks like an
Author
constructor, but it doesn't return an instance ofAuthor
(but an instance ofEither
). That's confusing. Making a function that clearly specifies that it validates and returns a validated result is better and easier to understand.