-
Notifications
You must be signed in to change notification settings - Fork 96
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
Add some extensions methods for validation #518
base: master
Are you sure you want to change the base?
Add some extensions methods for validation #518
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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>>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe an alias then?
|
||
let inline requireString propName = | ||
let errorMessage = | ||
sprintf "%s cannot be null, empty or whitespace." propName |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe an alias then?
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have this function already and it's being deprecated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
else | ||
Failure [ error ] | ||
|
||
let inline string error value = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.