-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature: Using Exact for data class / class with multiple properties #30
Comments
You are right, Exact is designed around the idea of single parameter classes (types). It can't and probably won't be able to support multiple parameters. Mostly because it will require code-generation or compiler plugins which we didn't want not to use for this project
Yes, looks too complicated for such scenario. I think for your example Exact won't be a good fit, your implementation with class Topic private constructor(
val id: UUID,
val name: String,
val title: String,
val category: String,
) {
init {
require(category.length > 5) {
"The category must be longer than 5 characters!"
}
require(name.isNotEmpty() || title.isNotEmpty()) {
"Either name or title must not be empty!"
}
}
companion object {
fun from(id: UUID, name: String, title: String, category: String): Either<Failure.ValidationFailure, Topic> {
return Either.catchOrThrow<IllegalArgumentException, Topic> {
Topic(id, name, title, category)
}.mapLeft { Failure.ValidationFailure(it.message) }
}
}
} |
I think the idea of Exact, and the fact that's build on top of @JvmInline value class NonEmptyString private constructor(val raw: String) {
companion object : Exact<String, NonEmptyString> by exact({ raw ->
ensure(raw.isNotEmpty()) // optional { "custom message" }
NonEmptString(raw)
})
}
@JvmInline value class Category private constructor(val raw: String) {
companion object : Exact<String, Category> by exact({ raw ->
ensure(raw.length > 5) // optional { "custom message" }
Category(raw)
})
}
class Topic private constructor(
val id: UUID,
val name: NonEmptyString,
val title: NonEmptyString,
val category: Category, // Custom exact type with min-length 5
) {
companion object {
fun from(id: UUID, name: String, title: String, category: String): EitherNel<ExactError, Topic> = either {
zipOrAccumulate(
{ ensure(name, NonEmptyString)) },
{ ensure(title, NonEmptyString) },
{ ensure(category, Category) }
) { name, title, category -> Topic(id, name, title, category) }
}
}
} I wish https://github.com/facebookincubator/dataclassgenerate could get rid of For completeness I've show here the @JvmInline value class Name private cosntructor(val raw: String) {
companion object : Exact<String, Name> by exact({ raw ->
ensure(raw.isNotEmpty()) { "Name cannot be empty" }
ensure(raw.length <= 50) { "Name cannot be longer than 50 characters" }
Name(raw)
})
}
private val ILLEGAL_TITLE_CHARS: Regex = ...
@JvmInline value class Title private cosntructor(val title: String) {
companion object : Exact<String, Title> by exact({ raw ->
ensure(raw.isNotEmpty()) { "Title cannot be empty" }
ensure(raw.length <= 200) { "Title cannot be longer than 200 characters" }
ensure(raw.contains(ILLEGAL_TITLE_CHARS)) { "Title cannot contain $ILLEGAL_TITLE_CHARS" }
Title(raw)
})
} |
Thanks for clarification. I think the main switch I have to do in my thinking is to change the way of creating domain models. Instead of using plain types like string or integer, it is intended to use custom types that already include a validation. and compose the domain model out of this. The part that is still remains is the problem, when there are multiple properties that needs to be validated together. |
We're hitting limitations of our approach sooner than I expected. Maybe we should step back and consider simpler alternatives? Since we are going to make the Exact DSL use the Arrow DSL, we could further reduce boilerplate with: class Raise<T> {
fun ensure(predicate: Boolean, lazyFailure: () -> T) {}
}
class Foo private constructor(
val a: Int,
val b: Int,
checked: Unit, // hack to force the signature to be different
) {
context(Raise<String>)
constructor(a: Int, b: Int) : this(a, b, Unit) {
ensure(a != 2) { "Single parameter check" }
ensure(a > 1 || b < 0) { "Multiple parameters check" }
}
} Though that does tie us to context receivers (and this example doesn't compile currently). |
I have the following requirement in one of my productive applications using Either for exeption handling.
I have a domain model that consists of multiple properties and it should only be possible to create valid objects. A valid object depends on a validation that includes multiple of the properties. To achieve this with a data class I have an implementation similar to below (simplified for better overview):
The complexity comes from the fact that I don't want to expose the copy - constructor.
For this requirement I can also use Exact but only by providing a kind of DTO object as constructor parameter. This does not feel great.
I used a class instead of a data class because the implementation is simpler.
I would like to have a solution to either use the spec - function with multiple parameters.
Is this a valid requirement or is the usage of Exact only intended for single parameter types like value classes and compose complexer types out of it (no longer contain own validation)?
The text was updated successfully, but these errors were encountered: