Tutorial: Validate and convert data

Json and query data is limited to a few basic types: strings, numbers, and booleans. Saon provides functions to convert from these types to richer F# types.

All conversion functions have the same signature:

1: 
type Transformer<'T, 'U> = string -> 'T -> ParserResult<'U>

The first parameter is the property or parameter name, the second parameter is the value being converted, and the result is the discriminated union ParserResult<'U> that will contain the new value or an error.

Saon calls transformers where the type of the data does no change validators, since the most common use case is to validate their inputs.

You can find a list of conversion functions in the Convert module reference, and a list of validation functions in the Validate module reference.

Converting between types

As an example, let's take a look at the Convert.stringToDecimal function.

1: 
Convert.stringToDecimal "my_prop" "123.4567890123456789"

Since the value "123.4567890123456789" is a properly formatted decimal number, the function will returnSuccess 123.4567890123456789M`.

If the value is not a valid number, the function will return a ValidationFailed that contains an helpful error message.

1: 
2: 
3: 
4: 
ValidationFailed
    (map [("my_prop", [{ Property = "my_prop"
                         Type = "stringToDecimal"
                         Message = "malformed decimal number" }])])

Validation

Validators are similar to converters, but they return the original input if the validation was successful, otherwise they return a ValidationFailed with more information.

Saon includes a good amount of validators, you can find an up-to-date list in the reference.

1: 
2: 
3: 
4: 
5: 
// This validation will pass
Validate.hasLengthBetween 1 10 "my_array" ["a"; "b"; "c"]

// This will fail
Validate.hasLengthBetween 1 10 "my_string" "this string is too long, will fail :("

Composing existing transformation functions

Composing transformers is awkward because of the property name parameter. Saon includes a Parser.pipe function that given a function f and a function g, will pass the result of f into g only if f is successful.

1: 
let parsePositiveAmount = Parser.pipe Convert.stringToDecimal (Validate.isGreaterThan 0m)

Composing transformers is such an important function that Saon includes an operator that can help make it easier.

1: 
2: 
3: 
4: 
open Saon.Operators

let parseSize = parsePositiveAmount /> Convert.withFunction Size
let parsePrice = parsePositiveAmount /> Convert.withFunction Price

Defining new transformations

Transformers are just functions so you can easily define your own. As you use Saon in your project, you will grow a collection of validators tailored to your domain, such as the parseSize and parsePrice functions above.

1: 
2: 
3: 
4: 
5: 
6: 
7: 
let isOdd propName value =
    if value % 2 = 1 then
        ParserResult.success value
    else
        ParserResult.validationFail "isOdd" propName "must be an odd number"

isOdd "my_int" 3
Multiple items
val string : value:'T -> string

--------------------
type string = System.String
namespace Saon
module Convert

from Saon
val stringToDecimal : propName:string -> value:string -> ParserResult<decimal>
module Validate

from Saon
val hasLengthBetween : minLength:int -> maxLength:int -> propName:string -> value:'T -> ParserResult<'T> (requires member get_Length)
val parsePositiveAmount : (string -> string -> ParserResult<decimal>)
Multiple items
module Parser

from Saon

--------------------
type Parser<'T,'E> = 'E -> ParserResult<'T> * 'E
val pipe : a:(string -> 'T -> ParserResult<'R>) -> b:(string -> 'R -> ParserResult<'S>) -> propName:string -> aValue:'T -> ParserResult<'S>
val isGreaterThan : minValue:'T -> propName:string -> value:'T -> ParserResult<'T> (requires comparison)
module Operators

from Saon
val parseSize : (string -> string -> ParserResult<obj>)
val withFunction : func:('T -> 'R) -> string -> value:'T -> ParserResult<'R>
val parsePrice : (string -> string -> ParserResult<obj>)
val isOdd : propName:string -> value:int -> ParserResult<int>
val propName : string
val value : int
Multiple items
module ParserResult

from Saon

--------------------
type ParserResult<'T> =
  | ParsingFailed of field: string option * message: string
  | ValidationFailed of ValidationFailedMap
  | Success of 'T
val success : value:'a -> ParserResult<'a>
val validationFail : typ:string -> propName:string -> msg:string -> ParserResult<'a>