Skip to content
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

Added some extensions methods for validation #518

Draft
wants to merge 10 commits into
base: master
Choose a base branch
from

Conversation

rodriguestiago0
Copy link
Contributor

No description provided.

@rodriguestiago0 rodriguestiago0 marked this pull request as draft November 14, 2022 21:34
Comment on lines 247 to 268
[<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()
Copy link
Member

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.

Copy link
Member

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>>

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
Copy link
Member

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.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rodriguestiago0

  1. Error strings must be declared as public static functions to be available for plain reuse (for example in unit tests)
  2. 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

Copy link
Contributor Author

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.

Comment on lines 247 to 268
[<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()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe an alias then?

@gusty
Copy link
Member

gusty commented Nov 15, 2022

Maybe an alias then?

That's an old discussion we have here.
This library contains highly reusable code
We can create a module with some CEs, but as you can see defining aliases are one-liner.

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.

@rodriguestiago0 rodriguestiago0 changed the title Added computation expression for validation and added some extensions methods Added some extensions methods for validation Nov 18, 2022
open System
open FSharpPlus.Data

let inline validate error f v =
Copy link
Member

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.

Copy link
Member

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 =
Copy link
Member

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 =
Copy link
Member

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?

@wallymathieu
Copy link
Member

wallymathieu commented Nov 20, 2022

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
https://github.com/jquense/yup

While for F# we have

https://github.com/lfr/FSharp.Domain.Validation
https://github.com/JamesRandall/AccidentalFish.FSharp.Validation#built-in-validators

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)?

@wallymathieu
Copy link
Member

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants