16 people like it.
Like the snippet!
Declarative validation
Simple combinator library to declarative validation.
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
|
module Validation =
open System
open Microsoft.FSharp.Quotations
type 'e Test = Test of ('e -> (string*string) option)
let CreateValidator (f: 'e -> ('e Test list -> 'e Test list)) =
let entries = f Unchecked.defaultof<_> []
fun entity -> List.choose (fun (Test test) -> test entity) entries
let private add (propQ:'x Expr) args message fx (xs: 'e Test list) =
let propName, eval =
match propQ with
| Patterns.PropertyGet (_,p,_) -> p.Name, fun x -> p.GetValue(x,[||])
| Patterns.Value (_, ty) when ty = typeof<'e> -> "x", box
| _ -> failwith "Unsupported expression"
let test entity =
let value = eval entity
if fx (unbox value) then None
else Some (propName, String.Format(message, Array.ofList (value::args)))
Test(test) :: xs
let email propQ =
let regex = Text.RegularExpressions.Regex(@"\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*")
add propQ [] "Please enter a valid email address" regex.IsMatch
let required propQ =
add propQ [] "Is a required field" (String.IsNullOrWhiteSpace >> not)
let between propQ min max =
add propQ [box min; box max] "Must be at least {2} and great {1}"
(fun v -> v >= min && v <= max)
let cardNumber propQ =
add propQ [] "Invlid card number" <| fun (value:string) ->
let checkSum index digit =
let v = (int digit - int '0') * (index % 2 + 1)
v % 10 + v / 10 % 10
Seq.forall Char.IsDigit value
&& Seq.sum (Seq.mapi checkSum value) % 10 = 0
// usage
open Validation
type Account =
{ Email : string
BirthYear : int
CardNumber : string }
let validateAccount =
CreateValidator <| fun x ->
required <@ x.Email @> >>
email <@ x.Email @> >>
between <@ x.BirthYear @> 1920 2000 >>
required <@ x.CardNumber @> >>
cardNumber <@ x.CardNumber @>
let acc = { Email = "example#mail.com"; BirthYear = 2003; CardNumber = "" }
let validResult = validateAccount acc
|
namespace System
namespace Microsoft
namespace Microsoft.FSharp
namespace Microsoft.FSharp.Quotations
Multiple items
union case Test.Test: ('e -> (string * string) option) -> 'e Test
--------------------
type 'e Test = | Test of ('e -> (string * string) option)
Full name: Script.Validation.Test<_>
Multiple items
val string : value:'T -> string
Full name: Microsoft.FSharp.Core.Operators.string
--------------------
type string = String
Full name: Microsoft.FSharp.Core.string
type 'T option = Option<'T>
Full name: Microsoft.FSharp.Core.option<_>
val CreateValidator : f:('e -> 'e Test list -> 'e Test list) -> ('e -> (string * string) list)
Full name: Script.Validation.CreateValidator
val f : ('e -> 'e Test list -> 'e Test list)
type 'T list = List<'T>
Full name: Microsoft.FSharp.Collections.list<_>
val entries : 'e Test list
module Unchecked
from Microsoft.FSharp.Core.Operators
val defaultof<'T> : 'T
Full name: Microsoft.FSharp.Core.Operators.Unchecked.defaultof
val entity : 'e
Multiple items
module List
from Microsoft.FSharp.Collections
--------------------
type List<'T> =
| ( [] )
| ( :: ) of Head: 'T * Tail: 'T list
interface IEnumerable
interface IEnumerable<'T>
member Head : 'T
member IsEmpty : bool
member Item : index:int -> 'T with get
member Length : int
member Tail : 'T list
static member Cons : head:'T * tail:'T list -> 'T list
static member Empty : 'T list
Full name: Microsoft.FSharp.Collections.List<_>
val choose : chooser:('T -> 'U option) -> list:'T list -> 'U list
Full name: Microsoft.FSharp.Collections.List.choose
val test : ('e -> (string * string) option)
val private add : propQ:Expr<'x> -> args:obj list -> message:string -> fx:('a -> bool) -> xs:'e Test list -> 'e Test list
Full name: Script.Validation.add
val propQ : Expr<'x>
Multiple items
type Expr =
override Equals : obj:obj -> bool
member GetFreeVars : unit -> seq<Var>
member Substitute : substitution:(Var -> Expr option) -> Expr
member ToString : full:bool -> string
member CustomAttributes : Expr list
member Type : Type
static member AddressOf : target:Expr -> Expr
static member AddressSet : target:Expr * value:Expr -> Expr
static member Application : functionExpr:Expr * argument:Expr -> Expr
static member Applications : functionExpr:Expr * arguments:Expr list list -> Expr
...
Full name: Microsoft.FSharp.Quotations.Expr
--------------------
type Expr<'T> =
inherit Expr
member Raw : Expr
Full name: Microsoft.FSharp.Quotations.Expr<_>
val args : obj list
val message : string
val fx : ('a -> bool)
val xs : 'e Test list
val propName : string
val eval : ('e -> obj)
module Patterns
from Microsoft.FSharp.Quotations
active recognizer PropertyGet: Expr -> (Expr option * Reflection.PropertyInfo * Expr list) option
Full name: Microsoft.FSharp.Quotations.Patterns.( |PropertyGet|_| )
val p : Reflection.PropertyInfo
property Reflection.MemberInfo.Name: string
val x : 'e
Reflection.PropertyInfo.GetValue(obj: obj, index: obj []) : obj
Reflection.PropertyInfo.GetValue(obj: obj, invokeAttr: Reflection.BindingFlags, binder: Reflection.Binder, index: obj [], culture: Globalization.CultureInfo) : obj
active recognizer Value: Expr -> (obj * Type) option
Full name: Microsoft.FSharp.Quotations.Patterns.( |Value|_| )
val ty : Type
val typeof<'T> : Type
Full name: Microsoft.FSharp.Core.Operators.typeof
val box : value:'T -> obj
Full name: Microsoft.FSharp.Core.Operators.box
val failwith : message:string -> 'T
Full name: Microsoft.FSharp.Core.Operators.failwith
val value : obj
val unbox : value:obj -> 'T
Full name: Microsoft.FSharp.Core.Operators.unbox
union case Option.None: Option<'T>
union case Option.Some: Value: 'T -> Option<'T>
Multiple items
type String =
new : value:char -> string + 7 overloads
member Chars : int -> char
member Clone : unit -> obj
member CompareTo : value:obj -> int + 1 overload
member Contains : value:string -> bool
member CopyTo : sourceIndex:int * destination:char[] * destinationIndex:int * count:int -> unit
member EndsWith : value:string -> bool + 2 overloads
member Equals : obj:obj -> bool + 2 overloads
member GetEnumerator : unit -> CharEnumerator
member GetHashCode : unit -> int
...
Full name: System.String
--------------------
String(value: nativeptr<char>) : unit
String(value: nativeptr<sbyte>) : unit
String(value: char []) : unit
String(c: char, count: int) : unit
String(value: nativeptr<char>, startIndex: int, length: int) : unit
String(value: nativeptr<sbyte>, startIndex: int, length: int) : unit
String(value: char [], startIndex: int, length: int) : unit
String(value: nativeptr<sbyte>, startIndex: int, length: int, enc: Text.Encoding) : unit
String.Format(format: string, [<ParamArray>] args: obj []) : string
String.Format(format: string, arg0: obj) : string
String.Format(provider: IFormatProvider, format: string, [<ParamArray>] args: obj []) : string
String.Format(format: string, arg0: obj, arg1: obj) : string
String.Format(format: string, arg0: obj, arg1: obj, arg2: obj) : string
type Array =
member Clone : unit -> obj
member CopyTo : array:Array * index:int -> unit + 1 overload
member GetEnumerator : unit -> IEnumerator
member GetLength : dimension:int -> int
member GetLongLength : dimension:int -> int64
member GetLowerBound : dimension:int -> int
member GetUpperBound : dimension:int -> int
member GetValue : [<ParamArray>] indices:int[] -> obj + 7 overloads
member Initialize : unit -> unit
member IsFixedSize : bool
...
Full name: System.Array
val ofList : list:'T list -> 'T []
Full name: Microsoft.FSharp.Collections.Array.ofList
val email : propQ:Expr<'a> -> ('b Test list -> 'b Test list)
Full name: Script.Validation.email
val propQ : Expr<'a>
val regex : Text.RegularExpressions.Regex
namespace System.Text
namespace System.Text.RegularExpressions
Multiple items
type Regex =
new : pattern:string -> Regex + 1 overload
member GetGroupNames : unit -> string[]
member GetGroupNumbers : unit -> int[]
member GroupNameFromNumber : i:int -> string
member GroupNumberFromName : name:string -> int
member IsMatch : input:string -> bool + 1 overload
member Match : input:string -> Match + 2 overloads
member Matches : input:string -> MatchCollection + 1 overload
member Options : RegexOptions
member Replace : input:string * replacement:string -> string + 5 overloads
...
Full name: System.Text.RegularExpressions.Regex
--------------------
Text.RegularExpressions.Regex(pattern: string) : unit
Text.RegularExpressions.Regex(pattern: string, options: Text.RegularExpressions.RegexOptions) : unit
Text.RegularExpressions.Regex.IsMatch(input: string) : bool
Text.RegularExpressions.Regex.IsMatch(input: string, startat: int) : bool
val required : propQ:Expr<'a> -> ('b Test list -> 'b Test list)
Full name: Script.Validation.required
String.IsNullOrWhiteSpace(value: string) : bool
val not : value:bool -> bool
Full name: Microsoft.FSharp.Core.Operators.not
val between : propQ:Expr<'a> -> min:'b -> max:'b -> ('c Test list -> 'c Test list) (requires comparison)
Full name: Script.Validation.between
val min : 'b (requires comparison)
val max : 'b (requires comparison)
val v : 'b (requires comparison)
val cardNumber : propQ:Expr<'a> -> ('b Test list -> 'b Test list)
Full name: Script.Validation.cardNumber
val value : string
val checkSum : (int -> char -> int)
val index : int
val digit : char
val v : int
Multiple items
val int : value:'T -> int (requires member op_Explicit)
Full name: Microsoft.FSharp.Core.Operators.int
--------------------
type int = int32
Full name: Microsoft.FSharp.Core.int
--------------------
type int<'Measure> = int
Full name: Microsoft.FSharp.Core.int<_>
module Seq
from Microsoft.FSharp.Collections
val forall : predicate:('T -> bool) -> source:seq<'T> -> bool
Full name: Microsoft.FSharp.Collections.Seq.forall
type Char =
struct
member CompareTo : value:obj -> int + 1 overload
member Equals : obj:obj -> bool + 1 overload
member GetHashCode : unit -> int
member GetTypeCode : unit -> TypeCode
member ToString : unit -> string + 1 overload
static val MaxValue : char
static val MinValue : char
static member ConvertFromUtf32 : utf32:int -> string
static member ConvertToUtf32 : highSurrogate:char * lowSurrogate:char -> int + 1 overload
static member GetNumericValue : c:char -> float + 1 overload
...
end
Full name: System.Char
Char.IsDigit(c: char) : bool
Char.IsDigit(s: string, index: int) : bool
val sum : source:seq<'T> -> 'T (requires member ( + ) and member get_Zero)
Full name: Microsoft.FSharp.Collections.Seq.sum
val mapi : mapping:(int -> 'T -> 'U) -> source:seq<'T> -> seq<'U>
Full name: Microsoft.FSharp.Collections.Seq.mapi
module Validation
from Script
type Account =
{Email: string;
BirthYear: int;
CardNumber: string;}
Full name: Script.Account
Account.Email: string
Multiple items
val string : value:'T -> string
Full name: Microsoft.FSharp.Core.Operators.string
--------------------
type string = System.String
Full name: Microsoft.FSharp.Core.string
Account.BirthYear: int
Account.CardNumber: string
val validateAccount : (Account -> (string * string) list)
Full name: Script.validateAccount
val x : Account
val required : propQ:Quotations.Expr<'a> -> ('b Test list -> 'b Test list)
Full name: Script.Validation.required
val email : propQ:Quotations.Expr<'a> -> ('b Test list -> 'b Test list)
Full name: Script.Validation.email
val between : propQ:Quotations.Expr<'a> -> min:'b -> max:'b -> ('c Test list -> 'c Test list) (requires comparison)
Full name: Script.Validation.between
val cardNumber : propQ:Quotations.Expr<'a> -> ('b Test list -> 'b Test list)
Full name: Script.Validation.cardNumber
val acc : Account
Full name: Script.acc
val validResult : (string * string) list
Full name: Script.validResult
More information