Represents the type information for a field. It specifies how a String is converted to a type a
and back to a String. Conversion may result in errors of type e.
Elm's primitive types: Int, Float, Bool, Char, and String, all have corresponding field types.
Any Int that can be parsed from a trimmed string using String.toInt.
(typeToConverters int).fromString "-1" == Ok -1
(typeToConverters int).fromString "0" == Ok 0
(typeToConverters int).fromString "1" == Ok 1
(typeToConverters int).fromString " 5 " == Ok 5
(typeToConverters int).fromString "" == Err blankError
(typeToConverters int).fromString "five" == Err (syntaxError "five")Any Int, n, that can be parsed from a trimmed string using String.toInt such that n >= 0.
(typeToConverters nonNegativeInt).fromString "-1" == Err (validationError "-1")
(typeToConverters nonNegativeInt).fromString "0" == Ok 0
(typeToConverters nonNegativeInt).fromString "1" == Ok 1
(typeToConverters nonNegativeInt).fromString " 5 " == Ok 5
(typeToConverters nonNegativeInt).fromString "" == Err blankError
(typeToConverters nonNegativeInt).fromString "five" == Err (syntaxError "five")Any Int, n, that can be parsed from a trimmed string using String.toInt such that n > 0.
(typeToConverters positiveInt).fromString "-1" == Err (validationError "-1")
(typeToConverters positiveInt).fromString "0" == Err (validationError "0")
(typeToConverters positiveInt).fromString "1" == Ok 1
(typeToConverters positiveInt).fromString " 5 " == Ok 5
(typeToConverters positiveInt).fromString "" == Err blankError
(typeToConverters positiveInt).fromString "five" == Err (syntaxError "five")Any Int, n, that can be parsed from a trimmed string using String.toInt such that n <= 0.
(typeToConverters nonPositiveInt).fromString "-1" == Ok -1
(typeToConverters nonPositiveInt).fromString "0" == Ok 0
(typeToConverters nonPositiveInt).fromString "1" == Err (validationError "1")
(typeToConverters nonPositiveInt).fromString " 5 " == Err (validationError "5")
(typeToConverters nonPositiveInt).fromString "" == Err blankError
(typeToConverters nonPositiveInt).fromString "five" == Err (syntaxError "five")Any Int, n, that can be parsed from a trimmed string using String.toInt such that n < 0.
(typeToConverters negativeInt).fromString "-1" == Ok -1
(typeToConverters negativeInt).fromString "0" == Err (validationError "0")
(typeToConverters negativeInt).fromString "1" == Err (validationError "1")
(typeToConverters negativeInt).fromString " 5 " == Err (validationError "5")
(typeToConverters negativeInt).fromString "" == Err blankError
(typeToConverters negativeInt).fromString "five" == Err (syntaxError "five")Any Int, n, that can be parsed from a trimmed string using String.toInt such that isGood n is True.
evens = subsetOfInt (modBy 2 >> (==) 0)
(typeToConverters evens).fromString "-2" == Ok -2
(typeToConverters evens).fromString "-1" == Err (validationError "-1")
(typeToConverters evens).fromString "0" == Ok 0
(typeToConverters evens).fromString "1" == Err (validationError "1")
(typeToConverters evens).fromString " 2 " == Ok 2
(typeToConverters evens).fromString "" == Err blankError
(typeToConverters evens).fromString "five" == Err (syntaxError "five")Similar to subsetOfInt but you get to customize the errors.
Any Float that can be parsed from a trimmed string using String.toFloat.
(typeToConverters float).fromString "-0.1" == Ok -0.1
(typeToConverters float).fromString "0" == Ok 0
(typeToConverters float).fromString "1.1" == Ok 1.1
(typeToConverters float).fromString " 3.14 " == Ok 3.14
(typeToConverters float).fromString "" == Err blankError
(typeToConverters float).fromString "pi" == Err (syntaxError "pi")Any Float, f, that can be parsed from a trimmed string using String.toFloat such that f >= 0.
(typeToConverters nonNegativeFloat).fromString "-0.1" == Err (validationError "-0.1")
(typeToConverters nonNegativeFloat).fromString "0" == Ok 0
(typeToConverters nonNegativeFloat).fromString "1.1" == Ok 1.1
(typeToConverters nonNegativeFloat).fromString " 3.14 " == Ok 3.14
(typeToConverters nonNegativeFloat).fromString "" == Err blankError
(typeToConverters nonNegativeFloat).fromString "pi" == Err (syntaxError "pi")Any Float, f, that can be parsed from a trimmed string using String.toFloat such that f > 0.
(typeToConverters positiveFloat).fromString "-0.1" == Err (validationError "-0.1")
(typeToConverters positiveFloat).fromString "0" == Err (validationError "0")
(typeToConverters positiveFloat).fromString "1.1" == Ok 1.1
(typeToConverters positiveFloat).fromString " 3.14 " == Ok 3.14
(typeToConverters positiveFloat).fromString "" == Err blankError
(typeToConverters positiveFloat).fromString "pi" == Err (syntaxError "pi")Any Float, f, that can be parsed from a trimmed string using String.toFloat such that f <= 0.
(typeToConverters nonPositiveFloat).fromString "-0.1" == Ok -0.1
(typeToConverters nonPositiveFloat).fromString "0" == Ok 0
(typeToConverters nonPositiveFloat).fromString "1.1" == Err (validationError "1.1")
(typeToConverters nonPositiveFloat).fromString " 3.14 " == Err (validationError "3.14")
(typeToConverters nonPositiveFloat).fromString "" == Err blankError
(typeToConverters nonPositiveFloat).fromString "pi" == Err (syntaxError "pi")Any Float, f, that can be parsed from a trimmed string using String.toFloat such that f < 0.
(typeToConverters negativeFloat).fromString "-0.1" == Ok -0.1
(typeToConverters negativeFloat).fromString "0" == Err (validationError "0")
(typeToConverters negativeFloat).fromString "1.1" == Err (validationError "1.1")
(typeToConverters negativeFloat).fromString " 3.14 " == Err (validationError "3.14")
(typeToConverters negativeFloat).fromString "" == Err blankError
(typeToConverters negativeFloat).fromString "pi" == Err (syntaxError "pi")Any Float, f, that can be parsed from a trimmed string using String.toFloat such that isGood f is True.
roundsToTwo = subsetOfFloat (round >> (==) 2)
(typeToConverters roundsToTwo).fromString "1.3" == Err (validationError "1.3")
(typeToConverters roundsToTwo).fromString "1.5" == Ok 1.5
(typeToConverters roundsToTwo).fromString "1.8" == Ok 1.8
(typeToConverters roundsToTwo).fromString "2.0" == Ok 2.0
(typeToConverters roundsToTwo).fromString "2.3" == Ok 2.3
(typeToConverters roundsToTwo).fromString "2.5" == Err (validationError "2.5")
(typeToConverters roundsToTwo).fromString "3.14" == Err (validationError "3.14")
(typeToConverters roundsToTwo).fromString "" == Err blankError
(typeToConverters roundsToTwo).fromString "pi" == Err (syntaxError "pi")Similar to subsetOfFloat but you get to customize the errors.
Any case-insensitive defaultTruthy or defaultFalsy trimmed string.
(typeToConverters bool).fromString "true" == Ok True
(typeToConverters bool).fromString "TrUe" == Ok True
(typeToConverters bool).fromString "false" == Ok False
(typeToConverters bool).fromString "fAlSe" == Ok False
(typeToConverters bool).fromString " 1 " == Ok True
(typeToConverters bool).fromString " 0 " == Ok False
(typeToConverters bool).fromString "" == Err blankError
(typeToConverters bool).fromString "not" == Err (syntaxError "not")Any case-insensitive defaultTruthy trimmed string.
(typeToConverters true).fromString "true" == Ok True
(typeToConverters true).fromString "TrUe" == Ok True
(typeToConverters true).fromString "false" == Err (validationError "false")
(typeToConverters true).fromString "fAlSe" == Err (validationError "false")
(typeToConverters true).fromString " 1 " == Ok True
(typeToConverters true).fromString " 0 " == Err (validationError "false")
(typeToConverters true).fromString "" == Err blankError
(typeToConverters true).fromString "not" == Err (syntaxError "not")Any case-insensitive defaultFalsy trimmed string.
(typeToConverters false).fromString "true" == Err (validationError "true")
(typeToConverters false).fromString "TrUe" == Err (validationError "true")
(typeToConverters false).fromString "false" == Ok False
(typeToConverters false).fromString "fAlSe" == Ok False
(typeToConverters false).fromString " 1 " == Err (validationError "true")
(typeToConverters false).fromString " 0 " == Ok False
(typeToConverters false).fromString "" == Err blankError
(typeToConverters false).fromString "not" == Err (syntaxError "not")Customize the errors and options.
import Set
type MyError
= MyBlank
| MySyntaxError String
| MyValidationError String
mySubsetOfBool : (Bool -> Bool) -> Type MyError Bool
mySubsetOfBool =
customSubsetOfBool
{ blankError = MyBlank
, syntaxError = MySyntaxError
, validationError = MyValidationError
}
{ truthy = Set.fromList [ "#t" ]
, falsy = Set.fromList [ "#f" ]
, toString =
\b ->
if b then
"#t"
else
"#f"
, caseSensitive = True
}
myBool : Type MyError Bool
myBool =
mySubsetOfBool (always True)
myTrue : Type MyError Bool
myTrue =
mySubsetOfBool ((==) True)
myFalse : Type MyError Bool
myFalse =
mySubsetOfBool ((==) False)Used by customSubsetOfBool to customize the options.
The default truthy strings: "true", "1", "yes", "on", "y", and "enabled".
The default falsy strings: "false", "0", "no", "off", "n", and "disabled".
defaultBoolToString True == "true"
defaultBoolToString False == "false"Any Char that can be parsed from a string of exactly length one. The string is not trimmed.
(typeToConverters char).fromString "a" == Ok 'a'
(typeToConverters char).fromString "A" == Ok 'A'
(typeToConverters char).fromString "8" == Ok '8'
(typeToConverters char).fromString "\n" == Ok '\n'
(typeToConverters char).fromString "" == Err blankError
(typeToConverters char).fromString " " == Ok ' '
(typeToConverters char).fromString " " == Err blankError
(typeToConverters char).fromString "aA" == Err (syntaxError "aA")
(typeToConverters char).fromString " 2 " == Err (syntaxError " 2 ")Any Char, c, that can be parsed from a string of exactly length one such that isGood c is True. The string is not trimmed.
digit = subsetOfChar Char.isDigit
(typeToConverters digit).fromString "a" == Err (validationError "a")
(typeToConverters digit).fromString "A" == Err (validationError "A")
(typeToConverters digit).fromString "8" == Ok '8'
(typeToConverters digit).fromString "\n" == Err (validationError "\n")
(typeToConverters digit).fromString "" == Err blankError
(typeToConverters digit).fromString " " == Err (validationError " ")
(typeToConverters digit).fromString " " == Err blankError
(typeToConverters digit).fromString "aA" == Err (syntaxError "aA")
(typeToConverters digit).fromString " 2 " == Err (syntaxError " 2 ")Similar to subsetOfChar but you get to customize the errors.
A blank string is either the empty string or a non-empty string consisting entirely of whitespace characters.
The empty string: ""
Some blank strings: "", " ", " ", " \t ", "\n \t \r"
Any trimmed String is accepted. It never fails.
(typeToConverters string).fromString "Hello" == Ok "Hello"
(typeToConverters string).fromString " Hello " == Ok "Hello"
(typeToConverters string).fromString "" == Ok ""
(typeToConverters string).fromString " \n\t " == Ok ""Any trimmed String, s, is accepted if isGood s is True.
atMost3 = subsetOfString (String.length >> (>=) 3)
(typeToConverters atMost3).fromString "p" == Ok "p"
(typeToConverters atMost3).fromString "pi" == Ok "pi"
(typeToConverters atMost3).fromString "pie" == Ok "pie"
(typeToConverters atMost3).fromString " pie " == Ok "pie"
(typeToConverters atMost3).fromString "Hello" == Err (validationError "Hello")
(typeToConverters atMost3).fromString " Hello " == Err (validationError "Hello")
(typeToConverters atMost3).fromString "" == Ok ""
(typeToConverters atMost3).fromString " \n\t " == Ok ""Similar to subsetOfString but you get to customize the validation error.
Any String except the empty string is accepted. The string is not trimmed.
(typeToConverters nonEmptyString).fromString "Hello" == Ok "Hello"
(typeToConverters nonEmptyString).fromString " Hello " == Ok " Hello "
(typeToConverters nonEmptyString).fromString "" == Err blankError
(typeToConverters nonEmptyString).fromString " \n\t " == Ok " \n\t "Any non-empty String, s, is only accepted if isGood s is True. The string is not trimmed.
hello = subsetOfNonEmptyString ((==) "Hello")
(typeToConverters hello).fromString "Hello" == Ok "Hello"
(typeToConverters hello).fromString " Hello " == Err (validationError " Hello ")
(typeToConverters hello).fromString "" == Err blankError
(typeToConverters hello).fromString " \n\t " == Err (validationError " \n\t ")Similar to subsetOfNonEmptyString but you get to customize the errors.
Any non-blank trimmed String is accepted.
(typeToConverters nonBlankString).fromString "Hello" == Ok "Hello"
(typeToConverters nonBlankString).fromString " Hello " == Ok "Hello"
(typeToConverters nonBlankString).fromString "" == Err blankError
(typeToConverters nonBlankString).fromString " \n\t " == Err blankErrorAny non-blank trimmed String, s, is accepted if isGood s is True.
atMost3 = subsetOfNonBlankString (String.length >> (>=) 3)
(typeToConverters atMost3).fromString "p" == Ok "p"
(typeToConverters atMost3).fromString "pi" == Ok "pi"
(typeToConverters atMost3).fromString "pie" == Ok "pie"
(typeToConverters atMost3).fromString " pie " == Ok "pie"
(typeToConverters atMost3).fromString "Hello" == Err (validationError "Hello")
(typeToConverters atMost3).fromString " Hello " == Err (validationError "Hello")
(typeToConverters atMost3).fromString "" == Err blankError
(typeToConverters atMost3).fromString " \n\t " == Err blankErrorSimilar to subsetOfNonBlankString but you get to customize the errors.
Accept a subset of the values accepted by the given type.
positiveEvens = subsetOfType (modBy 2 >> (==) 0) positiveInt
(typeToConverters positiveEvens).fromString " 2 " == Ok 2
(typeToConverters positiveEvens).fromString "-2" == Err (validationError "-2")
(typeToConverters positiveEvens).fromString "-1" == Err (validationError "-1")
(typeToConverters positiveEvens).fromString "0" == Err (validationError "0")
(typeToConverters positiveEvens).fromString "1" == Err (validationError "1")
(typeToConverters positiveEvens).fromString "" == Err blankError
(typeToConverters positiveEvens).fromString "five" == Err (syntaxError "five")Similar to subsetOfType but you get to customize the validation error.
Define a new field type.
type Email = Email String
email : Type (Error e) Email
email =
customType
{ fromString =
trim
(\s ->
if s |> String.contains "@" then
Ok (Email s)
else
Err (syntaxError s)
)
, toString =
\(Email s) -> s
}
(typeToConverters email).fromString "[email protected]" == Ok (Email "[email protected]")
(typeToConverters email).fromString " [email protected] " == Ok (Email "[email protected]")
(typeToConverters email).fromString "" == Err blankError
(typeToConverters email).fromString " ab.c " == Err (syntaxError "ab.c")Make any field type optional.
maybePositiveInt = optional positiveInt
(typeToConverters maybePositiveInt).fromString "-1" == Err (validationError "-1")
(typeToConverters maybePositiveInt).fromString "0" == Err (validationError "0")
(typeToConverters maybePositiveInt).fromString "1" == Ok (Just 1)
(typeToConverters maybePositiveInt).fromString " 5 " == Ok (Just 5)
(typeToConverters maybePositiveInt).fromString "" == Ok Nothing
(typeToConverters maybePositiveInt).fromString "five" == Err (syntaxError "five")Similar to optional but you get to customize the blank error check.
All newly constructed fields are clean.
Initialize a new field from the given type and the empty string.
For all field types, t, empty t is equivalent to fromString t "".
Initialize a new field from the given type and string.
toMaybe (fromString bool "enabled") == Just TrueInitialize a new field from the given type and value.
toMaybe (fromValue char 'a') == Just 'a'Any change to a field using these setter functions makes the field dirty. Furthermore, these are the only functions that can dirty a field.
field = empty positiveInt
toResult field == Err [ blankError ]
toResult (setFromString "-1" field) == Err [ validationError "-1" ]
toResult (setFromString "0" field) == Err [ validationError "0" ]
toResult (setFromString "1" field) == Ok 1
toResult (setFromString " 5 " field) == Ok 5
toResult (setFromString " " field) == Err [ blankError ]
toResult (setFromString "five" field) == Err [ syntaxError "five" ]field = empty (optional positiveInt)
toResult field == Ok Nothing
toResult (setFromValue (Just -1) field) == Err [ validationError "-1" ]
toResult (setFromValue (Just 0) field) == Err [ validationError "0" ]
toResult (setFromValue (Just 1) field) == Ok (Just 1)
toResult (setFromValue (Just 5) field) == Ok (Just 5)
toResult (setFromValue Nothing field) == Ok Nothingfield = empty int
error = customError "Not an integer"
toResult (setError error field) == Err [ error ]field = empty int
error = customError "Not an integer"
toResult (setCustomError "Not an integer" field) == Err [ error ]Determine whether or not the raw string is equal to the empty string.
isEmpty (empty int) == True
isEmpty (fromString int " ") == False
isEmpty (fromString int "1") == FalseisNonEmpty f is equivalent to not (isEmpty f).
isNonEmpty (empty int) == False
isNonEmpty (fromString int " ") == True
isNonEmpty (fromString int "1") == TrueDetermine whether or not the raw string is equal to a blank string.
isBlank (empty int) == True
isBlank (fromString int " ") == True
isBlank (fromString int "1") == FalseisNonBlank f is equivalent to not (isBlank f).
isNonBlank (empty int) == False
isNonBlank (fromString int " ") == False
isNonBlank (fromString int "1") == TrueTrue if the field is clean.
isClean (empty int) == True
isClean (fromString int "1") == True
isClean (setFromString "1" (empty int)) == FalseTrue if the field is dirty. isDirty f is equivalent to not (isClean f).
isDirty (empty int) == False
isDirty (fromString int "1") == False
isDirty (setFromString "1" (empty int)) == TrueTrue if the field does not have any errors.
isValid (empty int) == False
isValid (fromString int "1") == TrueTrue if the field has errors. isInvalid f is equivalent to not (isValid f).
isInvalid (empty int) == True
isInvalid (fromString int "1") == FalsetoRawString (empty int) == ""
toRawString (fromString int " \n5 \t \n") == " \n5 \t \n"toString (empty int) == ""
toString (fromString int " \n5 \t \n") == "5"toResult (empty int) == Err [ blankError ]
toResult (fromString int "5") == Ok 5import Validation as V
toValidation (empty int) == V.fail blankError
toValidation (fromString int "5") == V.succeed 5The internal state of a Field.
raw contains the unprocessed String input.
processed contains the result of parsing raw. Either it was successfully parsed into a type a or there were errors of type e.
clean indicates whether or not the corresponding Field is considered to be clean or dirty.
A Field is clean if it has never been changed, via one of setFromString, setFromValue, setError, setErrors, setCustomError,
or setCustomErrors, after it was initially created. Otherwise, the Field is considered to be dirty.
Re-export the Validation type for convenience.
Useful to use with applyValidation.
Let f represent a function that takes N >= 1 arguments. Then,
succeed f
|> applyValidation field1
|> applyValidation field2
|> ...
|> applyValidation fieldNapplies f to N arguments as long as no field has an error.
Convert from a Validaton to a Result for convenience.
Useful to use with applyValidation.
Let f represent a function that takes N >= 1 arguments. Then,
succeed f
|> applyValidation field1
|> applyValidation field2
|> ...
|> applyValidation fieldN
|> validationToResultapplies f to N arguments as long as no field has an error.
The collection of conversion functions that comprise a Type. The conversion functions are useful
for building new field types from existing field types.
type Positive
= Positive Int
fromString : String -> Result (Error e) Positive
fromString =
(typeToConverters positiveInt).fromString >> Result.map Positive
toString : Positive -> String
toString (Positive p) =
(typeToConverters positiveInt).toString p
fieldType : Type (Error e) Positive
fieldType =
customType
{ fromString = fromString
, toString = toString
}Apply a function to the processed value of a field. If the field is valid, the value will be converted. If the field is invalid, the same error will be propagated.
import Validation as V
validate sqrt (fromString float "4.0") == V.succeed 2.0
validate sqrt (fromString float "") == V.fail blankErrorApply a function if both fields are valid. If not, the errors will be accumulated in order of failure.
import Validation as V
validate2 max (fromString int "42") (fromString int "13") == V.succeed 42
validate2 max (fromString int "x") (fromString int "13") == V.fail (syntaxError "x")
validate2 max (fromString int "42") (fromString int "y") == V.fail (syntaxError "y")
validate2 max (fromString int "x") (fromString int "y") == V.failWithErrors (syntaxError "x") [ syntaxError "y" ]Apply a function if all three fields are valid. If not, the errors will be accumulated in order of failure.
validate3 (\a b c -> a + b + c) (fromString int "1") (fromString int "2") (fromString int "3") == V.succeed 6
validate3 (\a b c -> a + b + c) (fromString int "1") (fromString int "y") (fromString int "3") == V.fail (syntaxError "y")
validate3 (\a b c -> a + b + c) (fromString int "x") (fromString int "2") (fromString int "z") == V.failWithErrors (syntaxError "x") [ syntaxError "z" ]
validate3 (\a b c -> a + b + c) (fromString int "x") (fromString int "y") (fromString int "z") == V.failWithErrors (syntaxError "x") [ syntaxError "y", syntaxError "z" ]Field e is not applicative. However, Maybe, Result e, and Validaton e are all applicative.
The functions below attempt to make it convenient to do an applicative style of programming with fields by
leveraging the applicative nature of Maybe, Result e, and Validation e.
Suppose you want to use the values from multiple fields but you don't care about any of the errors that are present.
type Primitives
= Positive Int Float Bool Char String
| NonPositive Int Float Bool Char String
let
maybePrimitives =
(\n f b c s ->
if n > 0 then
Positive n f b c s
else
NonPositive n f b c s
)
|> Just
|> applyMaybe (fromString int "5")
|> applyMaybe (fromString float "3.14")
|> applyMaybe (fromValue bool True)
|> applyMaybe (fromValue char 'a')
|> applyMaybe (fromString string "Hello")
in
maybePrimitives == Just (Positive 5 3.14 True 'a' "Hello")Suppose you want to use the values from multiple fields and you only care about the first error that occurs.
type Primitives
= Positive Int Float Bool Char String
| NonPositive Int Float Bool Char String
let
resultPrimitives =
(\n f b c s ->
if n > 0 then
Positive n f b c s
else
NonPositive n f b c s
)
|> Ok
|> applyResult (fromString int "5")
|> applyResult (fromString float "pi")
|> applyResult (fromString bool "not")
|> applyResult (fromValue char 'a')
|> applyResult (fromString string "Hello")
in
resultPrimitives == Err [ syntaxError "pi" ]Suppose you want to use the values from multiple fields and you care about all the errors that occur.
type Primitives
= Positive Int Float Bool Char String
| NonPositive Int Float Bool Char String
let
resultPrimitives =
(\n f b c s ->
if n > 0 then
Positive n f b c s
else
NonPositive n f b c s
)
|> succeed
|> applyValidation (fromString int "5")
|> applyValidation (fromString float "pi")
|> applyValidation (fromString bool "not")
|> applyValidation (fromValue char 'a')
|> applyValidation (fromString string "Hello")
|> validationToResult
in
resultPrimitives == Err [ syntaxError "pi", syntaxError "not" ]Get rid of whitespace on both sides of a string.
If the trimmed string is empty return blankError,
otherwise apply the given function to the trimmed string.
It is useful when you're defining a custom field type.
type Username = Username String
username : Type (Error String) Username
username =
customType
{ fromString =
trim
(\s ->
let
len =
String.length s
in
if len < 3 then
Err <| customError "The username must have at least 3 characters"
else if len > 25 then
Err <| customError "The username must have at most 25 characters"
else
Ok (Username s)
)
, toString =
\(Username s) -> s
}
toResult (fromString username "abc") == Ok (Username "abc")
toResult (fromString username "ab") == Err [ customError "The username must have at least 3 characters" ]
toResult (fromString username "abcdefghijklmnopqrstuvwxyz") == Err [ customError "The username must have at most 25 characters" ]Similar to trim but you get to customize the blank error.
The error type returned by the built-in field types. It supports blank, syntax, validation, and custom validation errors.
The default blank, syntax, and validation errors. It's particularly useful when
customizing the Bool type with customSubsetOfBool and you don't want to customize the errors.
myBool = customSubsetOfBool defaultErrors { defaultCustomBoolOptions | caseSensitive = True } (always True)
toResult (fromString myBool "true") == Ok True
toResult (fromString bool "True") == Ok True
toResult (fromString myBool "True") == Err [ syntaxError "True" ]Indicates that the user's input string is either the empty string or a string containing only whitespace characters.
toResult (fromString positiveInt "") == Err [ blankError ]
toResult (fromString positiveInt "\n\t ") == Err [ blankError ]Indicates that the user's input string cannot be converted to the desired type.
toResult (fromString positiveInt "five") == Err [ syntaxError "five" ]The string used in the syntax error is the trimmed user's input string that caused the error.
toResult (fromString positiveInt " five\n \t") == Err [ syntaxError "five" ]Indicates that the user's input string was successfully converted to a value of the desired type but that the value wasn't contained in the subset of values under consideration.
toResult (fromString positiveInt "0") == Err [ validationError "0" ]
toResult (fromString positiveInt "-1") == Err [ validationError "-1" ]Let v represent the value to which the user's input string was successfully converted. The string used
in the validation error is the string returned by the toString function of the field's type when it's
applied to v.
v : Int
v = -1
field : Field (Error e) Int
field = fromString positiveInt " -1 \n \t"
negativeOne : String
negativeOne = (toConverters field).toString v
toResult field == Err [ validationError negativeOne ]In the above example, the user's input string, " -1 \n \t", would be successfully converted to the
value, -1, but -1 is not a positive integer. Hence, the string representation of -1 as determined
by (toConverters field).toString is used in the validation error.
Indicates that the user's input string was successfully converted to a value of the desired type but that the value wasn't contained in the subset of values under consideration.
It's similar to a validationError but rather than a String you get to customize
the type used to record any extra error details.
type Username = Username String
type UsernameError
= TooShort { actual : Int, min : Int }
| TooLong { actual : Int, max : Int }
username : Type (Error UsernameError) Username
username =
customType
{ fromString =
trim
(\s ->
let
len =
String.length s
in
if len < 3 then
Err <| customError (TooShort { actual = len, min = 3 })
else if len > 25 then
Err <| customError (TooLong { actual = len, max = 25 })
else
Ok (Username s)
)
, toString =
\(Username s) -> s
}
toResult (fromString username "abc") == Ok (Username "abc")
toResult (fromString username "ab") == Err [ customError (TooShort { actual = 2, min = 3 }) ]
toResult (fromString username "abcdefghijklmnopqrstuvwxyz") == Err [ customError (TooLong { actual = 26, max = 25 }) ]You get to design the type so that you'd have enough information available to display
a user-friendly error message. See errorToString for an example.
Convert your errors to user-friendly error messages.
Let's continue with the example from customError.
toErrorMessage : Error UsernameError -> String
toErrorMessage =
errorToString
{ onBlank = "The username is required"
, onSyntaxError = always ""
, onValidationError = always ""
, onCustomError =
\e ->
case e of
TooShort { actual, min } ->
"The username must have at least " ++ String.fromInt min ++ " characters: " ++ String.fromInt actual
TooLong { actual, max } ->
"The username must have at most " ++ String.fromInt max ++ " characters: " ++ String.fromInt actual
}
toErrorMessage (customError <| TooShort { actual = 2, min = 3 }) == "The username must have at least 3 characters: 2"
toErrorMessage (customError <| TooLong { actual = 26, max = 25 }) == "The username must have at most 25 characters: 26"Apply a function to the error value of the field type. It's useful for unifying multiple custom field errors under a single error type.
Let's continue with the example from customError and incorporate an Age type.
type Age = Age Int
type AgeError
= TooYoung { actual : Int, min : Int }
| TooOld { actual : Int, max : Int }
age : Type (Error AgeError) Age
age =
customType
{ fromString =
(typeToConverters positiveInt).fromString
>> Result.andThen
(\n ->
if n < 21 then
Err <| customError (TooYoung { actual = n, min = 21 })
else if n > 65 then
Err <| customError (TooOld { actual = n, max = 65 })
else
Ok (Age n)
)
, toString =
\(Age n) -> String.fromInt n
}
--
-- 1. The single error type under which we will unify the two custom field errors.
--
type Errors
= MyUsernameError (Error UsernameError)
| MyAgeError (Error AgeError)
usernameField : Field (Error UsernameError) Username
usernameField = fromString username "ab"
ageField : Field (Error AgeError) Age
ageField = fromString age "67"
type alias UserDetails = { username : Username, age : Age }
--
-- 2. We use mapError in order to validate both fields at the same time.
--
resultUserDetails =
validationToResult <|
validate2
UserDetails
(usernameField |> mapError MyUsernameError)
(ageField |> mapError MyAgeError)
resultUserDetails == Err [ MyUsernameError (customError <| TooShort { actual = 2, min = 3 }), MyAgeError (customError <| TooOld { actual = 67, max = 65 }) ]fromString string "A string"
|> firstError
|> (==) Nothing
fromString string "A string"
|> setErrors "First error" [ "Second error", "Third error" ]
|> firstError
|> (==) (Just "First error")fromString string "A string"
|> lastError
|> (==) Nothing
fromString string "A string"
|> setErrors "First error" [ "Second error", "Third error" ]
|> lastError
|> (==) (Just "Third error")
Tracks the raw
Stringinput and either the value of typeathat theStringrepresents or the errors of typeethat can accumulate.