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.
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,
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 rawTypically, 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.
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"))Put a valid value into a ValidationResult.
Initialize a ValidationResult to the empty case (no input).
Map a function into the Valid value.
Chain a function returning ValidationResult onto a ValidationResult
Put the results of two ValidationResults together.
Useful for merging field ValidationResults into a single 'form' ValidationResult. See the example above.
Map over the error message value, producing a new ValidationResult
Extract the Valid value, or the given default
Extract the error message of an Invalid, or Nothing
Return True if and only if Valid. Note Initial -> False
(Initial is not valid).
Return True if and only if Invalid. Note Initial -> False
(Initial is not invalid).
Convert a Maybe into either Initial (if Nothing) or Valid (if Just)
Convert a Maybe into either Invalid, with given message and input, or Valid.
Convert a ValidationResult to a Maybe. Note Invalid state is dropped.
Convert a Result into either Initial (if Err) or Valid (if Ok).
Note Err state is dropped.
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.
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.
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.