pmjoe

pmjoe

I can't get Elixir error handling right

In a with case, with many operations, how can i individually handle errors?

Most Liked

peerreynders

peerreynders

Scott Wlaschin: Use Functions!

...
  with {:ok, limit} <- parse_limit(request),
       {:ok, offset} <- parse_offset(request, limit),
       {:ok, filters} <- parse_filters(request, limit, offset) do
     # send the content back
  else
    error -> handle_parse_error(error)
  end
end

def handle_parse_error({:error, {:parse_limit, message}}) do
  # handle parse_limit error
  ...
end
def handle_parse_error({:error, {:parse_offset, message, limit}}) do
  # handle parse_offset error
  ...
end
def handle_parse_error({:error, {:parse_filters, message, limit, offset}}) do
  # handle parse_filters error
  ...
end

12
Post #7
mischov

mischov

A further step you can take is to create an error struct, which might implement the Exception behavior.

One big benefit of using structs over tuples for the error is you can add or remove data from a struct easier than you can a tuple (it won’t change the shape for pattern matching).

I generally tend towards creating a generic struct type for my app that looks like:

%YourApp.Error{type: :x, reason: :y, data: %{...}}

Using this, I would create instances of the error struct when parsing and @peerreynders’s handle error function might then look like this:

def handle_parse_error({:error, %YourApp.Error{type: :parse_limit} = error}) do
  do_some_thing_with(error.data.message)
end

def handle_parse_error({:error, %YourApp.Error{type: :parse_offset} = error}) do
  do_something_with(error.data.message, error.data.offset)
end

A further refinement I’ve been working on, particularly for Phoenix apps, is to limit the number of types for those generic errors. For example, the parse errors might be:

%YourApp.Error{
  type: :invalid_input,
  reason: :could_not_parse_limit,
  data: %{message: "..."}
}

%YourApp.Error{
  type: :invalid_input,
  reason: :could_not_parse_offset,
  data: %{message: "...", limit: ...}
}

%YourApp.Error{
  type: :invalid_input,
  reason: :could_not_parse_filters,
  data: %{message: "...", offset: ..., limit: ...}
}

Then you can define handlers for these generic errors in a action_fallback controller, in this case to render a 400 response for :invalid_input errors. Then your with clause in a controller just looks like:

with {:ok, limit} <- parse_limit(request),
     {:ok, offset} <- parse_offset(request, limit),
     {:ok, filters} <- parse_filters(request, limit, offset) do
  # send the content back
end # No need to handle errors, let them fall through and the action_fallback will render
josevalim

josevalim

Creator of Elixir

I would suggest for you change each individual parse_ function to already return the error in the format you desire. Your code should look like this:

with {:ok, limit} <- parse_limit(request),
     {:ok, offset} <- parse_offset(request, offset),
     {:ok, filters} <- parse_filters(request, limit, offset) do
  # send the content back
else
  {:error, message} -> # send error message back to the user
end

If each parse function already returns the error in the format desired, it gets much simpler, as you no longer need to tag everything just to untag it right after.

NobbZ

NobbZ

Match on the reason. If though all of them simply return the same reason, you can only wrap your stuff in extra tuples, eg (but untested as on mobile):

with {:fetch_data, {:ok, data}} <- {:fetch_data, fetch_data(stuff)} do
  data
else
  {:fetch_data, :error} -> {:error, :fetch_data_failed}
end
pmjoe

pmjoe

Yep, this is it, you nailed it. I’m still grasping with Elixir, but this looks very good.

Where Next?

Popular in Questions Top

pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: https://hexdocs.pm/ecto/Ecto.Schema.html#module-...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
Kagamiiiii
Student &amp; New to elixir. Nice language. I want to convert a english character, e.g. “a”, which is stored in a variable, to it’s asci...
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
mgjohns61585
Could someone help me? I'm making my first elixir program, number guessing game. I can't figure out how to convert the user's guess from ...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? https://hexdocs.pm/ecto/Ecto.Repo.h...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New

Other popular topics Top

lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 record...
New
Jim
As a follow up to my earlier question: I have the code compiling and running but not getting a successful login from the rest server. ...
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New

We're in Beta

About us Mission Statement