ericgj/elm-validation/1.0.1

NOTE: this package is not compatible with Elm 0.19.1

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 underlying, validated data model, and you typically need to map the form into the validated model (see example below).

Although this may seem awkward or "too much boilerplate", particularly if your forms have many fields, it is not surprising. Unless you can prevent invalid input altogether, as the user enters it, you have to retain it somewhere in order to render it and report back to the user what the issues are. And the shape of the (possibly invalid) input data is necessarily going to be different from the shape of valid data.

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 (form.input |> Validation.toString identity )
            -- (2.)
            , onInput
                (Validation.validate isRequired
                    >> SetInput
                )
    
    
    
    
            -- (1.)
            ]
            []
        , div
            [ class "error" ]
            [ text
                (Validation.message form.input
                    |> Maybe.withDefault ""
                )
    
    
    
    
            -- (3.)
            ]
        ]

(Note: often you will want an onBlur event as well, but this is left as an exercise for the reader.)

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

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.

Basics

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

A wrapped value has three states:

  • Initial - No input yet.
  • Valid - Input is valid, and here is the valid (parsed) data.
  • Invalid - Input is invalid, and here is the error message and your last input.
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).

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

Return True if and only if Valid. Note Initial -> False (Initial is not valid).

isInvalid : ValidationResult val -> Bool

Return True if and only if Invalid. Note Initial -> False (Initial is not invalid).

Converting

fromMaybeInitial : Maybe val -> ValidationResult val

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

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

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

toMaybe : ValidationResult val -> Maybe val

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

fromResultInitial : Result msg val -> ValidationResult val

Convert a Result into either Initial (if Err) or Valid (if Ok). Note Err 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.

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

Convert the ValidationResult to a String representation:

  • if Valid, convert the value to a string with the given function;
  • if Invalid, return the input (unvalidated) string;
  • if Initial, return the empty string ("").

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