Manejo de errores del SDK de administrador

Los errores del SDK de administración se dividen en dos categorías:

  1. Errores de programación: Son errores de programación y configuración en la aplicación de usuario. Ocurren principalmente debido al uso incorrecto del SDK (como pasar null a un método que no acepta valores null ) y otros errores de configuración en el proyecto de Firebase o a nivel de SDK (credenciales faltantes, cadena de ID de proyecto incorrecta, etc.). en).
  2. Errores de API: estos incluyen varios errores recuperables que ocurren dentro de la implementación del SDK, todos los errores que se originan en los servicios backend de Firebase y otros errores transitorios (como tiempos de espera) que pueden ocurrir al realizar llamadas RPC.

El SDK de administración señala errores de programación arrojando un error nativo de la plataforma en cuestión.

  • Java: lanza instancias de IllegalArgumentException , NullPointerException o un tipo de error de tiempo de ejecución integrado similar.
  • Python: genera instancias de ValueError , TypeError u otro tipo de error integrado.
  • Ir: devuelve un error genérico.
  • .NET: arroja instancias de ArgumentException , ArgumentNullException o un tipo de error integrado similar.

En la mayoría de las situaciones no deberías manejar explícitamente los errores de programación. En su lugar, debe corregir su código y configuración para evitar errores de programación por completo. Considere el siguiente fragmento de Java:

String uid = getUserInput();

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

Si el método getUserInput() devuelve cadenas null o vacías, la API FirebaseAuth.getUser() genera una IllegalArgumentException . En lugar de manejarlo explícitamente, puede mitigar el problema asegurándose de que el método getUserInput() nunca devuelva una cadena UID no válida. Si eso no es posible, implemente la verificación de argumentos necesaria en su propio código de la siguiente manera:

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

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

Como principio, nunca vuelva a intentarlo si hay errores de programación. Permitir una semántica rápida para los errores de programación es a menudo el mejor curso de acción porque expone errores de programación y errores de configuración durante el desarrollo, donde pueden corregirse rápidamente. En este contexto, la falla rápida puede significar permitir que los errores se propaguen a un controlador de errores global en su aplicación, o simplemente registrarlos con fines de auditoría seguido de la terminación del flujo de ejecución actual (la aplicación no debería tener que fallar). En general, siga las mejores prácticas de manejo de errores de su lenguaje de programación y el marco de la aplicación. Esto por sí solo suele ser suficiente para abordar correctamente esta clase de errores.

Normalmente, la mayor parte de sus esfuerzos de manejo de errores se centrarán en manejar errores de API . Algunos de estos errores son recuperables, como los errores que resultaron de un servicio no disponible temporalmente, y algunos incluso se anticipan durante el flujo normal de ejecución del programa, como la detección de tokens de identificación no válidos o vencidos. El resto de esta guía describe cómo el SDK de administración representa dichos errores de API y las diversas opciones disponibles para manejarlos.

Estructura de un error de API

Un error de API consta de los siguientes componentes:

  1. Código de error
  2. Mensaje de error
  3. Código de error de servicio (opcional)
  4. Respuesta HTTP (opcional)

Se garantiza que cada error de API contiene un código de error y un mensaje de error. Ciertos errores de API también contienen un código de error de servicio específico de la API que generó el error. Por ejemplo, algunos errores generados por la API de Firebase Auth contienen un código de error de servicio específico de Firebase Auth. Si el error fue el resultado de una respuesta de error HTTP de un servicio backend, el error de API también contiene la respuesta HTTP correspondiente. Esto se puede utilizar para inspeccionar los encabezados y el contenido exactos de la respuesta original, lo que resulta útil para depurar, registrar o implementar una lógica de manejo de errores más sofisticada.

Todas las implementaciones de Admin SDK, excepto Node.js, proporcionan API que permiten acceder a los componentes anteriores de errores de API.

Tipos de error y API por idioma

Java

En Java, todos los errores de API extienden la clase FirebaseException . Puede acceder al código de error, al mensaje de error y a la respuesta HTTP opcional desde esta clase base.

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

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

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

Las API que exponen códigos de error de servicio proporcionan subclases de FirebaseException específicas de API. Por ejemplo, todos los métodos públicos de la API FirebaseAuth se declaran para generar instancias de FirebaseAuthException . Puede acceder al código de error del servicio desde esta clase derivada.

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

Pitón

En Python, todos los errores de API extienden la clase exceptions.FirebaseError . Puede acceder al código de error, al mensaje de error y a la respuesta HTTP opcional desde esta clase base.

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

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

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

Además, Python Admin SDK ofrece clases derivadas independientes para cada código de error. Nos referimos a ellos como clases de error de plataforma .

class InvalidArgumentError(FirebaseError):
    # ...

class NotFoundError(FirebaseError):
    # ...

Puede detectar FirebaseError en su código y verificar su code , o realizar una verificación isinstance() con una clase de error de plataforma. O puede escribir código para detectar directamente tipos de errores de plataforma específicos. Es probable que este último enfoque dé como resultado un código de manejo de errores más legible.

Las API que exponen códigos de error de servicio proporcionan subclases de clases de error de plataforma específicas de API. Por ejemplo, todos los métodos públicos en el módulo auth pueden generar tipos de error específicos de API, como auth.UserNotFoundError y auth.ExpiredIdTokenError .

class UserNotFoundError(exceptions.NotFoundError):
    # …

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

Ir

El SDK de Go Admin proporciona un paquete errorutils que contiene una serie de funciones que permiten probar códigos de error.

package errorutils

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

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

El mensaje de error es simplemente la cadena devuelta por la función Error() de un error. Se puede acceder a la respuesta HTTP opcional llamando a la función errorutils.HTTPResponse() , que devuelve un *http.Response .

Es seguro pasar nil o cualquier otro valor de error a las funciones de verificación de errores en el paquete errorutils . Devuelven true si el argumento de entrada realmente contiene el código de error en cuestión y devuelven false para todo lo demás. La función HTTPResponse() tiene un comportamiento similar, excepto que devuelve nil en lugar de false .

Las API que exponen códigos de error de servicio proporcionan funciones de verificación de errores específicas de la API en los paquetes correspondientes. Por ejemplo, el paquete auth proporciona las funciones IsUserNotFound() e IsExpiredIDTokenError() .

.NETO

En .NET, todos los errores de API amplían la clase FirebaseException . Puede acceder al código de error de la plataforma, al mensaje de error y a la respuesta HTTP opcional desde esta clase base.

public class FirebaseException : Exception {

    public ErrorCode ErrorCode { get; }

    public String Message { get; }

    public HttpResponseMessage HttpResponse { get; }
}

Las API que exponen códigos de error de servicio proporcionan subclases de FirebaseException específicas de API. Por ejemplo, todos los métodos públicos de la API FirebaseAuth se declaran para generar instancias de FirebaseAuthException . Puede acceder al código de error del servicio desde esta clase derivada.

public class FirebaseAuthException : FirebaseException {

    public AuthErrorCode AuthErrorCode { get; }
}

Códigos de error de la plataforma

Los códigos de error son comunes en todos los servicios de Firebase y Google Cloud Platform. La siguiente tabla describe todos los posibles códigos de error de la plataforma. Se trata de una lista estable y se espera que permanezca sin cambios durante un largo período.

ARGUMENTO NO VÁLIDO El cliente especificó un argumento no válido.
FAILED_PRECONDITION La solicitud no se puede ejecutar en el estado actual del sistema, como eliminar un directorio que no esté vacío.
FUERA DE RANGO El cliente especificó un rango no válido.
NO AUTENTICADO Solicitud no autenticada debido a que falta un token de OAuth, no es válido o ha caducado.
PERMISO DENEGADO El cliente no tiene permiso suficiente. Esto puede suceder porque el token de OAuth no tiene los alcances correctos, el cliente no tiene permiso o la API no se ha habilitado para el proyecto del cliente.
EXTRAVIADO No se encontró el recurso especificado o la solicitud se rechazó por motivos no revelados, como la inclusión en la lista blanca.
CONFLICTO Conflicto de simultaneidad, como conflicto de lectura-modificación-escritura. Solo lo utilizan algunos servicios heredados. La mayoría de los servicios utilizan ABORTED o ALREADY_EXISTS en lugar de esto. Consulte la documentación específica del servicio para ver cuál manejar en su código.
ABORTADO Conflicto de simultaneidad, como conflicto de lectura-modificación-escritura.
YA EXISTE El recurso que un cliente intentó crear ya existe.
RECURSO_AGOTADO Ya sea fuera de la cuota de recursos o alcanzando el límite de velocidad.
CANCELADO Solicitud cancelada por el cliente.
PÉRDIDA DE DATOS Pérdida de datos irrecuperable o corrupción de datos. El cliente debe informar el error al usuario.
DESCONOCIDO Error de servidor desconocido. Normalmente es un error del servidor.

Este código de error también se asigna a errores de análisis (desordenación) de respuesta local y a una amplia gama de otros errores de E/S de bajo nivel que no son fácilmente diagnosticables.

INTERNO Error Interno del Servidor. Normalmente es un error del servidor.
INDISPONIBLE Servicio No Disponible. Normalmente, el servidor está temporalmente inactivo.

Este código de error también se asigna a errores de la red local (conexión rechazada, no hay ruta al host).

DEADLINE_EXCEEDED Se excedió el plazo de solicitud. Esto sucederá solo si la persona que llama establece una fecha límite más corta que la fecha límite predeterminada de la API de destino (es decir, la fecha límite solicitada no es suficiente para que el servidor procese la solicitud) y la solicitud no finalizó dentro de la fecha límite.

Este código de error también se asigna a la conexión local y a los tiempos de espera de lectura.

La mayoría de las API solo pueden generar un subconjunto de los códigos de error anteriores. En cualquier caso, no se espera que usted maneje explícitamente todos estos códigos de error al implementar sus controladores de errores. La mayoría de las aplicaciones solo estarían interesadas en 1 o 2 códigos de error específicos y tratarían todo lo demás como una falla genérica e irrecuperable.

Códigos de error específicos del servicio

Autenticación de base de fuego

CERTIFICATE_FETCH_FAILED No se pudieron recuperar los certificados de clave pública necesarios para verificar un JWT (token de ID o cookie de sesión).
EL EMAIL YA EXISTE Ya existe un usuario con el correo electrónico proporcionado.
EXPIRED_ID_TOKEN El token de ID especificado para verifyIdToken() ha caducado.
EXPIRED_SESSION_COOKIE La cookie de sesión especificada para verifySessionCookie() ha caducado.
INVALID_DYNAMIC_LINK_DOMAIN El dominio de enlace dinámico proporcionado no está configurado ni autorizado para el proyecto actual. Relacionado con las API de enlace de acción de correo electrónico.
INVALID_ID_TOKEN El token de ID especificado para verifyIdToken() no es válido.
INVALID_SESSION_COOKIE La cookie de sesión especificada para verifySessionCookie() no es válida.
PHONE_NUMBER_ALREADY_EXISTS Ya existe un usuario con el número de teléfono proporcionado.
REVOCED_ID_TOKEN Se revoca el token de ID especificado para verifyIdToken() .
REVOKED_SESSION_COOKIE La cookie de sesión especificada para verifySessionCookie() ha caducado.
UNAUTHORIZED_CONTINUE_URL El dominio de la URL de continuación no está en la lista blanca. Relacionado con las API de enlace de acción de correo electrónico.
USUARIO NO ENCONTRADO No se encontró ningún registro de usuario para el identificador proporcionado.

Mensajería en la nube de Firebase

THIRD_PARTY_AUTH_ERROR El certificado APN o la clave API de autenticación web push no eran válidos o faltaban.
ARGUMENTO NO VÁLIDO Uno o más argumentos especificados en la solicitud no eran válidos.
INTERNO Error Interno del Servidor.
CUOTA EXCEDIDA Se superó el límite de envío para el destino del mensaje.
SENDER_ID_MISMATCH El ID del remitente autenticado es diferente del ID del remitente del token de registro. Por lo general, esto significa que el remitente y el token de registro de destino no están en el mismo proyecto de Firebase.
INDISPONIBLE El servicio de mensajería en la nube no está disponible temporalmente.
NO REGISTRADO La instancia de la aplicación no se registró en FCM. Normalmente, esto significa que el token de registro del dispositivo utilizado ya no es válido y se debe utilizar uno nuevo.

Reintentos automáticos

El SDK de administración reintenta automáticamente ciertos errores antes de exponerlos a los usuarios. En general, los siguientes tipos de errores se reintentan de forma transparente:

  • Todos los errores de API resultantes de respuestas HTTP 503 (servicio no disponible).
  • Algunos errores de API resultantes de respuestas HTTP 500 (error interno del servidor).
  • La mayoría de los errores de E/S de bajo nivel (conexión rechazada, restablecimiento de conexión, etc.).

El SDK reintentará cada uno de los errores anteriores hasta 5 veces (el intento original + 4 reintentos) con un retroceso exponencial. Puede implementar sus propios mecanismos de reintento en el nivel de la aplicación si lo desea, pero normalmente esto no es necesario.

Soporte de reintento después

Las implementaciones Go y .NET del Admin SDK incluyen soporte para manejar el encabezado HTTP Retry-After . Es decir, si la respuesta de error enviada por los servidores backend contiene el encabezado Retry-After estándar, el SDK lo respetará al reintentar siempre que la duración de espera especificada no sea muy larga. Si el encabezado Retry-After indica una duración de espera muy larga, el SDK omitirá los reintentos y arrojará el error de API correspondiente.

Actualmente, el SDK de Python Admin no admite el encabezado Retry-After y solo admite un retroceso exponencial simple.

Ejemplos de manejo de errores de API

Implementación de un controlador de errores genérico

En la mayoría de los casos, lo que desea es un controlador de errores genérico que detecte una amplia gama de errores para evitar la terminación inesperada del flujo del programa debido a un error de API. Estos manejadores de errores generalmente simplemente registran los errores con fines de auditoría o invocan alguna otra rutina de manejo de errores predeterminada para todos los errores de API encontrados. No necesariamente están interesados ​​en los diferentes códigos de error o los motivos que pueden haber causado el error.

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ón

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)

.Neto

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

Comprobando códigos de error

En algunos casos, querrá inspeccionar los códigos de error exactos e invocar diferentes rutinas de manejo de errores basadas en el contexto. En el siguiente ejemplo, tenemos un controlador de errores que registra mensajes de error más específicos según el código de error del servicio.

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ón

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)

.Neto

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

A continuación se muestra otro ejemplo en el que verificamos los códigos de error de nivel superior y de servicio:

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ón

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
}

.Neto

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

Accediendo a la respuesta HTTP

En algunos casos excepcionales, es posible que desee inspeccionar la respuesta de error HTTP devuelta por un servicio backend y realizar alguna acción de manejo de errores en ella. El SDK de administración expone tanto los encabezados como el contenido de estas respuestas de error. El contenido de la respuesta generalmente se devuelve como una cadena o una secuencia de bytes sin formato y se puede analizar en cualquier formato de destino necesario.

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ón

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
}

.Neto

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