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
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
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
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.
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #javascript
- #code-sync
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance








