this was really hard to read. let's give it a shot, though.
@spec my_fn(a::input_type)::{:ok, res::result_type} | {:error, any}
def my_fn(a) do
a
|> get_resource
|> case do
{:ok, result} -> result
{:error, :recoverable} -> get_alt_resource(a)
err = {:error, :reallybad} -> throw err
end
|> many_more_steps_might_have_similar_pattern
catch err
err
end
However what you presented seems to me to be an antipattern. Sometimes code clarity trumps terseness; in this case the results from get_resource and get_alt_resource are categorically identical. If we are allowed to refactor to be actually sane code and not require everything to be in a single function:
def my_fn(a) do
with {:ok, res1} <- resource_wrapper(a),
{:ok, res2} <- next_step(res1), ...
case
#handle errors here.
end
end
@doc """
fetches resources of type `your-favorite-type` which
can either come from `get_resource` or `get_alt_resource`
"""
def resource_wrapper(a) do
case get_resource(a) do
ok = {:ok, a} -> ok
{:error, :recoverable} ->
get_alt_resource(a)
# note with this code if alt_resource
# gives you an error tuple you are still
# good to go
err = {:error, :fatal} -> err
end
end