Pragiva — Tecnologia na prática

A annotation que vira endpoint REST

@Get, @Post, @Put, @Delete e @Patch viram endpoint REST em TLPP acima de um método ou função. A caixa não importa; path param é :nome na rota.

Protheus Referência técnica 5 min de leitura

Assista o vídeo

Resposta rápida

O que é
a annotation (@Get, @Post…) que o compilador TLPP lê acima de um método ou função e transforma em rota de API REST, sem WSRESTFUL nem WSMETHOD.
Onde aparece
sobre a declaração de um método, dentro de uma class, ou direto acima de uma Function solta — classe é a forma mais comum.
A caixa importa?
Não — @Get, @GET e @get funcionam do mesmo jeito; não é convenção de estilo, é insensibilidade a caixa da própria linguagem.
Path param
dois-pontos dentro da própria string da rota (:id); a forma {id} do OpenAPI não é reconhecida pelo compilador.
Função ou classe?
As duas funcionam; classe é o padrão que a TOTVS segue com mais frequência.
Consulta rápida7 seções

Antes de decidir onde e como escrever a próxima annotation, vale saber como a própria TOTVS escreve as suas — não como opinião de curso, mas como observação de um vocabulário que se repete de fonte para fonte.

O REST em AdvPL — WSRESTFUL, WSMETHOD, WSDATA — não foi removido nem substituído de uma vez: continua presente em boa parte do padrão. Mas é a annotation, em TLPP, que a TOTVS usa para escrever API nova. As duas gerações convivem lado a lado; a annotation é a que está crescendo.

Cinco verbos, dois que não existem

@Get, @Post, @Put, @Delete e @Patch são os únicos verbos reconhecidos pelo compilador. Get é disparado o mais comum; Patch é raro, o menos usado de todos. @Head e @Options não existem como annotation — quem estiver migrando um contrato OpenAPI completo, com HEAD para checar existência, não encontra equivalente pronto na linguagem.

A caixa não importa — e isso é observável, não folclore

Este é o fato mais limpo da linguagem: a mesma annotation aparece escrita em três grafias diferentes ao longo do padrão — @Get, @GET e @get — e o compilador aceita as três sem reclamar, para qualquer um dos cinco verbos. “A caixa não importa” deixou de ser instrução de instrutor: é observação recorrente de gente diferente escrevendo o mesmo verbo de jeitos diferentes, sem que nenhuma das formas quebre.

Três jeitos de escrever a mesma rota

A string da rota não precisa vir sozinha entre parênteses. Três formas convivem no vocabulário real da linguagem: a posicional, mais comum de longe (@Get("rota")); a nomeada, com parâmetros explícitos; e a que usa uma constante #define no lugar do literal.

ADVPL / TLPP
@Get(endpoint='/api/rh/v1/feedz/:id', description='descrição do endpoint')

Só existem dois parâmetros nomeados reconhecidos: endpoint= e description=. E description= nunca aparece sozinho — sempre acompanha um endpoint= na mesma chamada. Vale dizer também o que não existe, porque são exatamente as invenções que circulam por aí: params=, tenant, wsservice, produces e consumes não são parâmetros reconhecidos — junto com path, route, security e as próprias annotations @Restful, @Api e @Route. Nenhuma dessas formas compila.

O parâmetro de rota é a própria string

Path param não tem cláusula própria — é sintaxe dentro do endpoint, dois-pontos mais o nome, e é presença comum em rota que identifica um registro específico. A forma {id}, comum em OpenAPI e em frameworks como Spring, não é reconhecida: não é que seja desaconselhada, é que não compila como o TLPP espera. Também não existe um parâmetro params= para declarar path param à parte — a única declaração possível é a própria string da rota; quem escreve o endpoint já escreveu, ali mesmo, o contrato de entrada dele.

ADVPL / TLPP
@Get("/api/sigaloja/smartview/v1/options/:modulo/:objeto/:funcao/:opcao")

Quatro parâmetros encadeados na mesma rota, cada um lido depois por oRest:getPathParamsRequest()['modulo'], ['objeto'], e assim por diante — a chave é o nome declarado, sem os dois-pontos. E a mesma rota pode aparecer em mais de uma annotation, uma por verbo:

ADVPL / TLPP
@Get("api/stock/trackcosts/kardex/v1/D4B/:id")
@Put("api/stock/trackcosts/kardex/v1/D4B/:id")
@Delete("api/stock/trackcosts/kardex/v1/D4B/:id")

Função solta ou método de classe

A annotation funciona nos dois lugares, e a preferência real não é igual: classe, dentro de class... EndClass, é o formato mais frequente — o contrário do que boa parte do material de curso ensina. Há um detalhe que separa uma annotation que funciona de uma que o compilador ignora: ela precisa ficar sobre a declaração do método, dentro do bloco class, nunca sobre a implementação:

ADVPL / TLPP
Class ProductTypeController
 public method new()

 @Get("api/stock/process/v1/producttypes")
 public method getProductType()
EndClass

Method getProductType() Class ProductTypeController
...
Return

A annotation nunca funciona sobre o corpo do método (Method getProductType() Class ProductTypeController) — só sobre a linha declarada dentro do Class... EndClass. Escrevê-la no lugar errado não gera erro de compilação; simplesmente não vira rota, e o sintoma só aparece em tempo de execução, na hora de subir o serviço.

Sem classe, a annotation fica direto acima da Function, e o objeto de resposta continua disponível — prova de que ele não depende de a rotina ser método de classe:

ADVPL / TLPP
@Get("api/retail/v1/orcamento")
Function Orcamento()
 Local orcamento := Orcamento():New( oRest, oRest:getQueryRequest())
 orcamento:Get()
Return oRest:setResponse( EncodeUtf8(orcamento:getReturn()) )

Repare que o objeto de resposta é passado como parâmetro de um construtor comum, Orcamento():New(oRest,...) — a rotina anotada não precisa ser a única a manuseá-lo.

O construtor que toda classe-API declara

Toda classe que carrega annotation de verbo declara public method new(. Vale separar o que é observação do que é dedução: que o compilador exige esse construtor para a classe funcionar como controller é inferência a partir do desenho — nada comprova isso por si só, porque nenhum caso real tenta subir sem ele. Na prática, isso significa que uma classe-controller que só declara os métodos anotados e nunca o New() compila sem erro nenhum — e falha exatamente no momento em que o serviço sobe, quando o runtime tenta instanciar o controller e não acha construtor. É um erro que aparece tarde, longe da linha que causou.

O fecho raro: @annotation... @end

Existe uma segunda gramática de annotation no TLPP, para declarar campo customizado — não verbo de rota. É rara, um recurso de nicho, mais visto em módulos de folha de pagamento e integrações de arquivo estruturado:

ADVPL / TLPP
@annotation MagneticFileField
 size as numeric default 0
 position as numeric
 fillment as character default " "
@end

Parâmetro sem default (position, no exemplo) fica obrigatório no uso — de novo, é leitura da forma, não contagem. E vale fechar com o ponto que explica por que @Get, @Post, @Put, @Delete e @Patch não aparecem nessa gramática: eles não são declarados por quem escreve a API. Vêm prontos do runtime do compilador — a annotation de verbo é reconhecida por nome, não montada por quem escreve a API.


Se uma annotation compilou e o endpoint não responde, fale com a gente.

Referências

  1. TDNtdn.totvs.com

    TDN — Criando o primeiro serviço (Hello World) com REST em TLPPA página retornou 403 na consulta direta, sem login.
  2. TDNtdn.totvs.com

    TDN — Migração de WSRESTFUL para REST em TLPPMesma ressalva: consulta direta bloqueada por 403.

Continue por aqui