Tratamento de Erros
Um tratamento adequado de erros é essencial para criar aplicações robustas que usam a API Speedio. Este guia apresenta estratégias e implementações para lidar com diferentes tipos de erros.Tipos de Erros
1. Erros de Autenticação
Credenciais inválidas ou expiradasCausas comuns:
- Username ou password incorretos
- Credenciais expiradas
- Header Authorization malformado
Acesso negado ao recursoCausas comuns:
- Conta sem permissão para o endpoint
- Plano não inclui a funcionalidade
- IP bloqueado
2. Erros de Requisição
Requisição malformada ou parâmetros inválidosCausas comuns:
- JSON inválido no corpo da requisição
- Parâmetros obrigatórios ausentes
- Formato de dados incorreto
Dados válidos mas não processáveisCausas comuns:
- CNPJ com formato correto mas inválido
- E-mail com sintaxe correta mas domínio inexistente
3. Erros de Rate Limiting
Limite de requisições excedidoHeaders importantes:
Retry-After: Tempo para tentar novamenteX-RateLimit-Remaining: Requisições restantesX-RateLimit-Reset: Quando o limite reseta
4. Erros do Servidor
Erro interno do servidor
Problema no gateway/proxy
Serviço temporariamente indisponível
Timeout no gateway
Estratégias de Tratamento
1. Hierarquia de Classes de Erro
Copy
// Classe base para erros da API Speedio
class SpeedioAPIError extends Error {
constructor(message, status, response, originalError = null) {
super(message);
this.name = 'SpeedioAPIError';
this.status = status;
this.response = response;
this.originalError = originalError;
this.timestamp = new Date().toISOString();
}
toJSON() {
return {
name: this.name,
message: this.message,
status: this.status,
timestamp: this.timestamp,
response: this.response ? {
status: this.response.status,
data: this.response.data
} : null
};
}
}
// Erros específicos
class AuthenticationError extends SpeedioAPIError {
constructor(message, response) {
super(message, 401, response);
this.name = 'AuthenticationError';
}
}
class AuthorizationError extends SpeedioAPIError {
constructor(message, response) {
super(message, 403, response);
this.name = 'AuthorizationError';
}
}
class ValidationError extends SpeedioAPIError {
constructor(message, response, validationErrors = []) {
super(message, 400, response);
this.name = 'ValidationError';
this.validationErrors = validationErrors;
}
}
class RateLimitError extends SpeedioAPIError {
constructor(message, response, retryAfter = null) {
super(message, 429, response);
this.name = 'RateLimitError';
this.retryAfter = retryAfter;
}
}
class ServerError extends SpeedioAPIError {
constructor(message, status, response) {
super(message, status, response);
this.name = 'ServerError';
}
}
class NetworkError extends SpeedioAPIError {
constructor(message, originalError) {
super(message, 0, null, originalError);
this.name = 'NetworkError';
}
}
2. Interceptador de Erros
Copy
function parseSpeedioError(error) {
if (error.response) {
// Erro HTTP da API
const { status, data } = error.response;
const message = data?.message || data?.error || 'Erro na API';
switch (status) {
case 401:
return new AuthenticationError(message, error.response);
case 403:
return new AuthorizationError(message, error.response);
case 400:
return new ValidationError(
message,
error.response,
data?.validation_errors || []
);
case 422:
return new ValidationError(
'Dados não processáveis',
error.response,
data?.errors || []
);
case 429:
const retryAfter = error.response.headers['retry-after'];
return new RateLimitError(
message,
error.response,
retryAfter ? parseInt(retryAfter) : null
);
case 500:
case 502:
case 503:
case 504:
return new ServerError(message, status, error.response);
default:
return new SpeedioAPIError(message, status, error.response);
}
} else if (error.request) {
// Erro de rede
return new NetworkError('Erro de conectividade', error);
} else {
// Erro de configuração
return new SpeedioAPIError('Erro de configuração da requisição', 0, null, error);
}
}
// Interceptador para Axios
axios.interceptors.response.use(
response => response,
error => {
throw parseSpeedioError(error);
}
);
3. Retry com Backoff Exponencial
Copy
class RetryManager {
constructor(options = {}) {
this.maxRetries = options.maxRetries || 3;
this.baseDelay = options.baseDelay || 1000; // 1 segundo
this.maxDelay = options.maxDelay || 30000; // 30 segundos
this.backoffFactor = options.backoffFactor || 2;
this.retryableErrors = options.retryableErrors || [
'NetworkError',
'ServerError',
'RateLimitError'
];
}
async executeWithRetry(fn, context = {}) {
let lastError;
for (let attempt = 1; attempt <= this.maxRetries + 1; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error;
// Não tentar novamente se não for um erro "retryável"
if (!this.isRetryableError(error)) {
throw error;
}
// Não tentar novamente se é a última tentativa
if (attempt > this.maxRetries) {
break;
}
const delay = this.calculateDelay(attempt, error);
console.warn(`❌ Tentativa ${attempt}/${this.maxRetries + 1} falhou: ${error.message}`);
console.info(`⏳ Aguardando ${delay / 1000}s antes da próxima tentativa...`);
await this.sleep(delay);
}
}
throw lastError;
}
isRetryableError(error) {
return this.retryableErrors.includes(error.name);
}
calculateDelay(attempt, error) {
// Para RateLimitError, respeitar o Retry-After
if (error.name === 'RateLimitError' && error.retryAfter) {
return error.retryAfter * 1000;
}
// Backoff exponencial com jitter
const exponentialDelay = this.baseDelay * Math.pow(this.backoffFactor, attempt - 1);
const jitter = Math.random() * 0.1 * exponentialDelay; // 10% de jitter
return Math.min(exponentialDelay + jitter, this.maxDelay);
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// Uso do RetryManager
const retryManager = new RetryManager({
maxRetries: 3,
baseDelay: 1000,
maxDelay: 30000
});
async function buscarEmpresaComRetry(cnpj) {
return await retryManager.executeWithRetry(async () => {
const response = await axios.get(
'https://api-get-leads.speedio.com.br/search_enriched_leads/cnpj',
{
params: { cnpjs: JSON.stringify([cnpj]) },
headers: {
'Authorization': `Basic ${auth}`,
'Content-Type': 'application/json'
}
}
);
return response.data[0] || null;
});
}
4. Circuit Breaker
Copy
class CircuitBreaker {
constructor(options = {}) {
this.failureThreshold = options.failureThreshold || 5;
this.recoveryTimeout = options.recoveryTimeout || 60000; // 1 minuto
this.monitoringPeriod = options.monitoringPeriod || 120000; // 2 minutos
this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
this.failureCount = 0;
this.lastFailureTime = null;
this.successCount = 0;
this.resetCounters();
}
async execute(fn) {
if (this.state === 'OPEN') {
if (Date.now() - this.lastFailureTime >= this.recoveryTimeout) {
this.state = 'HALF_OPEN';
console.info('🔄 Circuit breaker em estado HALF_OPEN, testando recuperação...');
} else {
throw new Error('Circuit breaker OPEN - serviço indisponível');
}
}
try {
const result = await fn();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
onSuccess() {
this.failureCount = 0;
if (this.state === 'HALF_OPEN') {
this.successCount++;
// Após 3 sucessos consecutivos, voltar ao estado CLOSED
if (this.successCount >= 3) {
this.state = 'CLOSED';
this.successCount = 0;
console.info('✅ Circuit breaker FECHADO - serviço recuperado');
}
}
}
onFailure() {
this.failureCount++;
this.lastFailureTime = Date.now();
if (this.state === 'HALF_OPEN') {
this.state = 'OPEN';
this.successCount = 0;
console.warn('🔴 Circuit breaker ABERTO - falha durante teste de recuperação');
} else if (this.failureCount >= this.failureThreshold) {
this.state = 'OPEN';
console.warn(`🔴 Circuit breaker ABERTO - ${this.failureCount} falhas consecutivas`);
}
}
getStatus() {
return {
state: this.state,
failureCount: this.failureCount,
successCount: this.successCount,
lastFailureTime: this.lastFailureTime,
nextRetryTime: this.state === 'OPEN' ?
this.lastFailureTime + this.recoveryTimeout : null
};
}
resetCounters() {
setInterval(() => {
if (this.state === 'CLOSED') {
this.failureCount = 0;
}
}, this.monitoringPeriod);
}
}
// Integração com RetryManager
class ResilientClient {
constructor() {
this.retryManager = new RetryManager({
maxRetries: 3,
baseDelay: 1000
});
this.circuitBreaker = new CircuitBreaker({
failureThreshold: 5,
recoveryTimeout: 60000
});
}
async request(fn) {
return await this.circuitBreaker.execute(async () => {
return await this.retryManager.executeWithRetry(fn);
});
}
getHealthStatus() {
return {
circuitBreaker: this.circuitBreaker.getStatus(),
timestamp: new Date().toISOString()
};
}
}
const client = new ResilientClient();
async function buscarEmpresaResilient(cnpj) {
return await client.request(async () => {
const response = await axios.get(
'https://api-get-leads.speedio.com.br/search_enriched_leads/cnpj',
{
params: { cnpjs: JSON.stringify([cnpj]) },
headers: {
'Authorization': `Basic ${auth}`,
'Content-Type': 'application/json'
}
}
);
return response.data[0] || null;
});
}
5. Logs Estruturados de Erro
Copy
class ErrorLogger {
constructor(options = {}) {
this.logLevel = options.logLevel || 'error';
this.includeStackTrace = options.includeStackTrace !== false;
this.maxStackLines = options.maxStackLines || 10;
}
logError(error, context = {}) {
const logEntry = {
timestamp: new Date().toISOString(),
level: 'ERROR',
error: {
name: error.name,
message: error.message,
status: error.status || null,
...(error instanceof SpeedioAPIError && {
response: error.response,
originalError: error.originalError?.message
})
},
context,
...(this.includeStackTrace && error.stack && {
stack: error.stack.split('\n').slice(0, this.maxStackLines)
})
};
console.error(JSON.stringify(logEntry, null, 2));
// Enviar para serviço de monitoramento (ex: Sentry, DataDog)
this.sendToMonitoring(logEntry);
}
logRecovery(error, attempt, success = false) {
const logEntry = {
timestamp: new Date().toISOString(),
level: success ? 'INFO' : 'WARN',
event: success ? 'ERROR_RECOVERY' : 'RETRY_ATTEMPT',
error: {
name: error.name,
message: error.message,
status: error.status
},
attempt,
success
};
console.log(JSON.stringify(logEntry));
}
sendToMonitoring(logEntry) {
// Implementar integração com Sentry, DataDog, etc.
// Exemplo com Sentry:
// Sentry.captureException(error, { extra: context });
}
}
const errorLogger = new ErrorLogger();
// Integrar com o cliente resiliente
class LoggingResilientClient extends ResilientClient {
async request(fn, context = {}) {
try {
const result = await super.request(fn);
return result;
} catch (error) {
errorLogger.logError(error, context);
throw error;
}
}
}
Estratégias Avançadas
1. Fallback e Degradação Graceful
Copy
class FallbackManager {
constructor() {
this.fallbackStrategies = new Map();
}
registerFallback(operation, fallbackFn) {
this.fallbackStrategies.set(operation, fallbackFn);
}
async executeWithFallback(operation, primaryFn, context = {}) {
try {
return await primaryFn();
} catch (error) {
console.warn(`❌ Operação principal falhou: ${error.message}`);
const fallback = this.fallbackStrategies.get(operation);
if (fallback) {
console.info('🔄 Executando estratégia de fallback...');
try {
return await fallback(context, error);
} catch (fallbackError) {
console.error(`❌ Fallback também falhou: ${fallbackError.message}`);
throw error; // Lançar erro original
}
}
throw error;
}
}
}
const fallbackManager = new FallbackManager();
// Registrar fallbacks
fallbackManager.registerFallback('buscar_empresa', async (context, error) => {
// Retornar dados do cache local se disponível
const cachedData = localStorage.getItem(`empresa_${context.cnpj}`);
if (cachedData) {
console.info('📋 Usando dados do cache local');
return JSON.parse(cachedData);
}
// Retornar dados básicos do CNPJ se possível
if (context.cnpj) {
console.info('📊 Retornando dados básicos do CNPJ');
return {
cnpj: context.cnpj,
status: 'dados_limitados',
source: 'fallback'
};
}
throw error;
});
// Uso
async function buscarEmpresaComFallback(cnpj) {
return await fallbackManager.executeWithFallback(
'buscar_empresa',
() => buscarEmpresaResilient(cnpj),
{ cnpj }
);
}
2. Health Check e Monitoring
Copy
class HealthMonitor {
constructor(options = {}) {
this.checkInterval = options.checkInterval || 30000; // 30 segundos
this.healthThreshold = options.healthThreshold || 0.8; // 80% de sucesso
this.windowSize = options.windowSize || 50; // Últimas 50 requisições
this.requestHistory = [];
this.isHealthy = true;
this.lastHealthCheck = null;
this.startMonitoring();
}
recordRequest(success, responseTime, error = null) {
const record = {
timestamp: Date.now(),
success,
responseTime,
error: error ? error.name : null
};
this.requestHistory.push(record);
// Manter apenas as últimas N requisições
if (this.requestHistory.length > this.windowSize) {
this.requestHistory.shift();
}
this.checkHealth();
}
checkHealth() {
if (this.requestHistory.length < 10) {
return; // Não temos dados suficientes
}
const recentRequests = this.requestHistory.slice(-this.windowSize);
const successRate = recentRequests.filter(r => r.success).length / recentRequests.length;
const wasHealthy = this.isHealthy;
this.isHealthy = successRate >= this.healthThreshold;
this.lastHealthCheck = Date.now();
if (wasHealthy !== this.isHealthy) {
const status = this.isHealthy ? 'SAUDÁVEL' : 'DEGRADADO';
console.log(`🏥 Status da API mudou para: ${status} (taxa de sucesso: ${(successRate * 100).toFixed(1)}%)`);
}
}
getHealthStatus() {
const recentRequests = this.requestHistory.slice(-this.windowSize);
const successCount = recentRequests.filter(r => r.success).length;
const avgResponseTime = recentRequests.reduce((sum, r) => sum + r.responseTime, 0) / recentRequests.length;
return {
healthy: this.isHealthy,
successRate: recentRequests.length > 0 ? successCount / recentRequests.length : 0,
averageResponseTime: avgResponseTime || 0,
totalRequests: this.requestHistory.length,
recentRequests: recentRequests.length,
lastHealthCheck: this.lastHealthCheck,
timestamp: Date.now()
};
}
startMonitoring() {
setInterval(() => {
const status = this.getHealthStatus();
if (!status.healthy) {
console.warn('⚠️ API em estado degradado:', status);
}
// Enviar métricas para sistema de monitoramento
this.sendMetrics(status);
}, this.checkInterval);
}
sendMetrics(status) {
// Enviar para Prometheus, DataDog, etc.
// prometheus.register.getSingleMetric('api_health').set(status.healthy ? 1 : 0);
// prometheus.register.getSingleMetric('api_success_rate').set(status.successRate);
}
}
const healthMonitor = new HealthMonitor();
// Integrar com cliente resiliente
class MonitoredResilientClient extends LoggingResilientClient {
async request(fn, context = {}) {
const startTime = Date.now();
try {
const result = await super.request(fn, context);
const responseTime = Date.now() - startTime;
healthMonitor.recordRequest(true, responseTime);
return result;
} catch (error) {
const responseTime = Date.now() - startTime;
healthMonitor.recordRequest(false, responseTime, error);
throw error;
}
}
getHealthStatus() {
return {
...super.getHealthStatus(),
health: healthMonitor.getHealthStatus()
};
}
}
Implementação Completa
Cliente Final com Todas as Funcionalidades
Copy
class SpeedioClient {
constructor(options = {}) {
this.username = options.username || process.env.SPEEDIO_USERNAME;
this.password = options.password || process.env.SPEEDIO_PASSWORD;
this.baseUrl = options.baseUrl || 'https://api-get-leads.speedio.com.br';
if (!this.username || !this.password) {
throw new Error('Credenciais Speedio são obrigatórias');
}
this.auth = Buffer.from(`${this.username}:${this.password}`).toString('base64');
// Componentes
this.retryManager = new RetryManager(options.retry);
this.circuitBreaker = new CircuitBreaker(options.circuitBreaker);
this.fallbackManager = new FallbackManager();
this.healthMonitor = new HealthMonitor(options.health);
this.errorLogger = new ErrorLogger(options.logging);
this.setupInterceptors();
this.registerFallbacks();
}
setupInterceptors() {
axios.interceptors.response.use(
response => response,
error => {
throw parseSpeedioError(error);
}
);
}
registerFallbacks() {
this.fallbackManager.registerFallback('buscar_cnpj', async (context, error) => {
// Implementar estratégia de fallback para busca de CNPJ
const cachedData = this.getFromCache(`cnpj_${context.cnpj}`);
if (cachedData) {
return cachedData;
}
throw error;
});
}
async buscarCNPJ(cnpjs, options = {}) {
const context = { operation: 'buscar_cnpj', cnpjs };
return await this.executeRequest(async () => {
const response = await axios.get(`${this.baseUrl}/search_enriched_leads/cnpj`, {
params: { cnpjs: JSON.stringify(cnpjs) },
headers: this.getHeaders(),
timeout: options.timeout || 10000
});
return response.data;
}, context);
}
async validarDados(dados, options = {}) {
const context = { operation: 'validar_dados', count: dados.length };
return await this.executeRequest(async () => {
const response = await axios.post(`${this.baseUrl}/validate`,
{ data: dados },
{
headers: this.getHeaders(),
timeout: options.timeout || 15000
}
);
return response.data;
}, context);
}
async executeRequest(fn, context = {}) {
const startTime = Date.now();
try {
const result = await this.fallbackManager.executeWithFallback(
context.operation,
() => this.circuitBreaker.execute(
() => this.retryManager.executeWithRetry(fn)
),
context
);
const responseTime = Date.now() - startTime;
this.healthMonitor.recordRequest(true, responseTime);
return result;
} catch (error) {
const responseTime = Date.now() - startTime;
this.healthMonitor.recordRequest(false, responseTime, error);
this.errorLogger.logError(error, context);
throw error;
}
}
getHeaders() {
return {
'Authorization': `Basic ${this.auth}`,
'Content-Type': 'application/json',
'User-Agent': 'Speedio-Client/1.0'
};
}
getStatus() {
return {
circuitBreaker: this.circuitBreaker.getStatus(),
health: this.healthMonitor.getHealthStatus(),
timestamp: new Date().toISOString()
};
}
// Métodos de cache (implementar conforme necessário)
getFromCache(key) {
// Implementar cache (localStorage, Redis, etc.)
return null;
}
setCache(key, data, ttl = 3600) {
// Implementar cache
}
}
// Uso do cliente completo
const client = new SpeedioClient({
retry: {
maxRetries: 3,
baseDelay: 1000
},
circuitBreaker: {
failureThreshold: 5,
recoveryTimeout: 60000
},
health: {
checkInterval: 30000,
healthThreshold: 0.8
},
logging: {
logLevel: 'error',
includeStackTrace: true
}
});
// Exemplos de uso
try {
const empresas = await client.buscarCNPJ(['21071712000171']);
console.log('✅ Empresas encontradas:', empresas.length);
} catch (error) {
if (error instanceof AuthenticationError) {
console.error('❌ Problema de autenticação - verifique credenciais');
} else if (error instanceof RateLimitError) {
console.error(`❌ Rate limit - tente novamente em ${error.retryAfter}s`);
} else {
console.error('❌ Erro inesperado:', error.message);
}
}
// Monitorar status
setInterval(() => {
const status = client.getStatus();
if (!status.health.healthy) {
console.warn('⚠️ API em estado degradado');
}
}, 60000);
Próximos Passos
Melhores Práticas
Implemente padrões de excelência no uso da API
Monitoramento
Configure monitoramento e alertas em produção
Performance
Otimize a performance das suas integrações
Segurança
Implemente práticas avançadas de segurança
Importante: O tratamento de erros robusto é fundamental para aplicações em produção. Implemente pelo menos retry com backoff exponencial e circuit breaker para garantir a resiliência da sua aplicação.
