Boas práticas de design de respostas de erro seguindo RFC 7807

1. Fundamentos da RFC 7807 (Problem Details for HTTP APIs)

1.1. Origem e objetivos da especificação

A RFC 7807, publicada em 2016 pelo IETF, estabelece um formato padronizado para representar problemas em APIs HTTP. Seu principal objetivo é eliminar a fragmentação de formatos de erro entre diferentes APIs, proporcionando uma experiência consistente para desenvolvedores e ferramentas de integração.

1.2. Estrutura básica do corpo de resposta

A especificação define cinco campos principais que compõem o corpo da resposta de erro:

{
  "type": "https://api.exemplo.com/erros/nao-encontrado",
  "title": "Recurso não encontrado",
  "status": 404,
  "detail": "O usuário com ID 12345 não foi encontrado no sistema.",
  "instance": "/api/usuarios/12345"
}
  • type: URI que identifica o tipo de problema
  • title: Resumo legível do problema
  • status: Código HTTP correspondente
  • detail: Descrição específica do erro
  • instance: URI do recurso onde o erro ocorreu

1.3. Diferenças entre RFC 7807 e formatos proprietários

Diferente de formatos proprietários comuns, a RFC 7807 oferece:

// Formato proprietário (inconsistente)
{
  "error": "Usuário não encontrado",
  "code": 404
}

// Formato RFC 7807 (padronizado)
{
  "type": "https://api.exemplo.com/erros/nao-encontrado",
  "title": "Recurso não encontrado",
  "status": 404,
  "detail": "O usuário com ID 12345 não foi encontrado.",
  "instance": "/api/usuarios/12345"
}

2. Mapeamento de Campos Obrigatórios e Opcionais

2.1. Campos obrigatórios

Os campos obrigatórios garantem a consistência mínima das respostas:

{
  "type": "https://api.exemplo.com/erros/erro-interno",
  "title": "Erro interno do servidor",
  "status": 500
}

2.2. Campos recomendados

Para melhor experiência do desenvolvedor, inclua detail e instance:

{
  "type": "https://api.exemplo.com/erros/validacao",
  "title": "Erro de validação",
  "status": 422,
  "detail": "O campo 'email' deve conter um endereço de e-mail válido.",
  "instance": "/api/usuarios/criar"
}

2.3. Extensões customizadas

Adicione campos específicos do domínio quando necessário:

{
  "type": "https://api.exemplo.com/erros/validacao",
  "title": "Erro de validação",
  "status": 422,
  "detail": "Múltiplos campos inválidos.",
  "instance": "/api/pedidos/criar",
  "errors": [
    {
      "field": "email",
      "message": "Formato de e-mail inválido"
    },
    {
      "field": "telefone",
      "message": "Número deve ter 11 dígitos"
    }
  ],
  "timestamp": "2024-01-15T10:30:00Z",
  "traceId": "abc-123-def-456"
}

3. Estratégias para Definir URIs de type

3.1. Criação de URIs estáveis e documentadas

Utilize URIs que apontem para documentação detalhada:

{
  "type": "https://docs.api.exemplo.com/erros/v1/validacao-campo-obrigatorio",
  "title": "Campo obrigatório não informado",
  "status": 400,
  "detail": "O campo 'nome' é obrigatório."
}

3.2. URIs relativas vs. absolutas

Em desenvolvimento, URIs relativas facilitam testes locais:

// Desenvolvimento
"type": "/erros/validacao"

// Produção
"type": "https://api.exemplo.com/erros/v1/validacao"

3.3. Versionamento de problem types

Evolua os tipos sem quebrar clientes existentes:

// Versão 1
"type": "https://api.exemplo.com/erros/v1/validacao"

// Versão 2 (adiciona novos campos)
"type": "https://api.exemplo.com/erros/v2/validacao"

4. Tratamento de Erros de Validação e Erros de Negócio

4.1. Representação de múltiplos erros de validação

{
  "type": "https://api.exemplo.com/erros/validacao-multipla",
  "title": "Erros de validação múltiplos",
  "status": 422,
  "detail": "Foram encontrados 3 erros de validação.",
  "instance": "/api/produtos/criar",
  "errors": [
    {
      "field": "preco",
      "message": "Preço deve ser maior que zero",
      "value": -10
    },
    {
      "field": "quantidade",
      "message": "Quantidade deve ser inteira",
      "value": 1.5
    }
  ]
}

4.2. Uso de detail para mensagens dinâmicas

{
  "type": "https://api.exemplo.com/erros/saldo-insuficiente",
  "title": "Saldo insuficiente",
  "status": 422,
  "detail": "Saldo atual: R$ 50,00. Valor necessário: R$ 200,00.",
  "instance": "/api/transferencias"
}

4.3. Exemplos práticos: 400 vs. 422

Erro 400 (Bad Request) para problemas de sintaxe:

{
  "type": "https://api.exemplo.com/erros/json-invalido",
  "title": "JSON inválido",
  "status": 400,
  "detail": "Erro de parsing na linha 3, coluna 10: caractere inesperado."
}

Erro 422 (Unprocessable Entity) para regras de negócio:

{
  "type": "https://api.exemplo.com/erros/limite-credito-excedido",
  "title": "Limite de crédito excedido",
  "status": 422,
  "detail": "O valor da compra (R$ 5.000,00) excede o limite disponível (R$ 3.000,00).",
  "instance": "/api/compras"
}

5. Integração com Códigos de Status HTTP e Headers

5.1. Consistência entre status no body e HTTP

O campo status deve corresponder exatamente ao código HTTP:

HTTP/1.1 404 Not Found
Content-Type: application/problem+json

{
  "type": "https://api.exemplo.com/erros/nao-encontrado",
  "title": "Recurso não encontrado",
  "status": 404,
  "detail": "Produto com SKU 'ABC-123' não encontrado."
}

5.2. Headers específicos

Content-Type: application/problem+json

5.3. Caching e retry

HTTP/1.1 429 Too Many Requests
Content-Type: application/problem+json
Retry-After: 120
Cache-Control: no-store

{
  "type": "https://api.exemplo.com/erros/limite-taxa",
  "title": "Limite de requisições excedido",
  "status": 429,
  "detail": "Aguarde 120 segundos antes de realizar novas requisições."
}

6. Boas Práticas para Documentação e Versionamento

6.1. Catálogo centralizado de problem types

Mantenha uma documentação acessível em:

https://docs.api.exemplo.com/erros/

6.2. Versionamento semântico

https://api.exemplo.com/erros/v1/validacao
https://api.exemplo.com/erros/v2/validacao

6.3. OpenAPI + RFC 7807

components:
  schemas:
    Problema:
      type: object
      properties:
        type:
          type: string
          format: uri
        title:
          type: string
        status:
          type: integer
        detail:
          type: string
        instance:
          type: string
          format: uri
      required:
        - type
        - title
        - status

7. Implementação em Diferentes Linguagens e Frameworks

7.1. Middleware em Node.js (Express)

function errorHandler(err, req, res, next) {
  const problema = {
    type: 'https://api.exemplo.com/erros/erro-interno',
    title: 'Erro interno do servidor',
    status: err.status || 500,
    detail: err.message || 'Ocorreu um erro inesperado.',
    instance: req.originalUrl
  };

  res.status(problema.status)
     .type('application/problem+json')
     .json(problema);
}

7.2. Handler em Python (FastAPI)

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

app = FastAPI()

@app.exception_handler(Exception)
async def problem_handler(request: Request, exc: Exception):
    return JSONResponse(
        status_code=500,
        content={
            "type": "https://api.exemplo.com/erros/erro-interno",
            "title": "Erro interno do servidor",
            "status": 500,
            "detail": str(exc),
            "instance": str(request.url)
        },
        media_type="application/problem+json"
    )

7.3. Adaptação para GraphQL

{
  "errors": [
    {
      "message": "Usuário não encontrado",
      "extensions": {
        "type": "https://api.exemplo.com/erros/nao-encontrado",
        "status": 404,
        "detail": "Usuário com ID 12345 não encontrado"
      }
    }
  ]
}

8. Monitoramento e Observabilidade de Erros

8.1. Rastreamento com instance

{
  "type": "https://api.exemplo.com/erros/pagamento-recusado",
  "title": "Pagamento recusado",
  "status": 402,
  "detail": "Cartão sem fundos suficientes.",
  "instance": "/api/pagamentos/transacao-abc-123"
}

8.2. Correlação com OpenTelemetry

{
  "type": "https://api.exemplo.com/erros/timeout-banco",
  "title": "Timeout na comunicação com banco",
  "status": 504,
  "detail": "Banco de dados não respondeu em 30 segundos.",
  "traceId": "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"
}

8.3. Alertas baseados em tipo de problema

Configure alertas para tipos específicos:

Alerta: Erro crítico detectado
Tipo: https://api.exemplo.com/erros/banco-indisponivel
Frequência: 50 ocorrências nos últimos 5 minutos
Status: 503

Referências