-
Notifications
You must be signed in to change notification settings - Fork 0
Elixir With
Elixir's with expression offers an interesting twist on error handling.
Suppose we are parsing ElasticSearch results with this simplified and converted result:
result = %{
hits: %{
total: 1,
hits: [
%{
_source: %{
my_document: %{
name: "Example",
buried_json: "{\"color\": \"red\"}"
}
}
}
]
}
}If we expect the result to always have exactly one hit and that hit always has the same structure, we might use a bit of Intentional Programming and let it crash and log the internal error for us:
{:ok, buried_json} = get_buried_json(result)
{:ok, %{"color" => color}} = Jason.decode(buried_json)
{:ok, hex} = color_to_hex(color)
hex # return thisHowever, we may expect to be parsing user data and need to return a useful
message to the user. The typical with approach I see in searches would
translate the above example to this expression:
with {:ok, buried_json} <- get_buried_json(result),
{:ok, %{"color" => color}} <- Jason.decode(buried_json),
{:ok, hex} <- color_to_hex(color) do
{:ok, "Color code is: #{hex}"} # return this
else
{:error, message} -> {:failed, message}
endThis is similar in feel to try/catch expressions or error pipelines in other
languages, but the approach suffers the same problem those other languages do:
which of those three parsing functions resulted in a Jason.DecodeError? Which
fell through to the more generic error form?
The solution I came up with recently is to tag, or type, each call:
with {_, {:ok, buried_json}} <- {:get_buried, get_buried_json(result)},
{_, {:ok, %{"color" => color}}} <- {:decode_buried, Jason.decode(buried_json)},
{_, {:ok, hex}} <- {:hexed, color_to_hex(color)} do
{:ok, "Color code is: #{hex}"} # return this
else
{:get_buried, {:error, message}} -> {:failed_to_get_buried, message}
{:decode_buried, {:error, message}} -> {:failed_to_decode_buried, message}
{:hexed, {:error, message}} -> {:failed_to_get_hexed_color, message}
end