Pragiva — Tecnologia na prática

Erro esperado e erro inesperado numa API TLPP

setFault recebe um parâmetro só, sem código HTTP: por isso o erro de negócio some atrás de um 500 genérico. Os dois tratamentos numa API TLPP.

Protheus Guia de processo 5 min de leitura

Assista o vídeo

Resposta rápida

Erro esperado
situação prevista — a chave não existe, Found() devolve falso. Resolve com setStatusCode + 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
setFault recebe um parâmetro só. Não existe onde passar o código HTTP.
A adoção real
try/catch ainda é minoria no código de Protheus — a maior parte do tratamento de erro segue resolvido pelo ErrorBlock() clássico do AdvPL.
A grafia trava
o fechamento é endtry, colado — end try com 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.

ADVPL / TLPP
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:

ADVPL / TLPP
method newException(cDescription as character) as object class MinhaExcecao
 local oError := ErrorClass():New()
 oError:description := cDescription
return oError

A 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

ADVPL / TLPP
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

  1. TDNtdn.totvs.com

    TDN — Try...CatchTDN devolve 403 sem login na consulta direta.
  2. TDNtdn.totvs.com

    TDN — UserException
  3. TDNtdn.totvs.com

    TDN — ErrorBlockCitada porque grande parte do código real de Protheus ainda resolve erro com este mecanismo clássico.

Continue por aqui