Resposta rápida
- Onde vêm os dados
- POST e PUT trazem no corpo (
oRest:getBodyRequest()); DELETE costuma trazer a chave no header ou na própria rota. - A armadilha nº 1
getBodyRequest()devolve string — semJsonObject():New()seguido deFromJson(cBody)não existe propriedade nenhuma para ler.- Quem grava
- a rotina automática,
MsExecAuto, com as operações 3 (inclusão), 4 (alteração) e 5 (exclusão). - A armadilha nº 2
- o array não basta para excluir — o registro precisa estar posicionado antes da chamada.
- Verbo sem rota correspondente
- o framework devolve
405 Method Not Allowedsozinho, sem rodar uma linha do seu fonte.
Nesta página10 seções
Até aqui a série só leu: query string, path param, listagem paginada com
FWAdapterBaseV2. Agora a API grava — e o eixo deste artigo é uma distinção
que vale para os três verbos: a API é a porta de entrada, quem grava é a
mesma rotina automática que a tela usaria, com as mesmas validações,
gatilhos de dicionário e pontos de entrada. Ignorar isso e gravar direto na
tabela é o erro que o
artigo sobre RecLock e ExecAuto já cobre —
esta é a versão dele para dentro de um método anotado.
De onde vêm os dados, verbo por verbo
| Verbo | Onde chega o dado | Método |
|---|---|---|
| POST | corpo da requisição | oRest:getBodyRequest() |
| PUT | corpo da requisição | oRest:getBodyRequest() |
| DELETE | chave no header, ou path param na rota | oRest:getHeaderRequest() |
getBodyRequest é o mais comum dos três; getHeaderRequest, usado
sobretudo para receber a chave de exclusão, aparece bem menos. Um DELETE
também pode receber a chave como path param, na mesma sintaxe de
dois-pontos já vista nos artigos de GET — a rota decide, não uma regra fixa
do framework.
A armadilha nº 1: o corpo chega como string
getBodyRequest() devolve o JSON cru, em texto. Não tem propriedade, não tem
[], não tem nada para acessar até virar objeto:
Local cBody := oRest:getBodyRequest()
Local jBody := JsonObject():New()
jBody:FromJson(cBody)
// só a partir daqui jBody['codigo'] existeO par que se confunde: FromJson converte string em objeto; ToJson faz o
caminho inverso, de objeto para string, na hora de montar a resposta. E a
caixa dos dois não é canônica: FromJson e fromJson convivem sem
distinção, e o mesmo vale para ToJson/toJson, sem que nenhuma das
grafias vença de forma consistente. Não existe grafia “certa” para copiar.
Quem grava não é a API, é o ExecAuto
Depois do objeto montado, a gravação segue o padrão que qualquer rotina
automática usa fora de API: lMsErroAuto declarada antes, MsExecAuto
chamado com um bloco que recebe os dados e a operação, e o teste da
variável depois.
Private lMsErroAuto := .F.
MsExecAuto({|x, y| MATA020(x, y)}, aDados, nOpc)
If lMsErroAuto
// trata o erro, não grava
EndIfO bloco não recebe aDados e nOpc por mágica: são o segundo e o terceiro
argumento do próprio MsExecAuto, passados depois do bloco — x é o array,
y é a operação. nOpc é um destes três números: 3 para inclusão, 4 para
alteração, 5 para exclusão. É essa rotina, com o mesmo nome usado por trás da
tela do módulo, que aplica validação de dicionário, gatilho do SX7 e ponto
de entrada — a API só entrega os dados que ela precisa.
A armadilha nº 2: array não basta, precisa posicionar
Para incluir, o array é suficiente. Para excluir — e também para alterar — o
ExecAuto identifica o registro pelo posicionamento da área de trabalho, não
pela chave que veio dentro do array:
DbSelectArea("SA2")
SA2->(DbSetOrder(1)) // A2_FILIAL + A2_COD + A2_LOJA
SA2->(DbSeek(xFilial("SA2") + cCodigo + cLoja))
If SA2->(Found())
MsExecAuto({|x, y| MATA020(x, y)}, aDados, 5)
EndIfA armadilha nº 3: o campo-chave sai do array na alteração
Numa operação 4, o campo-chave não se altera — e a validação do próprio
cadastro rejeita a tentativa. No fornecedor, isso significa tirar A2_CGC e
A2_TPESSOA do array de alteração; deixá-los lá, mesmo com o mesmo valor já
gravado, é o tipo de erro que só aparece em tempo de execução, nunca na
compilação.
A armadilha nº 4: a ordem dos campos é heurística, não regra
Em alguns ExecAuto a ordem dos campos dentro do array importa; em outros,
não — e não existe uma lista publicada dizendo quais. O hábito mais seguro é
seguir a ordem em que os campos aparecem na tela do cadastro que a API está
automatizando. É uma heurística que reduz o risco, não uma regra que a TOTVS
documenta; tratar como certeza seria inventar uma garantia que não existe.
O campo que o próprio ExecAuto preenche
Campo com GetSxeNum configurado no dicionário — a função de numeração
automática usada em muitos cadastros do padrão — não entra no array: é o
próprio ExecAuto quem gera o valor, na hora da gravação. Passar um valor
para esse campo não garante que ele seja usado; o caminho seguro é nem
declará-lo. Depois que a gravação termina com sucesso, o registro fica
posicionado na área de trabalho, e é dali — não do array de entrada — que se
lê o código gerado, para devolver na resposta.
Verbo sem rota: o framework responde sozinho
Uma rota declarada só com @Post não atende a um DELETE na mesma URL. O
framework devolve 405 Method Not Allowed antes de qualquer linha do fonte
rodar — não é um If que falta escrever, é o roteamento da própria API
recusando a combinação de verbo e caminho que não foi anotada.
Quantos fontes realmente gravam
MsExecAuto e lMsErroAuto aparecem juntos num universo bem menor do que
os fontes com alguma annotation de verbo — a maior parte das APIs do
padrão lê e não grava. Não é sintoma de que gravar por API seja incomum na
prática dos clientes; é o retrato de que o material distribuído ainda
expõe muito mais consulta do que escrita.
Um exemplo completo: incluir um fornecedor
O método abaixo é a inclusão inteira, no fonte anexado ao artigo de
paginação com FWAdapterBaseV2
(pgv.api.fornecedores.tlpp) — a mesma classe cuja listagem paginada aquele
artigo já mostrou, agora gravando:
Method PostFornecedores() Class FornecedoresApi
Local cCorpo as Character
Local jCorpo as Json
Local aDados as Array
Local aErroAuto as Array
Local cErroAuto as Character
Local nItemErro as Numeric
try
cCorpo := oRest:getBodyRequest()
jCorpo := JsonObject():New()
jCorpo:FromJson(cCorpo)
aDados := {}
Aadd(aDados, {"A2_FILIAL", xFilial("SA2")})
Aadd(aDados, {"A2_COD", jCorpo["codigo"]})
Aadd(aDados, {"A2_LOJA", jCorpo["loja"]})
Aadd(aDados, {"A2_NOME", jCorpo["nome"]})
Aadd(aDados, {"A2_NREDUZ", jCorpo["nomeReduzido"]})
Aadd(aDados, {"A2_CGC", jCorpo["cnpjCpf"]})
Private lMsErroAuto := .F.
MsExecAuto({|x, y| MATA020(x, y)}, aDados, 3)
If lMsErroAuto
aErroAuto := GetAutoGRLog()
cErroAuto := ""
For nItemErro := 1 To Len(aErroAuto)
cErroAuto += aErroAuto[nItemErro] + CRLF
Next nItemErro
oRest:setStatusCode(400)
oRest:setFault(cErroAuto)
Else
oRest:setStatusCode(201)
oRest:setResponse('{"mensagem":"fornecedor incluido"}')
EndIf
catch oErro
oRest:setStatusCode(500)
oRest:setFault(oErro:Description)
endtry
Return NilLer o corpo, transformar em objeto, montar o array, gravar pelo ExecAuto,
testar lMsErroAuto, responder — é a sequência inteira, e cada etapa dela é
uma das armadilhas deste artigo. GetAutoGRLog() devolve o log da rotina
automática como array de linhas — o mesmo mecanismo já coberto em
MsExecAuto: como descobrir por que a rotina falhou.
A mesma classe também tem a exclusão, com o posicionamento da armadilha
anterior antes do MsExecAuto com operação 5.
Se uma API sua grava e o retorno não bate com o que ficou na tabela, fale com a gente.
Referências
TDNtdn.totvs.com
TDN — Variáveis de controle do MsExecAutoConteúdo disponível no índice consultado; o acesso direto retornou 403.TDNtdn.totvs.com
TDN — Exemplo de execução automática do MATA410O exemplo do TDN é do pedido de venda; o mecanismo do ExecAuto é o mesmo aplicado aqui ao fornecedor.TDNtdn.totvs.com
TDN — REST em TLPP
Continue por aqui
- Vídeo
ProtheusGuia de processo
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.