Generic error reporting

I’d like to implement some sort generic reporting for HTTP responses that does not succeed, so that I don’t have to care about manually reporting errors, but have it “automatically” for all failing requests.

Does anyone have experience in implementing something that does this? How did you go about it? What did you learn?

I often use a shared branch for error handling.

Well-known structure of update is like this.

case msg of
  GotUser (Ok user) ->
  GotUser (Err e) ->
  GotArticles (Ok articles) ->
  GotArticles (Err e) ->

But, to handle all errors “automatically”, you can also handle responses like this.

case msg of
  GotUser user ->
  GotArticles articles ->
  GotError e ->
    -- both errors come here

Or, if you are making a SPA, I can introduce more tricky method if error handling. For each page, I use Return Model Msg type instead of (Model, Cmd Msg). The implementation is like below.

type alias Return model msg =
  { model : model
  , command : Cmd msg
  , requests : List (Cmd (Result Http.Error msg))
  }

The main module will get this data from each page’s init or update function. When a request is successful, the response will update the page’s model. When it fails, the error will be handled in the main module. The main module will have that error and show the message in main’s own view function (which typically renders a toast just under the header). You can also handle requests in the “normal” way using the command field.

You can see the example usage of Return in this repo. The structure can vary depending on the project, so it is different from the above.

What I learned is that there are some cases which we should “manually” handle errors for each. A validation error which is only created in server-side (e.g. “this account name is already taken”) should be handled in each page to show it in <form> element. If a specific logic is required after an error (e.g. logout), it cannot be done in the shared branch. For others, this method works well.

I’m also curious about how others do :slight_smile:

In all of my apps I care about the context of the error and as such, I have never implemented an automatic “catch-all” solution BUT, if I were to implement one, I would do it pretty much as @jinjor suggested.

A typical API call would have a signature like

callName : SomeParameters -> (Result Http.Error Payload -> msg ) -> Cmd msg
callName params toMsg = 
    ...

You would replace that with

callName : SomeParameters -> (Payload -> msg) -> (Http.Error -> msg)  -> Cmd msg 
callName params toSuccess toError = 
    ... 

This basically allows you to use the same error handler for every call. (The GotError from @jinjor’s example)

If you want to be even more fancy, you can also add a helper that would map Http.Error to Api.Error (a custom type that encapsulates all the errors you care about) and have your error handler receive Api.Error instead of Http.Error.

I usually create an error instead of mapping the Http.Error to an Api.Error directly, since I usually also use Http body with error codes to sometimes indicate details on problems, which get lost in translation in default Http.Error, using expectStringResponse.

I once had the same question and it seems to be just impossible.

The solution with single error message in my opinion isn’t generic, because you will have to repeat it in every single module, which has update function.

Thank you for your replies!

So Task.attempt is the secret sauce that makes it possible to send one message if it is a success and another if it fails?

Like you’ve done here @jinjor ?

So Task.attempt is the secret sauce that makes it possible to send one message if it is a success and another if it fails?

It is not necessarily a Task. You can also do it with Cmd like this.
You will only need Task when you want to chain or combine requests (e.g. get user and then get articles related to that user).

Yes, of course! I get it now. Thanks for explaining!

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.