Validus 3.1.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package Validus --version 3.1.0                
NuGet\Install-Package Validus -Version 3.1.0                
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="3.1.0" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Validus --version 3.1.0                
#r "nuget: Validus, 3.1.0"                
#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=3.1.0

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

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
open Validus.Operators

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 }

let validatePersonDto (input : PersonDto) : Result<Person, ValidationErrors> = 
    // Shared validator for first & last name
    let nameValidator = 
        Validators.Default.String.betweenLen 3 64

    // Composing multiple validators to form complex validation rules,
    // overriding default error message (Note: "Validators.String" as 
    // opposed to "Validators.Default.String")
    let emailValidator = 
        Validators.Default.String.betweenLen 8 512
        <+> Validators.String.pattern "[^@]+@[^\.]+\..+" (sprintf "Please provide a valid %s")

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

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

    validate {
      let! first = nameValidator "First name" input.FirstName
      and! last = nameValidator "Last name" input.LastName
      and! email = emailValidator "Email address" input.Email
      and! age = ageValidator "Age" input.Age
      and! startDate = dateValidator "Start Date" input.StartDate
      
      // Construct Person if all validators return Success
      return {
          Name = { First = first; Last = last }
          Email = email
          Age = age
          StartDate = startDate }
    }

And, using the validator:

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

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

Custom Validators

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

open Validus 
open Validus.Operators

let emailValidator = 
    Validators.Default.String.betweenLen 8 512
    <+> Validators.String.pattern "[^@]+@[^\.]+\..+" (sprintf "%s must be a valid email")

"fake@test"
|> emailValidator "Login email" 
// Outputs: [ "Login email", [ "Login email must be a valid email" ] ]

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.

Creating a bespoke validator

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" 
// Outputs: [ "Test string", [ "Test string must be a string that matches 'foo'" ] ]

Validating Collections

Applying validator(s) to a set of items will result in a Result<'a, ValidationErrors> seq

open Validus 
open Validus.Operators

let emailValidator = 
    Validators.Default.String.betweenLen 8 512
    <+> Validators.String.pattern "[^@]+@[^\.]+\..+" (sprintf "%s must be a valid email")

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

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

// result is a Result<string, ValidationErrors> seq

// Outputs: [ "Login email", [ "Login email must be a valid email" ] ]

Constrained Primitives (i.e., value types/objects)

It is generally a good idea to create value objects 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.

type Email = 
    private { Email : string } 

    override x.ToString () = x.Email
    
    static member Of 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 field input =
        let e164Regex = @"^\+[1-9]\d{1,14}$"       
        let message = sprintf "%s must be a valid E164 telephone number"

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

Built-in Validators

All of the built-in validators reside in the Validators module and follow a similar definition.

// Produce a validation result based on a field name and value
string -> 'a -> Result<'a, ValidationErrors>

Note: Validators pre-populated with English-language default error messages reside within the Validators.Default 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 = 
  Validators.Default.String.equals "foo" "fieldName"

equalsFoo "bar" // Result<string, ValidationErrors>

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

equalsFooCustom "bar" // Result<string, ValidationErrors>

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 = 
  Validators.Default.String.notEquals "foo" "fieldName"

notEqualsFoo "bar" // Result<string, ValidationErrors>

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

notEqualsFooCustom "bar" // Result<string, ValidationErrors>

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 = 
  Validators.Default.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 = 
  Validators.Int.between 1 100 (sprintf "%s must be between 1 and 100") "fieldName"

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 = 
  Validators.Default.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 = 
  Validators.Int.greaterThan 100 (sprintf "%s must be greater than 100") "fieldName"

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 = 
  Validators.Default.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 = 
  Validators.Int.lessThan 100 (sprintf "%s must be less than 100") "fieldName"

lessThan100Custom 12 // Result<int, ValidationErrors>

String specific validators

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 = 
  Validators.Default.String.betweenLen 1 100 "fieldName"

between1and100Chars "validus" // Result<string, ValidationErrors>

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

between1and100CharsCustom "validus" // Result<string, ValidationErrors>

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 = 
  Validators.Default.String.equalsLen 100 "fieldName"

equals100Chars "validus" // Result<string, ValidationErrors>

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

equals100CharsCustom "validus" // Result<string, ValidationErrors>

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 = 
  Validators.Default.String.greaterThanLen 100 "fieldName"

greaterThan100Chars "validus" // Result<string, ValidationErrors>

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

greaterThan100CharsCustom "validus" // Result<string, ValidationErrors>

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 = 
  Validators.Default.String.lessThanLen 100 "fieldName"

lessThan100Chars "validus" // Result<string, ValidationErrors>

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

lessThan100CharsCustom "validus" // Result<string, ValidationErrors>

empty

Applies to: string

open Validus 

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

stringIsEmpty "validus" // Result<string, ValidationErrors>

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

stringIsEmptyCustom "validus" // Result<string, ValidationErrors>

notEmpty

Applies to: string

open Validus 

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

stringIsNotEmpty "validus" // Result<string, ValidationErrors>

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

stringIsNotEmptyCustom "validus" // Result<string, ValidationErrors>

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 = 
  Validators.Default.String.pattern "[a-z]" "fieldName"

stringIsChars "validus" // Result<string, ValidationErrors>

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

stringIsCharsCustom "validus" // Result<string, ValidationErrors>

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