{"id":15392969,"url":"https://github.com/pimbrouwers/validus","last_synced_at":"2025-04-05T05:06:10.228Z","repository":{"id":45539124,"uuid":"314656093","full_name":"pimbrouwers/Validus","owner":"pimbrouwers","description":"An extensible F# validation library.","archived":false,"fork":false,"pushed_at":"2024-05-16T17:34:54.000Z","size":243,"stargazers_count":151,"open_issues_count":3,"forks_count":10,"subscribers_count":8,"default_branch":"main","last_synced_at":"2025-03-29T04:06:25.461Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"F#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pimbrouwers.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-11-20T19:57:24.000Z","updated_at":"2025-03-22T04:57:43.000Z","dependencies_parsed_at":"2024-10-19T01:07:13.495Z","dependency_job_id":null,"html_url":"https://github.com/pimbrouwers/Validus","commit_stats":{"total_commits":177,"total_committers":10,"mean_commits":17.7,"dds":0.5084745762711864,"last_synced_commit":"6a28f13b28c0374ca3f799a9f962f3bd0663283a"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pimbrouwers%2FValidus","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pimbrouwers%2FValidus/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pimbrouwers%2FValidus/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pimbrouwers%2FValidus/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pimbrouwers","download_url":"https://codeload.github.com/pimbrouwers/Validus/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247289426,"owners_count":20914464,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-01T15:16:57.752Z","updated_at":"2025-04-05T05:06:10.208Z","avatar_url":"https://github.com/pimbrouwers.png","language":"F#","readme":"# Validus\n\n[![NuGet Version](https://img.shields.io/nuget/v/Validus.svg)](https://www.nuget.org/packages/Validus)\n[![build](https://github.com/pimbrouwers/Validus/actions/workflows/build.yml/badge.svg)](https://github.com/pimbrouwers/Validus/actions/workflows/build.yml)\n\nValidus is an extensible validation library for F# with built-in validators for most primitive types and easily extended through custom validators.\n\n## Key Features\n\n- [Composable](#combining-validators) validation.\n- [Built-in](#built-in-validators) validators for most primitive types.\n- Easily extended through [custom-validators](#creating-a-custom-validators).\n- Infix [operators](#custom-operators) to provide clean composition syntax, via `Validus.Operators`.\n- [Applicative computation expression](#validating-complex-types).\n- Excellent for creating [value objects](#value-object) (i.e., cpnstrained primitives).\n\n## Quick Start\n\nA 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.\n\n```f#\nopen System\nopen System.Net.Mail\nopen Validus\n\ntype PersonDto =\n    { FirstName : string\n      LastName  : string\n      Email     : string\n      Age       : int option\n      StartDate : DateTime option }\n\ntype Name =\n    { First : string\n      Last  : string }\n\ntype Person =\n    { Name      : Name\n      Email     : string\n      Age       : int option\n      StartDate : DateTime }\n\nmodule Person =\n    let ofDto (dto : PersonDto) =\n        // A basic validator\n        let nameValidator =\n            Check.String.betweenLen 3 64\n\n        // A custom email validator, using the *built-in* functionality\n        // from System.Net.Mail\n        let emailValidator =\n            let msg = sprintf \"Please provide a valid %s\"\n            let rule v =\n                let success, _ = MailAddress.TryCreate v\n                success\n            Validator.create msg rule\n\n        // Composing multiple validators to form complex validation rules,\n        // overriding default error message (Note: \"Check.WithMessage.String\" as\n        // opposed to \"Check.String\")\n        let emailValidator =\n            let emailPatternValidator =\n                let msg = sprintf \"Please provide a valid %s\"\n                Check.WithMessage.String.pattern @\"[^@]+@[^\\.]+\\..+\" msg\n\n            ValidatorGroup(Check.String.betweenLen 8 512)\n                .And(emailPatternValidator)\n                .Build()\n\n        // Defining a validator for an option value\n        let ageValidator =\n            Check.optional (Check.Int.between 1 100)\n\n        // Defining a validator for an option value that is required\n        let dateValidator =\n            Check.required (Check.DateTime.greaterThan DateTime.Now)\n\n        validate {\n          let! first = nameValidator \"First name\" dto.FirstName\n          and! last = nameValidator \"Last name\" dto.LastName\n          and! email = emailValidator \"Email address\" dto.Email\n          and! age = ageValidator \"Age\" dto.Age\n          and! startDate = dateValidator \"Start Date\" dto.StartDate\n\n          // Construct Person if all validators return Success\n          return {\n              Name = { First = first; Last = last }\n              Email = email\n              Age = age\n              StartDate = startDate }\n        }\n```\n\n\u003e 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](#example-1-email-address-value-object).\n\nAnd, using the validator:\n\n```fsharp\nlet dto : PersonDto =\n    { FirstName = \"John\"\n      LastName  = \"Doe\"\n      Email     = \"john.doe@url.com\"\n      Age       = Some 63\n      StartDate = Some (new DateTime(2058, 1, 1)) }\n\nmatch validatePersonDto dto with\n| Ok p -\u003e printfn \"%A\" p\n| Error e -\u003e\n    e\n    |\u003e ValidationErrors.toList\n    |\u003e Seq.iter (printfn \"%s\")\n```\n\n## Validating Complex Types\n\nIncluded in Validus is an [applicative computation expression](https://docs.microsoft.com/en-us/dotnet/fsharp/whats-new/fsharp-50#applicative-computation-expressions), which in this case allow validation errors to be accumulated as validators are executed.\n\n\n\n```f#\nopen Validus\n\ntype PersonDto =\n    { FirstName : string\n      LastName  : string\n      Age       : int option }\n\ntype Name =\n    { First : string\n      Last  : string }\n\ntype Person =\n    { Name      : Name\n      Age       : int option }\n\nmodule Person =\n    let ofDto (dto : PersonDto) =\n        let nameValidator = Check.String.betweenLen 3 64\n\n        let firstNameValidator =\n            ValidatorGroup(nameValidator)\n                .Then(Check.String.notEquals dto.LastName)\n                .Build()\n\n        validate {\n          let! first = firstNameValidator \"First name\" dto.FirstName\n          and! last = nameValidator \"Last name\" dto.LastName\n          and! age = Check.optional (Check.Int.between 1 120) \"Age\" dto.Age\n\n          return {\n              Name = { First = first; Last = last }\n              Age = age }\n        }\n```\n\n## Creating A Custom Validator\n\n```f#\nopen System.Net.Mail\nopen Validus\n\nlet fooValidator =\n    let fooRule v = v = \"foo\"\n    let fooMessage = sprintf \"%s must be a string that matches 'foo'\"\n    Validator.create fooMessage fooRule\n\n\"bar\"\n|\u003e fooValidator \"Test string\"\n```\n\n## Combining Validators\n\nComplex validator chains and waterfalls can be created by combining validators together using the `ValidatorGroup` API. Alternatively, a full suite of [operators](#custom-operators) are available, for those who prefer that style of syntax.\n\n```f#\nopen System.Net.Mail\nopen Validus\n\nlet emailPatternValidator =\n    let msg = sprintf \"The %s input is not formatted as expected\"\n    Check.WithMessage.String.pattern @\"[^@]+@[^\\.]+\\..+\" msg\n\n// A custom validator that uses System.Net.Mail to validate email\nlet mailAddressValidator =\n    let msg = sprintf \"The %s input is not a valid email address\"\n    let rule (x : string) =\n        let success, _ = MailAddress.TryCreate x\n        success\n    Validator.create msg rule\n\nlet emailValidator =\n    ValidatorGroup(Check.String.betweenLen 8 512)\n        .And(emailPatternValidator)\n        .Then(mailAddressValidator) // only executes when prior two steps are `Ok`\n        .Build()\n\n\"fake@test\"\n|\u003e emailValidator \"Login email\"\n```\n\nWe can use any validator, or combination of validators to validate collections:\n\n```fsharp\nlet emails = [ \"fake@test\"; \"bob@fsharp.org\"; \"x\" ]\n\nlet result =\n    emails\n    |\u003e List.map (emailValidator \"Login email\")\n```\n\n## Value Objects\n\nIt is generally a good idea to create [value objects](https://blog.ploeh.dk/2015/01/19/from-primitive-obsession-to-domain-modelling/), 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.\n\n### Example 1: Email Address Value Object\n\nA 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.\n\nTo 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.\n\n```fsharp\nopen System.Net.Mail\n\ntype Email =\n    private { Email : string }\n\n    override x.ToString () = x.Email\n\n    // Note the transformation from string -\u003e Email\n    static member Of : Validator\u003cstring, Email\u003e = fun field input -\u003e\n        let rule (x : string) =\n            if x = \"\" then false\n            else\n                try\n                    let addr = MailAddress(x)\n                    if addr.Address = x then true\n                    else false\n                with\n                | :? FormatException -\u003e false\n\n        let message = sprintf \"%s must be a valid email address\"\n\n        input\n        |\u003e Validator.create message rule field\n        |\u003e Result.map (fun v -\u003e { Email = v })\n```\n\n### Example 2: E164 Formatted Phone Number\n\n```fsharp\ntype E164 =\n    private { E164 : string }\n\n    override x.ToString() = x.E164\n\n    static member Of : Validator\u003cstring, E164\u003e = fun field input -\u003e\n        let e164Regex = @\"^\\+[1-9]\\d{1,14}$\"\n        let message = sprintf \"%s must be a valid E164 telephone number\"\n\n        input\n        |\u003e Check.WithMessage.String.pattern e164Regex message field\n        |\u003e Result.map (fun v -\u003e { E164 = v })\n```\n\n## Built-in Validators\n\n\u003e Note: Validators pre-populated with English-language default error messages reside within the `Check` module.\n\n## `equals`\n\nApplies to: `string, int16, int, int64, decimal, float, DateTime, DateTimeOffset, TimeSpan, 'a array, 'a list, 'a seq`\n\n```fsharp\nopen Validus\n\n// Define a validator which checks if a string equals\n// \"foo\" displaying the standard error message.\nlet equalsFoo =\n  Check.String.equals \"foo\" \"fieldName\"\n\nequalsFoo \"bar\"\n\n// Define a validator which checks if a string equals\n// \"foo\" displaying a custom error message (string -\u003e string).\nlet equalsFooCustom =\n  let msg = sprintf \"%s must equal the word 'foo'\"\n  Check.WithMessage.String.equals \"foo\" msg \"fieldName\"\n\nequalsFooCustom \"bar\"\n```\n\n## `notEquals`\n\nApplies to: `string, int16, int, int64, decimal, float, DateTime, DateTimeOffset, TimeSpan, 'a array, 'a list, 'a seq`\n\n```fsharp\nopen Validus\n\n// Define a validator which checks if a string is not\n// equal to \"foo\" displaying the standard error message.\nlet notEqualsFoo =\n  Check.String.notEquals \"foo\" \"fieldName\"\n\nnotEqualsFoo \"bar\"\n\n// Define a validator which checks if a string is not\n// equal to \"foo\" displaying a custom error message (string -\u003e string)\nlet notEqualsFooCustom =\n  let msg = sprintf \"%s must not equal the word 'foo'\"\n  Check.WithMessage.String.notEquals \"foo\" msg \"fieldName\"\n\nnotEqualsFooCustom \"bar\"\n```\n\n## `between`\n\nApplies to: `int16, int, int64, decimal, float, DateTime, DateTimeOffset, TimeSpan`\n\n```fsharp\nopen Validus\n\n// Define a validator which checks if an int is between\n// 1 and 100 (inclusive) displaying the standard error message.\nlet between1and100 =\n  Check.Int.between 1 100 \"fieldName\"\n\nbetween1and100 12 // Result\u003cint, ValidationErrors\u003e\n\n// Define a validator which checks if an int is between\n// 1 and 100 (inclusive) displaying a custom error message.\nlet between1and100Custom =\n  let msg = sprintf \"%s must be between 1 and 100\"\n  Check.WithMessage.Int.between 1 100 msg \"fieldName\"\n\nbetween1and100Custom 12 // Result\u003cint, ValidationErrors\u003e\n```\n\n## `greaterThan`\n\nApplies to: `int16, int, int64, decimal, float, DateTime, DateTimeOffset, TimeSpan`\n\n```fsharp\nopen Validus\n\n// Define a validator which checks if an int is greater than\n// 100 displaying the standard error message.\nlet greaterThan100 =\n  Check.Int.greaterThan 100 \"fieldName\"\n\ngreaterThan100 12 // Result\u003cint, ValidationErrors\u003e\n\n// Define a validator which checks if an int is greater than\n// 100 displaying a custom error message.\nlet greaterThan100Custom =\n  let msg = sprintf \"%s must be greater than 100\"\n  Check.WithMessage.Int.greaterThan 100 msg \"fieldName\"\n\ngreaterThan100Custom 12 // Result\u003cint, ValidationErrors\u003e\n```\n\n## `greaterThanOrEqualTo`\n\nApplies to: `int16, int, int64, decimal, float, DateTime, DateTimeOffset, TimeSpan`\n\n```fsharp\nopen Validus\n\n// Define a validator which checks if an int is greater than\n// or equal to 100 displaying the standard error message.\nlet greaterThanOrEqualTo100 =\n  Check.Int.greaterThanOrEqualTo 100 \"fieldName\"\n\ngreaterThanOrEqualTo100 12 // Result\u003cint, ValidationErrors\u003e\n\n// Define a validator which checks if an int is greater than\n// or equal to 100 displaying a custom error message.\nlet greaterThanOrEqualTo100Custom =\n  let msg = sprintf \"%s must be greater than or equal to 100\"\n  Check.WithMessage.Int.greaterThanOrEqualTo 100 msg \"fieldName\"\n\ngreaterThanOrEqualTo100Custom 12 // Result\u003cint, ValidationErrors\u003e\n```\n\n## `lessThan`\n\nApplies to: `int16, int, int64, decimal, float, DateTime, DateTimeOffset, TimeSpan`\n\n```fsharp\nopen Validus\n\n// Define a validator which checks if an int is less than\n// 100 displaying the standard error message.\nlet lessThan100 =\n  Check.Int.lessThan 100 \"fieldName\"\n\nlessThan100 12 // Result\u003cint, ValidationErrors\u003e\n\n// Define a validator which checks if an int is less than\n// 100 displaying a custom error message.\nlet lessThan100Custom =\n  let msg = sprintf \"%s must be less than 100\"\n  Check.WithMessage.Int.lessThan 100 msg \"fieldName\"\n\nlessThan100Custom 12 // Result\u003cint, ValidationErrors\u003e\n```\n\n## `lessThanOrEqualTo`\n\nApplies to: `int16, int, int64, decimal, float, DateTime, DateTimeOffset, TimeSpan`\n\n```fsharp\nopen Validus\n\n// Define a validator which checks if an int is less than\n// or equal to 100 displaying the standard error message.\nlet lessThanOrEqualTo100 =\n  Check.Int.lessThanOrEqualTo 100 \"fieldName\"\n\nlessThanOrEqualTo100 12 // Result\u003cint, ValidationErrors\u003e\n\n// Define a validator which checks if an int is less than\n// or equal to 100 displaying a custom error message.\nlet lessThanOrEqualTo100Custom =\n  let msg = sprintf \"%s must be less than or equal to 100\"\n  Check.WithMessage.Int.lessThanOrEqualTo 100 msg \"fieldName\"\n\nlessThanOrEqualTo100Custom 12 // Result\u003cint, ValidationErrors\u003e\n```\n\n## `betweenLen`\n\nApplies to: `string, 'a array, 'a list, 'a seq`\n\n```fsharp\nopen Validus\n\n// Define a validator which checks if a string is between\n// 1 and 100 chars displaying the standard error message.\nlet between1and100Chars =\n  Check.String.betweenLen 1 100 \"fieldName\"\n\nbetween1and100Chars \"validus\"\n\n// Define a validator which checks if a string is between\n// 1 and 100 chars displaying a custom error message.\nlet between1and100CharsCustom =\n  let msg = sprintf \"%s must be between 1 and 100 chars\"\n  Check.WithMessage.String.betweenLen 1 100 msg \"fieldName\"\n\nbetween1and100CharsCustom \"validus\"\n```\n\n## `equalsLen`\n\nApplies to: `string, 'a array, 'a list, 'a seq`\n\n```fsharp\nopen Validus\n\n// Define a validator which checks if a string is equals to\n// 100 chars displaying the standard error message.\nlet equals100Chars =\n  Check.String.equalsLen 100 \"fieldName\"\n\nequals100Chars \"validus\"\n\n// Define a validator which checks if a string is equals to\n// 100 chars displaying a custom error message.\nlet equals100CharsCustom =\n  let msg = sprintf \"%s must be 100 chars\"\n  Check.WithMessage.String.equalsLen 100 msg \"fieldName\"\n\nequals100CharsCustom \"validus\"\n```\n\n## `greaterThanLen`\n\nApplies to: `string, 'a array, 'a list, 'a seq`\n\n```fsharp\nopen Validus\n\n// Define a validator which checks if a string is greater than\n// 100 chars displaying the standard error message.\nlet greaterThan100Chars =\n  Check.String.greaterThanLen 100 \"fieldName\"\n\ngreaterThan100Chars \"validus\"\n\n// Define a validator which checks if a string is greater than\n// 100 chars displaying a custom error message.\nlet greaterThan100CharsCustom =\n  let msg = sprintf \"%s must be greater than 100 chars\"\n  Check.WithMessage.String.greaterThanLen 100 msg \"fieldName\"\n\ngreaterThan100CharsCustom \"validus\"\n```\n\n## `greaterThanOrEqualToLen`\n\nApplies to: `string, 'a array, 'a list, 'a seq`\n\n```fsharp\nopen Validus\n\n// Define a validator which checks if a string is greater than\n// or equal to 100 chars displaying the standard error message.\nlet greaterThanOrEqualTo100Chars =\n  Check.String.greaterThanOrEqualToLen 100 \"fieldName\"\n\ngreaterThanOrEqualTo100Chars \"validus\"\n\n// Define a validator which checks if a string is greater than\n// or equal to 100 chars displaying a custom error message.\nlet greaterThanOrEqualTo100CharsCustom =\n  let msg = sprintf \"%s must be greater than or equal to 100 chars\"\n  Check.WithMessage.String.greaterThanOrEqualToLen 100 msg \"fieldName\"\n\ngreaterThanOrEqualTo100CharsCustom \"validus\"\n```\n\n## `lessThanLen`\n\nApplies to: `string, 'a array, 'a list, 'a seq`\n\n```fsharp\nopen Validus\n\n// Define a validator which checks if a string is less tha\n// 100 chars displaying the standard error message.\nlet lessThan100Chars =\n  Check.String.lessThanLen 100 \"fieldName\"\n\nlessThan100Chars \"validus\"\n\n// Define a validator which checks if a string is less tha\n// 100 chars displaying a custom error message.\nlet lessThan100CharsCustom =\n  let msg = sprintf \"%s must be less than 100 chars\"\n  Check.WithMessage.String.lessThanLen 100 msg \"fieldName\"\n\nlessThan100CharsCustom \"validus\"\n```\n\n## `lessThanOrEqualToLen`\n\nApplies to: `string, 'a array, 'a list, 'a seq`\n\n```fsharp\nopen Validus\n\n// Define a validator which checks if a string is less tha\n// or equal to 100 chars displaying the standard error message.\nlet lessThanOrEqualTo100Chars =\n  Check.String.lessThanOrEqualToLen 100 \"fieldName\"\n\nlessThanOrEqualTo100Chars \"validus\"\n\n// Define a validator which checks if a string is less tha\n// or equal to 100 chars displaying a custom error message.\nlet lessThanOrEqualTo100CharsCustom =\n  let msg = sprintf \"%s must be less than 100 chars\"\n  Check.WithMessage.String.lessThanOrEqualToLen 100 msg \"fieldName\"\n\nlessThanOrEqualTo100CharsCustom \"validus\"\n```\n\n## `empty`\n\nApplies to: `string, 'a array, 'a list, 'a seq`\n\n```fsharp\nopen Validus\n\n// Define a validator which checks if a string is empty\n// displaying the standard error message.\nlet stringIsEmpty =\n  Check.String.empty \"fieldName\"\n\nstringIsEmpty \"validus\"\n\n// Define a validator which checks if a string is empty\n// displaying a custom error message.\nlet stringIsEmptyCustom =\n  let msg = sprintf \"%s must be empty\"\n  Check.WithMessage.String.empty msg \"fieldName\"\n\nstringIsEmptyCustom \"validus\"\n```\n\n## `notEmpty`\n\nApplies to: `string, 'a array, 'a list, 'a seq`\n\n```fsharp\nopen Validus\n\n// Define a validator which checks if a string is not empty\n// displaying the standard error message.\nlet stringIsNotEmpty =\n  Check.String.notEmpty \"fieldName\"\n\nstringIsNotEmpty \"validus\"\n\n// Define a validator which checks if a string is not empty\n// displaying a custom error message.\nlet stringIsNotEmptyCustom =\n  let msg = sprintf \"%s must not be empty\"\n  Check.WithMessage.String.notEmpty msg \"fieldName\"\n\nstringIsNotEmptyCustom \"validus\"\n```\n\n## `pattern`\n\nApplies to: `string`\n\n```fsharp\nopen Validus\n\n// Define a validator which checks if a string matches the\n// provided regex displaying the standard error message.\nlet stringIsChars =\n  Check.String.pattern \"[a-z]+\" \"fieldName\"\n\nstringIsChars \"validus\"\n\n// Define a validator which checks if a string matches the\n// provided regex displaying a custom error message.\nlet stringIsCharsCustom =\n  let msg = sprintf \"%s must follow the pattern [a-z]\"\n  Check.WithMessage.String.pattern \"[a-z]\" msg \"fieldName\"\n\nstringIsCharsCustom \"validus\"\n```\n\n## `exists`\n\nApplies to: `'a array, 'a list, 'a seq`\n\n```fsharp\nopen Validus\n\n// Define a validator which checks if a collection matches the provided predicate\n// displaying the standard error message.\nlet collectionContains =\n  Check.List.exists (fun x -\u003e x = 1) \"fieldName\"\n\ncollectionContains [1]\n\n// Define a validator which checks if a string is not empty\n// displaying a custom error message.\nlet collectionContainsCustom =\n  let msg = sprintf \"%s must contain the value '1'\"\n  Check.WithMessage.List.exists (fun x -\u003e x = 1) msg \"fieldName\"\n\ncollectionContainsCustom [1]\n```\n\n## Custom Operators\n\n| Operator | Description |\n| -------- | ----------- |\n| `\u003c+\u003e` | Compose two validators of equal types |\n| `*\\|*` | Map the `Ok` result of a validator, high precedence, for use with choice `\u003c\\|\u003e`. |\n| `*\\|` | Set the `Ok` result of a validator to a fixed value, high precedence, for use with choice `\u003c\\|\u003e`. |\n| `\u003e\u003e\\|` | Map the `Ok` result of a validator, low precedence, for use in chained validation |\n| `\u003e\\|` | Set the `Ok` result of a validator to a fixed value, low precedence, for use in chained validation |\n| `\u003e\u003e=` | Bind the `Ok` result of a validator with a one-argument function that returns a Result |\n| `\u003c\u003c=` | Reverse-bind the `Ok` result of a validator with a one-argument function that returns a Result |\n| `\u003e\u003e%` | Set the `Ok` result of a validator to a fixed Result value |\n| `\u003c\\|\u003e` | Introduce choice: if the rh-side validates `Ok`, pick that result, otherwise, continue with the next validator |\n| `\u003e=\u003e` | Kleisli-bind two validators. Other than Compose `\u003c+\u003e`, this can change the result type. |\n| `\u003c=\u003c` | Reverse kleisli-bind two validators (rh-side is evaluated first). Other than Compose `\u003c+\u003e`, this can change the result type. |\n| `.\u003e\u003e` | Compose two validators, but keep the result of the lh-side. Ignore the result of the rh-side, unless it returns an Error. |\n| `\u003e\u003e.` | Compose two validators, but keep the result of the rh-side. Ignore the result of the lh-side, unless it returns an Error. |\n| `.\u003e\u003e.` | Compose two validators, and keep the result of both sides as a tuple. |\n\nRecreating the example code above using the combinator operators:\n\n```fsharp\nopen System.Net.Mail\nopen Validus\nopen Validus.Operators\n\nlet msg = sprintf \"Please provide a valid %s\"\n\nlet emailPatternValidator =\n    Check.WithMessage.String.pattern @\"[^@]+@[^\\.]+\\..+\" msg\n\n// A custom validator that uses System.Net.Mail to validate email\nlet mailAddressValidator =\n    let rule (x : string) =\n        if x = \"\" then false\n        else\n            try\n                let addr = MailAddress(x)\n                if addr.Address = x then true\n                else false\n            with\n            | :? FormatException -\u003e false\n\n    Validator.create msg rule\n\nlet emailValidator =\n    Check.String.betweenLen 8 512 // check string is between 8 and 512 chars\n    \u003c+\u003e emailPatternValidator     // and, check string match email regex\n    \u003e=\u003e mailAddressValidator      // then, check using System.Net.Mail if prior two steps are `Ok`\n\n\"fake@test\"\n|\u003e emailValidator \"Login email\"\n\n```\n\nA more complex example involving \"chained\" validators and both \"choice\" assignment \u0026 mapping:\n\n```fsharp\nopen System\nopen Validus\nopen Validus.Operators\n\ntype AgeGroup =\n    | Adult of int\n    | Child\n    | Senior\n\nlet ageValidator =\n    Check.String.pattern @\"\\d+\" *|* Int32.Parse // if pattern matches, convert to Int32\n    \u003e=\u003e Check.Int.between 0 120                 // first check age between 0 and 120\n    \u003e=\u003e (Check.Int.between 0 17  *| Child       // then, check age between 0 an 17 assigning Child\n    \u003c|\u003e Check.Int.greaterThan 65 *| Senior      // or, check age greater than 65 assiging Senior\n    \u003c|\u003e Check.Int.between 18 65  *|* Adult)     // or, check age between 18 and 65 assigning adult mapping converted input\n```\n\n## Find a bug?\n\nThere's an [issue](https://github.com/pimbrouwers/Validus/issues) for that.\n\n## License\n\nBuilt with ♥ by [Pim Brouwers](https://github.com/pimbrouwers) in Toronto, ON. Licensed under [Apache License 2.0](https://github.com/pimbrouwers/Validus/blob/master/LICENSE).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpimbrouwers%2Fvalidus","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpimbrouwers%2Fvalidus","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpimbrouwers%2Fvalidus/lists"}