Strategy for essential data that needs to be decoded

I have a user entry in my model with details of the current user. I decode it from flags.user and that can fail. But the whole app is basically meaningless if the user data isn’t there so I don’t particular want it to be a Maybe User or put the whole model in a Maybe based on the success of the decoding.

I realise that the answer might just be to deal with it and choose one of those two options but I’m curious what others think for this. I currently have the approach of unpacking the decode result and calling Debug.crash but obviously that feels terrible and doesn’t communicate anything to the user.

I guess I’d almost like the init function to be able to fail and the error be returned to the Javascript code that is mounting it to be handled there.

Any advice would be very welcome.

If the functioning of your app is dependent on this User being successfully decoded, and there is truly nothing to do without it, I would have the user field in my model be a User and not a Maybe User, decode the user in my init function, and Debug.crash if a user could not be decoded.

If you would like a more friendly error message and to eliminate all calls to Debug.crash, which are worthy goals, you could make your App.Model an ADT of something like:

Model
  = Invalid String
  | Valid ModelProps

Then in init if your user didn’t decode you’d set model equal to Invalid "Must have a valid user" or something and case off of your model type in update and view

Edit:
Here’s a link to an example that avoids Debug.crash in Ellie https://ellie-app.com/BCthmbKya1/0

if you change "a" on line 30 to a string of an int like "0", you can see how it works on successful decoding.

2 Likes

I’d do something similar to what @itsgreggreg suggested:

main =
    Html.program
        { init = init
        , update = updateResult
        , view = viewResult
        , subscriptions = always Sub.none
        }


viewResult : Result String Model -> Html Msg
viewResult result =
    case result of
        Ok model ->
            view model

        Err error ->
            viewError error

updateResult : Msg -> Result String Model -> ( Result String Model, Cmd Msg )
updateResult msg result =
    case result of
        Ok model ->
            let
                ( newModel, newCmd ) =
                    update msg model
            in
            ( Ok newModel, newCmd )

        Err _ ->
            ( result, Cmd.none )

init : Flags -> ( Result String Model, Cmd Msg )

view : Model -> Html Msg

viewError : String -> Html Msg

update : Msg -> Model -> ( Model, Cmd Msg )

In this way, your update and view still work in terms of Msg and the validated Model, but you also have a viewError : String -> Html Msg that lets you display something nice to the user about what went wrong!

We use a strategy like this at NoRedInk. Works great!

3 Likes