Validus 4.0.1

There is a newer version of this package available.
See the version list below for details.
dotnet add package Validus --version 4.0.1                
NuGet\Install-Package Validus -Version 4.0.1                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Validus" Version="4.0.1" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Validus --version 4.0.1                
#r "nuget: Validus, 4.0.1"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Validus as a Cake Addin
#addin nuget:?package=Validus&version=4.0.1

// Install Validus as a Cake Tool
#tool nuget:?package=Validus&version=4.0.1                

Validus

NuGet Version build

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

Key Features

Quick Start

A common example of receiving input from an untrusted source PersonDto (i.e., HTML form submission), applying validation and producing a result based on success/failure.

open System
open Validus

type PersonDto =
    { FirstName : string
      LastName  : string
      Email     : string
      Age       : int option
      StartDate : DateTime option }

type Name =
    { First : string
      Last  : string }

type Person =
    { Name      : Name
      Email     : string
      Age       : int option
      StartDate : DateTime }

module Person =
    let ofDto (dto : PersonDto) =
        // Shared validator for first & last name
        let nameValidator =
            Check.String.betweenLen 3 64

        // Composing multiple validators to form complex validation rules,
        // overriding default error message (Note: "Check.WithMessage.String" as
        // opposed to "Check.String")
        let emailValidator =
            let emailPatternValidator =
                let msg = sprintf "Please provide a valid %s"
                Check.WithMessage.String.pattern @"[^@]+@[^\.]+\..+" msg

            ValidatorGroup(Check.String.betweenLen 8 512)
                .And(emailPatternValidator)
                .Build()

        // Defining a validator for an option value
        let ageValidator =
            Check.optional (Check.Int.between 1 100)

        // Defining a validator for an option value that is required
        let dateValidator =
            Check.required (Check.DateTime.greaterThan DateTime.Now)

        validate {
          let! first = nameValidator "First name" dto.FirstName
          and! last = nameValidator "Last name" dto.LastName
          and! email = emailValidator "Email address" dto.Email
          and! age = ageValidator "Age" dto.Age
          and! startDate = dateValidator "Start Date" dto.StartDate

          // Construct Person if all validators return Success
          return {
              Name = { First = first; Last = last }
              Email = email
              Age = age
              StartDate = startDate }
        }

Note: This is for demo purposes only, it likely isn't advisable to attempt to validate emails using a regular expression. Instead, use System.Net.MailAddress.

And, using the validator:

let dto : PersonDto =
    { FirstName = "John"
      LastName  = "Doe"
      Email     = "john.doe@url.com"
      Age       = Some 63
      StartDate = Some (new DateTime(2058, 1, 1)) }

match validatePersonDto dto with
| Success p -> printfn "%A" p
| Failure e ->
    e
    |> ValidationErrors.toList
    |> Seq.iter (printfn "%s")

Validating Complex Types

Included in Validus is an applicative computation expression, which in this case allow validation errors to be accumulated as validators are executed.

open Validus

type PersonDto =
    { FirstName : string
      LastName  : string
      Age       : int option }

type Name =
    { First : string
      Last  : string }

type Person =
    { Name      : Name
      Age       : int option }

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

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

        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 }
        }

Creating A Custom Validator

open System.Net.Mail
open Validus

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

"bar"
|> fooValidator "Test string"

Combining Validators

Complex validator chains and waterfalls can be created by combining validators together using the ValidatorGroup API. Alternatively, a full suite of operators are available, for those who prefer that style of syntax.

open System.Net.Mail
open Validus

let msg = sprintf "Please provide a valid %s"

let emailPatternValidator =
    Check.WithMessage.String.pattern @"[^@]+@[^\.]+\..+" msg

// A custom validator that uses System.Net.Mail to validate email
let mailAddressValidator =
    let rule (x : string) =
        if x = "" then false
        else
            try
                let addr = MailAddress(x)
                if addr.Address = x then true
                else false
            with
            | :? FormatException -> false

    Validator.create msg rule

let emailValidator =
    ValidatorGroup(Check.String.betweenLen 8 512)
        .And(emailPatternValidator)
        .Then(mailAddressValidator) // only executes when prior two steps are `Ok`
        .Build()

"fake@test"
|> emailValidator "Login email"

We can use any validator, or combination of validators to validate collections:

let emails = [ "fake@test"; "bob@fsharp.org"; "x" ]

let result =
    emails
    |> List.map (emailValidator "Login email")

Custom Operators

Operator Description
<+> Compose two validators of equal types
*\|* Map the Ok result of a validator, high precedence, for use with choice <\|>.
*\| Set the Ok result of a validator to a fixed value, high precedence, for use with choice <\|>.
>>\| Map the Ok result of a validator, low precedence, for use in chained validation
>\| Set the Ok result of a validator to a fixed value, low precedence, for use in chained validation
>>= Bind the Ok result of a validator with a one-argument function that returns a Result
<<= Reverse-bind the Ok result of a validator with a one-argument function that returns a Result
>>% Set the Ok result of a validator to a fixed Result value
<\|> Introduce choice: if the rh-side validates Ok, pick that result, otherwise, continue with the next validator
>=> Kleisli-bind two validators. Other than Compose <+>, this can change the result type.
<=< Reverse kleisli-bind two validators (rh-side is evaluated first). Other than Compose <+>, this can change the result type.
.>> Compose two validators, but keep the result of the lh-side. Ignore the result of the rh-side, unless it returns an Error.
>>. Compose two validators, but keep the result of the rh-side. Ignore the result of the lh-side, unless it returns an Error.
.>>. Compose two validators, and keep the result of both sides as a tuple.

Recreating the example code above using the combinator operators:

open System.Net.Mail
open Validus
open Validus.Operators

let msg = sprintf "Please provide a valid %s"

let emailPatternValidator =
    Check.WithMessage.String.pattern @"[^@]+@[^\.]+\..+" msg

// A custom validator that uses System.Net.Mail to validate email
let mailAddressValidator =
    let rule (x : string) =
        if x = "" then false
        else
            try
                let addr = MailAddress(x)
                if addr.Address = x then true
                else false
            with
            | :? FormatException -> false

    Validator.create msg rule

let emailValidator =
    Check.String.betweenLen 8 512 // check string is between 8 and 512 chars
    <+> emailPatternValidator     // and, check string match email regex
    >=> mailAddressValidator      // then, check using System.Net.Mail if prior two steps are `Ok`

"fake@test"
|> emailValidator "Login email"

A more complex example involving "chained" validators and both "choice" assignment & mapping:

open System
open Validus
open Validus.Operators

type AgeGroup =
    | Adult of int
    | Child
    | Senior

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

Value Objects

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

Example 1: Email Address Value Object

A good example of this is an email address being represented as a string literal, as it exists in many programs. This is however a flawed approach in that the domain of an email address is more tightly scoped than a string will allow. For example, "" or null are not valid emails.

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.

open System.Net.Mail

type Email =
    private { Email : string }

    override x.ToString () = x.Email

    // Note the transformation from string -> Email
    static member Of : Validator<string, Email> = fun field input ->
        let rule (x : string) =
            if x = "" then false
            else
                try
                    let addr = MailAddress(x)
                    if addr.Address = x then true
                    else false
                with
                | :? FormatException -> false

        let message = sprintf "%s must be a valid email address"

        input
        |> Validator.create message rule field
        |> Result.map (fun v -> { Email = v })

Example 2: E164 Formatted Phone Number

type E164 =
    private { E164 : string }

    override x.ToString() = x.E164

    static member Of : Validator<string, E164> = fun field input ->
        let e164Regex = @"^\+[1-9]\d{1,14}$"
        let message = sprintf "%s must be a valid E164 telephone number"

        input
        |> Check.WithMessage.String.pattern e164Regex message field
        |> Result.map (fun v -> { E164 = v })

Built-in Validators

Note: Validators pre-populated with English-language default error messages reside within the Check module.

equals

Applies to: string, int16, int, int64, decimal, float, DateTime, DateTimeOffset, TimeSpan

open Validus

// Define a validator which checks if a string equals
// "foo" displaying the standard error message.
let equalsFoo =
  Check.String.equals "foo" "fieldName"

equalsFoo "bar"

// Define a validator which checks if a string equals
// "foo" displaying a custom error message (string -> string).
let equalsFooCustom =
  let msg = sprintf "%s must equal the word 'foo'" "fieldName"
  Check.WithMessage.String.equals "foo" msg

equalsFooCustom "bar"

notEquals

Applies to: string, int16, int, int64, decimal, float, DateTime, DateTimeOffset, TimeSpan

open Validus

// Define a validator which checks if a string is not
// equal to "foo" displaying the standard error message.
let notEqualsFoo =
  Check.String.notEquals "foo" "fieldName"

notEqualsFoo "bar"

// Define a validator which checks if a string is not
// equal to "foo" displaying a custom error message (string -> string)
let notEqualsFooCustom =
  let msg = sprintf "%s must not equal the word 'foo'" "fieldName"
  Check.WithMessage.String.notEquals "foo" msg

notEqualsFooCustom "bar"

between (inclusive)

Applies to: int16, int, int64, decimal, float, DateTime, DateTimeOffset, TimeSpan

open Validus

// Define a validator which checks if an int is between
// 1 and 100 (inclusive) displaying the standard error message.
let between1and100 =
  Check.Int.between 1 100 "fieldName"

between1and100 12 // Result<int, ValidationErrors>

// Define a validator which checks if an int is between
// 1 and 100 (inclusive) displaying a custom error message.
let between1and100Custom =
  let msg = sprintf "%s must be between 1 and 100" "fieldName"
  Check.WithMessage.Int.between 1 100 msg

between1and100Custom 12 // Result<int, ValidationErrors>

greaterThan

Applies to: int16, int, int64, decimal, float, DateTime, DateTimeOffset, TimeSpan

open Validus

// Define a validator which checks if an int is greater than
// 100 displaying the standard error message.
let greaterThan100 =
  Check.Int.greaterThan 100 "fieldName"

greaterThan100 12 // Result<int, ValidationErrors>

// Define a validator which checks if an int is greater than
// 100 displaying a custom error message.
let greaterThan100Custom =
  let msg = sprintf "%s must be greater than 100" "fieldName"
  Check.WithMessage.Int.greaterThan 100 msg

greaterThan100Custom 12 // Result<int, ValidationErrors>

lessThan

Applies to: int16, int, int64, decimal, float, DateTime, DateTimeOffset, TimeSpan

open Validus

// Define a validator which checks if an int is less than
// 100 displaying the standard error message.
let lessThan100 =
  Check.Int.lessThan 100 "fieldName"

lessThan100 12 // Result<int, ValidationErrors>

// Define a validator which checks if an int is less than
// 100 displaying a custom error message.
let lessThan100Custom =
  let msg = sprintf "%s must be less than 100" "fieldName"
  Check.WithMessage.Int.lessThan 100 msg

lessThan100Custom 12 // Result<int, ValidationErrors>

betweenLen

Applies to: string

open Validus

// Define a validator which checks if a string is between
// 1 and 100 chars displaying the standard error message.
let between1and100Chars =
  Check.String.betweenLen 1 100 "fieldName"

between1and100Chars "validus"

// Define a validator which checks if a string is between
// 1 and 100 chars displaying a custom error message.
let between1and100CharsCustom =
  let msg = sprintf "%s must be between 1 and 100 chars" "fieldName"
  Check.WithMessage.String.betweenLen 1 100 msg

between1and100CharsCustom "validus"

equalsLen

Applies to: string

open Validus

// Define a validator which checks if a string is equals to
// 100 chars displaying the standard error message.
let equals100Chars =
  Check.String.equalsLen 100 "fieldName"

equals100Chars "validus"

// Define a validator which checks if a string is equals to
// 100 chars displaying a custom error message.
let equals100CharsCustom =
  let msg = sprintf "%s must be 100 chars" "fieldName"
  Check.WithMessage.String.equalsLen 100 msg

equals100CharsCustom "validus"

greaterThanLen

Applies to: string

open Validus

// Define a validator which checks if a string is greater than
// 100 chars displaying the standard error message.
let greaterThan100Chars =
  Check.String.greaterThanLen 100 "fieldName"

greaterThan100Chars "validus"

// Define a validator which checks if a string is greater than
// 100 chars displaying a custom error message.
let greaterThan100CharsCustom =
  let msg = sprintf "%s must be greater than 100 chars" "fieldName"
  Check.WithMessage.String.greaterThanLen 100 msg

greaterThan100CharsCustom "validus"

lessThanLen

Applies to: string

open Validus

// Define a validator which checks if a string is less tha
// 100 chars displaying the standard error message.
let lessThan100Chars =
  Check.String.lessThanLen 100 "fieldName"

lessThan100Chars "validus"

// Define a validator which checks if a string is less tha
// 100 chars displaying a custom error message.
let lessThan100CharsCustom =
  let msg = sprintf "%s must be less than 100 chars" "fieldName"
  Check.WithMessage.String.lessThanLen 100 msg

lessThan100CharsCustom "validus"

empty

Applies to: string

open Validus

// Define a validator which checks if a string is empty
// displaying the standard error message.
let stringIsEmpty =
  Check.String.empty "fieldName"

stringIsEmpty "validus"

// Define a validator which checks if a string is empty
// displaying a custom error message.
let stringIsEmptyCustom =
  let msg = sprintf "%s must be empty" "fieldName"
  Check.WithMessage.String.empty msg

stringIsEmptyCustom "validus"

notEmpty

Applies to: string

open Validus

// Define a validator which checks if a string is not empty
// displaying the standard error message.
let stringIsNotEmpty =
  Check.String.notEmpty "fieldName"

stringIsNotEmpty "validus"

// Define a validator which checks if a string is not empty
// displaying a custom error message.
let stringIsNotEmptyCustom =
  let msg = sprintf "%s must not be empty" "fieldName"
  Check.WithMessage.String.notEmpty msg

stringIsNotEmptyCustom "validus"

pattern (Regular Expressions)

Applies to: string

open Validus

// Define a validator which checks if a string matches the
// provided regex displaying the standard error message.
let stringIsChars =
  Check.String.pattern "[a-z]+" "fieldName"

stringIsChars "validus"

// Define a validator which checks if a string matches the
// provided regex displaying a custom error message.
let stringIsCharsCustom =
  let msg = sprintf "%s must follow the pattern [a-z]" "fieldName"
  Check.WithMessage.String.pattern "[a-z]" msg

stringIsCharsCustom "validus"

Find a bug?

There's an issue for that.

License

Built with ♥ by Pim Brouwers in Toronto, ON. Licensed under Apache License 2.0.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
4.1.4 3,233 3/15/2024
4.1.3 5,301 7/15/2023
4.1.2 8,376 1/12/2023
4.1.1 284 1/12/2023
4.1.0 529 12/26/2022
4.0.3 325 12/21/2022
4.0.2 745 11/22/2022
4.0.1 2,000 9/19/2022
4.0.0 556 9/5/2022
3.1.0 388 9/1/2022
3.0.1 2,295 12/9/2021
3.0.0 345 9/1/2021
2.0.3 383 8/17/2021
2.0.2 338 8/13/2021
2.0.1 342 8/6/2021
2.0.0 377 7/6/2021
1.4.2 329 6/28/2021
1.4.1 397 6/23/2021
1.3.1 369 6/15/2021
1.3.0 308 6/4/2021
1.2.0 317 6/2/2021
1.1.0 347 6/2/2021
1.0.1 382 4/22/2021
1.0.0 511 11/28/2020
1.0.0-alpha5 271 11/23/2020
1.0.0-alpha4 280 11/23/2020
1.0.0-alpha3 276 11/23/2020
1.0.0-alpha2 231 11/21/2020
1.0.0-alpha1 281 11/20/2020