Validus is a composable validation library for F#, with built-in validators for most primitive types and easily extended through custom validators.
- Composable validation.
- Built-in for most primitive types.
- Easily extended through custom-validators.
- Infix operators to provide clean syntax.
A common example of receiving input from an untrusted source (ex: user form submission), applying validation and producing a result based on success/failure.
open Validus
open Validus.Operators
// Untrusted input
type PersonInput =
{ FirstName : string
LastName : string
Email : string
Age : int option }
// Internal domain model for names
type Name =
{ First : string
Last : string }
static member Create first last =
{ First = first
Last = last }
// Internal person record, which has been validated
type Person =
{ Name : Name
Email : string
Age : int option }
static member Create first last email age =
{ Name = Name.create first last
Email = email
Age = age }
// PersonInput -> ValidationResult<Person>
let validatePersonInput input =
// Shared validator for first & last name
let nameValidator =
Validators.String.betweenLen 3 64 None
// Composing multiple validators to form complex validation rules
let emailValidator =
Validators.String.betweenLen 8 512 None
<+> Validators.String.pattern "[^@]+@[^\.]+\..+" (Some (sprintf "Please provide a valid %s")) // Overriding default error message
// Defining a validator for an optional value, then composing
// multiple validators to form complex validation rule
let ageValidator =
Validators.optional
(Validators.Int.greaterThan 0 None <+> Validators.Int.lessThan 100 None)
"Age"
expected.Age
// Construct Person if all validators return Success
Person.Create
<!> nameValidator "First name" input.FirstName // <!> is alias for ValidationResult.map
<*> nameValidator "Last name" input.LastName // <*> is an alias for ValidationResult.apply
<*> emailValidator "Email address" input.Email
<*> Validators.Int.between 1 100 None "Age" input.Age
// Successful execution
let validPersonInput : PersonInput =
// ...
let person : ValidationResult<Person> =
validatePerson validPersonInput
match person with
| Success p -> printfn "%A" p
| Failure e -> // ...
// Unsuccessful execution
let invalidPersonInput : PersonInput =
// ...
let person : ValidationResult<Person> =
validatePerson invalidPersonInput // ValidationResult<Person>
match person with
| Success p -> // ...
| Failure e ->
e
|> ValidationErrors.toList
|> Seq.iter (printfn "%s")
All of the built-in validators reside in the Validators
module and follow a similar definition.
// Produce a validation message based on a field name
type ValidationMessage = string -> string
// Produce a validation result based on a field name and result
type Validator<'a> = string -> 'a -> ValidationResult<'a>
// Given 'a value, and optional validtion message produce
// a ready to use validator for 'a
'a -> ValidationMessage option -> Validator<'a>
Applies to: string, int16, int, int64, decimal, float, DateTime, DateTimeOffset, TimeSpan
// Define a validator which checks if a string equals
// "foo" displaying the standard error message.
let equalsFoo =
Validators.String.equals "foo" None "field"
equalsFoo "bar" // ValidationResult<string>
Applies to: `string, int16, int, int64, decimal, float, DateTime, DateTimeOffset, TimeSpan
// Define a validator which checks if a string is not
// equal to "foo" displaying the standard error message.
let notEqualsFoo =
Validators.String.equals "foo" None "field"
notEqualsFoo "bar" // ValidationResult<string>
between
(inclusive)
Applies to: int16, int, int64, decimal, float, DateTime, DateTimeOffset, TimeSpan
// Define a validator which checks if an int is between
// 1 and 100 (inclusive) displaying the standard error message.
let between1and100 =
Validators.Int.between 1 100 None "field"
between1and100 12 // ValidationResult<int>
Applies to: int16, int, int64, decimal, float, DateTime, DateTimeOffset, TimeSpan
// Define a validator which checks if an int is greater than
// 100 displaying the standard error message.
let greaterThan100 =
Validators.Int.greaterThan 100 None "field"
greaterThan100 12 // ValidationResult<int>
Applies to: int16, int, int64, decimal, float, DateTime, DateTimeOffset, TimeSpan
// Define a validator which checks if an int is less than
// 100 displaying the standard error message.
let lessThan100 =
Validators.Int.lessThan 100 None "field"
lessThan100 12 // ValidationResult<int>
Applies to: string
// Define a validator which checks if a string is between
// 1 and 100 chars displaying the standard error message.
let between1and100Chars =
Validators.String.betweenLen 1 100 None "field"
between1and100Chars "validus" // ValidationResult<string>
Applies to: string
// Define a validator which checks if a string is greater tha
// 100 chars displaying the standard error message.
let greaterThan100Chars =
Validators.String.greaterThanLen 100 None "field"
greaterThan100Chars "validus" // ValidationResult<string>
Applies to: string
// Define a validator which checks if a string is less tha
// 100 chars displaying the standard error message.
let lessThan100Chars =
Validators.String.lessThanLen 100 None "field"
lessThan100Chars "validus" // ValidationResult<string>
Applies to: string
// Define a validator which checks if a string is empty
// displaying the standard error message.
let stringIsEmpty =
Validators.String.empty None "field"
stringIsEmpty "validus" // ValidationResult<string>
Applies to: string
// Define a validator which checks if a string is not empty
// displaying the standard error message.
let stringIsNotEmpty =
Validators.String.notEmpty None "field"
stringIsNotEmpty "validus" // ValidationResult<string>
pattern
(Regular Expressions)
Applies to: string
// Define a validator which checks if a string matches the
// provided regex displaying the standard error message.
let stringIsChars =
Validators.String.pattern "[a-z]" None "field"
stringIsChars "validus" // ValidationResult<string>
Custom validators can be created by combining built-in validators together using Validator.compose
, or the <+>
infix operator, as well as creating bespoke validator's using Validator.create
.
// Combining built-in validators
let emailValidator =
Validators.String.betweenLen 8 512 None
<+> Validators.String.pattern "[^@]+@[^\.]+\..+" None
let email = "fake@test.com"
let emailResult = emailValidator "Login email" email
// Creating a custom validator
let fooValidator =
let fooRule : ValidationRule<string> = fun v -> v = "foo"
let fooMessage = sprintf "%s must be a string that matches 'foo'"
Validator.create fooMessage fooRule
"bar"
|> fooValidator "Test string"
// Outputs: [ "Test string", [ "Test string must be a string that matches 'foo'" ] ]
There's an issue for that.
Built with ♥ by Pim Brouwers in Toronto, ON. Licensed under Apache License 2.0.