# Erro genérico

Quando nos conectamos a um serviço externo, abrimos uma brecha em nossas defesas. Podemos cair em cenários que não esperamos, como algum erro não mapeado ou até mesmo, o serviço parar de responder. Quando isso acontece, precisamos avisar nosso usuário elegantemente que algo deu errado.&#x20;

Vamos a um exemplo prático. Nosso tratamento esta assim

{% code title="lib/coffee\_shop/integrations/coffee/response.ex" lineNumbers="true" %}

```elixir
defmodule CoffeeShop.Integrations.Coffee.Response do
  defstruct status: :integer, body: %{}

  def build({:ok, %Tesla.Env{status: status, body: body}}) do
    response = %__MODULE__{
      status: status,
      body: JSON.decode!(body)
    }

    {:ok, response}
  end
end
```

{% endcode %}

Agora, vamos pensar. Caso algo de errado, ele vai entrar em nosso `build/1`, e fazer o `JSON.decode!/1`. Mas, e se o body, quando der erro, vir em um formato diferente? O que vai acontecer. Vamos atualizar nosso teste:

{% code title="test/coffee\_shop/integrations/coffee/response\_test.exs" lineNumbers="true" %}

```elixir
defmodule CoffeeShop.Integrations.Coffee.ResponseTest do
  use ExUnit.Case

  alias CoffeeShop.Integrations.Coffee.Response

  describe "build/1" do
    # ...

    test "build an 500 error" do
      tesla_response = {:ok, %Tesla.Env{status: 500, body: "Server exploded"}}
      
      assert {:ok, %Response{body: _body}} = Response.build(tesla_response)
    end
  end
end
```

{% endcode %}

Criamos um teste simples com uma resposta em formato fora do padrão. Vamos rodar esse teste.

```
mix test 
```

<pre class="language-elixir"><code class="lang-elixir">> mix test 
1) test build/1 build an 500 error (CoffeeShop.Integrations.Coffee.ResponseTest)
     test/coffee_shop/integrations/coffee/response_test.exs:19
<strong>     ** (JSON.Decoder.UnexpectedTokenError) Invalid JSON - unexpected token >>Server exploded&#x3C;&#x3C;
</strong>     code: assert {:ok, %Response{body: _body}} = Response.build(tesla_response)
     stacktrace:
       (json 1.4.1) lib/json.ex:83: JSON.decode!/1
       (coffee_shop 0.1.0) lib/coffee_shop/integrations/coffee/response.ex:7: CoffeeShop.Integrations.Cof
fee.Response.build/1
       test/coffee_shop/integrations/coffee/response_test.exs:24: (test)


Finished in 0.05 seconds (0.00s async, 0.05s sync)
2 tests, 1 failure, 1 excluded

</code></pre>

Recebemos um erro de `unexpected token`. Isso ocorre porque o JSON.decode!/1 nao conseguir fazer o parse de "Server exploded". Vamos tentar fazer na mao

```
iex -S mix
```

```elixir
JSON.decode!("Server exploded")
```

```elixir
** (JSON.Decoder.UnexpectedTokenError) Invalid JSON - unexpected token >>Server exploded<<
```

Precisamos de uma estrutura em JSON, mas nosso serviço não trás esse formato para nós. Temos várias formas de lidar com isso. A mais simples é responder um erro genérico quando receber um status 500. Ou melhor, quando não receber o status esperado. Isso quer dizer, tudo o que não for esperado, vai cair no erro genérico, apenas para não gerar um erro incompreenssivel ou feio para o usuário.

{% hint style="info" %}
Aqui é um bom local para mapearmos erros não conhecidos e ai sim, implementar uma solução para eles.&#x20;
{% endhint %}

Entendido isso, vamos melhorar nosso teste. Nele precisamos saber que&#x20;

* Um erro ocorreu;
* &#x20;Mensagem para avisarmos o usuário.

{% code title="test/coffee\_shop/integrations/coffee/response\_test.exs" lineNumbers="true" %}

```elixir
defmodule CoffeeShop.Integrations.Coffee.ResponseTest do
  use ExUnit.Case

  alias CoffeeShop.Integrations.Coffee.Response

  describe "build/1" do
    # ...

    test "build an 500 error" do
      tesla_response = {:ok, %Tesla.Env{status: 500, body: "Server exploded"}}
      
      assert {:error, %Response{body: _body, error: error}} = Response.build(tesla_response)
      assert error == "Não foi possível se conectar ao serviço de cafés. Tento novamente mais tarde"
    end
  end
end
```

{% endcode %}

No teste alteramos a assertion da resposta, onde esperamos agora um `{:error, %Response{}}` para refletir que um erro aconteceu. Tambem adicionamos o error sendo a mensagem que ele retorna.

Vamos em nosso `response.ex` atualizar a construção de nossa resposta

<pre class="language-elixir" data-title="lib/coffee_shop/integrations/coffee/response.ex" data-line-numbers><code class="lang-elixir">defmodule CoffeeShop.Integrations.Coffee.Response do
  defstruct status: :integer, body: :map

<strong>  @success_status [200]
</strong>
  def build({:ok, %Tesla.Env{status: status, body: body}})
<strong>      when status in @success_status do
</strong>    response = %__MODULE__{
      status: status,
      body: JSON.decode!(body)
    }

    {:ok, response}
  end
end
</code></pre>

Adicionamos o *status* que podem realizar o `build/1`. Isso quer dizer, caso um *status* nao esteja no `@success_status`, ele não entrará na função. &#x20;

Feito isso, precisamos criar uma função de mesmo nome para capturar todos os cenários restantes.

<pre class="language-elixir" data-title="lib/coffee_shop/integrations/coffee/response.ex" data-line-numbers><code class="lang-elixir">defmodule CoffeeShop.Integrations.Coffee.Response do
  defstruct status: :integer, body: :map, error: :string

<strong>  @success_status [200]
</strong>
  def build({:ok, %Tesla.Env{status: status, body: body}})
<strong>      when status in @success_status do
</strong>    response = %__MODULE__{
      status: status,
      body: JSON.decode!(body)
    }

    {:ok, response}
  end

<strong>  def build({:ok, %Tesla.Env{status: status, body: body}}) do
</strong><strong>    message = "Não foi possível se conectar ao serviço de cafés. Tento novamente mais tarde"
</strong><strong>
</strong><strong>    response = %__MODULE__{
</strong><strong>      status: status,
</strong><strong>      error: message
</strong><strong>    }
</strong><strong>
</strong><strong>    {:error, response}
</strong><strong>  end
</strong>end
</code></pre>

Adicionamos um *guard clause* na primeira função, liberando apenas para `status 200`. Todo o restante cairá na segunda função e irá gerar um erro genérico da integração. Tabém adicionamos uma mensagem padrão e retornamos ao invés de `:ok`, um `:error` para refletir que algo deu errado.

```
mix test test/coffee_shop/integrations/coffee/response_test.exs
```

```elixir
mix test test/coffee_shop/integrations/coffee/response_test.exs
..
Finished in 0.6 seconds (0.00s async, 0.6s sync)
1 doctest, 4 tests, 0 failures
```

Criamos um mecanismo simples de controle de status, onde podemos gerar N cenários para a resposta que vem do nosso serviço e tratar da melhor forma que quisermos.

{% hint style="warning" %}
Podemos criar outras funções com outras guard clauses para isolar melhor os tipos de resposta.
{% endhint %}

Ao rodar todos os testes, teremos uma quebra no client\_test.exs, isso devido a resposta mudar para uma tupla com :error. Basta altera-la e tudo volta a funciona.

<pre class="language-elixir" data-title="test/coffee_shop/integrations/coffee/client_test.exs" data-line-numbers><code class="lang-elixir">defmodule CoffeeShop.Integrations.Coffee.ClientTest do
  use ExUnit.Case

  alias CoffeeShop.Integrations.Coffee.Response
  alias CoffeeShop.Integrations.Coffee.Client

  describe "all_hot_coffees/0" do
    # ...
    test "service is crashed", %{bypass: bypass} do
      response = %{message: "Server exploded"} |> JSON.encode!()

      Bypass.expect(bypass, "GET", "/coffee/hot", fn conn ->
        Plug.Conn.resp(conn, 500, response)
      end)

      opts = [
        base_url: "http://localhost:3000"
      ]

<strong>      assert {:error, %Response{status: status, body: _body}} = Client.all_hot_coffees(opts)
</strong>      assert status == 500
    end
  end
end
</code></pre>

Feito. Agora temos outro problema para lidar.&#x20;


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://consumindo-apis-com-elixir.cafecomelixir.com.br/problemas-de-api-externa/erro-generico.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
