0Pricing
Lua Academy · Aula

Objetos de erro estruturados

Passe tabelas como objetos de erro para transmitir tipo e contexto.

Objetos de erro estruturados é uma aula grátis de Lua Academy no CoddyKit. Esta é a aula 4 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de Lua Academy, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de Lua Academy inclui 4 aulas no total.

Por que usar erros estruturados?

Erros simples em strings são difíceis de tratar programaticamente. Objetos de erro estruturados (tabelas) contêm informações de tipo e dados de contexto, e podem ser inspecionados e tratados pelos chamadores. Isso permite despachar com base em erros sem analisar strings.

-- Plain string: hard to handle programmatically
error("database error: connection refused")

-- Structured: type + data
error({
  type = "DatabaseError",
  code = "CONN_REFUSED",
  host = "localhost",
  port = 5432,
  message = "connection refused"
})

Padrão de construtor de erros

Crie uma função fábrica de erros para cada tipo de erro. A fábrica constrói uma tabela com campos consistentes: tipo, mensagem e qualquer contexto relevante. Um metamétodo __tostring faz com que o erro seja exibido de forma adequada.

local ErrorMT = {__tostring = function(e)
  return string.format("[%s] %s", e.type, e.message)
end}

local function makeError(errType, msg, data)
  local e = {type=errType, message=msg}
  if data then for k,v in pairs(data) do e[k]=v end end
  return setmetatable(e, ErrorMT)
end

local E = {
  notFound = function(name) return makeError("NOT_FOUND","not found: "..name,{name=name}) end,
  badInput = function(msg,field) return makeError("BAD_INPUT",msg,{field=field}) end,
}

local ok, err = pcall(error, E.notFound("user:42"))
print(tostring(err))   -- [NOT_FOUND] not found: user:42

Verificação de tipo de objetos de erro

Depois de capturar um erro, verifique se ele é uma tabela com um campo de tipo conhecido. Isso permite escolher diferentes estratégias de recuperação com base no tipo de erro, sem depender da correspondência de padrões em strings.

local function handleRequest(fn)
  local ok, err = pcall(fn)
  if ok then return true end
  if type(err) == "table" then
    if err.type == "NOT_FOUND" then
      print("404: " .. err.message)
    elseif err.type == "BAD_INPUT" then
      print("400: " .. err.message .. " (field: " .. (err.field or "?") .. ")")
    else
      print("500: unhandled error: " .. tostring(err))
    end
  else
    print("500: " .. tostring(err))
  end
  return false
end

Hierarquia de erros

Simule uma hierarquia de erros verificando campos is_a ou usando metatabelas. Os tipos de erro filhos herdam os campos do tipo pai e podem ser tratados como o pai pelo código que não precisa de detalhes específicos.

local function isError(e, errType)
  if type(e) ~= "table" then return false end
  return e.type == errType or e.parentType == errType
end

local function makeDbError(code, msg)
  return {type="DbError:"..code, parentType="DbError", code=code, message=msg}
end

local err = makeDbError("TIMEOUT","query timed out")
print(isError(err, "DbError"))          -- true
print(isError(err, "DbError:TIMEOUT"))  -- true
print(isError(err, "NetworkError"))     -- false

Envolvimento de erros

Ao capturar e gerar novamente um erro, envolva o erro original para adicionar contexto sem perdê-lo. O envoltório tem seu próprio tipo e contém o erro original como causa.

local function wrapError(msg, cause)
  return {
    type = "WrappedError",
    message = msg,
    cause = cause,
  }
end

local function loadConfig(path)
  local ok, err = pcall(function()
    local f = assert(io.open(path,"r"))
    local content = f:read("a")
    f:close()
    return content
  end)
  if not ok then
    error(wrapError("failed to load config: "..path, err))
  end
end

local ok2, e = pcall(loadConfig, "missing.cfg")
if not ok2 then
  print(e.message)
  print("Caused by:", tostring(e.cause))
end

Códigos de erro versus tipos de erro

Duas convenções comuns são: códigos de erro (numéricos, como códigos de status HTTP) e strings de tipo de erro (nomes semânticos). Os códigos de erro são fáceis de comparar numericamente; as strings de tipo são autoexplicativas. Muitos sistemas usam ambos.

local STATUS = {OK=200, NOT_FOUND=404, SERVER_ERROR=500, BAD_REQUEST=400}

local function makeStatusError(status, msg)
  return {status=status, message=msg, type="HTTPError"}
end

local function handleError(e)
  if e.status == STATUS.NOT_FOUND then
    print("Resource not found:", e.message)
  elseif e.status >= 500 then
    print("Server error:", e.message)
  else
    print("Error", e.status, e.message)
  end
end

handleError(makeStatusError(404, "user not found"))

Pilha de erros (cadeia de causas)

Quando um erro é causado por outro, encadeie-os. Isso fornece uma visão completa do que deu errado em cada camada da aplicação. Desfaça a cadeia para registrar ou exibir toda a história do erro.

local function unwindCause(e, depth)
  depth = depth or 0
  local pad = string.rep("  ", depth)
  if type(e) == "table" then
    print(pad .. (e.type or "Error") .. ": " .. (e.message or "?"))
    if e.cause then unwindCause(e.cause, depth+1) end
  else
    print(pad .. tostring(e))
  end
end

local inner = {type="IoError", message="permission denied"}
local outer = {type="ConfigError", message="cannot load config", cause=inner}
unwindCause(outer)
-- ConfigError: cannot load config
--   IoError: permission denied

Erro no contexto de uma função de retorno

Quando ocorrem erros dentro de funções de retorno (manipuladores de eventos, iteradores), eles se propagam para o chamador da função de retorno. Use pcall para capturá-los e relatá-los com contexto sobre qual função de retorno falhou.

local function runCallbacks(callbacks, data)
  local errors = {}
  for name, fn in pairs(callbacks) do
    local ok, err = pcall(fn, data)
    if not ok then
      errors[#errors+1] = {callback=name, error=err}
    end
  end
  return errors
end

local cbs = {
  validate = function(d) assert(d.name, "name required") end,
  transform = function(d) d.name = d.name:upper() end,
}

local errs = runCallbacks(cbs, {})
for _, e in ipairs(errs) do
  print(e.callback, "->", e.error)
end

Exibição dos detalhes do erro

Um auxiliar que exibe um objeto de erro de maneira estruturada e legível, tratando tanto erros em strings quanto erros em tabelas. Isso é útil nos limites da aplicação, onde os erros são registrados ou exibidos aos usuários.

local function printError(err, prefix)
  prefix = prefix or "Error"
  if type(err) ~= "table" then
    print(prefix .. ": " .. tostring(err))
    return
  end
  print(prefix .. " [" .. (err.type or "unknown") .. "]")
  print("  Message: " .. (err.message or "?"))
  for k, v in pairs(err) do
    if k ~= "type" and k ~= "message" and k ~= "cause" then
      print("  " .. k .. ": " .. tostring(v))
    end
  end
  if err.cause then printError(err.cause, "  Caused by") end
end

Asserções com erros estruturados

Crie um assertT (assert com erros tipados) que gere um erro estruturado em vez de uma string simples. Isso facilita testar tipos de erro específicos nos chamadores.

local function assertT(cond, errType, msg, data)
  if not cond then
    local e = {type=errType, message=msg}
    if data then for k,v in pairs(data) do e[k]=v end end
    error(e, 2)
  end
  return cond
end

local function createUser(name, age)
  assertT(type(name)=="string", "BAD_INPUT", "name must be string", {field="name"})
  assertT(age >= 0 and age <= 150, "BAD_INPUT", "invalid age", {field="age", value=age})
  return {name=name, age=age}
end

local ok, err = pcall(createUser, "Alice", -5)
if not ok then print(err.type, err.field, err.value) end

Verificação rápida

Qual é a principal vantagem de passar uma tabela para error() em vez de uma string?

Recapitulação: erros estruturados

Resumo:

  • Passe tabelas para error() para obter erros estruturados e inspecionáveis
  • Inclua: tipo, mensagem e campos de contexto relevantes
  • Adicione __tostring para obter uma saída legível
  • Envolva os erros para adicionar contexto sem perder a causa
  • Escolha o tratamento pelo tipo: verifique err.type, não padrões de string

Perguntas Frequentes

A aula “Objetos de erro estruturados” é grátis?

Sim — o texto completo de “Objetos de erro estruturados” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de Lua Academy, atualize para CoddyKit PRO. O curso de Lua Academy inclui 4 aulas no total.

O que vou aprender em “Objetos de erro estruturados”?

Passe tabelas como objetos de erro para transmitir tipo e contexto. Você pratica Lua Academy com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.

Preciso ter experiência prévia para começar Lua Academy?

Nenhuma experiência prévia é necessária. Lua Academy no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 4 de 4.

Quanto tempo leva a aula “Objetos de erro estruturados”?

A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.

Posso escrever e executar código nesta aula de Lua Academy?

Sim. Cada aula de Lua Academy inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.

Todas as aulas deste curso

  1. A função error()
  2. Chamadas protegidas com pcall
  3. xpcall e manipuladores de mensagens
  4. Objetos de erro estruturados
← Voltar para Lua Academy