Manejo de errores del SDK de Admin

Los errores del SDK de Admin 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. La mayoría de las veces ocurren debido a un uso incorrecto del SDK. (como pasar null a un método que no acepta valores null) otros errores de configuración a nivel del proyecto o del SDK de Firebase (faltan credenciales, una cadena de ID de proyecto incorrecta, etcétera).
  2. Errores de API: Estos incluir varios errores recuperables que ocurren en la implementación del SDK, los errores que se originan en los servicios de backend de Firebase y otros errores errores (como tiempos de espera) que pueden ocurrir al realizar llamadas RPC.

El SDK de Admin indica los errores de programación arrojando un error nativo del la plataforma en cuestión.

  • Java: Muestra instancias de IllegalArgumentException y NullPointerException. o un tipo de error de entorno de ejecución integrado similar.
  • Python: Genera instancias de ValueError, TypeError o algún otro tipo de error integrado.
  • Go: Muestra un error genérico.
  • .NET: Muestra instancias de ArgumentException, ArgumentNullException o tipo de error integrado similar.

En la mayoría de las situaciones, no debes manejar explícitamente los errores de programación. En cambio, debes corregir tu código y configuración para evitar errores de programación por completo. Considera el siguiente fragmento de Java:

String uid = getUserInput();

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

Si el método getUserInput() muestra null o cadenas vacías, el La API de FirebaseAuth.getUser() arroja un IllegalArgumentException. En lugar de cuando lo manejas explícitamente, puedes mitigar el problema si te aseguras de que El método getUserInput() nunca muestra una cadena de UID no válida. Si no es así posible, implementa la comprobación de argumentos necesaria en tu 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 vuelvas a intentarlo sobre los errores de programación. Cómo permitir la falla rápida la semántica sobre errores de programación suele ser la mejor forma de proceder porque expone errores de programación y configuración durante el desarrollo, donde se puedan corregir rápidamente. La falla rápida en este contexto puede significar dejar que los errores propagarse a un controlador de errores global en tu aplicación, o solo registrarlos con fines de auditoría seguidas de la finalización del flujo de ejecución actual (la aplicación no debería fallar). En general, sigue las instrucciones para manejar las prácticas recomendadas del lenguaje de programación y la aplicación en un framework de aplicaciones. Esto solo suele ser suficiente para abordar correctamente esta clase de errores.

Normalmente, la mayor parte de tus esfuerzos por solucionar los errores se centrarán en manejar las APIs de API errores. Algunos de estos errores se pueden recuperar, como los errores causados por 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 ID no válidos o vencidos. En el resto de esta guía, se describe cómo el SDK de Admin representa esos 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 del servicio (opcional)
  4. Respuesta HTTP (opcional)

Se garantiza que cada error de API contenga un código 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 Firebase Auth La API contiene 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 de backend, el error de API también contiene la respuesta HTTP correspondiente. Esto se puede usar para inspeccionar los encabezados y el contenido exactos de la respuesta original, lo cual es útil para la depuración, el registro o la implementación de lógica de manejo de errores más sofisticada.

Todas las implementaciones del SDK de Admin, excepto Node.js, proporcionan APIs que permiten el acceso los componentes anteriores de los errores de API.

Tipos de errores y APIs por lenguaje

Java

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

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

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

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

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

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

Python

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

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

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

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

Además, el SDK de Admin de Python ofrece clases derivadas separadas para cada código de error. Nos referimos a ellas como clases de error de la plataforma.

class InvalidArgumentError(FirebaseError):
    # ...

class NotFoundError(FirebaseError):
    # ...

Puedes capturar FirebaseError en tu código y verificar su code, o Realiza una verificación de isinstance() en una clase de error de la plataforma. También puedes escribir código para detectar directamente tipos de errores específicos de la plataforma. El último enfoque lo que generará un código de manejo de errores más legible.

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

class UserNotFoundError(exceptions.NotFoundError):
    # …

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

Go

El SDK de Admin de Go proporciona un paquete errorutils que contiene una serie de 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 que muestra la función Error() de un . Se puede acceder a la respuesta HTTP opcional llamando al Función errorutils.HTTPResponse(), que muestra un *http.Response.

Es seguro pasar nil o cualquier otro valor de error a la comprobación de errores en el paquete errorutils. Muestran true si el argumento de entrada contiene el código de error en cuestión y muestra false para todo. más. La función HTTPResponse() tiene un comportamiento similar, pero muestra nil en lugar de false.

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

.NET

En .NET, todos los errores de la API extienden el FirebaseException . Puedes acceder a la código de error de la plataforma, el mensaje de error y la respuesta HTTP opcional de esta base .

public class FirebaseException : Exception {

    public ErrorCode ErrorCode { get; }

    public String Message { get; }

    public HttpResponseMessage HttpResponse { get; }
}

Las APIs que exponen los códigos de error del servicio proporcionan subclases específicas de la API de FirebaseException Por ejemplo, todos los métodos públicos en la API de FirebaseAuth se declaran para arrojar instancias de FirebaseAuthException. Puedes acceder a la código de error del servicio de 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. En la siguiente tabla, se describen todos los posibles códigos de error de la plataforma. Este es un estable y se espera que permanezca sin cambios durante un largo período.

INVALID_ARGUMENT El cliente especificó un argumento no válido.
FAILED_PRECONDITION La solicitud no se puede ejecutar en el estado actual del sistema, como borrar un directorio que no esté vacío.
OUT_OF_RANGE El cliente especificó un rango no válido.
UNAUTHENTICATED La solicitud no se autenticó debido a que falta un token de OAuth, no es válido o venció.
PERMISSION_DENIED El cliente no cuenta con los permisos necesarios. Esto puede suceder porque el token de OAuth no tiene los permisos correctos, el cliente no tiene permiso o la API no se habilitó para el proyecto del cliente.
NOT_FOUND No se encontró el recurso especificado o se rechazó la solicitud por motivos no divulgados, como la lista blanca.
CONFLICTO Conflicto de concurrencia, como conflicto de lectura-modificación-escritura. Solo se usa en algunos servicios heredados. La mayoría de los servicios usan ABORTED o ALREADY_EXISTS en lugar de esto. Consulta la documentación específica del servicio para saber cuál debes administrar en tu código.
ABORTED Conflicto de concurrencia, como conflicto de lectura-modificación-escritura.
ALREADY_EXISTS El recurso que el cliente intentó crear ya existe.
RESOURCE_EXHAUSTED Sin cuota de recursos o a punto de alcanzar el límite de frecuencia.
CANCELADO El cliente canceló la solicitud.
DATA_LOSS Daño o pérdida de datos no recuperable. El cliente debe informar el error al usuario.
DESCONOCIDO Error desconocido del servidor. Por lo general, un error de servidor.

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

INTERNAL Error interno del servidor. Por lo general, un error de servidor.
UNAVAILABLE Servicio no disponible. Por lo general, el servidor está temporalmente fuera de servicio.

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

DEADLINE_EXCEEDED Se excedió el plazo de la solicitud. Esto sucederá solo si el llamador establece una fecha límite que es 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 del plazo establecido.

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 APIs solo pueden generar un subconjunto de los códigos de error anteriores. En cualquier caso, no se espera que manejen explícitamente todos estos códigos de error cuando implementar los controladores de errores. A la mayoría de las aplicaciones solo les interesaría de 1 a 2 códigos de error específicos y tratar todo lo demás como genérico e irrecuperable. falla.

Códigos de error específicos del servicio

Firebase Auth

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).
EMAIL_ALREADY_EXISTS Ya existe un usuario con el correo electrónico proporcionado.
EXPIRED_ID_TOKEN Venció el token de ID especificado para verifyIdToken().
COOKIE_DE_SESSION_VENCIDA Venció la cookie de sesión especificada para verifySessionCookie().
INVALID_DYNAMIC_LINK_DOMAIN El dominio del vínculo dinámico proporcionado no se configuró o no se autorizó para el proyecto actual. Se relaciona con las APIs de vínculos de acción de correo electrónico.
INVALID_ID_TOKEN El token de ID especificado en verifyIdToken() no es válido.
COOKIE_DE_SESSION_NO VÁLIDA 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.
REVOKED_ID_TOKEN Se revoca el token de ID especificado en verifyIdToken().
COCINA_DE_SESSION_REVOKED Venció la cookie de sesión especificada para verifySessionCookie().
URL CONTINUAR NO AUTORIZADA El dominio de la URL de continuación no está en la lista blanca. Se relaciona con las APIs de vínculos de acción de correo electrónico.
NO_ENCONTRADO_USUARIO No se encontró ningún registro de usuario para el identificador en cuestión.

Firebase Cloud Messaging

TERCER_PARTY_AUTH_ERROR El certificado APNS o la clave de API de autenticación push web no eran válidos o no se encontraron.
INVALID_ARGUMENT Uno o más argumentos especificados en la solicitud no eran válidos.
INTERNAL Error interno del servidor.
CUOTA_EXCEDAD Se superó el límite de envío para el destino del mensaje.
ID DE REMITENTE_MISMATCH El ID de remitente autenticado es diferente del ID de remitente del token de registro. Por lo general, esto significa que el token de registro del remitente y el de destino no están en el mismo proyecto de Firebase.
UNAVAILABLE El servicio de Cloud Messaging no está disponible en este momento.
NO REGISTRADO Se canceló el registro de la instancia de app en FCM. Por lo general, esto significa que el token de registro de dispositivo ya no es válido y que se debe usar uno nuevo.

Reintentos automáticos

El SDK de Admin reintenta automáticamente ciertos errores antes de exponerlos a los usuarios. En general, los siguientes tipos de errores se reintentan con transparencia:

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

El SDK volverá a intentar cada uno de los errores anteriores hasta 5 veces (el intento original + 4 reintentos) con retirada exponencial. Puedes implementar tu propio reintento a nivel de la aplicación si lo deseas, pero no suele ser como en los productos necesarios.

Reintentar después de recibir asistencia

Las implementaciones de Go y .NET del SDK de Admin incluyen compatibilidad con que controla el encabezado HTTP Retry-After. Es decir, si la respuesta de error enviada por los servidores de backend contienen el encabezado Retry-After estándar, el SDK respecto de que, al volver a intentarlo, siempre y cuando la duración de espera especificada no sea muy por mucho tiempo. Si el encabezado Retry-After indica un tiempo de espera muy largo, el SDK omitirá los reintentos y arrojará el error de API correspondiente.

Actualmente, el SDK de Admin de Python no es compatible con el encabezado Retry-After. solo admite una retirada exponencial simple

Ejemplos de manejo de errores de la API

Implementa un controlador de errores genérico

En la mayoría de los casos, lo que deseas es un controlador de errores genérico que detecte una amplia varios errores para evitar la finalización inesperada del flujo del programa debido a una Error de API. Estos controladores, por lo general, registran los errores con fines de auditoría o invocar alguna otra rutina predeterminada de manejo de errores para todas las APIs encontradas errores. No están necesariamente interesados en los diferentes códigos de error ni en el 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());
}

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

Verificar códigos de error

En algunos casos, querrás inspeccionar los códigos de error exactos y, luego, invocar diferentes rutinas de manejo de errores adaptados al contexto. En el siguiente ejemplo, tener un controlador de errores que registre 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());
  }
}

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 es otro ejemplo en el que verificamos los códigos de error de nivel superior y del 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());
  }
}

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

Accede a la respuesta HTTP

En algunos casos excepcionales, es posible que desees inspeccionar la respuesta de error de HTTP devuelta por un servicio de backend y realizar alguna acción de manejo de errores en él. El SDK de Admin Expone los encabezados y el contenido de estas respuestas de error. La respuesta contenido generalmente se devuelve como una cadena o una secuencia de bytes sin procesar, y se puede analizarse 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());
  }
}

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