Last active
April 18, 2017 22:04
-
-
Save krishnabhargav/e06d2b96877e9110270d50b28a1a732f to your computer and use it in GitHub Desktop.
a quick validation description library in F#
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type ValidationResult<'a> = | |
| Success of 'a | |
| Failure of string array //you can also make a ValidationFailure DU which is more "typesafe" | |
type Validate<'a> = ValidationResult<'a> -> ValidationResult<'a> | |
module Validators = | |
let bind<'a,'b> (f : 'a -> ValidationResult<'b>) = | |
function | |
| Success x -> | |
f x | |
| Failure errors -> Failure errors | |
let length tag min max f = | |
Validators.bind (fun x -> | |
let value : string = f x | |
if value.Length >= min && value.Length < max then | |
ValidationResult.Success x | |
else | |
let currentLength = value.Length | |
let error = sprintf "Field=%s.ExpectedLength=%d-%d.Current=%d" tag min max currentLength | |
Failure ([| error |]) //there can be better errors | |
) | |
let size tag min f = | |
Validators.bind( fun x -> | |
let value = f x |> Seq.length | |
if value >= min then | |
ValidationResult.Success x | |
else | |
let error = sprintf "Field=%s.ExpectedSize=%d.CurrentSize=%d" tag min value | |
Failure ([| error |]) //there can be better errors | |
) | |
let has tag key f = | |
Validators.bind ( fun x -> | |
match f x |> Map.tryFind key with | |
| Some _ -> ValidationResult.Success x | |
| None -> Failure ([| sprintf "Field=%s.MissingKey=%s" tag key |]) | |
) | |
(* Mother of all operators; i guess. Can take any condition that we dont support today and use it here. *) | |
let predicate tag condition f = | |
Validators.bind (fun x -> | |
match condition <| f x with | |
| true -> Success x | |
| false -> Failure [| sprintf "Condition=%s failed" tag |] | |
) | |
let apply validation f = | |
Validators.bind (fun x -> | |
let results = | |
f x | |
|> Seq.map (Success >> validation) | |
let errors = | |
results | |
|> Seq.collect (function | ValidationResult.Failure (x) -> x | _ -> [||]) | |
|> Seq.toArray | |
match errors with | |
| [||] -> Success x | |
| xs -> Failure xs | |
) | |
(* option type is expected to be present. Failure otherwise. If present; apply the validation result *) | |
let applyOpt validation f = | |
Validators.bind (fun x -> | |
let result = | |
f x |> Option.map (Success >> validation) | |
match result with | |
| None -> Failure [| "Missing required field" |] | |
| Some (ValidationResult.Failure f) -> Failure f | |
| Some (ValidationResult.Success _) -> Success x | |
) | |
(* a helper .. just need to ensure the value exists *) | |
let required tag f x = | |
match applyOpt id f x with | |
| Failure fs -> Failure (Array.append fs [|sprintf "Field=%s" tag|]) | |
| Success x -> Success x | |
///example usage | |
type InputRecord = | |
{ | |
title: string | |
description: string | |
bullets : string [] | |
partNumber : string | |
attributes : Map<string, string> | |
mpq : string | |
id : string option | |
correlationId : string option | |
complexArray : string option array | |
} | |
with | |
static member Empty = | |
{ | |
title = "Sample" | |
description = "" | |
bullets = [||] | |
partNumber = "" | |
attributes = Map.empty | |
mpq = "" | |
id = None | |
correlationId = None | |
complexArray = [||] | |
} | |
let validateRecord (ip: InputRecord) = | |
let describeSpecification = | |
applyOpt (length "id" 1 5 id) (fun x -> x.id) | |
>> required "correlationId" (fun x -> x.correlationId) | |
>> apply (required "nested" id) (fun x -> x.complexArray) //just a proof of concept .. if complexArray has some elements; then all has to be Some .. | |
>> (length "title" 5 500) (fun x -> x.title) | |
>> (length "description" 1 2000) (fun x -> x.description) | |
>> (size "bullets" 5) (fun x -> x.bullets) | |
>> (has "attributes" "id") (fun x -> x.attributes) | |
>> apply (length "bullet" 5 10 id) (fun x -> x.bullets) | |
>> apply (predicate "bulletIsUpper" (String.forall (System.Char.IsUpper)) id) (fun x -> x.bullets) | |
let validate = | |
Success ip |> describeSpecification | |
validate |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment