This library helps with building custom validators of a user input.
Many validators can be chained to perform validation of a single value.
Provides interface for combining validatiors of multiple input fields with (|>)
operator.
Allows combination of required and optional fields.
Represents a validator of the input value. Validation can fail with a list of custom error messages.
custom : (a -> Result (List e) b) -> Validator a e b
Build a custom validator.
intValidator : Validator String InputError Int
intValidator =
Validator.custom (String.toInt >> Result.fromMaybe [ NaN ])
run : Validator a e b -> a -> Result (List e) b
Run validator.
intValidator : Validator String String Int
intValidator =
Validator.custom (String.toInt >> Result.fromMaybe [ "Not a number" ])
Validator.run intValidator "42"
--> (Ok 42)
Validator.run intValidator "Fat fingers"
--> (Err [ "Not a number" ])
succeed : b -> Validator a e b
Create validator that always succeed.
Validator.run (Validator.succeed 3) "blah" --> (Ok 3)
fail : e -> Validator a e b
Create validator that always fails.
Validator.run (Validator.fail "Invalid") 123 --> (Err [ "Invalid" ])
map : (b -> c) -> Validator a e b -> Validator a e c
Transform validated value with a given function.
Validator.run (Validator.map ((+) 1) (Validator.succeed 2)) Nothing
--> (Ok 3)
map2 : (b -> c -> d) -> Validator a e b -> Validator a e c -> Validator a e d
Apply a function if both arguments passed validation.
Validator.map2 (\x y -> x + y)
(Validator.succeed 1)
(Validator.succeed 2)
|> (\validator -> Validator.run validator ())
--> (Ok 3)
andMap : Validator a e b -> Validator a e (b -> c) -> Validator a e c
Apply wrapped function to another wrapped value.
intValidator : Validator String String Int
intValidator =
Validator.custom (String.toInt >> Result.fromMaybe [ "Not a number" ])
Validator.run (Validator.succeed ((+) 2) |> Validator.andMap intValidator) "3"
--> (Ok 5)
andThen : (b -> Validator a e c) -> Validator a e b -> Validator a e c
Chain together many validators.
intValidator
|> Validator.andThen
(\x ->
if x < 0 then
Validator.fail "Must be a positive number"
else
Validator.succeed x
)
-- ...
required : (a -> b) -> (b -> Basics.Bool) -> e -> Validator b e c -> Validator a e (c -> d) -> Validator a e d
Combine validators with required field.
type alias User =
{ name : String
, age : Int
, email : Email
}
type Email = Email String
parseEmail : String -> Result String Email
parseEmail str =
if String.contains "@" str then
Ok (Email str)
else
Err "Invalid email"
emailValidator : Validator String String Email
emailValidator =
Validator.custom (parseEmail >> Result.mapError List.singleton)
intValidator : Validator String String Int
intValidator =
Validator.custom (String.toInt >> Result.fromMaybe [ "Not a number" ])
type alias FormValues r =
{ r
| name : String
, age : String
, email : String
}
userValidator : Validator (FormValues r) String User
userValidator =
Validator.succeed User
|> Validator.required .name String.isEmpty "Name is required" (Validator.custom Ok)
|> Validator.required .age String.isEmpty "Age is required" intValidator
|> Validator.required .email String.isEmpty "Email is required" emailValidator
Validator.run userValidator { name = "John Doe", age = "", email = "test" }
--> Err [ "Age is required", "Invalid email" ]
optional : (a -> b) -> (b -> Basics.Bool) -> c -> Validator b e c -> Validator a e (c -> d) -> Validator a e d
Combine validators with optional field.
type alias User =
{ name : String
, age : Int
, email : Email
}
type Email = Email String
parseEmail : String -> Result String Email
parseEmail str =
if String.contains "@" str then
Ok (Email str)
else
Err "Invalid email"
emailValidator : Validator String String Email
emailValidator =
Validator.custom (parseEmail >> Result.mapError List.singleton)
intValidator : Validator String String Int
intValidator =
Validator.custom (String.toInt >> Result.fromMaybe [ "Not a number" ])
type alias FormValues r =
{ r
| name : String
, age : String
, email : String
}
userValidator : Validator (FormValues r) String User
userValidator =
Validator.succeed User
|> Validator.required .name String.isEmpty "Name is required" (Validator.custom Ok)
|> Validator.optional .age String.isEmpty 10 intValidator
|> Validator.required .email String.isEmpty "Email is required" emailValidator
Validator.run userValidator { name = "John Doe", age = "", email = "happy@path" }
--> Ok (User "John Doe" 10 (Email "happy@path"))