Tratamento de erros do SDK Admin

Os erros do Admin SDK 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 e assim por diante). 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 Admin SDK 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 integrado semelhante.
  • Python: gera instâncias de ValueError , TypeError ou outro tipo de erro integrado.
  • 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 lidar explicitamente com 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 Java:

String uid = getUserInput();

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

Se o método getUserInput() retornar strings null ou vazias, a API FirebaseAuth.getUser() lançará uma IllegalArgumentException . Em vez de lidar com isso 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 costuma ser o melhor curso de ação porque expõe bugs de programação e erros de configuração durante o desenvolvimento, onde podem ser corrigidos imediatamente. Fail-fast neste 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 seguido pelo encerramento do fluxo de execução atual (o aplicativo não deve travar). Em geral, siga as práticas recomendadas de tratamento de erros da sua linguagem de programação e da estrutura do aplicativo. Muitas vezes, isso por si só é suficiente para lidar corretamente com essa classe de erros.

Normalmente, a maior parte dos seus esforços de tratamento de erros se concentrará no tratamento de erros de API . Alguns desses erros são recuperáveis, como erros resultantes de um serviço temporariamente indisponível, e alguns são até antecipados durante o fluxo normal de execução do programa, como a detecção de tokens de ID inválidos ou expirados. O restante deste guia descreve como o Admin SDK representa esses erros de API e as diversas 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 de serviço (opcional)
  4. Resposta HTTP (opcional)

É garantido que cada erro de API contenha 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 da 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 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 conteúdos exatos da resposta original, o que é útil para depuração, registro ou implementação de uma lógica de tratamento de erros mais sofisticada.

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

Tipos de erros 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() {
        // ...
    }
}

APIs que expõem códigos de erro de serviço fornecem subclasses de FirebaseException específicas da API. 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 do serviço nesta 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 Python Admin SDK 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 seu code ou executar uma verificação isinstance() em uma classe de erro de plataforma. Ou você pode escrever código para capturar diretamente tipos de erros específicos da plataforma. 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 lançar tipos de erros específicos da API, como auth.UserNotFoundError e auth.ExpiredIdTokenError .

class UserNotFoundError(exceptions.NotFoundError):
    # …

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

Ir

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 contém o código de erro em questão e retornam false para todo o resto. A função HTTPResponse() tem comportamento semelhante, exceto que retorna nil em vez de false .

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() .

.LÍQUIDO

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

APIs que expõem códigos de erro de serviço fornecem subclasses de FirebaseException específicas da API. 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 do serviço nesta 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 a exclusão de 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 possui 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 tratar 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.
RECURSO_ESGOTADO Fora da cota de recursos ou atingindo a limitação de taxa.
CANCELADO Solicitação cancelada pelo cliente.
DATA_LOSS Perda irrecuperável de dados 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 (desempacotamento) e a uma ampla gama 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 não disponí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á apenas 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 for concluída dentro do prazo.

Este 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ê lide explicitamente com todos esses códigos de erro ao implementar seus manipuladores de erros. A maioria dos aplicativos estaria interessada apenas em 1 ou 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 e-mail 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 às 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 às APIs de link de ação de e-mail.
USUÁRIO NÃO ENCONTRADO Nenhum registro de usuário encontrado para o identificador fornecido.

Mensagens na nuvem do Firebase

THIRD_PARTY_AUTH_ERROR O certificado de APNs ou a chave de API de autenticação Web Push 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 O registro da instância do aplicativo foi cancelado 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 Admin SDK tenta 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 (erro interno do servidor).
  • 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é cinco vezes (a tentativa original + quatro tentativas) com espera exponencial. Você pode implementar seus próprios mecanismos de nova tentativa no nível do aplicativo, se desejar, mas isso normalmente não é necessário.

Suporte para nova tentativa

As implementações Go e .NET do Admin SDK vêm com suporte para lidar com o cabeçalho HTTP Retry-After . Ou seja, se a resposta de erro enviada pelos servidores 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 novas tentativas e gerará o erro de API apropriado.

O Python Admin SDK atualmente não oferece suporte ao cabeçalho Retry-After e oferece suporte apenas à espera exponencial simples.

Exemplos de tratamento de erros de API

Implementando um manipulador de erros genérico

Na maioria dos casos, o que você deseja é um manipulador de erros genérico que capture uma ampla gama 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}')

Ir

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

performPrivilegedOperation(token)

.Líquido

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ê desejaria 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}')

Ir

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)

.Líquido

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 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}')

Ir

_, 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
}

.Líquido

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 executar alguma ação de tratamento de erros nela. O Admin SDK 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 brutos 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}')

Ir

_, 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
}

.Líquido

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}");
  }
}