Add some extensions methods for validation#518
Add some extensions methods for validation#518rodriguestiago0 wants to merge 10 commits intofsprojects:masterfrom
Conversation
src/FSharpPlus/Data/Validation.fs
Outdated
| [<AutoOpen>] | ||
| module ComputationExpression = | ||
| type ValidationBuilder() = | ||
| member _.Bind(x, f) = | ||
| match x with | ||
| | Failure e -> Failure e | ||
| | Success a -> f a | ||
|
|
||
| member _.MergeSources(left : Validation<list<string>, _>, right : Validation<list<string>, _>) = | ||
| match left, right with | ||
| | Success l, Success r -> Success (l, r) | ||
| | Failure l, Success _ -> Failure l | ||
| | Success _, Failure r -> Failure r | ||
| | Failure r, Failure l -> List.append r l |> Failure | ||
|
|
||
| member _.Return(x) = | ||
| Success x | ||
|
|
||
| member _.ReturnFrom(x) = | ||
| x | ||
|
|
||
| let validator = ValidationBuilder() |
There was a problem hiding this comment.
Note that this is just a special case of the applicative computation expression, closed to Validation<list<string>, 'T>.
I mean, right now you can just define it like: let validator<'T> = applicative<Validation<list<string>, 'T>> and you'll have the same methods and more available.
There was a problem hiding this comment.
Actually the bind is not part of the applicative CE, but Validation is not a monad, as it doesn't obey monad rules. However in order to get the short-circuit behavior you can use let validator<'T> = monad'<Result<'T, List<string>>
|
|
||
| let inline requireString propName = | ||
| let errorMessage = | ||
| sprintf "%s cannot be null, empty or whitespace." propName |
There was a problem hiding this comment.
In my experience using string as errors doesn't scale well, unless they are at the boundaries with the user.
But otherwise, validations errors should be represented with a custom type, normally a DU, which should be directly related to the business model of validation errors.
Having said that, we could add an additional parameter which is a function that creates the error, so the user is free to supply whatever structure he uses.
There was a problem hiding this comment.
- Error strings must be declared as public static functions to be available for plain reuse (for example in unit tests)
- We have a case right now when we need to delay error string creation till it is required:
a. Output to log as is
b. Output to API response with camel case field name modification
There was a problem hiding this comment.
I know to let the user define the Error to be added. It can be a string, int or whatever the developer wants.
src/FSharpPlus/Data/Validation.fs
Outdated
| [<AutoOpen>] | ||
| module ComputationExpression = | ||
| type ValidationBuilder() = | ||
| member _.Bind(x, f) = | ||
| match x with | ||
| | Failure e -> Failure e | ||
| | Success a -> f a | ||
|
|
||
| member _.MergeSources(left : Validation<list<string>, _>, right : Validation<list<string>, _>) = | ||
| match left, right with | ||
| | Success l, Success r -> Success (l, r) | ||
| | Failure l, Success _ -> Failure l | ||
| | Success _, Failure r -> Failure r | ||
| | Failure r, Failure l -> List.append r l |> Failure | ||
|
|
||
| member _.Return(x) = | ||
| Success x | ||
|
|
||
| member _.ReturnFrom(x) = | ||
| x | ||
|
|
||
| let validator = ValidationBuilder() |
That's an old discussion we have here. My original view was that F#+ is kind of a library for libraries, meaning that you can have your own enterprise specific libraries where you take what you need, the way you need, this is giving a specific name and closing it over certain type parameters. I had feedback that some users did that at the beginning, which lead to the desire of making CEs more parameterizable, we did that in v1.2 However, if everybody is strongly for providing aliases we can evaluate the impact. Maybe provide a script with those aliases or a separate package. |
| open System | ||
| open FSharpPlus.Data | ||
|
|
||
| let inline validate error f v = |
There was a problem hiding this comment.
We have this function already and it's being deprecated.
| else | ||
| Failure [ error ] | ||
|
|
||
| let inline string error value = |
There was a problem hiding this comment.
I'm unsure if just calling it string makes it obvious. Would it be better to call it notNullOrWhiteSpace ?
|
|
||
| validate (error value) check value | ||
|
|
||
| let inline guid error value = |
There was a problem hiding this comment.
this type of validator goes into the question if there is some more general class of nonEmpty restrictions?
|
There seems to be prior work that we can reuse some of the thoughts from. For dynamic languages we have: https://clojure.org/about/spec While for F# we have https://github.com/lfr/FSharp.Domain.Validation and the new library: https://github.com/pimbrouwers/Validus#built-in-validators Perhaps we can reuse some of the design work while being more in line with the vision that you promote in this PR (possible to write strictly typed validations instead of stringly typed validation keys/messages)? |
|
Some of the operators seems to not have stabilised in Validus yet. The different libraries all have their built in validators that we can look at. Seems like there is some polymorphism going on for some of the validators. |
5b61ffc to
ad56a34
Compare
61dfe6f to
67d1b75
Compare
105ed2c to
d80a4ad
Compare
f92d910 to
39638fb
Compare
9b34ece to
b2f3c8c
Compare
484cff5 to
142c806
Compare
eef4e98 to
f2e49ba
Compare
No description provided.