Los errores del SDK de administrador se dividen en dos categorías:
- 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 valoresnull
) 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). - 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:
- Código de error
- Mensaje de error
- Código de error de servicio (Opcional)
- 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}");
}
}