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).
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,
pipe input through a validation function and into your update;
set the value to either the validated or the last-entered input; and
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.
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 ValidationResult
s.
For an approach that does accumulate validation errors, see elm-verify.
A wrapped value has four states:
Initial
- No input yet.Unvalidated
- Input received but not yet validated, and here it is.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).
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
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.
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.