Resposta rápida
- Erro esperado
- situação prevista — a chave não existe,
Found()devolve falso. Resolve comsetStatusCode+setResponse+Return, em linhas separadas. - Erro inesperado
- falha de programação — variável errada, campo que não existe. Sem tratamento, vira uma mensagem genérica de erro interno.
- O detalhe que explica o resto
setFaultrecebe um parâmetro só. Não existe onde passar o código HTTP.- A adoção real
try/catchainda é minoria no código de Protheus — a maior parte do tratamento de erro segue resolvido peloErrorBlock()clássico do AdvPL.- A grafia trava
- o fechamento é
endtry, colado —end trycom espaço não é reconhecido pela linguagem.
Nesta página5 seções
Uma API que devolve 500 para tudo — chave que não existe, campo obrigatório
ausente, NULL inesperado num agregador — não está “tratando erro”: está
escondendo a diferença entre duas causas que pedem respostas diferentes. Erro
esperado é situação prevista, que o próprio fluxo sabe reconhecer. Erro
inesperado é falha de programação, que ninguém previu. O TLPP dá para os
dois um caminho, e vale entender por que um é natural e o outro esbarra numa
limitação real do próprio objeto de resposta.
Os artigos anteriores desta série já mostraram que
o oRest chega pronto dentro do método anotado,
sem que o fonte precise declará-lo, e que dentro de uma classe com
annotation de verbo o acesso
é sempre oRest:, nunca ::. Falta a parte que decide o que a API responde
quando alguma coisa sai errado — e é aí que a distinção entre erro esperado
e erro inesperado importa.
Erro esperado: dizer o que aconteceu, com o código certo
Quando o DbSeek não encontra a chave, ou o getPathParamsRequest() não traz
o parâmetro que a regra de negócio exige, a resposta certa não é lançar uma
exceção — é responder. setStatusCode(<código>) e setResponse(<json>), em
linhas separadas, seguidos de Return, é o caminho mais comum para esse
caso. 400 é, disparado, o código mais usado para sinalizar que o pedido
chegou errado — mais do que qualquer outro código de erro.
setStatusCode recebe um número inteiro, e nada além disso: nenhuma forma
com dois argumentos existe na linguagem. E o framework não critica o valor —
aceita o que for passado e devolve exatamente aquilo no cabeçalho da
resposta, mesmo que não seja um código HTTP válido.
setFault tem um parâmetro só — e é por isso que o erro de negócio some
setFault sempre recebe um único argumento: o corpo do erro já pronto, em
string.
oRest:SetFault( jResult['result']:ToJson() )
oRest:setFault('{"error": "' + cMsg + '"}')
oRest:setFault(fGetJsonErro(nErroCod, cMsg, cMsgDesc, cMsgTipo))Não existe uma segunda forma com código HTTP embutido — nenhuma chamada
válida aceita um segundo argumento numérico. setFault simplesmente não
tem parâmetro para receber um 401, um 403 ou um 409: só o corpo. É
essa limitação, não falta de cuidado de quem escreveu o serviço, que
explica por que tanta documentação de curso resume setFault como “sempre
devolve erro de servidor”: ele não tem onde dizer outra coisa. Erro de
negócio e falha técnica ficam indistinguíveis para quem consome, a não ser
que o fonte separe os dois de propósito — respondendo o esperado com
setStatusCode/setResponse, e reservando setFault só para o que
realmente é falha.
Erro inesperado: try/catch existe, e ainda é minoria
Para a falha que ninguém previu — variável que não existe no escopo, campo
que mudou de nome, um NULL que quebra uma conta —, o try/catch do TLPP
troca a mensagem genérica por uma que aponta causa. Só que ele ainda é
prática rara no código real de Protheus: a maior parte do tratamento de
erro, contando as três gerações da linguagem, ainda depende do
ErrorBlock() clássico do AdvPL, com begin sequence como opção
intermediária. Vale usar o try/catch numa API nova — é o caminho certo
para devolver um status previsível —, mas não vale apresentá-lo como
prática consolidada: no material que a própria TOTVS distribui, ele ainda é
a exceção.
A grafia do fechamento trava quem vem de outra linguagem: é endtry,
colado. end try com espaço não é reconhecido pela linguagem. E o catch
não usa parênteses — é catch oErro, a variável ao lado, sem ().
Disparar erro de propósito: prefira UserException
Quando o próprio fonte precisa interromper a execução e sinalizar uma falha,
a forma mais usada em código real não é Throw com ErrorClass(): é
UserException(cMensagem). Throw aparece bem menos, e sempre sem
parênteses — Throw oError; a forma throw(...), como em outras
linguagens, não é reconhecida. O idioma real da linguagem é uma fábrica que
monta o objeto e um Throw que o dispara:
method newException(cDescription as character) as object class MinhaExcecao
local oError := ErrorClass():New()
oError:description := cDescription
return oErrorA caixa da propriedade de descrição também não é canônica: Description,
description e DESCRIPTION convivem, e este artigo não inventa uma forma
“correta”. ErrorStack, dentro do objeto de erro, é texto corrido, não um
array de frames: é comum recortar a parte que interessa com SubStr.
Vale notar a ordem de preferência real, porque ela inverte o que um curso
costuma ensinar: o mecanismo mais usado de todos não é Throw com
ErrorClass(), nem UserException — é o ErrorBlock() clássico do AdvPL,
ainda o mais presente em código de produção. UserException vem em segundo
lugar, na frente de Throw/ErrorClass(), que seguem raros mesmo em TLPP
novo.
O esqueleto completo
Method getCustomer() as json Class CustomerController
local jResponse := JsonObject():New() as json
try
DbSelectArea("SA1")
DbSetOrder(1)
If !DbSeek(xFilial("SA1") + oRest:getPathParamsRequest()['id'])
jResponse['message'] := "Cliente nao encontrado"
oRest:setStatusCode(404)
oRest:setResponse(jResponse:ToJson())
Return .T.
EndIf
jResponse['id'] := SA1->A1_COD
jResponse['nome'] := SA1->A1_NOME
oRest:setStatusCode(200)
oRest:setResponse(jResponse:ToJson())
catch oErro
jResponse['message'] := "Falha interna: " + oErro:description
oRest:setFault(jResponse:ToJson())
endtry
Return .T.A chave que não existe sai pelo If !DbSeek(...), com setStatusCode e
setResponse — um caminho que a própria API previu. A falha de programação
cai no catch, e o consumidor recebe uma mensagem que aponta a causa real,
não um 500 mudo. Nenhum dos dois blocos substitui o outro: o primeiro é
sobre o que o fonte sabia que podia acontecer; o segundo é sobre o que
ninguém sabia.
Decidir entre os dois caminhos antes de escrever o método evita o erro mais
comum desta série: tratar toda saída ruim como a mesma coisa. Uma chave que
não existe é informação de negócio, e o consumidor da API precisa saber
disso num 400 ou 404 que ele consiga tratar no próprio código. Uma falha
de programação é outra categoria — o try/catch existe para essa
diferença não desaparecer atrás de um setFault genérico.
Se sua API TLPP devolve 500 para tudo, o problema pode não ser falta de tratamento, e sim tratamento indo todo para o mesmo lugar — fale com a gente.
Referências
TDNtdn.totvs.com
TDN — Try...CatchTDN devolve 403 sem login na consulta direta.TDNtdn.totvs.com
TDN — UserExceptionTDNtdn.totvs.com
TDN — ErrorBlockCitada porque grande parte do código real de Protheus ainda resolve erro com este mecanismo clássico.
Continue por aqui
- Vídeo
ProtheusTutorial
API GET: a listagem e o detalhe de um registro
GET de listagem devolve um resumo por SQL; GET de detalhe busca o registro inteiro por DbSeek pela chave do path. A armadilha do DbSeek de largura fixa.