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 em aplicativo do usuário. Eles ocorrem principalmente devido ao uso incorreto do SDK. (como transmitir null para um método que não aceita valores null) e outros erros de configuração no nível do projeto do Firebase ou do SDK (ausentes credenciais, string de ID do projeto incorreta etc.).
  2. Erros de API:estes incluem vários erros recuperáveis que ocorrem na implementação do SDK, os erros que se originam nos serviços de back-end do Firebase e outros erros (como tempos limite) que podem ocorrer ao fazer chamadas RPC.

O SDK Admin sinaliza erros de programação enviando um erro nativo do a plataforma em questão.

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

Na maioria das situações, você não deve lidar com erros de programação explicitamente. Em vez disso, corrija o código e a configuração para evitar erros de programação. Considere o seguinte snippet Java:

String uid = getUserInput();

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

Se o método getUserInput() retornar null ou strings vazias, o A API FirebaseAuth.getUser() gera uma IllegalArgumentException. Em vez de lidar com isso de maneira explícita, é possível mitigar o problema garantindo O método getUserInput() nunca retorna uma string UID inválida. Se não for possível, implemente a verificação de argumentos necessária no seu 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 em erros de programação. Possibilitar falhas rápidas a semântica de erros de programação é muitas vezes a melhor providência a ser tomada, expõe bugs de programação e erros de configuração durante o desenvolvimento, em que podem ser corrigidos de imediato. Nesse contexto, a falha rápido pode significar permitir que os erros propagar para um gerenciador de erros global no aplicativo, ou apenas registrá-los Para fins de auditoria, seguido pelo encerramento do fluxo de execução atual (o aplicativo não deve ter que falhar). Em geral, siga o erro lidando com as práticas recomendadas da linguagem de programação e do aplicativo de análise de dados em nuvem. Isso por si só costuma ser suficiente para lidar corretamente com essa classe de erros.

Normalmente, a maior parte dos esforços de tratamento de erros se concentra em lidar com as APIs erros. Alguns desses erros são recuperáveis, como os que resultaram de um serviço temporariamente indisponível, e alguns estão até mesmo previstos 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. Código do erro
  2. Mensagem de erro
  3. Código do erro de serviço (opcional)
  4. Resposta HTTP (opcional)

Todo erro de API contém um código e uma mensagem de erro. Alguns 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 pelo Firebase Auth A API contém um código de erro de serviço específico do Firebase Auth. Se o erro foi o resultado de uma resposta de erro HTTP de um serviço de back-end, o erro de API também contém a resposta HTTP correspondente. Isso pode ser usado para inspecionar cabeçalhos e conteúdos exatos da resposta original, que é útil para depuração, geração de registros ou implementação de lógica mais sofisticada de tratamento de erros.

Todas as implementações do SDK Admin, exceto Node.js, oferecem APIs que permitem o acesso os componentes acima dos erros da API.

Tipos de erros e APIs por linguagem

Java

Em Java, todos os erros de API estendem a classe FirebaseException. Acesse o o código de erro, a mensagem de erro e a resposta HTTP opcional desta classe de 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 da API de FirebaseException: Por exemplo, todos os métodos públicos na API FirebaseAuth são declaradas para gerar instâncias de FirebaseAuthException. Acesse o código de erro de serviço dessa classe derivada.

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

Python

Em Python, todos os erros de API estendem o exceptions.FirebaseError . É possível acessar o código de erro, a mensagem de erro e a solicitação HTTP resposta dessa classe de base.

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

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

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

Além disso, o SDK Admin para Python oferece classes derivadas separadas para cada código de erro. Nós as chamamos de classes de erro de plataforma.

class InvalidArgumentError(FirebaseError):
    # ...

class NotFoundError(FirebaseError):
    # ...

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

As APIs que expõem códigos de erro de serviço oferecem subclasses específicas de API de plataforma classes de erro. 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):
    # ...

Go

O SDK Admin para Go fornece um pacote errorutils que contém uma série de 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 o Função errorutils.HTTPResponse(), que retorna um *http.Response.

É seguro transmitir nil ou qualquer outro valor de erro para a verificação de erros. no pacote errorutils. Elas retornam true se o argumento de entrada contém o código de erro em questão e retorna false para tudo outros. A função HTTPResponse() tem um comportamento semelhante, mas retorna nil em vez de false.

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

.NET

Em .NET, todos os erros de API estendem o FirebaseException . Acesse o código de erro da plataforma, mensagem de erro e a resposta HTTP opcional desta 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 da API de FirebaseException: Por exemplo, todos os métodos públicos na API FirebaseAuth. são declaradas para gerar instâncias de FirebaseAuthException. Acesse 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 possíveis da plataforma. Esta é uma lista estável e deve permanecer inalterada por um longo período.

INVALID_ARGUMENT 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 que não esteja vazio.
OUT_OF_RANGE O cliente especificou um intervalo inválido.
UNAUTHENTICATED Solicitação não autenticada devido ao token OAuth ausente, inválido ou expirado.
PERMISSION_DENIED O cliente não tem permissão suficiente. Isso pode acontecer porque o token OAuth não tem os escopos certos, o cliente não tem permissão ou a API não foi ativada para o projeto do cliente.
NOT_FOUND O recurso especificado não foi encontrado ou a solicitação foi rejeitada devido a motivos não revelados, como a inclusão na lista de permissões.
CONFLITO Conflito de simultaneidade, como 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 saber qual deles processar no código.
ABORTED Conflito de simultaneidade, como leitura-modificação-gravação.
ALREADY_EXISTS O recurso que um cliente tentou criar já existe.
RESOURCE_EXHAUSTED Excedeu a cota de recursos ou está perto de atingir a limitação de taxa.
CANCELADO Solicitação cancelada pelo cliente.
PERDA_DE_DADOS Perda ou corrupção de dados irrecuperável. O cliente deve comunicar o erro ao usuário.
DESCONHECIDO Erro desconhecido de servidor. Geralmente, um bug do servidor.

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

INTERNAL Erro interno do servidor. Geralmente, um bug do servidor.
UNAVAILABLE Serviço indisponível. Normalmente, o servidor está temporariamente fora do ar.

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

DEADLINE_EXCEEDED O prazo de solicitação foi excedido. Isso acontecerá somente se o autor da chamada definir um prazo menor que o padrão da API de destino (ou seja, se o prazo solicitado não for suficiente para o servidor processar a solicitação) e a solicitação não for 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 devem lidar explicitamente com todos esses códigos de erro ao implementar os gerenciadores de erros. A maioria das inscrições só teria interesse em Um ou dois códigos de erro específicos e tratar todo o restante como algo genérico e irrecuperável falha.

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

Firebase Auth

FALHA EM CERTIFICADO_FETCH Falha ao buscar os certificados de chave pública necessários para verificar um JWT (token de ID ou cookie de sessão).
EMAIL_ALREADY_EXISTS 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() i está expirado.
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.
CONTINUAR_URL NÃO AUTORIZADO O domínio da URL de confirmação não está na lista de permissões. Relacionado a APIs de link de ação de e-mail.
USUÁRIOS NÃO ENCONTRADOS Nenhum registro de usuário foi encontrado para o identificador fornecido.

Firebase Cloud Messaging

ERRO DE TERCEIROS O certificado de APNs ou a chave de API de autenticação por push na Web era inválido ou estava ausente.
INVALID_ARGUMENT Um ou mais argumentos especificados na solicitação eram inválidos.
INTERNAL Erro interno do servidor.
COTA_EXCEDIDA O limite de envio foi 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.
UNAVAILABLE O serviço Cloud Messaging está temporariamente indisponível.
CANCELADO O registro da instância do app no FCM foi cancelado. Isso geralmente significa que o token de registro do dispositivo usado não é mais válido e é necessário usar um novo.

Novas tentativas automáticas

O SDK Admin repete automaticamente alguns erros antes de expô-los aos usuários. Em geral, os seguintes tipos de erro 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 vai tentar realizar cada um dos erros acima até cinco vezes (a tentativa original + 4 novas tentativas) com espera exponencial. É possível implementar sua própria mecanismos no nível do aplicativo, se você quiser, mas isso normalmente não é obrigatórios.

Tentar novamente/depois do suporte

As implementações Go e .NET do SDK Admin incluem suporte para processando o cabeçalho HTTP Retry-After. Ou seja, se a resposta de erro enviada pelo servidores de back-end contiverem o cabeçalho Retry-After padrão, o SDK respeitando isso, ao tentar novamente, contanto que a duração de espera especificada não seja muito de comprimento. Se o cabeçalho Retry-After indicar uma duração de espera muito longa, o SDK ignorará novas tentativas e gerará o erro de API apropriado.

No momento, o SDK Admin para Python não é compatível com o cabeçalho Retry-After. oferece suporte apenas à espera exponencial simples.

Exemplos de tratamento de erros de API

Como implementar um gerenciador de erros genérico

Na maioria dos casos, você quer um gerenciador de erros genérico que capture uma intervalo de erros para evitar o encerramento inesperado do fluxo do programa devido a um Erro de API. Em geral, esses gerenciadores de erros só registram os erros para fins de auditoria, ou invocar outra rotina de tratamento de erros padrão para todas as APIs encontradas erros. 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());
}

Python

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

Go

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

performPrivilegedOperation(token)

.Net

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

Como verificar códigos de erro

Em alguns casos, convém inspecionar os códigos de erro exatos e invocar diferentes rotinas de tratamento de erros com base no contexto. No exemplo a seguir, um gerenciador de erros que registre mensagens de erro mais específicas com base no código de erro de 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());
  }
}

Python

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

Go

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)

.Net

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

Este é 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());
  }
}

Python

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

Go

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

.Net

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

Como acessar a resposta HTTP

Em alguns casos raros, convém inspecionar a resposta de erro HTTP retornada pelo um serviço de back-end e executar alguma ação de tratamento de erros nele. SDK Admin expõe os cabeçalhos e o conteúdo dessas respostas de erro. A resposta conteúdo geralmente é retornado como uma string ou uma sequência de bytes brutos e pode ser 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());
  }
}

Python

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

Go

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

.Net

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