-
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
[DRAFT] Exact Builder Dsl #7
[DRAFT] Exact Builder Dsl #7
Conversation
💡 [IDEA] We can also add |
A good idea! Another name suggestions: |
Great job @ILIYANGERMANOV 💪 , I like builder DSL even more than what I have proposed in the previous PR, actually don't have anything to add here. I can't wait to start using Exact in my projects We are only are missing one thing, on DSL example it became even more obvious that we need an ability to have custom errors like ArtificialConstraintType.from(" ").mapLeft {
when(it) {
is InvalidUser -> TODO()
is BadInput -> TODO()
}
} 🤔 We will face with two problems
|
I'm glad that you liked it! :) And very good point @ustits 👍 You're right, I also think that we should support custom errors. I haven't tried it but there should be an easy way to introduce a generic type |
Yes, @JvmInline
value class Username(val value: String) {
companion object : Exact<String, Username> by exactSpec({ spec ->
spec.mustBe(String::isNotBlank)
.with(NotOffensiveName.Companion) // Exact<String, NotOffensiveName>
.transform { it.value.trim() } // we need to flatten the NotOffensiveName wrapping
.mustBe { it.all(Char::isLetterOrDigit) }
.transform { it.uppercase() }
.create(::Username)
})
} IMO, the creation of any arbitrary Exact type is already easy and obvious enough.
val x = PositiveInt.fromOrThrow(2)
val y = PositiveInt.fromOrThrow(3)
val z = PositiveInt.fromOrThrow(10)
// How do we do "(x + y)/z"
PositiveInt.from(
(x.value + y.value) / z.value
) I'm wondering can we do better than that? val first = NotBlankTrimmedString("Iliyan") // we can have operator fun invoke() = fromOrThrow
val last = NotBlankTrimmedString("Germanov")
val fullName = first.map { "$it ${last.value}" }
// or
combine(first, last) { f, l -> "$f $l" } // combineOrThrow, ... 💡 For brainstorming purposes 👇
|
Amount.from(10)
.mapLeft {
when (it) {
NonPositiveNumber -> ValidationError("Amount must be positive")
is NumberTooLarge -> ValidationError("Amount is too large. Enter value less than: ${it.limit}")
}
} I can prepare a PR with changes, but then we will need to update DSL as well
sealed class ArtificialError(val message: String) {
object BadFormat : ArtificialError("Bad format")
class BadUserId(id: String) : ArtificialError("Invalid user id in: $id")
class Unknown(value: String) : ArtificialError("Unknown value: $value")
}
exact({ str ->
ensure(str, NotBlankTrimmedString) {
ArtificialError.BadFormat
}
val type = when {
str.startsWith("a/") -> Artificial.Admin
str.startsWith("u/") -> {
val userId = it.drop(2).toIntOrNull()
ensureNotNull(userId) { ArtificialError.BadUserId(str) }
Artificial.User(d)
}
else -> raise(ArtificialError.Unknown(str))
}
ArtificialConstraintType(type)
}) |
Nicely done @ustits! I like it! Feel free to draft the PR 👍
I'm also playing around with a few other ideas but this is definitely an improvement. Feel free to change anything in your PRs, we're exploration phase so all ideas/changes are more than welcome! IMO, we should target simplicity and every complexity that we remove is more than welcome! |
This "builder' pattern is obsolete. @ustits suggested a less complex approach which I find way better. |
Hi @ustits can you have a look at #8 I implemented your approach with |
Created a prototype of an
exactBuilder()
which can allow us to build complex domain types easily following the already known "Builder" pattern. See #6 (comment)💡 In a nutshell, we have two types of operations:
validation
: a predicate(A) -> Boolean
that is:NotBlankString
transformation
: mapping(A) -> B
:TrimmedString
Demo:
What are your thoughts?