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.
Vamos a um exemplo prático. Nosso tratamento esta assim
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
endAgora, 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:
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
endCriamos um teste simples com uma resposta em formato fora do padrão. Vamos rodar esse teste.
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
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.
Entendido isso, vamos melhorar nosso teste. Nele precisamos saber que
Um erro ocorreu;
Mensagem para avisarmos o usuário.
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
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.
Feito isso, precisamos criar uma função de mesmo nome para capturar todos os cenários restantes.
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.
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.
Podemos criar outras funções com outras guard clauses para isolar melhor os tipos de resposta.
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.
Feito. Agora temos outro problema para lidar.
Atualizado
Isto foi útil?