Moving from elm-validate to elm-verify
Tags: elm, validation
Published: 2018-02-16 14:50:06

---
title: Moving from elm-validate to elm-verify
published: true
description: Outlining the benefits of switching validation strategy
tags: elm, validation
---

At [Zaptic](https://www.zaptic.com), we use Elm for our business administration website to allow customers to configure the processes that they want to carry out. The site contains a number of different forms for adding & editing various entities in our system. All of these forms benefit from client side validation before the data is submitted to our servers. 

When we started writing our Elm code, the recommended approach to form validation was the [rtfeldman/elm-validate](https://github.com/rtfeldman/elm-validate) library. For a form backed by a data structure like this:

```elm
type alias ProductGroup =
    { name : String
    , mode : Maybe Mode
    , selectedProducts : Dict Int Product

    -- Used for storing validation errors
    , errors : List ( Field, String )
    }
```

We might have a validation function like this:

```elm
validateProductGroup : Validator ( Field, String ) ProductGroup
validateProductGroup =
    Validate.all
        [ .name >> ifBlank ( NameField, "Please provide a name" )
        , .mode >> ifNothing ( ModeField, "Please select a mode" )
        , .selectedProducts >> ifEmptyDict ( ProductsField, "Please select at least one product" )
        ]
```

This checks that the `name` field isn't blank, that a `mode` has been selected and that some products have been selected. The result is a list of errors corresponding to the criteria that aren't met:

```elm
validateProductGroup { name = "", mode = Just MultiMode, selectedProducts = Dict.empty }
-- [(NameField, "Please provide a name"), (ProductsField, "Please select at least one product")] 
```

The list can be saved in the model and then the view searches the list for relevant entries when displaying the various input fields in order to display the error state to the user if there is one.

With this in mind, we can create our form to send a `SaveProductGroup` message when the form is submitted and we can handle this message in our `update` function with:

```elm
        SaveProductGroup ->
            case validateProductGroup model.productGroupData of
                [] ->
                    { model | productGroupData = { productGroupData | errors = Nothing } }
                        ! [ postProductGroup model.productGroupData
                          ]

                results ->
                    { model | productGroupData = { productGroupData | errors = Just results } } ! []
```

Here, we run the `validateProductGroup` function and if we get an empty list then we know that there are no errors and we can post our data to the server using `postProductGroup` that will create a command for us to carry out the necessary HTTP request.

The problem we encounter is that the `postProductGroup` needs to encode the `ProductGroup` structure to JSON and when it does that it has to navigate the data that we've given it. We know that the `mode` value is a `Just` because we've validated it but the convert to JSON function cannot take any shortcuts because Elm doesn't let us (which is a good thing!) We can try to write the encoding function like:

```elm
encodeProductGroup : ProductGroup -> Json.Encode.Value
encodeProductGroup { name, mode, selectedProducts } =
    Json.Encode.object
        [ ( "name", Json.Encode.string name )
        , ( "mode"
          , case mode of
                Just value ->
                    encodeMode value

                Nothing ->
                    -- Help! What do we put here?
          )
        , ( "products", Json.Encode.list <| List.map encodeProduct <| Dict.values selectedProducts )
        ]
```

As you can see from the comment, it is hard to figure out what to write there. Elm gives us some escape hatches like `Debug.crash` but that feels wrong. We could not include the `mode` key if we don't have a value but then we're knowingly allowing a code path that sends incorrect data to the server.

So what do we do? Well, we need to recognise that this function shouldn't be making this decision. It shouldn't have to deal with the `Maybe`, especially when we have already validated that it has a `Just` value.

So we create a new type:

```elm
type alias ProductGroupUploadData =
    { name : String
    , mode : Mode
    , selectedProducts : List Product
    }
```

Which is the same data that we're interested in but with the `Maybe` resolved to an actual value and the `Dict.values` transformation already applied to `selectedProducts`. If we change our `encodeProductGroup` function to expect this type then the implementation becomes trivial:

```elm
encodeProductGroup : ProductGroupUploadData -> Json.Encode.Value
encodeProductGroup { name, mode, selectedProducts } =
    Json.Encode.object
        [ ( "name", Json.Encode.string name )
        , ( "mode", encodeMode mode )
        , ( "products", Json.Encode.list <| List.map encodeProduct selectedProducts )
        ]
```

But how do we convert our `ProductGroup` to `ProductGroupUploadData`? This is where the [stoeffel/elm-verify](https://github.com/stoeffel/elm-verify) library comes in. It allows us to both validate our data and transform it to another structure in the same operation. It does this by using the `Result` type to allow it to report validation errors, if any are encountered, or the new data structure for us to use. And it does this with a `Json.Decode.Pipeline`-like interface:

```elm
validateProductGroup : ProductGroup -> Result (List ( Field, String )) ProductGroupUploadData
validateProductGroup =
    let
        validateProducts productGroup =
            if Dict.isEmpty productGroup.selectedProducts then
                Err [ ( ProductsField, "Please select at least one product" ) ]
            else
                Ok (Dict.values productGroup.selectedProducts)
    in
    V.validate ProductGroupUploadData
        |> V.verify .name (String.Verify.notBlank ( NameField, "Please provide a name" ))
        |> V.verify .mode (Maybe.Verify.isJust ( ConfigField, "Please select a mode" ))
        |> V.custom validateProducts

```

Where `V` is the result of `import Verify as V`. You can see the "pipeline" approach that might be familiar from `Json.Decode.Pipeline`. That means we're using `ProductGroupUploadData` as a constructor and each step of the pipeline is providing an argument to complete the data. We use `String.Verify` to check that the `name` isn't blank and `Maybe.Verify` to check that the `mode` is specified. Then we use `Verify.custom` to provide a slight more complex check for the `selectedProducts`. `Verify.custom` allows us to write a function that takes the incoming data and returns a [`Result`](http://package.elm-lang.org/packages/elm-lang/core/latest/Result) with either an `Err` with an array of errors or an `Ok` with the valid value. We use it to not only check that the dictionary is empty but also extract just the values from the dictionary. We don't have to run `Dict.values` here, we could also do that in the `encodeProductGroup` function when generating the JSON but I have a personal preference for the `UploadData` to match the JSON closely if possible.

With that in place, we can change our `SaveProductGroup` case in our `update` function to look like this:

```elm
        SaveProductGroup ->
            case validateProductGroup model.productGroupData of
                Ok uploadData ->
                    { model | productGroupData = { productGroupData | errors = Nothing } }
                        ! [ postProductGroup uploadData
                          ]

                Err errors ->
                    { model | productGroupData = { productGroupData | errors = Just errors } } ! []
```

Which means that the `postProductGroup` is given a nice `ProductGroupUploadData` record and no longer has to worry about the `Maybe` type.

Prior to using the `elm-verify` library, we used a separate function to convert between the types and we only called `postProductGroup` if both the validation & the conversion functions were successful. The conversion function always felt a little strange though and switching to `elm-verify` cleans that up nicely.

**Further note**: Whilst the `elm-verify` interface is similar to `Json.Decode.Pipeline` it isn't quite the same. It has a version of `andThen` but it doesn't provide some ways to combine operations like `Json.Decode.oneOf` or helper functions like `Json.Decode.map`. This is partly to keep the interface simple and partly because it is always manipulating `Result` values so with a bit of thought you can use helper functions like `Result.map` quite easily. 

I only include this as originally sought to use `elm-verify` exactly as I would `Json.Decode.Pipeline` and ended up writing a bunch of unnecessary functions in order to allow me to do just that. I should have spent more time understanding the types in the interface and working with those.