Error Handling

Tratamento de Erros Padronizado - C-Suite

Visão Geral

Este documento descreve o padrão de tratamento de erros padronizado para todo o ecossistema C-Suite. O objetivo é ter respostas de erro consistentes e informativas em todos os apps.

Módulo de Erros

Localização

O módulo principal está em: common_errors.py (raiz do projeto)

Uso Básico

from common_errors import CSuiteError, ErrorCode

# Lançar erro padronizado
raise CSuiteError(
    code=ErrorCode.CUSTOMER_NOT_FOUND,
    message="Cliente não encontrado",
    status_code=404,
    details={"customer_id": 123}
)

Formato de Resposta Padronizado

Todas as respostas de erro seguem este formato:

{
  "error": {
    "code": "ERR_4000",
    "message": "Cliente não encontrado",
    "timestamp": "2025-12-01T10:30:00Z",
    "details": {
      "customer_id": 123
    }
  }
}

Campos

Códigos de Erro Padronizados

Erros Gerais (1xxx)

Código Descrição HTTP Status
ERR_1000 Erro interno 500
ERR_1001 Erro de validação 400
ERR_1002 Não autorizado 401
ERR_1003 Acesso negado 403
ERR_1004 Recurso não encontrado 404
ERR_1005 Conflito 409
ERR_1006 Rate limit excedido 429
ERR_1007 Serviço indisponível 503
ERR_1008 Timeout 504

Erros de Banco de Dados (2xxx)

Código Descrição HTTP Status
ERR_2000 Erro de conexão com banco 503
ERR_2001 Erro em query 500
ERR_2002 Erro em transação 500
ERR_2003 Violação de constraint 409

Erros de Serviços Externos (3xxx)

Código Descrição HTTP Status
ERR_3000 Erro em serviço externo 502
ERR_3001 Timeout em serviço externo 504
ERR_3002 Serviço externo indisponível 503

Erros de Negócio 4C (4xxx)

Código Descrição HTTP Status
ERR_4000 Cliente não encontrado 404
ERR_4001 Candidato inválido 400
ERR_4002 Score de intent muito baixo 400
ERR_4003 Margem insuficiente 400
ERR_4004 Violação de opt-out 403
ERR_4005 Limite de contatos excedido 429
ERR_4006 Janela de silêncio ativa 429
ERR_4007 Nenhuma oferta válida 400

Erros de Negócio C-Suite (5xxx)

Código Descrição HTTP Status
ERR_5000 Organização não encontrada 404
ERR_5001 Agente não encontrado 404
ERR_5002 Política não encontrada 404
ERR_5003 Política inválida 400
ERR_5004 Métrica não encontrada 404
ERR_5005 Alerta não encontrado 404

Erros de Integração (6xxx)

Código Descrição HTTP Status
ERR_6000 Erro de sincronização 500
ERR_6001 Erro ao carregar políticas 500
ERR_6002 Erro ao sincronizar métricas 500

Integração com FastAPI

Configurar Exception Handler Global

from fastapi import FastAPI
from common_errors import error_handler

app = FastAPI()

# Adiciona handler global
app.add_exception_handler(Exception, error_handler)

Usar em Endpoints

from fastapi import APIRouter
from common_errors import CSuiteError, ErrorCode, not_found_error

router = APIRouter()

@router.get("/customers/{customer_id}")
async def get_customer(customer_id: int):
    customer = await find_customer(customer_id)
    if not customer:
        raise not_found_error("Cliente", customer_id)

    return customer

Funções de Conveniência

Erro 404 - Not Found

from common_errors import not_found_error

raise not_found_error("Cliente", customer_id=123)
# Retorna: {"error": {"code": "ERR_1004", "message": "Cliente não encontrado: 123", ...}}

Erro 400 - Validation Error

from common_errors import validation_error

raise validation_error(
    "Dados inválidos",
    details={"field": "email", "reason": "formato inválido"}
)

Erro 401 - Unauthorized

from common_errors import unauthorized_error

raise unauthorized_error("Token inválido ou expirado")

Erro 403 - Forbidden

from common_errors import forbidden_error

raise forbidden_error("Você não tem permissão para acessar este recurso")

Erro 409 - Conflict

from common_errors import conflict_error

raise conflict_error(
    "Política já existe",
    details={"rule_key": "min_margin", "organization_id": 1}
)

Erro 503 - Service Unavailable

from common_errors import service_unavailable_error

raise service_unavailable_error(
    "Feature Service",
    details={"endpoint": "/features/customer", "timeout": 5.0}
)

Exemplos de Uso

Exemplo 1: Erro de Cliente Não Encontrado

@router.get("/customers/{customer_id}")
async def get_customer(customer_id: int):
    customer = db.query(Customer).filter(Customer.id == customer_id).first()
    if not customer:
        raise CSuiteError(
            code=ErrorCode.CUSTOMER_NOT_FOUND,
            message=f"Cliente {customer_id} não encontrado",
            status_code=404,
            details={"customer_id": customer_id}
        )
    return customer

Exemplo 2: Erro de Validação

@router.post("/decide")
async def decide(request: DecideRequest):
    if not request.customer_id:
        raise validation_error(
            "customer_id é obrigatório",
            details={"field": "customer_id", "type": "required"}
        )

    if request.customer_id < 1:
        raise validation_error(
            "customer_id deve ser positivo",
            details={"field": "customer_id", "value": request.customer_id}
        )

    # ... resto do código

Exemplo 3: Erro de Serviço Externo

async def call_feature_service(customer_id: int):
    try:
        async with httpx.AsyncClient() as client:
            response = await client.post(
                f"{FEATURE_URL}/features/customer",
                json={"customer_id": customer_id},
                timeout=5.0
            )
            response.raise_for_status()
            return response.json()
    except httpx.TimeoutException:
        raise service_unavailable_error(
            "Feature Service",
            details={"timeout": 5.0, "customer_id": customer_id}
        )
    except httpx.HTTPStatusError as e:
        raise CSuiteError(
            code=ErrorCode.EXTERNAL_SERVICE_ERROR,
            message=f"Erro ao chamar Feature Service: {e.response.status_code}",
            status_code=502,
            details={"service": "feature_service", "http_status": e.response.status_code}
        )

Exemplo 4: Erro de Banco de Dados

async def get_organization(org_id: int):
    try:
        org = db.query(Organization).filter(Organization.id == org_id).first()
        if not org:
            raise not_found_error("Organização", org_id)
        return org
    except Exception as e:
        logger.exception(f"Erro ao buscar organização {org_id}")
        raise CSuiteError(
            code=ErrorCode.DB_QUERY_ERROR,
            message="Erro ao consultar banco de dados",
            status_code=500,
            details={"organization_id": org_id},
            cause=e
        )

Migração de Código Existente

Antes (sem padrão)

from fastapi import HTTPException

if not customer:
    raise HTTPException(status_code=404, detail="Cliente não encontrado")

Depois (padronizado)

from common_errors import not_found_error

if not customer:
    raise not_found_error("Cliente", customer_id)

Ou usando CSuiteError diretamente

from common_errors import CSuiteError, ErrorCode

if not customer:
    raise CSuiteError(
        code=ErrorCode.CUSTOMER_NOT_FOUND,
        message=f"Cliente {customer_id} não encontrado",
        status_code=404,
        details={"customer_id": customer_id}
    )

Logging

Todos os erros são automaticamente logados com:
- Código de erro
- Mensagem
- Status HTTP
- Detalhes (se fornecidos)
- Traceback (se houver causa)

Ambiente de Desenvolvimento vs Produção

Desenvolvimento

Em desenvolvimento (ENVIRONMENT=development), erros incluem:
- Mensagem completa da exceção
- Tipo da exceção
- Traceback completo

Produção

Em produção, erros são genéricos:
- Mensagem genérica ("Erro interno do servidor")
- Sem detalhes técnicos
- Traceback apenas em logs

Próximos Passos

  1. ✅ Módulo de erros padronizados criado
  2. ⏳ Migrar todos os apps para usar common_errors
  3. ⏳ Adicionar exception handlers em todos os apps FastAPI
  4. ⏳ Criar testes para tratamento de erros
  5. ⏳ Documentar códigos de erro específicos por app

🔊 Text-to-Speech

1.0x
1.0
Pronto para reproduzir