arowM / elm-form-decoder / 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, while raising errors 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 Basics.Int

Decoder into Int, while raising errors of type 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 Basics.Float

Decoder into Float, while raising errors of type 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" ]

minBoundWith : (a -> a -> Basics.Order) -> err -> a -> Validator a err

Primitive validator limiting by minimum bound with a custom comparison function.

run (minBoundWith compare "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" ]

maxBoundWith : (a -> a -> Basics.Order) -> err -> a -> Validator a err

Primitive validator limiting by maximum bound with a custom comparison function.

run (maxBoundWith compare "Too large" 100) 200
--> Err [ "Too large" ]

minLength : err -> Basics.Int -> Validator String err

Primitive validator limiting by minimum length.

run (minLength "Too short" 10) "short"
--> Err [ "Too short" ]

maxLength : err -> Basics.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 -> Basics.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 -> Basics.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

The lift function "lifts" a decoder up to operate on a larger structure.

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

map2 : (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)

map3 : (a -> b -> c -> value) -> Decoder input x a -> Decoder input x b -> Decoder input x c -> Decoder input x value

map4 : (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

map5 : (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

pass : Decoder b x c -> Decoder a x b -> Decoder a x c

Chain together a sequence of decoders.

type Error
    = InvalidInt
    | TooLong
    | TooBig

advancedDecoder : Decoder String Error Int
advancedDecoder =
    Form.Decoder.identity
        |> assert (maxLength TooLong 5)
        |> pass (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 ]

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

Similar to with, but convenient for chaining a sequence of decoders.

type Image
    = B64Image Base64
    | ImagePath Path

type Base64
    = Base64 String

type Path
    = Path (List String)

type Error
    = Required

base64Decoder : Decoder String Error Base64
base64Decoder =
    custom <| \s -> Ok <| Base64 s


pathDecoder : Decoder String Error Path
pathDecoder =
    custom <| \s -> Ok <| Path <| String.split "/" s

imageDecoder : Decoder String Error Image
imageDecoder =
    Form.Decoder.identity
        |> assert (minLength Required 1)
        |> andThen
            (\str ->
                if String.startsWith "data:" str
                    then map B64Image base64Decoder
                    else map ImagePath pathDecoder
            )

run imageDecoder ""
--> Err [ Required ]

run imageDecoder "foo"
--> Ok <| ImagePath <| Path [ "foo" ]

run imageDecoder "/foo/bar"
--> Ok <| ImagePath <| Path [ "", "foo", "bar" ]

run imageDecoder "data:image/png;base64,xxxxx..."
--> Ok <| B64Image <| Base64 "data:image/png;base64,xxxxx..."

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 append 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 ]

listOf : Decoder a err b -> Decoder (List a) ( Basics.Int, err ) (List b)

Similar to list, but also returns the index of the element where the error occurred.

type Error
    = TooShort
    | TooLong

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

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

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

run (listOf decoder) [ "foo", "", "bazbaz", "barbar" ]
--> Err [ (1, TooShort), (2, TooLong), (3, 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 ]

arrayOf : Decoder a err b -> Decoder (Array a) ( Basics.Int, err ) (Array b)

Similar to array, but also returns the index of the element where the error occurred.

import Array

type Error
    = TooShort
    | TooLong

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

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

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

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