# 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;
