lfr / FSharp.Domain.Validation

Designing with types requires a lot of code - this library fixes that
MIT License
143 stars 7 forks source link

FsCheck Generator support #3

Open farlee2121 opened 3 years ago

farlee2121 commented 3 years ago

I found your library while thinking of how I could better handle Type-Driven Design constraints. I like the way your library lets users bring their own validation errors.

Another benefit I'd like from this approach is type-driven generative testing like is available with Clojure Spec. In other words, property testing where the property is the type validators (or for any valid input we get valid output based on given constraints).

This can be achieved fairly easily if the validator can feed into an FsCheck generator. That doesn't seem to be supported as is, but would be possible if constraints were aggregated in a type, monad/applicative style, so that different consumers could interpret the rules differently. The key bit is that the .Validate would need to return a Monad instead directly returning the error type.

Is this a use case you have given thought to? Thoughts?

lfr commented 3 years ago

Interesting, I haven't thought about this but it seems pretty easy to implement unless there's something I haven't considered, I'll definitely take a look at it at the same time as I work on #2 (closed by reporter but still valid imo)

farlee2121 commented 3 years ago

I was able to make a decent prototype validation data structure last night.

type Constraint<'a> = 
    | MaxLength of int
    | MinLength of int
    | Regex of string
    | Max of 'a 
    | Min of 'a
    | Choice of 'a list
    | And of Constraint<'a> list
    | Or of Constraint<'a> list
    | Custom of string * ('a -> bool) 

It's pretty easy to construct complex validations with a few simple combinators and constructors

let (&&&) left right = And [left; right]
let (|||) left right = Or [left; right]
let matchRegex pattern = Regex pattern
let max = (Max)
//... 

This collapses down to a validation result via catamorphism pretty predictably. But I got stuck on on the generator catamorphism, the main issue being no clear concept of And for generators. Ranges, or, choice, filter, and even regex have pretty easy mappings.

This form is still pretty useful though. It makes it straight forward interpret constraints many ways: validation, explainer, serializers, other validation libraries, generators (hopefully).

I'll let you know if I have any breakthroughs