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

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

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