With/1: else matching condition

I’m trying to employ with/1 to handle a paginated HTTP response from a server.

If the results are paginated, I want to return the tuple {:ok, next_page, response}. If they are not paginated, or there are no more pages, I want to return the tuple {:ok, response}.

I want to try out with/1 for this. However, I’m a bit unclear about how to handle the else matching condition properly (the ?? in my code below). On the first expression, I could get a value of nil if the response is not paginated; on the second expression I could get %{"next_page" => nil}. Do I need both these conditions in the else part?

  def handle_response(%{body: body, headers: headers}) do
    with {_key, pagination} <- Enum.find(headers, fn {k, _v} -> k == "X-Pagination" end),
         %{"next_page" => next_page} when not is_nil(next_page) <- pagination |> Poison.decode! |> Map.take(["next_page"]) do
      {:ok, next_page, decode_response(body)}
    else
      ?? ->
      {:ok, decode_response(body)}
    end
  end
1 Like

Yes, you need to handle all possible return values.

I don’t think this a good usecase for with.

To me, with is about splitting a sequence of transformations (the right side of your <- with clauses) into a “happy path” of assertions (the matches on the left side of your <- with clauses) with a consistent “success case” where you know you have good reliable actionable stuff (the contents of your do block), and an “unhappy path” where you handle any failed assertions (your else block) by converting things that didn’t fit the happy path matches (the left side of your -> else clauses) into reliable actionable error stuff (the right side of your -> else clauses).

It’s an incredible dense syntax but great when you need it. In my mind, they should only ever return two shapes: :ok tuples when either the pipeline finished or it was aborted in a manageable way, or :error tuples when the pipeline couldn’t recover but you want to normalize the failure into a single shape that can be used for understanding what failed in a way that is agnostic to the potential failure points of the pipeline.

You’re simply trying to define two different success paths, one when there is a next page and one where there isn’t. A conditional should suffice.

1 Like

If you don’t want to handle failure cases differently, you can always just use _ IE

with # stuff do
  {:ok, next_page, decode_response(body)}
else
  _ ->
    {:ok, decode_response(body)}
end

Thanks for color!