Classe base para criação de adapters utilizados em serviços REST suportando filtros de paginação e filtros baseados no padrão oData.



New

Descrição

Método construtor da classe


Parâmetros:

NomeTipoDescrição
cVerbCarácterVerbo Rest utilizado no adapter
lListLógicoSe irá listar o json

Retorno Logico, Se foi construído corretamente



AddMapFields

Descrição

Adiciona campo a campo as configurações de campos utilizado no Adapter


Parâmetros:

NomeTipoDescrição
cFieldJsonCarácterNome do campo no objeto Json
cFieldQueryCarácterNome do campo que será utilizado no ResultSet
lJsonFieldLógicoSe .T. informa que o campo será exportado ao Json
lFixedLógicoSe .T. informa que o campo não pode ser removido pelo FIELDS do QueryParam
aStructArrayVetor com a estrutura do campo no padrão {"CAMPO", "TIPO", Tamanho, Decimal}, caso não seja informada a estrutura, utiliza como base o dicionário SX3
cRenameFieldCarácterDetermina o nome real do campo na tabela, para o caso de identificadores ambíguos na query



O parâmetro cRenameField só está disponível em libs com label a partir de 20200727.

	// O próximo campo é um exemplo da possibilidade de renomear um campo ambíguo, contudo não é recomendado pois o recno pode mudar.
	// Quando uma tabela sofre backup e é restaurada com append existe a chance do recno ser reconstruído.
	// Portanto é melhor não oferecer a chave por PK igual ao recno e evitar que este problema ocorra.
	oSelf:AddMapFields( 'PK1'	, 'SB1RECNO' , .T., .F., { 'SB1RECNO', 'N', 15, 0 }, 'SB1.R_E_C_N_O_' )
	// Caso precise oferecer uma forma de recuperar o registro prefira a concatenação da chave do Protheus
	// essa expressão depende do banco de dados e não sofrerá parser pela changequery.
	oSelf:AddMapFields( 'PK2'	, 'SB1KEY' , .T., .F., { 'SB1KEY', 'C', 50, 0 }, 'B1_FILIAL+B1_COD' )

	// Aplicando em um outro campo
	oSelf:AddMapFields( 'NOTAS'	, 'MEUMEMO' , .T., .F., { 'B1_XMEMO', 'M', 10, 0 }, 'SB1.B1_XMEMO' )




SetQuery

Descrição
Informa a query para a geração do Json


Parâmetros

NomeTipoDescrição
cQueryCarácterQuery para geração do Json

Deverá ser utilizado os Id's:
#QueryFields# Campos do SELECT, existe tratamento para o FIELDS no QueryParam
#QueryWhere# Condições do WHERE, existe tratamento para FILTER no QueryParam



SetWhere

Descrição

Faz o set da condição de filtro utilizada para ordenação do ResultSet, substitui o id #QueryWhere#


Parâmetros

NomeTipoDescrição
cWhereCarácterString contendo as condições do where para o ResultSet, substitui o id #QueryWhere#



SetOrder

Descrição

Faz o set da condição padrão utilizada para ordenação do ResultSet

Utilizada para definir a ordem padrão retornada caso não seja informada nenhum SetOrderQuery


Parâmetros

NomeTipoDescrição
cOrderCarácterString contendo a ordenação que irá ser utilizada no ResultSet



SetOrderQuery

Descrição

String contendo a ordenação informada via QueryParam

Substitui o cOrder padrão definida pelo método SetOrder


Parâmetros

NomeTipoDescrição
cOrderCarácterString contendo a ordenação que irá ser utilizada no ResultSet.
Obs.: Adicione "-" a esquera do cOrder para ordenar decrescente (ex.: "-filial").



SetFields

Descrição

Informa os campos que serão utilizados para geração do Json


Parâmetros

NomeTipoDescrição

cFields

Carácter

Campos que serão exportados para o Json, utilizado para setar os campos informados via QueryParam



Execute

Descrição

Realiza o parse dos ids #QueryFields# e #QueryWhere# gerando o ResultSet


Parâmetros

NomeTipoDescrição
lUseTmpTableLogicoForça o uso de tabela temporária, mesmo para os bancos com tratamento para geração de paginação via banco


Retorno Logico, Retorna se a execução foi realizada com sucesso



FillGetResponse

Descrição

Método chamado linha a linha do ResultSet para geração do Json



SetPage

Descrição

Configura qual pagina sera retornada pelo adapter


Parâmetros

NomeTipoDescrição
nPageNuméricoNúmero da pagina a ser retornada



SetPageSize

Descrição

Configura o tamanho da pagina


Parâmetros

NomeTipoDescrição
nPageSizeNuméricoTamanho da página



SetUrlFilter

Descrição

Faz a definição do filtro informado via parâmetros de query.


Parâmetros

NomeTipoDescrição
aUrlFilterArrayFiltro via QueryParam


O parâmetro aUrlFilter precisa seguir o formato do array da propriedade aQueryString do Rest Advpl.

No Rest Advpl este array é uma lista de outros array com duas posições, sendo a primeira posição a chave do parâmetro de query e a segunda posição o valor.

// filtros simples
// a requição com: ?propriedade1=valor1&propriedade2=valor2
// exigiria o array como
aUrlFilter := { ;
  {"propriedade1", "valor1"},;
  {"propriedade2", "valor2"} ;
}
self:SetUrlFilter(aUrlFilter)

// filtro complexos
// ?filter=propriedade1 eq 'valor1' and propriedade2 eq 'valor2'
aUrlFilter := { ;
  {"FILTER", "propriedade1 eq 'valor1' and propriedade2 eq 'valor2'"};
}
self:SetUrlFilter(aUrlFilter)

// Rest Advpl
// Essa versão do Rest já possui preparado um array com os parâmetros de query no formato adequado
// Com isso é possível indicar diretamento o atributo da classe aQueryString
self:SetUrlFilter(self:aQueryString)




SetStyleReturn

Descrição

Permite configurar o nome da propriedade de retorno dos itens da listagem do verbo GET.

Por padrão, o JSON retornado tem a propriedade items, esse método permite trocar o nome dessa propriedade.


Parâmetros

NomeTipoDescrição
cPropItemsCarácterNome da propriedade de retorno dos itens no JSON de listagem do verbo GET do adapter


Exemplo

oAdapter:SetStyleReturn("data")


Observação

Esse método está disponível somente para lib igual ou superior a 20221010



GetJSONResponse

Descrição

Irá retornar o Json

Retorno Carácter, Resposta da API



setIsCaseSensitive

Descrição

Indica que as propriedades do json são case sensitive

Exemplo

oAdapter:setIsCaseSensitive(.T.)

Observação

Esse método está disponível somente para lib igual ou superior a 20230515



Exemplo de utilização com a tabela de produtos:
Classe de criação do adapter
#include 'totvs.ch'
#include 'parmtype.ch'
//-------------------------------------------------------------------
/*/{Protheus.doc} PrdAdapter
Classe Adapter para o serviço
@author  Anderson Toledo
/*/
//-------------------------------------------------------------------
CLASS PrdAdapter FROM FWAdapterBaseV2
	METHOD New()
	METHOD GetListProd()
EndClass

Method New( cVerb ) CLASS PrdAdapter
	_Super:New( cVerb, .T. )
return

Method GetListProd( ) CLASS PrdAdapter
	Local aArea 	AS ARRAY
	Local cWhere	AS CHAR
	aArea   := FwGetArea()
	//Adiciona o mapa de campos Json/ResultSet
	AddMapFields( self )
	//Informa a Query a ser utilizada pela API
	::SetQuery( GetQuery() )
	//Informa a clausula Where da Query
	cWhere := " B1_FILIAL = '"+ FWxFilial('SB1') +"' AND SB1.D_E_L_E_T_ = ' '"
	::SetWhere( cWhere )
	//Informa a ordenação padrão a ser Utilizada pela Query
	::SetOrder( "B1_COD" )
	//Executa a consulta, se retornar .T. tudo ocorreu conforme esperado
	If ::Execute() 
		// Gera o arquivo Json com o retorno da Query
		::FillGetResponse()
	EndIf
	FwrestArea(aArea)
Return

Static Function AddMapFields( oSelf )
	
	oSelf:AddMapFields( 'CODE'              , 'B1_COD'  , .T., .T., { 'B1_COD', 'C', TamSX3( 'B1_COD' )[1], 0 } )
	oSelf:AddMapFields( 'DESCRIPTION'	    , 'B1_DESC' , .T., .F., { 'B1_DESC', 'C', TamSX3( 'B1_DESC' )[1], 0 } )	
	oSelf:AddMapFields( 'GROUP'		        , 'B1_GRUPO', .T., .F., { 'B1_GRUPO', 'C', TamSX3( 'B1_GRUPO' )[1], 0 } )
	oSelf:AddMapFields( 'GROUPDESCRIPTION'	, 'BM_DESC' , .T., .F., { 'BM_DESC', 'C', TamSX3( 'BM_DESC' )[1], 0 } )
Return 

Static Function GetQuery()
	Local cQuery AS CHARACTER
	
	//Obtem a ordem informada na requisição, a query exterior SEMPRE deve ter o id #QueryFields# ao invés dos campos fixos
	//necessáriamente não precisa ser uma subquery, desde que não contenha agregadores no retorno ( SUM, MAX... )
	//o id #QueryWhere# é onde será inserido o clausula Where informado no método SetWhere()
	cQuery := " SELECT #QueryFields#"
    cQuery +=   " FROM " + RetSqlName( 'SB1' ) + " SB1 "
    cQuery +=   " LEFT JOIN " + RetSqlName( 'SBM' ) + " SBM"
	cQuery +=       " ON B1_GRUPO = BM_GRUPO"
	cQuery +=           " AND BM_FILIAL = '"+ FWxFilial( 'SBM' ) +"'"
	cQuery +=           " AND SBM.D_E_L_E_T_ = ' '"
    cQuery += " WHERE #QueryWhere#"	
Return cQuery
Criação do serviço que irá executar o adapter
#include "totvs.ch"
#include "restful.ch"
//-------------------------------------------------------------------
/*/{Protheus.doc} products
Declaração do ws products
@author Anderson Toledo
/*/
//-------------------------------------------------------------------
WSRESTFUL products DESCRIPTION 'endpoint products API' FORMAT "application/json,text/html"
    WSDATA Page     AS INTEGER OPTIONAL
    WSDATA PageSize AS INTEGER OPTIONAL
    WSDATA Order    AS CHARACTER OPTIONAL
    WSDATA Fields   AS CHARACTER OPTIONAL

 	WSMETHOD GET ProdList;
	    DESCRIPTION "Retorna uma lista de produtos";
	    WSSYNTAX "/api/v1/products" ;
        PATH "/api/v1/products" ;
	    PRODUCES APPLICATION_JSON
 	
END WSRESTFUL

WSMETHOD GET ProdList QUERYPARAM Page WSREST products
Return getPrdList(self)

Static Function getPrdList( oWS )
   Local lRet  as logical
   Local oProd as object
   DEFAULT oWS:Page      := 1  
   DEFAULT oWS:PageSize  := 10
   DEFAULT oWS:Fields    := ""
   lRet        := .T.
   //PrdAdapter será nossa classe que implementa fornecer os dados para o WS
   // O primeiro parametro indica que iremos tratar o método GET
   oProd := PrdAdapter():new( 'GET' )  
   //o método setPage indica qual página deveremos retornar
   //ex.: nossa consulta tem como resultado 100 produtos, e retornamos sempre uma listagem de 10 itens por página.
   // a página 1 retorna os itens de 1 a 10
   // a página 2 retorna os itens de 11 a 20
   // e assim até chegar ao final de nossa listagem de 100 produtos 
   oProd:setPage(oWS:Page)
   // setPageSize indica que nossa página terá no máximo 10 itens
   oProd:setPageSize(oWS:PageSize)
   // SetOrderQuery indica a ordem definida por querystring
   oProd:SetOrderQuery(oWS:Order)
   // setUrlFilter indica o filtro querystring recebido (pode se utilizar um filtro oData)
   oProd:SetUrlFilter(oWS:aQueryString )
   // SetFields indica os campos que serão retornados via querystring
   oProd:SetFields( oWS:Fields )   
   // Esse método irá processar as informações
   oProd:GetListProd()
   //Se tudo ocorreu bem, retorna os dados via Json
   If oProd:lOk
       oWS:SetResponse(oProd:getJSONResponse())
   Else
   //Ou retorna o erro encontrado durante o processamento
       SetRestFault(oProd:GetCode(),oProd:GetMessage())
       lRet := .F.
   EndIf
   //faz a desalocação de objetos e arrays utilizados
   oProd:DeActivate()
   oProd := nil   
Return lRet

A partir do exemplo acima é possível realizar filtros no retorno do GET, abaixo exemplos utilizando paginação e o padrão oData.

Obs. Endereço e conteúdo da comparação deve ser ajustado de acordo com o ambiente utilizado.
Filtro utilizando paginação
http://localhost:8080/teste/rest/api/v1/products?pagesize=1&page=3
Filtro utilizando o padrão oData
http://localhost:8080/teste/rest/api/v1/products?filter=code eq '000001'
Filtro utilizando ordenação
http://localhost:8080/teste/rest/api/v1/products?order=description
Filtro informando os campos que serão retornados
http://localhost:8080/teste/rest/api/v1/products?fields=code,description



A partir da lib 20220322 é possível utilizar os filtros toupper e tolower:

filter=toupper(description) eq 'XISTO'

filter=tolower(description) eq 'xis'




A partir da lib 20220502 o JSON de retorno do GET com listagem, além de possuir a propriedade hasNext indicando que ainda existem registros a serem retornados, contará também com a propriedade remainingRecords, que retornará quantos registros ainda existem para retorno, facilitando assim por exemplo o cálculo da quantidade de páginas existentes.



A classe não dá suporte ao campos memo que utilizam da SYP.