Pragiva — Tecnologia na prática

POST, PUT e DELETE: a API que grava

POST, PUT e DELETE na API gravam pelo ExecAuto da rotina padrão: sem FromJson não há corpo para ler, e sem posicionamento ele pode agir no registro errado.

Protheus Tutorial 5 min de leitura

Assista o vídeo

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 — sem JsonObject():New() seguido de FromJson(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 Allowed sozinho, 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

VerboOnde chega o dadoMétodo
POSTcorpo da requisiçãooRest:getBodyRequest()
PUTcorpo da requisiçãooRest:getBodyRequest()
DELETEchave no header, ou path param na rotaoRest: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:

ADVPL / TLPP
Local cBody := oRest:getBodyRequest()
Local jBody := JsonObject():New()

jBody:FromJson(cBody)

// só a partir daqui jBody['codigo'] existe

O 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.

ADVPL / TLPP
Private lMsErroAuto := .F.

MsExecAuto({|x, y| MATA020(x, y)}, aDados, nOpc)

If lMsErroAuto
 // trata o erro, não grava
EndIf

O 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:

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

A 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 e 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:

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

Ler 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

  1. TDNtdn.totvs.com

    TDN — Variáveis de controle do MsExecAutoConteúdo disponível no índice consultado; o acesso direto retornou 403.
  2. 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.
  3. TDNtdn.totvs.com

    TDN — REST em TLPP

Continue por aqui