Manejo de errores del SDK de administrador

Los errores del SDK de administrador se dividen en dos categorías:

  1. Errores de programación: Son errores de programación y configuración en la aplicación del usuario. En su mayoría ocurren 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 nivel de SDK (credenciales faltantes, cadena de ID de proyecto incorrecta, etc. sobre).
  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 de backend de Firebase y otros errores transitorios (como tiempos de espera) que pueden ocurrir al realizar llamadas RPC.

Admin SDK señala los errores de programación lanzando un error que es 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 incorporado.
  • Ir: Devuelve un error genérico.
  • .NET: genera instancias de ArgumentException , ArgumentNullException o un tipo de error integrado similar.

En la mayoría de las situaciones, no debe 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 vacías o null , 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 reintentar errores de programación. Permitir una semántica a prueba de fallas en los errores de programación es a menudo el mejor curso de acción porque expone los errores de programación y los errores de configuración durante el desarrollo, donde pueden corregirse rápidamente. Fail-fast en este contexto 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 seguidos de la terminación del flujo de ejecución actual (la aplicación no debería 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 tratar correctamente esta clase de errores.

Por lo general, la mayor parte de sus esfuerzos de manejo de errores se centrarán en el manejo de errores de API . Algunos de estos errores son recuperables, como los errores que resultaron de un servicio temporalmente no disponible, y algunos incluso se anticipan durante el flujo de ejecución normal del programa, como la detección de tokens de identificación no válidos o caducados. El resto de esta guía describe cómo Admin SDK 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 la API contenga un código de error y un mensaje de error. Ciertos errores de API también contienen un código de error de servicio que es 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 que es específico de Firebase Auth. Si el error fue el resultado de una respuesta de error HTTP de un servicio de backend, el error de la API también contiene la respuesta HTTP correspondiente. Esto se puede usar para inspeccionar los encabezados y contenidos exactos de la respuesta original, lo cual es ú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 los errores de API.

Tipos de error y API por idioma

Java

En Java, todos los errores de API amplían la clase FirebaseException . Puede acceder al código de error, el mensaje de error y 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 específicas de API de FirebaseException . Por ejemplo, todos los métodos públicos en la API de 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 la API amplían la exceptions.FirebaseError . Puede acceder al código de error, el mensaje de error y 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 separadas para cada código de error. Nos referimos a ellos como clases de error de plataforma .

class InvalidArgumentError(FirebaseError):
    # ...

class NotFoundError(FirebaseError):
    # ...

Puede capturar FirebaseError en su código y verificar su code , o realizar una isinstance() contra una clase de error de plataforma. O puede escribir código para detectar directamente tipos de errores de plataforma específicos. Es probable que el último enfoque resulte en un código de manejo de errores más legible.

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

class UserNotFoundError(exceptions.NotFoundError):
    # …

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

Vamos

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 API en los paquetes correspondientes. Por ejemplo, el paquete auth proporciona las funciones IsUserNotFound() e IsExpiredIDTokenError() .

.RED

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 específicas de API de FirebaseException . Por ejemplo, todos los métodos públicos en la API de 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. Esta es 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.
PRECONDICIÓN FALLIDA 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 AUTENTIFICADO Solicitud no autenticada debido a un token de OAuth faltante, no válido o caducado.
PERMISO DENEGADO El cliente no tiene suficientes permisos. 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ó debido a motivos no revelados, como la inclusión en la lista blanca.
CONFLICTO Conflicto de simultaneidad, como un conflicto de lectura, modificación y escritura. Solo lo utilizan algunos servicios heredados. La mayoría de los servicios usan ABORTED o YA_EXISTE 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 un conflicto de lectura, modificación y escritura.
YA EXISTE El recurso que un cliente intentó crear ya existe.
RECURSO_AGOTADO Fuera de la cuota de recursos o llegando al límite de velocidad.
CANCELADO Solicitud cancelada por el cliente.
PÉRDIDA DE DATOS Pérdida irrecuperable de datos o corrupción de datos. El cliente debe informar el error al usuario.
DESCONOCIDO Error de servidor desconocido. Por lo general, un error del servidor.

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

INTERNO Error de servidor interno. Por lo general, un error del servidor.
INDISPONIBLE Servicio no disponible. Por lo general, el servidor está temporalmente inactivo.

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

FECHA LÍMITE_EXCEEDED Plazo de solicitud excedido. 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 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 obtener los certificados de clave pública necesarios para verificar un JWT (token de identificación 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() 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.
TELÉFONO_NUMBER_ALREADY_EXISTS Ya existe un usuario con el número de teléfono proporcionado.
REVOKED_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

TERCEROS_PARTY_AUTH_ERROR El certificado de APN o la clave API de autenticación web push no es válido o falta.
ARGUMENTO NO VÁLIDO Uno o más argumentos especificados en la solicitud no son válidos.
INTERNO Error de servidor interno.
CUOTA EXCEDIDA Se excedió el límite de envío para el destino del mensaje.
SENDER_ID_MISMATCH La ID del remitente autenticado es diferente de la ID del remitente para el token de registro. Esto generalmente 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 Se anuló el registro de la instancia de la aplicación en FCM. Esto generalmente significa que el token de registro del dispositivo usado ya no es válido y se debe usar uno nuevo.

Reintentos automáticos

Admin SDK vuelve a intentar automáticamente ciertos errores antes de exponer esos errores a los usuarios. En general, los siguientes tipos de errores se vuelven a intentar de forma transparente:

  • Todos los errores de API resultantes de las respuestas HTTP 503 (Servicio no disponible).
  • Algunos errores de API resultantes de las 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 volverá a intentar cada uno de los errores anteriores hasta 5 veces (el intento original + 4 reintentos) con retroceso exponencial. Si lo desea, puede implementar sus propios mecanismos de reintento en el nivel de la aplicación, pero normalmente no es necesario.

Reintentar después del soporte

Las implementaciones de Go y .NET del SDK de administrador son compatibles con el manejo del encabezado HTTP Retry-After . Es decir, si la respuesta de error enviada por los servidores back Retry-After , el SDK lo respetará cuando vuelva a intentarlo, 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 generará el error de API correspondiente.

El SDK de administración de Python actualmente no es compatible con el encabezado Retry-After y solo es compatible con el 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 la API. Dichos controladores de errores generalmente solo 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 están necesariamente interesados ​​en los diferentes códigos de error o las razones 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}')

Vamos

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

performPrivilegedOperation(token)

.Red

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

Comprobación de códigos de error

En algunos casos, querrá inspeccionar los códigos de error exactos e invocar diferentes rutinas de manejo de errores sensibles al 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}')

Vamos

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)

.Red

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

Aquí hay 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}')

Vamos

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

.Red

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

Acceso a la respuesta HTTP

En algunos casos raros, es posible que desee inspeccionar la respuesta de error HTTP devuelta por un servicio de back-end y realizar alguna acción de manejo de errores en ella. Admin SDK expone tanto los encabezados como los contenidos 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}')

Vamos

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

.Red

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