Code Monkey home page Code Monkey logo

elm-form-decoder's Introduction

elm-form-decoder

Build Status

logo

Summary

Do you need form validation libraries? Wait! What you actually need would be form decoding library.

This library provides a scalable way to decode user inputs into neat structure. In the process, it also does validations.

What's form decoding?

Here is a blog post about form decoding and brief introduction to elm-form-decoder.

Example codes

Say that you are building an SNS for goats. (exclude the problem how they use keyboards by their two-fingered hands.)

First thing to do is declaring Goat type bellow representing a goat profile.

type alias Goat =
    { name : String
    , age : Int
    , horns : Int
    , contact : Contact
    , memo : Maybe String
    }


{-| Users (goats) can choose email or phone number for their contact info.
-}
type Contact
    = ContactEmail Email
    | ContactPhone PhoneNumber

Next, let's declare a special type for profile forms.

type alias Form
    { name : String
    , age : String
    , horns : String
    , contact : SelectContact
    , email : String
    , phone : String
    , memo : String
    }


{-| Represents active tab
-}
type SelectContact
    = SelectEmail
    | SelectPhone

Okay, it's time to decode the Form type into Goat type.

First thing to decode is declaring Error type.

type Error
    = NameRequired
    | AgeInvalidInt
    | AgeNegative
    | AgeRequired
    ...

Then make decoders for each field.

import Form.Decoder as Decoder

{-| Decoder for name field.

    import Form.Decoder as Decoder

    Decoder.run name ""
    --> Err [ NameRequired ]

    Decoder.run name "foo"
    --> Ok "foo"
-}
name : Decoder String Error String
name =
    Decoder.identity
        |> Decoder.assert (Decoder.minLength NameRequired 1)


{-| Decoder for name field.

    import Form.Decoder as Decoder

    Decoder.run age ""
    --> Err [ AgeRequired ]

    Decoder.run age "foo"
    --> Err [ AgeInvalidInt ]

    Decoder.run age "-30"
    --> Err [ AgeNegative ]

    Decoder.run age "30"
    --> Ok 30

-}
age : Decoder String Error Int
age =
    Decoder.identity
        |> Decoder.assert (Decoder.minLength AgeRequired 1)
        |> Decoder.pass (Decoder.int AgeInvalidInt)
        |> Decoder.assert (Decoder.minBound AgeNegative 0)

Decoder input err out indicates that the decoder consumes inputs of type input and converts it into out, while raising errors of type err.

These decoders also can be used to show errors on each input field.

ageErrorField : String -> Html msg
ageErrorField input =
    div
        [ class "errorField"
        ]
        <| List.map errorText
            (Decoder.errors age input)

errorText : String -> Html msg
errorText err =
    p
        [ class "errorText"
        ]
        [ text err
        ]

Next, lift decoders to consume Form type instead of String.

name_ : Decoder Form Error String
name_ =
    Decoder.lift .name name

age_ : Decoder Form Error Int
age_ =
    Decoder.lift .age age

Finally, build up decoder for Form.

form : Decoder Form Error Goat
form =
    Decoder.top Goat
        |> Decoder.field name_
        |> Decoder.field age_
        |> Decoder.field horns_
        |> Decoder.field contact_
        |> Decoder.field memo_

Wow, it's amazing!

This decoder enables you to:

  1. Validate user inputs
  2. Create Goat type from user inputs

Real world examples

Here's real world examples using elm-form-decoder in sample directory (demo).

elm-form-decoder's People

Contributors

arowm avatar dependabot[bot] avatar miyamoen avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

elm-form-decoder's Issues

Run multiple validators

Hey!

First off, thanks for the great package. Solves so many problems around form validation & the resulting data type.

Say you have a decoder setup like:

import Form.Decoder as D

type Err = MustBeGreaterThan5 | MustBeEven

decoder = 
    D.identity
        |> D.assert (D.minBound MustBeGreaterThan5 5)
        |> D.assert (even MustBeEven)

If we ran this with a few inputs, we'd get:

D.errors decoder 10 -- []
D.errors decoder 7   -- [ MustBeEven ]
D.errors decoder 4   -- [ MustBeGreaterThan5 ]
D.errors decoder 3   -- [ MustBeGreaterThan5 ]

The problem is with the last case of 3. Here, we would expect to see both MustBeGreaterThan5 and MustBeEven in the errors list, since both of them are failures. However, since this is based on applicative style parsing, Form.Decoder must short circuit after the first failure.

To remedy this in my use case, I wrote the following function:

joinResult : Result (List err) () -> Result (List err) () -> Result (List err) ()
joinResult result1 result2 =
    case ( result1, result2 ) of
        ( Err err, Ok () ) ->
            Err err

        ( Ok (), Err err ) ->
            Err err

        ( Err err1, Err err2 ) ->
            Err (err1 ++ err2)

        ( Ok (), Ok () ) ->
            Ok ()


assertMany : List (Decoder.Validator input x) -> Decoder input x value -> Decoder input x value
assertMany validators inputDecoder =
    Decoder.custom <|
        \a ->
            a
                |> Decoder.run inputDecoder
                |> Result.andThen
                    (\val ->
                        let
                            joinedValidators =
                                validators
                                    |> List.map (\decoder -> Decoder.run decoder a)
                                    |> List.foldl joinResult (Ok ())
                        in
                        Result.map (\() -> val) joinedValidators
                    )

Which could be used like:

decoder = 
    D.identity
        |> D.assertMany [ D.minBound MustBeGreaterThan5 5, even MustBeEven ]

D.errors decoder 10 -- []
D.errors decoder 7   -- [ MustBeEven ]
D.errors decoder 4   -- [ MustBeGreaterThan5 ]
D.errors decoder 3   -- [ MustBeGreaterThan5, MustBeEven ]

Essentially, this runs many Validators on an input and joins the errors together. Since we're only validating & not transforming data, this works nicely. However, we could not do something like this for pass or mapN, since later steps require the result type of the pass or mapN.

If this is something that'd make sense to be in the package, I'd be happy to create a PR.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.