Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question: Custom middleware for unwrapping responses #483

Closed
nmbrone opened this issue Jul 20, 2021 Discussed in #474 · 1 comment
Closed

Question: Custom middleware for unwrapping responses #483

nmbrone opened this issue Jul 20, 2021 Discussed in #474 · 1 comment
Labels

Comments

@nmbrone
Copy link

nmbrone commented Jul 20, 2021

Hi there 👋.

First of all many thanks for a great library.

I like to have a common response handler that does the response status check and unwraps the body.

It can be easily achieved by implementing a middleware:

defmodule HandleResponse do
  @behaviour Tesla.Middleware

  @impl true
  def call(env, next, _opts) do
    env
    |> Tesla.run(next)
    |> case do
      {:ok, %Tesla.Env{status: status, body: body}} when status in 200..299 ->
        {:ok, body}

      {:ok, %Tesla.Env{body: body}} ->
        {:error, body}

      other ->
        other
    end
  end
end

Obviously, it breaks the middleware contract since it returns {:ok, any()} instead of {:ok, %Tesla.Env{}}. But as long as you keep such middleware at the tail of the response handlers chain it works perfectly fine and I'm okay with it.

The problem is that Dyalizer is not okay with it.

defmodule Client do
  use Tesla

  plug HandleResponse
  # ... plug other middleware
end

{:ok, %{"json" => "here"}} = Client.get("url") # dialyzer gets crazy

Tesla already supports docs: false option. Maybe we could add specs: false option to tell
Tesla.Builder to not inject @specs for get, post etc. functions?

Or maybe someone has a better idea of how to solve it?

@teamon
Copy link
Member

teamon commented Jul 26, 2021

Middleware must be composable and conform to a single interface, which is (%Tesla.Env{}, stack, opts) -> {:ok, %Tesla.Env{}} | {:error, any}.

If you want to remove some code duplication, I recommend doing something similar to this:

defmodule MyAPI.Success do
  def call(env, next, _opts) do
    case Tesla.run(env, next) do
      {:ok, %{status: 200, body: %{"success" => true, "response" => response}} = env} ->
        {:ok, %{env | body: response}}

      {:ok, %{status: 200, body: %{"success" => false, "error" => error}}} ->
        {:error, error}

      {:ok, env} ->
        {:error, env}

      {:error, error} ->
        {:error, error}
    end
  end
end

then in client

  def list_things(client \\ new()) do
    with {:ok, %{body: plans}} <- Tesla.get(client, "/things") do
      {:ok, plans}
    end
  end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants