Hi, I am referring to this [Error Handling Pattern...
# arrow
g
Hi, I am referring to this [Error Handling Pattern using ApplicativeError](https://next.arrow-kt.io/docs/patterns/error_handling/#example--alternative-validation-strategies-using-applicativeerror) How can I extend for other datatypes (apart from
FormField
used in this example)? I could come-up with something like below, using inheritance, but I feel that’s not the ideal way, as I hv to touch the Framework code (
Rules<F>
) every time I extend this for a new type. Cc: @raulraja
Copy code
interface FormFieldValidation<F> : ApplicativeError<F, Nel<ValidationError>> {
...// This has contain FormField.contains, FormField.maxLength, FormField.validateWithEmailRules() 
}

interface FormFieldValidation2<F> : ApplicativeError<F, Nel<ValidationError>> {
...// This has contain FormField2.contains, FormField2.maxLength, FormField2.validateWithEmailRules()
}

sealed class Rules<F>(A: ApplicativeError<F, Nel<ValidationError>>) : 
        ApplicativeError<F, Nel<ValidationError>> by A,
        FormFieldValidation2<F>,
        FormFieldValidation<F> {
.....
}
r
The abstraction there is over Applicative Error and Either which are that target data types
FormField is just any value you can lift in there
g
Yes @raulraja, I am trying to make this like a sort of library, where the consumer should be able to provide rules for a type. I see the Rules<F> is agnostic of type which is an awesome thing
r
You could make a sealed AST instead of using FormField if there was other values
Or constrain a new type argument in Rules by the kind of model it acts over
Rules<F, A: Model> where A is anything that extends Model
g
But then how can I supply new rules like FormField2.newRule and make use of the DSL Rules failFast {…}
r
Now A in the algebra has access to the members of Model.
g
As the rule runner
validateWithEmailRules
is coupled with the
F
in Rules
r
Those also would be constrained by the same A.
Where A: Model
Is also expressed in the factory function in failfast etc
Sorry traveling and on my phone
g
oh thanks for replying promptly, no problem, give me few mins, let me comeup with some code on what you just said, will ping you where I am stuck so it is more clear
Hi @raulraja based on your suggestion this is how I structured my code: Sealed Hierarchy:
Copy code
sealed class FFType {
    data class FormField1(val label: String, val value: String) : FFType()
    data class FormField2(val label: String, val value: String) : FFType()
}
Rules Engine:
Copy code
sealed class Rules2<F, M: FFType>(A: ApplicativeError<F, Nel<ValidationError>>) :
        FormField1Validation<F, M>,
        ApplicativeError<F, Nel<ValidationError>> by A {
    object ErrorAccumulationStrategy :
            Rules2<ValidatedPartialOf<Nel<ValidationError>>, FormField1>(Validated.applicativeError(NonEmptyList.semigroup()))

    object FailFastStrategy :
            Rules2<EitherPartialOf<Nel<ValidationError>>, FormField1>(Either.applicativeError())

    companion object {
        infix fun <A> failFast(f: FailFastStrategy.() -> A): A = f(FailFastStrategy)
        infix fun <A> accumulateErrors(f: ErrorAccumulationStrategy.() -> A): A = f(ErrorAccumulationStrategy)
    }
}
Then I had to duplicate the Rules for both FormField1 and FormField2
Copy code
interface FormFieldValidation<F, M: FFType> : ApplicativeError<F, Nel<ValidationError>> {
    private fun FormField1.contains(needle: String): Kind<F, FormField1> =
            if (value.contains(needle, false)) just(this)
            else raiseError(ValidationError.DoesNotContain(needle).nel())

    private fun FormField1.maxLength(maxLength: Int): Kind<F, FormField1> =
            if (value.length <= maxLength) just(this)
            else raiseError(ValidationError.MaxLength(maxLength).nel())

    fun FormField1.validateWithEmailRules(): Kind<F, Email> =
            mapN(
                    contains("@"),
                    maxLength(250)
            ) {
                Email(value)
            }.handleErrorWith { raiseError(ValidationError.NotAnEmail(it).nel()) }

    private fun FormField2.contains(needle: String): Kind<F, FormField2> =
            if (value.contains(needle, false)) just(this)
            else raiseError(ValidationError.DoesNotContain(needle).nel())

    private fun FormField2.maxLength(maxLength: Int): Kind<F, FormField2> =
            if (value.length <= maxLength) just(this)
            else raiseError(ValidationError.MaxLength(maxLength).nel())

    fun FormField2.validateWithEmailRules(): Kind<F, Email> =
            mapN(
                    contains("@"),
                    maxLength(250)
            ) {
                Email(value)
            }.handleErrorWith { raiseError(ValidationError.NotAnEmail(it).nel()) }
}
This is the consumer/client side code:
Copy code
val failFastErrors = Rules2 failFast {
        listOf(
                FormField1("Invalid Email Domain Label", "<http://nowhere.com|nowhere.com>"),
                FormField1("Too Long Email Label", "nowheretoolong${(0..251).map { "g" }}"), //this fails fast
                FormField1("Valid Email Label", "<mailto:getlost@nowhere.com|getlost@nowhere.com>")
        ).map { it.validateWithEmailRules() }
    }
    println("FailFast Errors: $failFastErrors")
    
    val accumulatedErrors = Rules2 accumulateErrors {
        listOf(
                FormField2("Invalid Email Domain Label", "<http://nowhere.com|nowhere.com>"),
                FormField2("Too Long Email Label", "nowheretoolong${(0..251).map { "g" }}"), //this accumulates N errors
                FormField2("Valid Email Label", "<mailto:getlost@nowhere.com|getlost@nowhere.com>")
        ).map { it.validateWithEmailRules() }
    }
My problem statement is: • Assume the Rule engine (
Rules<F>
) is a library which operates on two modes, exposed by its DSL - failFast and Error Accumulation • As a client I have various DataTypes (
FormField1
and
FormField2
). I want to make use of this library to run my rules. Client should be able to supply in a different module: 1. Validation constraints for the Type (like ``Type.maxLength`,
Type.contains
) 2. Order to run these validations (like
Type.validateWithEmailRules()
) • Then Client should consume the library by calling the respective DSL (like
Rules FailFast{...}
)
But things I couldn’t figure out: • How can I declare these Validation constraints fns in different modules for different types (without having a giant module like
FormFieldValidation
above)? • How can I extend my engine without touching it. (In this case I have to explicitly add this inheritance
Rules2_<_F, M: FFType_>:_ FormFieldValidation_<_F, M_>_
to call
validateWithEmailRules
in the context) ?
Sorry for a lengthy question 🙂
r
M should replace wherever FormField appear before, since you are making it generic now and it's a type arg to the algebra
g
But that way I can’t access FormField1 props like
value
Inside the extension function
Copy code
interface FormFieldValidation<F, M: FFType> : ApplicativeError<F, Nel<ValidationError>> {
    private fun M.contains(needle: String): Kind<F, M> =
            //  vvv Compiler error at value
            if (value.contains(needle, false)) just(this) 
            else raiseError(ValidationError.DoesNotContain(needle).nel())
r
Yes, just make FFtype include the abstract properties you need and ensure its implementors implement it.
M there is a subtype of FFtype and can reference any of it's members, sorry still in transit or I would have provided a code snippet
g
oh I get your point, but I wish to extend it for unrelated types
assume FormField1 and FormField2 don’t have any common properties
r
They have to, otherwise you couldn't validate anything for those fields. What would you like the consumer of this API to look like in order to make calls to validate different models ?
In other words how do you determine if a field is valid or not?
g
The Validation constraints for a Particular type are supposed be supplied by the consumer. In our case, imagine I want to validate a new Datatype
NewFormField1
, and I will supply this interface
Copy code
nterface NewFormFieldValidationConstraints<F, M: FFType> : ApplicativeError<F, Nel<ValidationError>> {
    private fun NewFormField1.someDifferntConstraint1(needle: String): Kind<F, NewFormField1> =
            if (value.contains(needle, false)) just(this)
            else raiseError(ValidationError.DoesNotContain(needle).nel())

    private fun NewFormField1.someDifferntConstraint2(maxLength: Int): Kind<F, NewFormField1> =
            if (value.length <= maxLength) just(this)
            else raiseError(ValidationError.MaxLength(maxLength).nel())

    fun NewFormField1.validateNewFormField(): Kind<F, Email> =
            mapN(
                    someDifferntConstraint1("@"),
                    someDifferntConstraint2(250)
            ) {
                // something, nothing related to email
            }.handleErrorWith { raiseError(ValidationError.NotValid(it).nel()) }
}
Which means these validation constraints are specific to my new data type
Now I am struck with, how can I supply and wire this
NewFormFieldValidationConstraints
constraints to the
Rule<F>
class, inorder to extend my library capability to validate this
NewFormField1
data type`
My call site remains the same
Copy code
val failFastErrors = Rules2 failFast {
        listOf(
                NewFormField1("Invalid Email Domain Label", "<http://nowhere.com|nowhere.com>"),
                NewFormField1("Too Long Email Label", "nowheretoolong${(0..251).map { "g" }}"), //this fails fast
                NewFormField1("Valid Email Label", "<mailto:getlost@nowhere.com|getlost@nowhere.com>")
        ).map { it.validateNewFormField1() }
    }
Pls Note: This has no connection with email, we are only talking about some random type
NewFormField1
which has a
value
field to validate
I m rewriting the code give me few mins
Let me restate my blockers: • Currently I hooked/wired my
RandomeTypeValidation
using inheritance (Line: 41). But it doesn’t seem to be idea, as a consumer I won’t have access to library code, I should be able to supply this in some other way • How to extend my design so I can fit in
RandomeType2
which has different props and different validation constraints/rules?
r
The kind of stuff you want is what we are building with refined types
but it can be also done in this style with an algebra
let me see, when I have some time I’ll try to reply here
g
Thanks @raulraja
My goal is to achieve, validation strategy switching without code duplication (which this example already has) and extend it for different types without the manipulation of lib code. I see refined types are more for lifting primitives to strong types, but my focus is not on Validations as such, as my validations are not just data validations but some do DB operations as well (where I need to enhance this design to abstract out that effect of reactive vs blocking)
r
So some of your validation actions may be running on IO?
g
Hi @raulraja, I made a small POC on reusable code between reactive & blocking using ad-hoc polymorphism: https://github.com/overfullstack/ad-hoc-poly
The module
validation-fx
has the code which is used in the other two modules, I used Mono and IO as examples
I used the
Rules FailFast {…}
here (only for data validations on one of the field types
Email
). • But short-term goal is to extend this for other types, without touching
sealed class Rules_<_F_>_
• Then next goal is to make client supply these validation constraints like
Email.contains
instead of having them in the library • Then next goal is to make
Rules Strategy{…}
as the entry point for all validations, so client call looks like
Rules FailFast/Accumulate{…all validation constraints…}
Hi @raulraja, did u get a chance to review this code? Thanks
r
Hi Gopal, I'm currently in transit traveling back home from one of the Arrow courses, when I get to my comp ill take a look sometime today or tomorrow
g
Thanks @raulraja
r
@Gopal S Akshintala Regarding this
There is nothing that prevents you from decoupling the biz logic functions from the rules interface from the actual interface.
See this example:
This example shows how you can define arbitrary rules outside the Rules class
you just need further parametrization to make E an unknown
so you can defer what you mean by E or A down the road
g
Thanks a lot @raulraja, this is very helpful… I extended the same design to have rules that perform effectful db operations. Whenever you find time, can you please review my approach: https://github.com/overfullstack/ad-hoc-poly/blob/master/validation-fx/src/main/kotlin/com/validation/RepoTC.kt
r
That looks good. If you want to run your rules in parallel and you are in the context of IO you may use
parMapN
instead of
mapN
Additionaly for functions like
update
and
insert
I’d use
Kind<F, Unit>
if there is no valuable return
g
Thanks for reviewing @raulraja, the reason I used
Any?
instead of
Unit
as I am struck with
Mono<Void>
as below Screen Shot 2020-03-11 at 11.28.49 AM.png
👍 1
Also
parMapN
I think can only be used in accumulate error strategy (should not be used for fail fast strategy), right?
r
parMapN if it's over IO will failfast there autocancelling all other jobs if one fails
g
Ok awesome!
Is there a way to map Void to Unit in Kotlin?
r
Can you write a function from Void? to Unit?
Hahaha I was asking the same
g
I tried, it doesn’t really look idiomatic
r
What does it look like?
Mono should have a functor instance in Arrow and functor should have a
unit
which maps any
A
in this case
Void?
to
Kind<F, Unit>
g
ya that would be really helpful
forIO { ref<UserRepository>().update(this) }.map {}
- empty
map
did the job, but kind of feels non-idiomatic
r
Is .unit not available there?
g
Oh just saw one, although it is deprecated and replaced with
void()
r
Then that one should do the same as map { Unit }
g
ya