This module contains a set of functions for data validation.
A type like result but accumulates errors.
normal usage would entail map
, pure
and withValidation
.
`andThen` can be used if we depend on the result of a previous
validation, this will cause incremental error collection however.
This is inspired by the Haskell equivalent: https://hackage.haskell.org/package/validation
pure : a -> Validation e a
Wrap some value in Validation
Wrap in Validation data constructor
pure "John" == Validation <| Ok "John"
extract : Validation e r -> Result e r
Result extraction
Extract a Result from Validation
extract <| Validation <| Err "Wrong number" == Err "Wrong number"
extract <| Validation <| Ok "John" == Ok "John"
andThen : (a -> Validation e b) -> Validation e a -> Validation e b
Chaining computations
Chain together many computations that may failed.
val1 = Validation <| Ok "One"
val2 = Validation <| Ok "Two"
val3 = Validation <| Ok "Three"
res = val1
|> andThen (\v1 -> val2
|> andThen (\v2 -> val3
|> andThen (\v3 -> pure <| String.join " " [ v1, v2, v3 ]
)))
-- `res` is `Validation <| Ok "One Two Three"`
Note that if one of the values in the chain is an error, the result will be the first error encountered. If you want to accumulate errors, then use the @withValidation function
type alias ValidatedPerson =
{ firstName : String
, lastName : String
, age : Integer
, email : String
, ipAddress : String
}
let
validatedFirstName = Validation <| Ok "John"
validatedLastName = Validation <| Err "The last name field is missing"
validatedAge = Validation <| Ok 40
validatedEmail = Validation <| Err "Invalid email: johnwick.com"
validatedIpAddress = Validation <| Ok "70.235.93.79"
res = validatedFirstName
|> andThen (\vFn -> validatedLastName
|> andThen (\fLn -> validatedAge
|> andThen (\vAge -> validatedEmail
|> andThen (\vEmail -> validatedIpAddress
|> andThen (\vIp -> pure <| ValidatedPerson vId vFn vLn vAge vEmail vIp
)))))
-- `res` is equal to `Validation <| Err "The last name field is missing"
withValidation : Validation (List e) a -> Validation (List e) (a -> b) -> Validation (List e) b
Chaining computations with error accumulation
Chain together many computations that may failed. Errors are stored in a list.
If you want to display only the first encountered error, use andThen
.
If you don't want to work with lists, you can create your own function to do a chain of calculations. To do this, see the @applyOrCollect
function
type alias ValidatedPerson =
{ firstName : String
, lastName : String
, age : Int
, email : String
, ipAddress : String
}
let firstName = Validation <| Ok "John"
lastName = Validation <| Ok "Wick"
age = Validation <| Ok 40
email = Validation <| Ok "john@wick.com"
ip = Validation <| Ok "70.235.93.79"
res = pure ValidatedPerson
|> withValidation firstName
|> withValidation lastName
|> withValidation age
|> withValidation email
|> withValidation ip
-- `res` is equal to `Validation <| Ok <| ValidatedPerson { ... }
If there are errors:
let firstName = Validation <| Ok "John"
lastName = Validation <| Ok "Wick"
age = Validation <| Err ["Invalid age"]
email = Validation <| Ok "john@wick.com"
ip = Validation <| Err ["Invalid ip"]
res = pure ValidatedPerson
|> withValidation firstName
|> withValidation lastName
|> withValidation age
|> withValidation email
|> withValidation ip
-- `res` is equal to `Validation <| Err ["Invalid age", "Invalid ip"]
applyOrCollect : (e -> e -> e) -> Validation e (a -> b) -> Validation e a -> Validation e b
Combines two validations into one, and gets an opportunity to
collect the errors with the given function.
This is preferable over andThen
because you can collect errors.
typically usage trough withValidation
, but applyOrCollect
is exposed to allow usage of different data structures. For example:
withValidationDict : Validation (Dict comparable e) (a -> b)
-> Validation (Dict comparable e) a
-> Validation (Dict comparable e) b
withValidationDict = applyOrCollect Dict.union
map : (a -> b) -> Validation e a -> Validation e b
Validated value mapping
Mapping a validated value
map toUpper <| Validation <| Ok "John" == Validation <| Ok "JOHN"
map toUpper <| Validation <| Err "Wrong number" == Validation <| Err "Wrong Number"
map2 : (a1 -> a2 -> a3) -> Validation (List e) a1 -> Validation (List e) a2 -> Validation (List e) a3
map3 : (a1 -> a2 -> a3 -> a4) -> Validation (List e) a1 -> Validation (List e) a2 -> Validation (List e) a3 -> Validation (List e) a4
map4 : (a1 -> a2 -> a3 -> a4 -> a5) -> Validation (List e) a1 -> Validation (List e) a2 -> Validation (List e) a3 -> Validation (List e) a4 -> Validation (List e) a5
map5 : (a1 -> a2 -> a3 -> a4 -> a5 -> a6) -> Validation (List e) a1 -> Validation (List e) a2 -> Validation (List e) a3 -> Validation (List e) a4 -> Validation (List e) a5 -> Validation (List e) a6
map6 : (a1 -> a2 -> a3 -> a4 -> a5 -> a6 -> a7) -> Validation (List e) a1 -> Validation (List e) a2 -> Validation (List e) a3 -> Validation (List e) a4 -> Validation (List e) a5 -> Validation (List e) a6 -> Validation (List e) a7
map7 : (a1 -> a2 -> a3 -> a4 -> a5 -> a6 -> a7 -> a8) -> Validation (List e) a1 -> Validation (List e) a2 -> Validation (List e) a3 -> Validation (List e) a4 -> Validation (List e) a5 -> Validation (List e) a6 -> Validation (List e) a7 -> Validation (List e) a8
mapError : (e -> e1) -> Validation e a -> Validation e1 a
Error mapping
mapError toUpper <| Validation <| Err "Wrong number" == Validation <| Err "WRONG NUMBER"
mapError toUpper <| Validation <| Ok "John" == Validation <| Ok "John"
toMaybe : Validation (List e) a -> Maybe a
Converting Validation to Maybe
toMaybe <| Validation <| Err ["The field age is missing"] == Nothing
toMaybe <| Validation <| Ok 48 == Just 48
fromMaybe : Maybe a -> List e -> Validation (List e) a
Converting Maybe to Validation
fromMaybe Nothing ["The age field is missing"] == Validation <| Err ["The field age is missing"]
fromMaybe (Just 48) ["The age field is missing"] == Validation <| Ok 48