Tratamento de erros do SDK Admin

Os erros do SDK Admin são divididos em duas categorias:

  1. Erros de programação: São erros de programação e configuração na aplicação do usuário. Eles ocorrem principalmente devido ao uso incorreto do SDK (como passar null para um método que não aceita valores null ) e outros erros de configuração no projeto do Firebase ou no nível do SDK (credenciais ausentes, string de ID do projeto incorreta etc.) sobre).
  2. Erros de API: incluem vários erros recuperáveis ​​que ocorrem na implementação do SDK, todos os erros originados nos serviços de back-end do Firebase e outros erros transitórios (como tempos limite) que podem ocorrer ao fazer chamadas RPC.

O SDK Admin sinaliza erros de programação gerando um erro nativo da plataforma em questão.

  • Java: lança instâncias de IllegalArgumentException , NullPointerException ou tipo de erro de tempo de execução interno semelhante.
  • Python: gera instâncias de ValueError , TypeError ou outro tipo de erro interno.
  • Go: Retorna um erro genérico.
  • .NET: lança instâncias de ArgumentException , ArgumentNullException ou tipo de erro interno semelhante.

Na maioria das situações, você não deve tratar explicitamente os erros de programação. Em vez disso, você deve corrigir seu código e configuração para evitar erros de programação. Considere o seguinte trecho de Java:

String uid = getUserInput();

UserRecord user = FirebaseAuth.getInstance().getUser(uid);

Se o método getUserInput() retornar strings null ou vazias, a API FirebaseAuth.getUser() gerará uma IllegalArgumentException . Em vez de manipulá-lo explicitamente, você pode atenuar o problema garantindo que o método getUserInput() nunca retorne uma string UID inválida. Se isso não for possível, implemente a verificação de argumentos necessária em seu próprio código da seguinte maneira:

String uid = getUserInput();
if (Strings.isNullOrEmpty(uid)) {
    log.warn("UID must not be null or empty");
    return;
}

UserRecord user = FirebaseAuth.getInstance().getUser(uid);

Como princípio, nunca tente novamente erros de programação. Permitir uma semântica rápida em erros de programação geralmente é o melhor curso de ação porque expõe bugs de programação e erros de configuração durante o desenvolvimento, onde eles podem ser corrigidos prontamente. Fail-fast nesse contexto pode significar permitir que os erros se propaguem para um manipulador de erros global em seu aplicativo ou apenas registrá-los para fins de auditoria seguidos pelo encerramento do fluxo de execução atual (o aplicativo não deve ter que travar). Em geral, siga as práticas recomendadas de tratamento de erros de sua linguagem de programação e da estrutura do aplicativo. Isso por si só muitas vezes é suficiente para lidar corretamente com essa classe de erros.

Normalmente, a maior parte de seus esforços de tratamento de erros se concentrará no tratamento de erros de API . Alguns desses erros são recuperáveis, como erros que resultaram de um serviço temporariamente indisponível, e alguns são até mesmo antecipados durante o fluxo normal de execução do programa, como detectar tokens de ID inválidos ou expirados. O restante deste guia descreve como o SDK Admin representa esses erros de API e as várias opções disponíveis para lidar com eles.

Estrutura de um erro de API

Um erro de API consiste nos seguintes componentes:

  1. Erro de código
  2. Mensagem de erro
  3. Código de erro do serviço (opcional)
  4. Resposta HTTP (opcional)

Cada erro de API contém um código de erro e uma mensagem de erro. Certos erros de API também contêm um código de erro de serviço específico para a API que gerou o erro. Por exemplo, alguns erros gerados pela API Firebase Auth contêm um código de erro de serviço específico do Firebase Auth. Se o erro for o resultado de uma resposta de erro HTTP de um serviço de back-end, o erro da API também conterá a resposta HTTP correspondente. Isso pode ser usado para inspecionar os cabeçalhos e o conteúdo exatos da resposta original, o que é útil para depurar, registrar em log ou implementar uma lógica de tratamento de erros mais sofisticada.

Todas as implementações do SDK Admin, exceto Node.js, fornecem APIs que permitem acessar os componentes de erros de API acima.

Tipos de erro e APIs por idioma

Java

Em Java, todos os erros de API estendem a classe FirebaseException . Você pode acessar o código de erro, a mensagem de erro e a resposta HTTP opcional desta classe base.

public class FirebaseException extends Exception {
    @NonNull
    public ErrorCode getErrorCode() {
        // ...
    }

    @NonNull
    public String getMessage() {
        // ...
    }

    @Nullable
    public IncomingHttpResponse getHttpResponse() {
        // ...
    }
}

As APIs que expõem códigos de erro de serviço fornecem subclasses específicas de API de FirebaseException . Por exemplo, todos os métodos públicos na API FirebaseAuth são declarados para lançar instâncias de FirebaseAuthException . Você pode acessar o código de erro de serviço dessa classe derivada.

public class FirebaseAuthException extends FirebaseException {
    @Nullable
    public AuthErrorCode getAuthErrorCode() {
        // ...
    }
}

Pitão

Em Python todos os erros de API estendem a classe exceptions.FirebaseError . Você pode acessar o código de erro, a mensagem de erro e a resposta HTTP opcional desta classe base.

class FirebaseError(Exception):
    @property
    def code(self):
          # ...

    @property
    def message(self):
          # ...

    @property
    def http_response(self):
          # ...

Além disso, o SDK Admin do Python oferece classes derivadas separadas para cada código de erro. Nós nos referimos a eles como classes de erro de plataforma .

class InvalidArgumentError(FirebaseError):
    # ...

class NotFoundError(FirebaseError):
    # ...

Você pode capturar FirebaseError em seu código e verificar o code dele ou realizar uma verificação isinstance() em uma classe de erro de plataforma. Ou você pode escrever código para capturar diretamente tipos de erro de plataforma específicos. A última abordagem provavelmente resultará em um código de tratamento de erros mais legível.

APIs que expõem códigos de erro de serviço fornecem subclasses específicas de API de classes de erro de plataforma. Por exemplo, todos os métodos públicos no módulo auth podem gerar tipos de erro específicos da API, como auth.UserNotFoundError e auth.ExpiredIdTokenError .

class UserNotFoundError(exceptions.NotFoundError):
    # …

class ExpiredIdTokenError(exceptions.InvalidArgumentError):
    # ...

Vai

O Go Admin SDK fornece um pacote errorutils que contém uma série de funções que permitem testar códigos de erro.

package errorutils

func IsInvalidArgument(err error) bool {
    // ...
}

func IsNotFound(err error) bool {
    // ...
}

A mensagem de erro é simplesmente a string retornada pela função Error() de um erro. A resposta HTTP opcional pode ser acessada chamando a função errorutils.HTTPResponse() , que retorna um *http.Response .

É seguro passar nil ou qualquer outro valor de erro para as funções de verificação de erros no pacote errorutils . Eles retornam true se o argumento de entrada realmente contiver o código de erro em questão e retornam false para todo o resto. A função HTTPResponse() tem um comportamento semelhante, exceto que retorna nil em vez de false .

As APIs que expõem códigos de erro de serviço fornecem funções de verificação de erros específicas da API nos pacotes correspondentes. Por exemplo, o pacote auth fornece as funções IsUserNotFound() e IsExpiredIDTokenError() .

.INTERNET

No .NET, todos os erros de API estendem a classe FirebaseException . Você pode acessar o código de erro da plataforma, a mensagem de erro e a resposta HTTP opcional desta classe base.

public class FirebaseException : Exception {

    public ErrorCode ErrorCode { get; }

    public String Message { get; }

    public HttpResponseMessage HttpResponse { get; }
}

As APIs que expõem códigos de erro de serviço fornecem subclasses específicas de API de FirebaseException . Por exemplo, todos os métodos públicos na API FirebaseAuth são declarados para lançar instâncias de FirebaseAuthException . Você pode acessar o código de erro de serviço dessa classe derivada.

public class FirebaseAuthException : FirebaseException {

    public AuthErrorCode AuthErrorCode { get; }
}

Códigos de erro da plataforma

Os códigos de erro são comuns em todos os serviços do Firebase e do Google Cloud Platform. A tabela a seguir descreve todos os códigos de erro de plataforma possíveis. Esta é uma lista estável e espera-se que permaneça inalterada por um longo período.

ARGUMENTO INVÁLIDO O cliente especificou um argumento inválido.
FAILED_PRECONDITION A solicitação não pode ser executada no estado atual do sistema, como excluir um diretório não vazio.
FORA DE ALCANCE O cliente especificou um intervalo inválido.
NÃO AUTENTICADO Solicitação não autenticada devido a token OAuth ausente, inválido ou expirado.
PERMISSÃO NEGADA O cliente não tem permissão suficiente. Isso pode acontecer porque o token OAuth não tem os escopos corretos, o cliente não tem permissão ou a API não foi habilitada para o projeto do cliente.
NÃO ENCONTRADO O recurso especificado não foi encontrado ou a solicitação foi rejeitada devido a motivos não divulgados, como lista de permissões.
CONFLITO Conflito de simultaneidade, como conflito de leitura-modificação-gravação. Usado apenas por alguns serviços legados. A maioria dos serviços usa ABORTED ou ALREADY_EXISTS em vez disso. Consulte a documentação específica do serviço para ver qual deles manipular em seu código.
ABORTADO Conflito de simultaneidade, como conflito de leitura-modificação-gravação.
JÁ EXISTE O recurso que um cliente tentou criar já existe.
RESOURCE_EXHAUSTED Fora da cota de recursos ou atingindo o limite de taxa.
CANCELADO Pedido cancelado pelo cliente.
DATA_LOSS Perda de dados irrecuperável ou corrupção de dados. O cliente deve relatar o erro ao usuário.
DESCONHECIDO Erro de servidor desconhecido. Normalmente um bug do servidor.

Esse código de erro também é atribuído a erros de análise de resposta local (unmarshal) e a uma ampla variedade de outros erros de E/S de baixo nível que não são facilmente diagnosticáveis.

INTERNO Erro do Servidor Interno. Normalmente um bug do servidor.
INDISPONÍVEL Serviço indisponível. Normalmente, o servidor está temporariamente inativo.

Este código de erro também é atribuído a erros de rede local (conexão recusada, sem rota para o host).

DEADLINE_EXCEEDED Prazo de solicitação excedido. Isso acontecerá somente se o chamador definir um prazo menor que o prazo padrão da API de destino (ou seja, o prazo solicitado não é suficiente para o servidor processar a solicitação) e a solicitação não foi concluída dentro do prazo.

Esse código de erro também é atribuído à conexão local e aos tempos limite de leitura.

A maioria das APIs só pode resultar em um subconjunto dos códigos de erro acima. De qualquer forma, não se espera que você manipule explicitamente todos esses códigos de erro ao implementar seus manipuladores de erro. A maioria dos aplicativos só estaria interessada em 1-2 códigos de erro específicos e trataria todo o resto como uma falha genérica e irrecuperável.

Códigos de erro específicos do serviço

Autenticação do Firebase

CERTIFICATE_FETCH_FAILED Falha ao buscar certificados de chave pública necessários para verificar um JWT (token de ID ou cookie de sessão).
E-MAIL JÁ EXISTE Já existe um usuário com o email fornecido.
EXPIRED_ID_TOKEN O token de ID especificado para verifyIdToken() expirou.
EXPIRED_SESSION_COOKIE O cookie de sessão especificado para verifySessionCookie() expirou.
INVALID_DYNAMIC_LINK_DOMAIN O domínio de link dinâmico fornecido não está configurado ou autorizado para o projeto atual. Relacionado a APIs de link de ação de e-mail.
INVALID_ID_TOKEN O token de ID especificado para verifyIdToken() é inválido.
INVALID_SESSION_COOKIE O cookie de sessão especificado para verifySessionCookie() é inválido.
PHONE_NUMBER_ALREADY_EXISTS Já existe um usuário com o número de telefone fornecido.
REVOKED_ID_TOKEN O token de ID especificado para verifyIdToken() foi revogado.
REVOKED_SESSION_COOKIE O cookie de sessão especificado para verifySessionCookie() expirou.
UNAUTHORIZED_CONTINUE_URL O domínio do URL de continuação não está na lista de permissões. Relacionado a APIs de link de ação de e-mail.
USUÁRIO NÃO ENCONTRADO Nenhum registro de usuário encontrado para o identificador fornecido.

Firebase Cloud Messaging

THIRD_PARTY_AUTH_ERROR O certificado de APNs ou a chave de API de autenticação de push da Web eram inválidos ou ausentes.
ARGUMENTO INVÁLIDO Um ou mais argumentos especificados na solicitação eram inválidos.
INTERNO Erro do Servidor Interno.
COTA EXCEDIDA Limite de envio excedido para o destino da mensagem.
SENDER_ID_MISMATCH O ID do remetente autenticado é diferente do ID do remetente do token de registro. Isso geralmente significa que o remetente e o token de registro de destino não estão no mesmo projeto do Firebase.
INDISPONÍVEL O serviço Cloud Messaging está temporariamente indisponível.
NÃO REGISTRADO A instância do aplicativo não foi registrada no FCM. Isso geralmente significa que o token de registro do dispositivo usado não é mais válido e um novo deve ser usado.

Novas tentativas automáticas

O SDK Admin repete automaticamente determinados erros antes de expô-los aos usuários. Em geral, os seguintes tipos de erros são repetidos de forma transparente:

  • Todos os erros de API resultantes de respostas HTTP 503 (Serviço indisponível).
  • Alguns erros de API resultantes de respostas HTTP 500 (Internal Server Error).
  • A maioria dos erros de E/S de baixo nível (conexão recusada, redefinição de conexão, etc.).

O SDK tentará novamente cada um dos erros acima até 5 vezes (a tentativa original + 4 tentativas) com retirada exponencial. Você pode implementar seus próprios mecanismos de repetição no nível do aplicativo, se desejar, mas isso normalmente não é necessário.

Suporte Repetir-Depois

As implementações Go e .NET do SDK Admin vêm com suporte para manipular o cabeçalho HTTP Retry-After . Ou seja, se a resposta de erro enviada pelos servidores de back-end contiver o cabeçalho Retry-After padrão, o SDK respeitará isso ao tentar novamente, desde que a duração de espera especificada não seja muito longa. Se o cabeçalho Retry-After indicar uma duração de espera muito longa, o SDK ignorará as tentativas e lançará o erro de API apropriado.

Atualmente, o SDK Admin do Python não oferece suporte ao cabeçalho Retry-After e oferece suporte apenas à retirada exponencial simples.

Exemplos de tratamento de erros da API

Implementando um manipulador de erros genérico

Na maioria dos casos, o que você deseja é um manipulador de erros genérico que detecte uma ampla variedade de erros para evitar o encerramento inesperado do fluxo do programa devido a um erro de API. Esses manipuladores de erros geralmente apenas registram os erros para fins de auditoria ou invocam alguma outra rotina de tratamento de erros padrão para todos os erros de API encontrados. Eles não estão necessariamente interessados ​​nos diferentes códigos de erro ou nos motivos que podem ter causado o erro.

Java

try {
  FirebaseToken token = FirebaseAuth.getInstance().verifyIdToken(idToken);
  performPrivilegedOperation(token.getUid());
} catch (FirebaseAuthException ex) {
  System.err.println("Failed to verify ID token: " + ex.getMessage());
}

Pitão

try:
  token = auth.verify_id_token(idToken)
  perform_privileged_pperation(token.uid)
except exceptions.FirebaseError as ex:
  print(f'Failed to verify ID token: {ex}')

Vai

token, err := client.VerifyIDToken(ctx, idToken)
if err != nil {
  log.Printf("Failed to verify ID token: %v", err)
  return
}

performPrivilegedOperation(token)

.Internet

try
{
  var token = await FirebaseAuth.DefaultInstance.VerifyIdTokenAsync(idToken);
  PerformPrivilegedOperation(token.getUid());
}
catch (FirebaseAuthException ex) 
{
  Conole.WriteLine($"Failed to verify ID token: {ex.Message}");
}

Verificando códigos de erro

Em alguns casos, você deseja inspecionar os códigos de erro exatos e invocar diferentes rotinas de tratamento de erros com reconhecimento de contexto. No exemplo a seguir, temos um manipulador de erros que registra mensagens de erro mais específicas com base no código de erro do serviço.

Java

try {
  FirebaseToken token = FirebaseAuth.getInstance().verifyIdToken(idToken);
  performPrivilegedOperation(token.getUid());
} catch (FirebaseAuthException ex) {
  if (ex.getAuthErrorCode() == AuthErrorCode.ID_TOKEN_EXPIRED) {
    System.err.println("ID token has expired");
  } else if (ex.getAuthErrorCode() == AuthErrorCode.ID_TOKEN_INVALID) {
    System.err.println("ID token is malformed or invalid");
  } else {
    System.err.println("Failed to verify ID token: " + ex.getMessage());
  }
}

Pitão

try:
  token = auth.verify_id_token(idToken)
  perform_privileged_operation(token.uid)
except auth.ExpiredIdTokenError:
  print('ID token has expired')
except auth.InvalidIdTokenError:
  print('ID token is malformed or invalid')
except exceptions.FirebaseError as ex:
  print(f'Failed to verify ID token: {ex}')

Vai

token, err := client.VerifyIDToken(ctx, idToken)
if auth.IsIDTokenExpired(err) {
  log.Print("ID token has expired")
  return
}
if auth.IsIDTokenInvalid(err) {
  log.Print("ID token is malformed or invalid")
  return
}
if err != nil {
  log.Printf("Failed to verify ID token: %v", err)
  return
}

performPrivilegedOperation(token)

.Internet

try
{
  var token = await FirebaseAuth.DefaultInstance.VerifyIdTokenAsync(idToken);
  PerformPrivilegedOperation(token.getUid());
}
catch (FirebaseAuthException ex)
{
  if (ex.AuthErrorCode == AuthErrorCode.ExpiredIdToken)
  {
    Console.WriteLine("ID token has expired");
  }
  else if (ex.AuthErrorCode == AuthErrorCode.InvalidIdToken)
  {
    Console.WriteLine("ID token is malformed or invalid");
  }
  else
  {
    Conole.WriteLine($"Failed to verify ID token: {ex.Message}");
  }
}

Aqui está outro exemplo em que verificamos os códigos de erro de nível superior e de serviço:

Java

try {
  FirebaseMessaging.getInstance().send(createMyMessage());
} catch (FirebaseMessagingException ex){
  if (ex.getMessagingErrorCode() == MessagingErrorCode.UNREGISTERED) {
    System.err.println("App instance has been unregistered");
    removeTokenFromDatabase();
  } else if (ex.getErrorCode() == ErrorCode.Unavailable) {
    System.err.println("FCM service is temporarily unavailable");
    scheduleForRetryInAnHour();
  } else {
    System.err.println("Failed to send notification: " + ex.getMessage());
  }
}

Pitão

try:
  messaging.send(create_my_message())
except messaging.UnregisteredError:
  print('App instance has been unregistered')
  remove_token_from_database()
except exceptions.UnavailableError:
  print('FCM service is temporarily unavailable')
  schedule_for_retry_in_an_hour()
except exceptions.FirebaseError as ex:
  print(f'Failed to send notification: {ex}')

Vai

_, err := client.Send(ctx, createMyMessage())
if messaging.IsUnregistered(err) {
  log.Print("App instance has been unregistered")
  removeTokenFromDatabase()
  return
}
if errorutils.IsUnavailable(err) {
  log.Print("FCM service is temporarily unavailable")
  scheduleForRetryInAnHour()
  return
}
if err != nil {
  log.Printf("Failed to send notification: %v", err)
  return
}

.Internet

try
{
  await FirebaseMessaging.DefaultInstance.SendAsync(createMyMessage());
}
catch (FirebaseMessagingException ex)
{
  if (ex.MessagingErrorCode == MessagingErrorCode.UNREGISTERED)
  {
    Console.WriteLine("App instance has been unregistered");
    removeTokenFromDatabase();
  }
  else if (ex.ErrorCode == ErrorCode.Unavailable)
  {
    Console.WriteLine("FCM service is temporarily unavailable");
    scheduleForRetryInAnHour();
  }
  else
  {
    Console.WriteLine($"Failed to send notification: {ex.Message}");
  }
}

Acessando a resposta HTTP

Em alguns casos raros, você pode querer inspecionar a resposta de erro HTTP retornada por um serviço de back-end e realizar alguma ação de tratamento de erros nela. O SDK Admin expõe os cabeçalhos e o conteúdo dessas respostas de erro. O conteúdo da resposta geralmente é retornado como uma string ou uma sequência de bytes bruta e pode ser analisado em qualquer formato de destino necessário.

Java

try {
  FirebaseMessaging.getInstance().send(createMyMessage());
} catch (FirebaseMessagingException ex){
  IncomingHttpResponse response = ex.getHttpResponse();
  if (response != null) {
    System.err.println("FCM service responded with HTTP " + response.getStatusCode());

    Map<String, Object> headers = response.getHeaders();
    for (Map.Entry<String, Object> entry : headers.entrySet()) {
      System.err.println(">>> " + entry.getKey() + ": " + entry.getValue());
    }

    System.err.println(">>>");
    System.err.println(">>> " + response.getContent());
  }
}

Pitão

try:
  messaging.send(create_my_message())
except exceptions.FirebaseError as ex:
  response = ex.http_response
  if response is not None:
    print(f'FCM service responded with HTTP {response.status_code}')

    for key, value in response.headers.items():
      print(f'>>> {key}: {value}')

    print('>>>')
    print(f'>>> {response.content}')

Vai

_, err := client.Send(ctx, createMyMessage())
if resp := errorutils.HTTPResponse(err); resp != nil {
  log.Printf("FCM service responded with HTTP %d", resp.StatusCode)

  for key, value := range resp.Header {
      log.Printf(">>> %s: %v", key, value)
  }

  defer resp.Body.Close()
  b, _ := ioutil.ReadAll(resp.Body)
  log.Print(">>>")
  log.Printf(">>> %s", string(b))

  return
}

.Internet

try
{
  await FirebaseMessaging.DefaultInstance.SendAsync(createMyMessage());
}
catch (FirebaseMessagingException ex)
{
  var response = ex.HttpResponse
  if response != null
  {
    Console.WriteLine($"FCM service responded with HTTP { response.StatusCode}");

    var headers = response.Headers;
    for (var entry in response.Headers)
    {
      Console.WriteLine($">>> {entry.Key}: {entry.Value}");
    }

    var body = await response.Content.ReadAsString();
    Console.WriteLine(">>>");
    Console.WriteLine($">>> {body}");
  }
}