United Kingdom: +44 (0)208 088 8978

Validation with Validus

Tom gives us a quick introduction to using Validus to validate data in your application.

We're hiring Software Developers

Click here to find out more

What is Validus?

Validus is a composable validation library for F#, with built-in validators for most primitive types and easily extended through custom validators.

What does Validus offer?

Composable validation

You can create complex validator chains and waterfalls by combining validators using the ValidatorGroup API. Alternatively, you can use a full suite of operators.

let emailValidator =
        ValidatorGroup(Check.String.betweenLen 8 512)                                                        // check string is between 8 and 512 chars
            .And(Check.WithMessage.String.pattern @"[^@]+@[^\.]+\..+" (sprintf "Please provide a valid %s")) // and, check string match email regex

Built-in validators for most primitive types

Validus comes with a set of default validators which may be useful in some situations. Additionally, it's possible to specify a custom error message by using Check.WithMessage.

    let dateValidator fieldName input = Check.WithMessage.DateTime.between DateTime.MinValue DateTime.MaxValue (sprintf "%s is invalid.")
    dateValidator "Date" DateTime.Now

    let positiveNumberValidator = Check.WithMessage.Int.greaterThan 0 (sprintf "Negative values are not allowed for %s")
    positiveNumberValidator "Number" 1

    let listEmptyOrSingleton = Check.List.betweenLen 0 1
    listEmptyOrSingleton "List" []

    let optionalEmailValidator = Check.optional (Check.String.pattern @"[^@]+@[^\.]+\..+")
    optionalEmailValidator "Optional" (Some "test@example.com")

Easily extended through custom-validators

Custom validators are easy to create with Validator.create that takes a message and a ('a -> bool) rule.

let fooValidator =
    let fooRule v = v = "foo"
    let fooMessage = sprintf "%s must be a string that matches 'foo'"
    Validator.create fooMessage fooRule

    |> fooValidator "Test string"

Infix operators to provide clean composition syntax, via Validus.Operators

Custom operators might not be everyone's cup of tea but, as mentioned earlier, composition can be done with the ValidatorGroup().And().Build() syntax instead.

type AgeGroup =
    | Adult of int
    | Child of int
    | Senior of int

let ageValidator =
    Check.String.pattern @"\d+" *|* Int32.Parse  // if pattern matches, convert to Int32
    >=> Check.Int.between 0 120                  // first check age between 0 and 120
    >=> (Check.Int.between 0 17  *|* Child       // then, check age between 0 an 17 assigning Child
    <|> Check.Int.greaterThan 65 *|* Senior      // or, check age greater than 65 assiging Senior
    <|> Check.Int.between 18 65  *|* Adult)      // or, check age between 18 and 65 assigning adult mapping converted input

Applicative computation expression

Validating bigger models is one of the things Validus does best. It uses an applicative computation expression, which lets it collect validation errors as it runs the validators.

module Person =
    let ofDto (dto : PersonDto) =
        let nameValidator = Check.String.betweenLen 3 64

        let firstNameValidator =
                .Then(Check.String.notEquals dto.LastName)

        validate {
          let! first = firstNameValidator "First name" dto.FirstName
          and! last = nameValidator "Last name" dto.LastName
          and! age = Check.optional (Check.Int.between 1 120) "Age" dto.Age

          return {
              Name = { First = first; Last = last }
              Age = age

Excellent for creating value objects (i.e., constrained primitives)

It is generally a good idea to create value objects, sometimes referred to as value types or constrained primitives, to represent individual data points that are more classified than the primitive types usually used to represent them.

For example, it is preferable that an email address is represented as a wrapper type rather than a string literal. This is because the domain of an email address is more tightly scoped than a string will allow.

To address this, we can create a wrapper type to represent the email address which hides away the implementation details and provides a smart construct to produce the type.

type Email =
    private { Email : string }

    override x.ToString () = x.Email

    static member Of : Validator<string, Email> = fun field input ->
        |> Check.String.pattern @"[^@]+@[^\.]+\..+" field
        |> Result.map (fun v -> { Email = v })

let email = 
    match Email.Of "Login email" "test@example.com" with
    | Ok email ->
    | Error e ->
        failwithf "Validation errors: %A" (ValidationErrors.toList e)