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

Add some extensions methods for validation #518

Draft
wants to merge 10 commits into
base: master
Choose a base branch
from
5 changes: 4 additions & 1 deletion src/FSharpPlus/Data/Validation.fs
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,10 @@ module Validation =
List.iter (function Success e -> coll1.Add e | Failure e -> coll2.Add e) source
coll1.Close (), coll2.Close ()
#endif


[<AutoOpen>]
module ComputationExpression =
let validator<'Error,'T> = applicative<Validation<list<'Error>, 'T>>

type Validation<'err,'a> with

Expand Down
65 changes: 65 additions & 0 deletions src/FSharpPlus/Extensions/Validation.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
namespace FSharpPlus

[<RequireQualifiedAccess>]
module Validations =
rodriguestiago0 marked this conversation as resolved.
Show resolved Hide resolved
open System
open FSharpPlus.Data

let inline validate error f v =
if f v then
Success v
else
Failure [ error ]

let inline requireString error =
validate error (String.IsNullOrWhiteSpace >> not)

let inline requireGreaterThan error min =
validate error (flip (>) min)

let inline requireGreaterOrEqualThan error min =
validate error (flip (>=) min)

let inline requireEmail error =
let check (v: string) =
try
let _ = Net.Mail.MailAddress(v)
true
with
| ex -> false

validate error check

let inline requireGuid error =
validate error (fun v -> v <> Guid.Empty)

let inline requireObject error =
let check value = box value <> null
validate error check

let inline requireWhenSome value checkWhenSome =
match value with
| Some v -> checkWhenSome v |> Validation.map Some
| _ -> Success None

let inline requireArrayValues values check =
let validated : Validation<_,_> [] =
values
|> Array.map check
validated
|> sequence
|> Validation.map Seq.toArray

let inline requireListValues values check =
let validated : List<Validation<_,_>> =
values
|> List.map check
validated
|> sequence
|> Validation.map Seq.toArray

let inline requireAtLeastOne error =
let check values =
Seq.isEmpty values |> not

validate error check
1 change: 1 addition & 0 deletions src/FSharpPlus/FSharpPlus.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@
<Compile Include="Data/Coproduct.fs" />
<Compile Include="Extensions/Observable.fs" />
<Compile Include="Extensions/AsyncEnumerable.fs" />
<Compile Include="Extensions/Validation.fs" />
<Compile Include="Memoization.fs" />
<Compile Include="Parsing.fs" />
</ItemGroup>
Expand Down
73 changes: 71 additions & 2 deletions tests/FSharpPlus.Tests/Validations.fs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,27 @@ module Validation =
open FSharpPlus.Data
open Validation
open FSharpPlus.Tests.Helpers


let private isSuccess =
function
| Success _ -> true
| Failure _ -> false

let private isFailure =
function
| Success _ -> false
| Failure _ -> true
rodriguestiago0 marked this conversation as resolved.
Show resolved Hide resolved

let private getSuccess =
function
| Success s -> s
| Failure _ -> failwith "It's a failure"
rodriguestiago0 marked this conversation as resolved.
Show resolved Hide resolved

let private getFailure =
function
| Success _ -> failwith "It's a Success"
| Failure f -> f

let fsCheck s x = Check.One({Config.QuickThrowOnFailure with Name = s}, x)
module FunctorP =
[<Test>]
Expand Down Expand Up @@ -337,4 +357,53 @@ module Validation =
let v: Validation<string Async, int Async> = Success (async {return 42})
let r = Validation.bisequence v
let subject = Async.RunSynchronously r
areStEqual subject (Success 42)
areStEqual subject (Success 42)

[<Test>]
[<TestCase("", false)>]
[<TestCase(" ", false)>]
[<TestCase(null, false)>]
[<TestCase("NotEmpty", true)>]
let testValidateRequireString (str, success) =
let error = "Str"
let r = Validations.requireString error str
areStEqual (isSuccess r) success

if not success then
let failure = getFailure r
areStEqual failure.Length 1
areStEqual failure.[0] error
else
()

[<Test>]
[<TestCase(1, 0, true)>]
[<TestCase(0, 0, false)>]
[<TestCase(-1, 0, false)>]
let testValidateRequireGreaterThan (value, limit, success) =
let error = "Int"
let r = Validations.requireGreaterThan error limit value
areStEqual (isSuccess r) success

if not success then
let failure = getFailure r
areStEqual failure.Length 1
areStEqual failure.[0] error
else
()

[<Test>]
[<TestCase(1, 0, true)>]
[<TestCase(0, 0, true)>]
[<TestCase(-1, 0, false)>]
let testValidateRequireGreaterOrEqualThan (value, limit, success) =
let error = "Int"
let r = Validations.requireGreaterOrEqualThan error limit value
areStEqual (isSuccess r) success

if not success then
let failure = getFailure r
areStEqual failure.Length 1
areStEqual failure.[0] error
else
()