ericgj / elm-validation / Validation

A data type representing the validity and error state of data, for example user-supplied input, with functions for combining results.

There are various ways of using the tools this library provides. The recommended way is to store ValidationResult state in your model, in much the same way as you store RemoteData in your model.

This means your form model is separate from the validated data model, and you typically need to map the form into the validated model (see example below).

A simple example

Here's a simple example: a 'required' (String) input field, showing an error message below the input field as the user types.

First, define a form model with the field to be validated wrapped in a ValidationResult:

type alias Model =
    { input : ValidationResult String }

In your view,

  1. pipe input through a validation function and into your update;

  2. set the value to either the validated or the last-entered input; and

  3. display any error message below the input element.

    view : Model -> Html Msg view form = div [] [ input [ type_ "text" , value -- (2.) (form.input |> Validation.toString identity ) , onInput -- (1.) (Validation.validate isRequired >> SetInput ) ] [] , div -- (3.) [ class "error" ] [ text (Validation.message form.input |> Maybe.withDefault "" ) ] ]

Your validation functions are defined as a -> Result String a:

isRequired : String -> Result String String
isRequired raw =
    if String.length raw < 1 then
        Err "Required"

    else
        Ok raw

Often you will want to validate input when the input loses focus (onBlur), instead of immediately (onInput). ValidationResult supports this with the Unvalidated state, which allows you to store input before validation (see below, and full example here).

Also note if you do validate onInput as above, in most cases you should also validate onBlur if the field is required.

Combining validation results

Typically, you want to combine validation results of several fields, such that if all of the fields are valid, then their values are extracted and the underlying model is updated, perhaps via a remote http call.

This library provides andMap, which allows you to do this (assuming your form model is Form, and your underlying validated model is Model):

validateForm : Form -> ValidationResult Model
validateForm form =
    Validation.valid Model
        |> Validation.andMap form.field1
        |> Validation.andMap form.field2

Using such a function, you can Validation.map the result into encoded form and package it into an http call, etc.

Note that this library does not currently support accumulating validation errors (e.g. multiple validations). The error message type is fixed as String. So the andMap example above is not intended to give you a list of errors in the Invalid case. Instead, it simply returns the first Initial or Invalid of the applied ValidationResults.

For an approach that does accumulate validation errors, see elm-verify.

Basics


type ValidationResult value
    = Initial
    | Unvalidated String
    | Valid value
    | Invalid String String

A wrapped value has four states:

validate : (String -> Result String val) -> String -> ValidationResult val

Run a validation function on an input string, to create a ValidationResult.

Note the validation function you provide is String -> Result String a, where a is the type of the valid value.

So a validation function for "integer less than 10" looks like:

lessThanTen : String -> Result String Int
lessThanTen input =
    String.toInt input
        |> Result.andThen
            (\i ->
                if i < 10 then
                    Ok i

                else
                    Err "Must be less than 10"
            )

valid : val -> ValidationResult val

Put a valid value into a ValidationResult.

initial : ValidationResult val

Initialize a ValidationResult to the empty case (no input).

unvalidated : String -> ValidationResult val

Initialize a ValidationResult to unvalidated input.

map : (a -> b) -> ValidationResult a -> ValidationResult b

Map a function into the Valid value.

andThen : (a -> ValidationResult b) -> ValidationResult a -> ValidationResult b

Chain a function returning ValidationResult onto a ValidationResult

andMap : ValidationResult a -> ValidationResult (a -> b) -> ValidationResult b

Put the results of two ValidationResults together.

Useful for merging field ValidationResults into a single 'form' ValidationResult. See the example above.

mapMessage : (String -> String) -> ValidationResult val -> ValidationResult val

Map over the error message value, producing a new ValidationResult

Extracting

withDefault : val -> ValidationResult val -> val

Extract the Valid value, or the given default

message : ValidationResult val -> Maybe String

Extract the error message of an Invalid, or Nothing

isValid : ValidationResult val -> Basics.Bool

Return True if and only if Valid. Note Initial and Unvalidated results are False.

isInvalid : ValidationResult val -> Basics.Bool

Return True if and only if Invalid. Note Initial and Unvalidated results are False.

Converting

fromMaybe : String -> String -> Maybe val -> ValidationResult val

Convert a Maybe into either Invalid, with given message and input, or Valid.

fromMaybeInitial : Maybe val -> ValidationResult val

Convert a Maybe into either Initial (if Nothing) or Valid (if Just)

fromMaybeUnvalidated : String -> Maybe val -> ValidationResult val

Convert a Maybe into either Unvalidated, with given input, or Valid.

toMaybe : ValidationResult val -> Maybe val

Convert a ValidationResult to a Maybe. Note Invalid and Unvalidated state is dropped.

fromResult : (msg -> String) -> String -> Result msg val -> ValidationResult val

Convert a Result into either Invalid, using given function mapping the Err value to the error message (String), and the given input string; or Valid.

Note: this function may be useful for unusual scenarios where you have a Result already and you need to convert it. More typically you would pass a Result-returning function to validate — which calls fromResult internally.

fromResultInitial : Result msg val -> ValidationResult val

Convert a Result into either Initial (if Err) or Valid (if Ok). Note Err state is dropped.

fromResultUnvalidated : (msg -> String) -> Result msg val -> ValidationResult val

Convert a Result into either Unvalidated (if Err) or Valid (if Ok).

toString : (val -> String) -> ValidationResult val -> String

Convert the ValidationResult to a String representation:

Note: this is mainly useful as a convenience function for setting the value attribute of an Html.input element.