arowM/elm-form-decoder/1.1.0

NOTE: the latest version is 1.4.0

Form.Decoder

Main module that exports primitive decoders and helper functions for form decoding.

Types

type Decoder input err a

Core type representing a decoder. It decodes input into type a, raising error of type err.

type alias Validator input err =
Decoder input err ()

An alias for special decoder that does not produce any outputs. It is used for just validating inputs.

Decode functions

run : Decoder input err a -> input -> Result (List err) a

Basic function that decodes input by given decoder.

run (int "Invalid") "foo"
--> Err [ "Invalid" ]


run (int "Invalid") "34"
--> Ok 34
errors : Decoder input err a -> input -> List err

Checks if there are errors.

errors (int "Invalid") "foo"
--> [ "Invalid" ]


errors (int "Invalid") "34"
--> []

Primitive decoders

int : err -> Decoder String err Int

Decoder into Int, raising err when a input is invalid for an integer.

run (int "Invalid") "foo"
--> Err [ "Invalid" ]


run (int "Invalid") "34"
--> Ok 34


run (int "Invalid") "34.3"
--> Err [ "Invalid" ]


run (int "Invalid") "34e3"
--> Err [ "Invalid" ]
float : err -> Decoder String err Float

Decoder into Float, raising err when a input is invalid for an float.

run (float "Invalid") "foo"
--> Err [ "Invalid" ]


run (float "Invalid") "34"
--> Ok 34


run (float "Invalid") "34.3"
--> Ok 34.3


run (float "Invalid") "34e3"
--> Ok 34000
always : a -> Decoder input never a

Primitive decoder that always succeeds with constant value.

run (Form.Decoder.always "bar") "foo"
--> Ok "bar"


run (Form.Decoder.always 34) 23
--> Ok 34
identity : Decoder input never input

Primitive decoder that always succeeds with input as it is.

run Form.Decoder.identity "foo"
--> Ok "foo"


run Form.Decoder.identity 34
--> Ok 34
fail : err -> Decoder input err a

Primitive decoder which always results to invalid.

run (fail "error") "foo"
--> Err [ "error" ]


run (fail "error") <| Just 34
--> Err [ "error" ]


run (when (\n -> n < 0) <| fail "error") -1
--> Err [ "error" ]


run (when (\n -> n < 0) <| fail "error") 0
--> Ok ()

Primitive validators

minBound : err -> comparable -> Validator comparable err

Primitive validator limiting by minimum bound.

run (minBound "Too small" 10) 2
--> Err [ "Too small" ]
maxBound : err -> comparable -> Validator comparable err

Primitive validator limiting by maximum bound.

run (maxBound "Too large" 100) 200
--> Err [ "Too large" ]
minLength : err -> Int -> Validator String err

Primitive validator limiting by minimum length.

run (minLength "Too short" 10) "short"
--> Err [ "Too short" ]
maxLength : err -> Int -> Validator String err

Primitive validator limiting by maximum length.

run (maxLength "Too long" 10) "tooooooooo long"
--> Err [ "Too long" ]

Custom decoders

custom : (input -> Result (List err) a) -> Decoder input err a

Constructor for Decoder input err a.

type Error
    = TooSmall
    | InvalidInt


customValidator : Validator Int Error
customValidator =
    custom <| \n ->
        if n < 10
            then Err [ TooSmall ]
            else Ok ()


customInt : Decoder String Error Int
customInt =
    custom <| Result.fromMaybe [ InvalidInt ] << String.toInt


run customValidator 8
--> Err [ TooSmall ]


run customInt "foo"
--> Err [ InvalidInt ]

Helper functions for validation

assert : Validator a err -> Decoder input err a -> Decoder input err a

Apply validator on given decoder. If a input is invalid for given validator, decoding fails.

type Error
    = Invalid
    | TooSmall
    | TooBig


validator1 : Validator Int Error
validator1 =
    minBound TooSmall 3


validator2 : Validator Int Error
validator2 =
    maxBound TooBig 6


myDecoder : Decoder String Error Int
myDecoder =
    int Invalid
        |> assert validator1
        |> assert validator2


run myDecoder "foo"
--> Err [ Invalid ]


run myDecoder "32"
--> Err [ TooBig ]


run myDecoder "2"
--> Err [ TooSmall ]


run myDecoder "3"
--> Ok 3
when : (a -> Bool) -> Validator a err -> Validator a err

Only checks validity if a condition is True.

type alias Form =
    { enableCheck : Bool
    , input : String
    }


type Error
    = TooShort


myValidator : Validator Form Error
myValidator =
    when .enableCheck <|
        lift .input <|
            minLength TooShort 3


run myValidator { enableCheck = True, input = "f" }
--> Err [ TooShort ]


run myValidator { enableCheck = False, input = "f" }
--> Ok ()


run myValidator { enableCheck = True, input = "foo" }
--> Ok ()
unless : (a -> Bool) -> Validator a err -> Validator a err

Only checks validity unless a condition is True.

type alias Form =
    { skipCheck : Bool
    , input : String
    }


type Error
    = TooShort


myValidator : Validator Form Error
myValidator =
    unless .skipCheck <|
        lift .input <|
            minLength TooShort 3


run myValidator { skipCheck = False, input = "f" }
--> Err [ TooShort ]


run myValidator { skipCheck = True, input = "f" }
--> Ok ()


run myValidator { skipCheck = False, input = "foo" }
--> Ok ()

Function to build up decoder for forms

lift : (j -> i) -> Decoder i err a -> Decoder j err a

lift is mainly used for accessing sub model of target value.

type alias Form =
    { field1 : String
    , field2 : String
    }


type Error
    = TooShort


run (lift .field1 <| minLength TooShort 5)
    (Form "foo" "barrrrrrrrrrr")
--> Err [ TooShort ]
map : (a -> b) -> Decoder input x a -> Decoder input x b
(a -> b -> value)
-> Decoder input x a
-> Decoder input x b
-> Decoder input x value
type alias Form =
    { str : String
    , int : String
    }


type alias Decoded =
    { str : String
    , int : Int
    }


type Error
    = TooShort
    | InvalidInt


strDecoder : Decoder String Error String
strDecoder =
    Form.Decoder.identity
        |> assert (minLength TooShort 5)


intDecoder : Decoder String Error Int
intDecoder =
    int InvalidInt


formDecoder : Decoder Form Error Decoded
formDecoder =
    map2 Decoded
        (lift .str strDecoder)
        (lift .int intDecoder)


run formDecoder (Form "foo" "bar")
--> Err [ TooShort, InvalidInt ]


run formDecoder (Form "foo" "23")
--> Err [ TooShort ]


run formDecoder (Form "foobar" "bar")
--> Err [ InvalidInt ]


run formDecoder (Form "foobar" "23")
--> Ok (Decoded "foobar" 23)
(a -> b -> c -> value)
-> Decoder input x a
-> Decoder input x b
-> Decoder input x c
-> Decoder input x value
(a -> b -> c -> d -> value)
-> Decoder input x a
-> Decoder input x b
-> Decoder input x c
-> Decoder input x d
-> Decoder input x value
(a -> b -> c -> d -> e -> value)
-> Decoder input x a
-> Decoder input x b
-> Decoder input x c
-> Decoder input x d
-> Decoder input x e
-> Decoder input x value
field : Decoder i err a -> Decoder i err (a -> b) -> Decoder i err b

Build up decoder for form. It can be used as mapN.

mapN f d1 d2 d3 ... dN =
    top f
        |> field d1
        |> field d2
        |> field d3
        ...
        |> field dN
top : f -> Decoder i err f
mapError : (x -> y) -> Decoder input x a -> Decoder input y a
type alias Form =
    { str : String
    , int : String
    }


type alias Decoded =
    { str : String
    , int : Int
    }


type FormError
    = FormErrorStr StrError
    | FormErrorInt IntError




type StrError
    = TooShort


type IntError
    = Invalid


strDecoder : Decoder String StrError String
strDecoder =
    Form.Decoder.identity
        |> assert (minLength TooShort 5)


intDecoder : Decoder String IntError Int
intDecoder =
    int Invalid


formDecoder : Decoder Form FormError Decoded
formDecoder =
    map2 Decoded
        (mapError FormErrorStr <| lift .str strDecoder)
        (mapError FormErrorInt <| lift .int intDecoder)




run formDecoder (Form "foo" "bar")
--> Err [ FormErrorStr TooShort, FormErrorInt Invalid ]


run formDecoder (Form "foo" "23")
--> Err [ FormErrorStr TooShort ]


run formDecoder (Form "foobar" "bar")
--> Err [ FormErrorInt Invalid ]


run formDecoder (Form "foobar" "23")
--> Ok (Decoded "foobar" 23)

Advanced

andThen : (a -> Decoder input x b) -> Decoder input x a -> Decoder input x b

Chain together a sequence of decoders.

type Error
    = InvalidInt
    | TooLong
    | TooBig


advancedDecoder : Decoder String Error Int
advancedDecoder =
    Form.Decoder.identity
        |> assert (maxLength TooLong 5)
        |> andThen (\_ -> int InvalidInt)
        |> assert (maxBound TooBig 300)


run advancedDecoder "foooooo"
--> Err [ TooLong ]


run advancedDecoder "foo"
--> Err [ InvalidInt ]


run advancedDecoder "1000000"
--> Err [ TooLong ]


run advancedDecoder "500"
--> Err [ TooBig ]


run advancedDecoder "200"
--> Ok 200
with : (i -> Decoder i err a) -> Decoder i err a

Advanced function to build up case-sensitive decoder.

type alias Form =
    { selection : Maybe Selection
    , int : String
    , str : String
    }


type Selection
    = IntField
    | StrField




type Selected
    = SelectInt Int
    | SelectStr String


type Error
    = TooShort
    | InvalidInt
    | NoSelection


myDecoder : Decoder Form Error Selected
myDecoder =
    with <| \form ->
        case form.selection of
            Just IntField ->
                map SelectInt <|
                    lift .int intDecoder
            Just StrField ->
                map SelectStr <|
                    lift .str strDecoder
            Nothing ->
                fail NoSelection


intDecoder : Decoder String Error Int
intDecoder =
    int InvalidInt


strDecoder : Decoder String Error String
strDecoder =
    Form.Decoder.identity
        |> assert (minLength TooShort 5)




run myDecoder <| Form (Just IntField) "foo" "bar"
--> Err [ InvalidInt ]


run myDecoder <| Form (Just StrField) "foo" "bar"
--> Err [ TooShort ]


run myDecoder <| Form (Just IntField) "23" "bar"
--> Ok <| SelectInt 23


run myDecoder <| Form (Just StrField) "23" "bar"
--> Err [ TooShort ]


run myDecoder <| Form (Just IntField) "foo" "barrrrr"
--> Err [ InvalidInt ]


run myDecoder <| Form (Just StrField) "foo" "barrrrr"
--> Ok <| SelectStr "barrrrr"


run myDecoder <| Form Nothing "foo" "barrrrr"
--> Err [ NoSelection ]

Helper functions for special situation

list : Decoder a err b -> Decoder (List a) err (List b)

Supposed to be used for advanced input fields that user can append new input.

For example, some forms would accept arbitrary number of email addresses by providing "Add" button to prepend new input field. list-sample

type Error
    = TooShort
    | TooLong


decoder : Decoder String Error String
decoder =
    Form.Decoder.identity
        |> assert (minLength TooShort 1)
        |> assert (maxLength TooLong 5)


run (list decoder) [ "foo", "bar", "baz" ]
--> Ok [ "foo", "bar", "baz" ]


run (list decoder) [ "foo", "", "baz" ]
--> Err [ TooShort ]


run (list decoder) [ "foo", "", "bazbaz", "barbar" ]
--> Err [ TooShort, TooLong, TooLong ]
array : Decoder a err b -> Decoder (Array a) err (Array b)
import Array


type Error
    = TooShort
    | TooLong


decoder : Decoder String Error String
decoder =
    Form.Decoder.identity
        |> assert (minLength TooShort 1)
        |> assert (maxLength TooLong 5)


run (array decoder) <| Array.fromList [ "foo", "bar", "baz" ]
--> Ok <| Array.fromList [ "foo", "bar", "baz" ]


run (array decoder) <| Array.fromList [ "foo", "", "baz" ]
--> Err [ TooShort ]


run (array decoder) <| Array.fromList [ "foo", "", "bazbaz", "barbar" ]
--> Err [ TooShort, TooLong, TooLong ]