rtfeldman / elm-validate / Validate

Convenience functions for validating data.

import Validate exposing (Validator, ifBlank, ifNotInt, validate)

type Field = Name | Email | Age

type alias Model = { name : String, email : String, age : String }

modelValidator : Validator String Model
modelValidator =
    Validate.all
        [ ifBlank .name "Please enter a name."
        , Validate.firstError
            [ ifBlank .email "Please enter an email address."
            , ifInvalidEmail .email "This is not a valid email address."
            ]
        , ifNotInt .age "Age must be a whole number."
        ]

validate modelValidator { name = "Sam", email = "blah", age = "abc" }
    --> Err [ "This is not a valid email address.", "Age must be a whole number." ]

Validating a subject


type Validator error subject

A Validator contains a function which takes a subject and returns a list of errors describing anything invalid about that subject.

Pass it to validate to get the list of errors. An empty error list means the subject was valid.


type Valid subject

Guarantees that a subject has been run through a Validator which passed.

The only way to obtain one of these is to call validate, so if you have a Valid Form, you can be certain the wrapped Form value has been run through a Validator that returned in no errors.

Once you have a Valid Form, you can unwrap the validated Form by calling fromValid.

This lets you write functions like sendToServer : Valid Form -> ... which make it impossible to forget to run the form through a Validator before sending it off to the server!

validate : Validator error subject -> subject -> Result (List error) (Valid subject)

Return an error if the given predicate returns True for the given subject.

import Validate exposing (Validator, ifBlank, ifNotInt, validate)

type Field = Name | Email | Age

type alias Model = { name : String, email : String, age : String }

modelValidator : Validator ( Field, String ) Model
modelValidator =
    Validate.all
        [ ifBlank .name ( Name, "Please enter a name." )
        , ifBlank .email ( Email, "Please enter an email address." )
        , ifNotInt .age ( Age, "Age must be a whole number." )
        ]

validate modelValidator { name = "Sam", email = "", age = "abc" }
    --> Err [ ( Email, "Please enter an email address." ), ( Age, "Age must be a whole number." ) ]

fromValid : Valid subject -> subject

Extract the wrapped Valid value.

Creating validators

ifBlank : (subject -> String) -> error -> Validator error subject

Return an error if the given String is empty, or if it contains only spaces, tabs, newlines, or carriage returns.

import Validate exposing (Validator, ifBlank)

modelValidator : Validator Model String
modelValidator =
    Validate.all
        [ ifBlank .name "Please enter a name."
        , ifBlank .email "Please enter an email address."
        ]

ifNotInt : (subject -> String) -> (String -> error) -> Validator error subject

Return an error if the given String cannot be parsed as an Int.

import Validate exposing (Validator, ifNotInt)

modelValidator : Validator Model String
modelValidator =
    Validate.all
        [ ifNotInt .followers (\_ -> "Please enter a whole number for followers.")
        , ifNotInt .stars (\stars -> "Stars was \"" ++ stars ++ "\", but it needs to be a whole number.")"
        ]

ifNotFloat : (subject -> String) -> error -> Validator error subject

Return an error if a String cannot be parsed as an Float.

ifEmptyList : (subject -> List a) -> error -> Validator error subject

Return an error if a List is empty.

ifEmptyDict : (subject -> Dict comparable v) -> error -> Validator error subject

Return an error if a Dict is empty.

ifEmptySet : (subject -> Set comparable) -> error -> Validator error subject

Return an error if a Set is empty.

ifNothing : (subject -> Maybe a) -> error -> Validator error subject

Return an error if a Maybe is Nothing.

ifInvalidEmail : (subject -> String) -> (String -> error) -> Validator error subject

Return an error if an email address is malformed.

import Validate exposing (Validator, ifBlank, ifNotInt)

modelValidator : Validator Model String
modelValidator =
    Validate.all
        [ ifInvalidEmail .primaryEmail (\_ -> "Please enter a valid primary email address.")
        , ifInvalidEmail .superSecretEmail (\email -> "Unfortunately, \"" ++ email ++ "\" is not a valid Super Secret Email Address.")
        ]

ifTrue : (subject -> Basics.Bool) -> error -> Validator error subject

Return an error if a predicate returns True for the given subject.

import Validate exposing (Validator, ifTrue)

modelValidator : Validator Model String
modelValidator =
    ifTrue (\model -> countSelected model < 2)
        "Please select at least two."

ifFalse : (subject -> Basics.Bool) -> error -> Validator error subject

Return an error if a predicate returns False for the given subject.

import Validate exposing (Validator, ifFalse)

modelValidator : Validator Model String
modelValidator =
    ifFalse (\model -> countSelected model >= 2)
        "Please select at least two."

fromErrors : (subject -> List error) -> Validator error subject

Create a custom validator, by providing a function that returns a list of errors given a subject.

import Validate exposing (Validator, fromErrors)

modelValidator : Validator Model String
modelValidator =
    fromErrors modelToErrors

modelToErrors : Model -> List String
modelToErrors model =
    let
        usernameLength =
            String.length model.username
    in
    if usernameLength < minUsernameChars then
        [ "Username not long enough" ]

    else if usernameLength > maxUsernameChars then
        [ "Username too long" ]

    else
        []

Combining validators

all : List (Validator error subject) -> Validator error subject

Run each of the given validators, in order, and return their concatenated error lists.

import Validate exposing (Validator, ifBlank, ifNotInt)

modelValidator : Validator Model String
modelValidator =
    Validate.all
        [ ifBlank .name "Please enter a name."
        , ifBlank .email "Please enter an email address."
        , ifNotInt .age "Age must be a whole number."
        ]

any : List (Validator error subject) -> subject -> Basics.Bool

Return True if none of the given validators returns any errors for the given subject, and False if any validator returns one or more errors.

firstError : List (Validator error subject) -> Validator error subject

Run each of the given validators, in order, stopping after the first error and returning it. If no errors are encountered, return Nothing.

import Validate exposing (Validator, ifBlank, ifInvalidEmail, ifNotInt)


type alias Model =
    { email : String, age : String }


modelValidator : Validator String Model
modelValidator =
    Validate.all
        [ Validate.firstError
            [ ifBlank .email "Please enter an email address."
            , ifInvalidEmail .email "This is not a valid email address."
            ]
        , ifNotInt .age "Age must be a whole number."
        ]


validate modelValidator { email = " ", age = "5" }
    --> Err [ "Please enter an email address." ]

validate modelValidator { email = "blah", age = "5" }
    --> Err [ "This is not a valid email address." ]

validate modelValidator { email = "foo@bar.com", age = "5" }
    --> Ok (Valid { email = "foo@bar.com", age = "5" })

Checking values directly

isBlank : String -> Basics.Bool

Returns True if the given string is nothing but whitespace.

ifBlank uses this under the hood.

isInt : String -> Basics.Bool

Returns True if String.toInt on the given string returns an Ok.

ifNotInt uses this under the hood.

isFloat : String -> Basics.Bool

Returns True if String.toFloat on the given string returns an Ok.

ifNotFloat uses this under the hood.

isValidEmail : String -> Basics.Bool

Returns True if the email is valid.

ifInvalidEmail uses this under the hood.