PC SOFT

PROFESSIONAL NEWSGROUPS
WINDEVWEBDEV and WINDEV Mobile

Home → WINDEV 25 → Classe Oop Api Rest
Classe Oop Api Rest
Started by Boller, Apr., 03 2025 8:55 AM - 17 replies
Registered member
3,851 messages
Posted on April, 03 2025 - 8:55 AM
Versao 1.0

///////////////////////////////////////////////////////////////////////////
// Classe RESTAPIClient
// Versão: 1.0
// Autor: Adriano
// Descrição: Classe OOP para facilitar integração com APIs REST
// Suporta múltiplos métodos de autenticação de forma padronizada
///////////////////////////////////////////////////////////////////////////

CLASSE RESTAPIClient
//=======================================================================
// ATRIBUTOS PRIVADOS
//=======================================================================
PRIVATE
// Configurações básicas
m_URL is string // URL base da API
m_Timeout is int = 30 // Timeout em segundos
m_ContentType is string = "application/json" // Tipo de conteúdo padrão
m_DefaultHeaders is array of string dynamic // Array para armazenar cabeçalhos padrões

// Configurações de autenticação
m_AuthType is int = authNone // Tipo de autenticação (constantes definidas abaixo)
m_Username is string // Para Basic Auth
m_Password is string // Para Basic Auth
m_Token is string // Para Bearer/JWT
m_APIKey is string // Para API Key
m_APIKeyName is string = "X-API-Key" // Nome do cabeçalho para API Key
m_APIKeyLocation is int = keyHeader // Localização da API Key (header, query, etc)
m_CertificatePath is string // Para mTLS
m_CertificatePassword is string // Para mTLS
m_OAuthClientID is string // Para OAuth 2.0
m_OAuthClientSecret is string // Para OAuth 2.0
m_OAuthTokenURL is string // URL para obtenção de token OAuth
m_HMACSecret is string // Chave secreta para HMAC

// Tokens OAuth
m_AccessToken is string // Token de acesso atual
m_RefreshToken is string // Token de atualização
m_TokenExpiry is datetime // Data/hora de expiração do token

// Configurações de log e debug
m_EnableLogging is boolean = False // Ativar logging de requisições
m_LogPath is string // Caminho para salvar logs
m_LastResponse is httpResponse // Última resposta HTTP recebida
m_LastRequest is httpRequest // Última requisição HTTP enviada

//=======================================================================
// CONSTANTES INTERNAS
//=======================================================================
PUBLIC CONSTANT
// Tipos de autenticação
authNone = 0 // Sem autenticação
authBasic = 1 // Autenticação básica (usuário/senha)
authBearer = 2 // Bearer token / JWT
authAPIKey = 3 // Chave de API
authOAuth2 = 4 // OAuth 2.0
authHMAC = 5 // HMAC (assinatura)
authMTLS = 6 // Mutual TLS (certificados)
authCustom = 7 // Autenticação personalizada

// Localizações para API Key
keyHeader = 1 // API Key no cabeçalho
keyQuery = 2 // API Key na query string

// Métodos HTTP
httpMethodGET = httpGet // GET - Obter dados
httpMethodPOST = httpPost // POST - Enviar dados
httpMethodPUT = httpPut // PUT - Atualizar dados
httpMethodDELETE = httpDelete // DELETE - Remover dados
httpMethodPATCH = httpPatch // PATCH - Atualização parcial

//=======================================================================
// MÉTODOS PÚBLICOS
//=======================================================================
PUBLIC
//-------------------------------------------------------------------
// Construtor - Inicializa a classe com a URL base
//-------------------------------------------------------------------
PROCEDURE Constructor(URLAPI is string)
// Inicializa a URL base da API
m_URL = URLAPI

// Inicializa array de cabeçalhos padrão
ArrayAdd(m_DefaultHeaders, "User-Agent", "WLanguage-API-Client/1.0")
ArrayAdd(m_DefaultHeaders, "Accept", "application/json")

// Log de inicialização
IF m_EnableLogging THEN LogOperation("Inicialização do cliente API para: " + m_URL)
END

//-------------------------------------------------------------------
// Configura timeout para requisições
//-------------------------------------------------------------------
PROCEDURE SetTimeout(Segundos is int)
// Atualiza o timeout com validação
IF Segundos > 0 THEN
m_Timeout = Segundos
LogOperation("Timeout configurado para: " + Segundos + " segundos")
ELSE
Error("Timeout deve ser maior que zero")
END
RESULT True
END

//-------------------------------------------------------------------
// Configura o tipo de conteúdo padrão para requisições
//-------------------------------------------------------------------
PROCEDURE SetContentType(TipoConteudo is string)
// Atualiza o tipo de conteúdo
m_ContentType = TipoConteudo
LogOperation("Tipo de conteúdo configurado para: " + TipoConteudo)
RESULT True
END

//-------------------------------------------------------------------
// Adiciona um cabeçalho padrão para todas as requisições
//-------------------------------------------------------------------
PROCEDURE AddDefaultHeader(Nome is string, Valor is string)
// Adiciona ou atualiza o cabeçalho no array
bEncontrado is boolean = False

// Procura se já existe este cabeçalho
FOR i = 1 TO ArrayCount(m_DefaultHeaders) STEP 2
IF m_DefaultHeaders[i] = Nome THEN
// Atualiza valor existente
m_DefaultHeaders[i+1] = Valor
bEncontrado = True
BREAK
END
END

// Se não encontrou, adiciona novo
IF bEncontrado = False THEN
ArrayAdd(m_DefaultHeaders, Nome)
ArrayAdd(m_DefaultHeaders, Valor)
END

LogOperation("Cabeçalho padrão adicionado/atualizado: " + Nome + " = " + Valor)
RESULT True
END

//-------------------------------------------------------------------
// Remove um cabeçalho padrão pelo nome
//-------------------------------------------------------------------
PROCEDURE RemoveDefaultHeader(Nome is string)
// Procura e remove o cabeçalho
FOR i = 1 TO ArrayCount(m_DefaultHeaders) STEP 2
IF m_DefaultHeaders[i] = Nome THEN
// Remove nome e valor (dois elementos)
ArrayDelete(m_DefaultHeaders, i)
ArrayDelete(m_DefaultHeaders, i) // Agora i+1 virou i
LogOperation("Cabeçalho padrão removido: " + Nome)
RESULT True
END
END

// Não encontrou
LogOperation("Cabeçalho não encontrado para remover: " + Nome)
RESULT False
END

//-------------------------------------------------------------------
// Configura autenticação básica (usuário e senha)
//-------------------------------------------------------------------
PROCEDURE SetBasicAuth(Usuario is string, Senha is string)
// Valida parâmetros
IF Usuario = "" THEN
Error("Usuário não pode ser vazio para autenticação básica")
RESULT False
END

// Configura autenticação básica
m_AuthType = authBasic
m_Username = Usuario
m_Password = Senha

LogOperation("Autenticação Básica configurada para usuário: " + Usuario)
RESULT True
END

//-------------------------------------------------------------------
// Configura autenticação JWT/Bearer Token
//-------------------------------------------------------------------
PROCEDURE SetBearerAuth(Token is string)
// Valida parâmetros
IF Token = "" THEN
Error("Token não pode ser vazio para autenticação Bearer")
RESULT False
END

// Configura autenticação Bearer
m_AuthType = authBearer
m_Token = Token

LogOperation("Autenticação Bearer Token configurada")
RESULT True
END

//-------------------------------------------------------------------
// Configura autenticação via API Key
//-------------------------------------------------------------------
PROCEDURE SetAPIKeyAuth(APIKey is string, NomeParametro is string = "X-API-Key", Localizacao is int = keyHeader)
// Valida parâmetros
IF APIKey = "" THEN
Error("API Key não pode ser vazia")
RESULT False
END

// Configura autenticação API Key
m_AuthType = authAPIKey
m_APIKey = APIKey
m_APIKeyName = NomeParametro
m_APIKeyLocation = Localizacao

LogOperation("Autenticação API Key configurada: " + NomeParametro)
RESULT True
END

//-------------------------------------------------------------------
// Configura autenticação via OAuth 2.0
//-------------------------------------------------------------------
PROCEDURE SetOAuth2Auth(ClientID is string, ClientSecret is string, TokenURL is string)
// Valida parâmetros
IF ClientID = "" OR ClientSecret = "" OR TokenURL = "" THEN
Error("Parâmetros OAuth incompletos")
RESULT False
END

// Configura autenticação OAuth 2.0
m_AuthType = authOAuth2
m_OAuthClientID = ClientID
m_OAuthClientSecret = ClientSecret
m_OAuthTokenURL = TokenURL

LogOperation("Autenticação OAuth 2.0 configurada para: " + TokenURL)
RESULT True
END

//-------------------------------------------------------------------
// Define manualmente tokens OAuth (útil para restaurar sessão)
//-------------------------------------------------------------------
PROCEDURE SetOAuthTokens(AccessToken is string, RefreshToken is string, ExpirySeconds is int)
// Valida parâmetros
IF AccessToken = "" THEN
Error("Access token não pode ser vazio")
RESULT False
END

// Configura tokens
m_AccessToken = AccessToken
m_RefreshToken = RefreshToken

// Calcula data de expiração
IF ExpirySeconds > 0 THEN
m_TokenExpiry = DateTimeAddSecond(Now(), ExpirySeconds)
ELSE
// Token sem expiração definida, usa padrão de 1 hora
m_TokenExpiry = DateTimeAddSecond(Now(), 3600)
END

LogOperation("Tokens OAuth configurados manualmente")
RESULT True
END

//-------------------------------------------------------------------
// Configura autenticação HMAC (baseada em assinatura)
//-------------------------------------------------------------------
PROCEDURE SetHMACAuth(ChaveSecreta is string)
// Valida parâmetros
IF ChaveSecreta = "" THEN
Error("Chave secreta HMAC não pode ser vazia")
RESULT False
END

// Configura autenticação HMAC
m_AuthType = authHMAC
m_HMACSecret = ChaveSecreta

LogOperation("Autenticação HMAC configurada")
RESULT True
END

//-------------------------------------------------------------------
// Configura autenticação mTLS (certificado mútuo)
//-------------------------------------------------------------------
PROCEDURE SetMTLSAuth(CaminhoCertificado is string, SenhaCertificado is string = "")
// Valida parâmetros
IF CaminhoCertificado = "" THEN
Error("Caminho do certificado não pode ser vazio")
RESULT False
END

// Verifica se o arquivo existe
IF fFileExist(CaminhoCertificado) = False THEN
Error("Certificado não encontrado: " + CaminhoCertificado)
RESULT False
END

// Configura autenticação mTLS
m_AuthType = authMTLS
m_CertificatePath = CaminhoCertificado
m_CertificatePassword = SenhaCertificado

LogOperation("Autenticação mTLS configurada com certificado: " + CaminhoCertificado)
RESULT True
END

//-------------------------------------------------------------------
// Habilita ou desabilita logging das operações
//-------------------------------------------------------------------
PROCEDURE SetLogging(Ativar is boolean, CaminhoLogs is string = "")
// Configura logging
m_EnableLogging = Ativar

IF Ativar AND CaminhoLogs <> "" THEN
m_LogPath = CaminhoLogs
// Verifica/cria diretório
IF fDirectoryExist(CaminhoLogs) = False THEN
fMakeDir(CaminhoLogs)
END
END

// Log da operação
IF Ativar THEN
LogOperation("Logging ativado em: " + (m_LogPath <> "" ? m_LogPath : "saída padrão"))
ELSE
LogOperation("Logging desativado")
END

RESULT True
END

//-------------------------------------------------------------------
// Executa requisição GET
//-------------------------------------------------------------------
PROCEDURE Get(Endpoint is string, Parametros is string = "", Cabecalhos is array of string dynamic = Null)
// Executa uma requisição GET
RESULT ExecuteRequest(httpMethodGET, Endpoint, Parametros, "", Cabecalhos)
END

//-------------------------------------------------------------------
// Executa requisição POST
//-------------------------------------------------------------------
PROCEDURE Post(Endpoint is string, Dados is variant, Cabecalhos is array of string dynamic = Null)
// Executa uma requisição POST
DadosString is string = ConvertData(Dados)
RESULT ExecuteRequest(httpMethodPOST, Endpoint, "", DadosString, Cabecalhos)
END

//-------------------------------------------------------------------
// Executa requisição PUT
//-------------------------------------------------------------------
PROCEDURE Put(Endpoint is string, Dados is variant, Cabecalhos is array of string dynamic = Null)
// Executa uma requisição PUT
DadosString is string = ConvertData(Dados)
RESULT ExecuteRequest(httpMethodPUT, Endpoint, "", DadosString, Cabecalhos)
END

//-------------------------------------------------------------------
// Executa requisição DELETE
//-------------------------------------------------------------------
PROCEDURE Delete(Endpoint is string, Parametros is string = "", Cabecalhos is array of string dynamic = Null)
// Executa uma requisição DELETE
RESULT ExecuteRequest(httpMethodDELETE, Endpoint, Parametros, "", Cabecalhos)
END

//-------------------------------------------------------------------
// Executa requisição PATCH
//-------------------------------------------------------------------
PROCEDURE Patch(Endpoint is string, Dados is variant, Cabecalhos is array of string dynamic = Null)
// Executa uma requisição PATCH
DadosString is string = ConvertData(Dados)
RESULT ExecuteRequest(httpMethodPATCH, Endpoint, "", DadosString, Cabecalhos)
END

//-------------------------------------------------------------------
// Obtém a última resposta como JSON
//-------------------------------------------------------------------
PROCEDURE GetResponseJSON()
// Verifica se existe resposta
IF m_LastResponse = Null THEN
Error("Nenhuma resposta disponível para conversão")
RESULT Null
END

// Tenta converter para JSON
TRY
JsonData is JSON = JSONToValue(m_LastResponse.Content)
RESULT JsonData
CATCH
Error("Falha ao converter resposta para JSON: " + ErrorInfo())
RESULT Null
END
END

//-------------------------------------------------------------------
// Obtém código de status da última resposta
//-------------------------------------------------------------------
PROCEDURE GetStatusCode()
// Retorna código de status da última resposta
IF m_LastResponse <> Null THEN
RESULT m_LastResponse.StatusCode
ELSE
RESULT 0
END
END

//-------------------------------------------------------------------
// Verifica se a última requisição foi bem-sucedida (2xx)
//-------------------------------------------------------------------
PROCEDURE IsSuccess()
// Verifica se o status está na faixa de sucesso (2xx)
StatusCode is int = GetStatusCode()
RESULT (StatusCode >= 200 AND StatusCode < 300)
END

//-------------------------------------------------------------------
// Obtém mensagem de erro em caso de falha
//-------------------------------------------------------------------
PROCEDURE GetErrorMessage()
// Se não houve resposta, retorna erro genérico
IF m_LastResponse = Null THEN
RESULT "Nenhuma resposta recebida"
END

// Verifica se foi sucesso
IF IsSuccess() THEN
RESULT ""
END

// Tenta extrair mensagem de erro do corpo JSON
TRY
JsonData is JSON = JSONToValue(m_LastResponse.Content)

// Verifica campos comuns de erro em APIs
IF JsonData.error <> Null THEN
IF JsonData.error.message <> Null THEN
RESULT JsonData.error.message
ELSE
RESULT JsonData.error
END
ELSEIF JsonData.message <> Null THEN
RESULT JsonData.message
ELSEIF JsonData.errorMessage <> Null THEN
RESULT JsonData.errorMessage
ELSEIF JsonData.errors <> Null AND JsonData.errors..Count > 0 THEN
// Tenta pegar o primeiro erro de um array de erros
RESULT JsonData.errors[1]
ELSE
// Retorna código e descrição HTTP padrão
RESULT "Erro HTTP " + m_LastResponse.StatusCode + ": " + m_LastResponse.StatusInfo
END
CATCH
// Não conseguiu extrair erro do JSON, retorna descrição HTTP
RESULT "Erro HTTP " + m_LastResponse.StatusCode + ": " + m_LastResponse.StatusInfo
END
END

//=======================================================================
// MÉTODOS PRIVADOS
//=======================================================================
PRIVATE
//-------------------------------------------------------------------
// Registra operações em log se ativado
//-------------------------------------------------------------------
PROCEDURE LogOperation(Mensagem is string)
// Verifica se logging está ativado
IF m_EnableLogging = False THEN RETURN

// Formata mensagem com timestamp
MensagemLog is string = DateToString(Today(), "yyyy-MM-dd") + " " + TimeToString(Now(), "HH:mm:ss") + " - " + Mensagem

// Decide onde salvar o log
IF m_LogPath <> "" THEN
// Formato do nome do arquivo: api_log_YYYY-MM-DD.log
NomeArquivo is string = m_LogPath + [fSep] + "api_log_" + DateToString(Today(), "yyyy-MM-dd") + ".log"
fSaveText(NomeArquivo, MensagemLog + CR, fAddText)
ELSE
// Log para console/trace
Trace(MensagemLog)
END
END

//-------------------------------------------------------------------
// Converte dados para formato string adequado
//-------------------------------------------------------------------
PROCEDURE ConvertData(Dados is variant)
// Se já é string, retorna direto
IF TypeInfo(Dados, tiIsString) THEN
RESULT Dados
END

// Se é JSON, converte para string
IF TypeInfo(Dados, tiIsJSON) THEN
RESULT ValueToJSON(Dados)
END

// Array ou objeto, tenta converter para JSON
IF TypeInfo(Dados, tiIsArray) OR TypeInfo(Dados, tiIsObject) THEN
TRY
RESULT ValueToJSON(Dados)
CATCH
Error("Não foi possível converter dados para JSON: " + ErrorInfo())
RESULT ""
END
END

// Para outros tipos, tenta conversão para string
TRY
RESULT Dados
CATCH
Error("Tipo de dados não suportado para envio")
RESULT ""
END
END

//-------------------------------------------------------------------
// Constrói a URL completa da requisição
//-------------------------------------------------------------------
PROCEDURE BuildURL(Endpoint is string, Parametros is string = "")
// Remove barra à direita da URL base e à esquerda do endpoint se existirem
BaseURL is string = m_URL
IF Right(BaseURL, 1) = "/" THEN BaseURL = Left(BaseURL, Length(BaseURL) - 1)

// Adiciona barra ao início do endpoint se não existir
EndpointFormatado is string = Endpoint
IF Left(EndpointFormatado, 1) <> "/" THEN EndpointFormatado = "/" + EndpointFormatado

// Combina URL base com endpoint
URLFinal is string = BaseURL + EndpointFormatado

// Adiciona parâmetros de query string se existirem
IF Parametros <> "" THEN
// Adiciona ? se não existir na URL
IF Position(URLFinal, "?") = 0 THEN
URLFinal += "?"
ELSE
// Se já tem parâmetros, adiciona &
IF Right(URLFinal, 1) <> "?" AND Right(URLFinal, 1) <> "&" THEN
URLFinal += "&"
END
END

// Adiciona parâmetros
URLFinal += Parametros
END

// Caso tenha API Key na query string
IF m_AuthType = authAPIKey AND m_APIKeyLocation = keyQuery THEN
// Adiciona ? ou & se necessário
IF Position(URLFinal, "?") = 0 THEN
URLFinal += "?"
ELSE
// Se já tem parâmetros, adiciona &
IF Right(URLFinal, 1) <> "?" AND Right(URLFinal, 1) <> "&" THEN
URLFinal += "&"
END
END

// Adiciona API Key à query string
URLFinal += m_APIKeyName + "=" + URLEncode(m_APIKey)
END

RESULT URLFinal
END

//-------------------------------------------------------------------
// Configura autenticação para a requisição
//-------------------------------------------------------------------
PROCEDURE ApplyAuthentication(Requisicao is httpRequest)
// Configura autenticação conforme o tipo
SWITCH m_AuthType
CASE authNone:
// Sem autenticação, não faz nada
BREAK

CASE authBasic:
// Autenticação Básica (usuário/senha)
Requisicao.AuthenticationBasic(m_Username, m_Password)
BREAK

CASE authBearer:
// Autenticação Bearer/JWT Token
Requisicao.AddHeader("Authorization", "Bearer " + m_Token)
BREAK

CASE authAPIKey:
// API Key - apenas no cabeçalho, query string tratada em BuildURL
IF m_APIKeyLocation = keyHeader THEN
Requisicao.AddHeader(m_APIKeyName, m_APIKey)
END
BREAK

CASE authOAuth2:
// OAuth 2.0
// Verifica se precisa obter/renovar token
IF m_AccessToken = "" OR m_TokenExpiry < Now() THEN
IF RefreshOAuthToken() = False THEN
Error("Falha ao obter/renovar token OAuth")
BREAK
END
END

// Adiciona token obtido
Requisicao.AddHeader("Authorization", "Bearer " + m_AccessToken)
BREAK

CASE authHMAC:
// Autenticação HMAC com assinatura
// Gera timestamp para evitar replay attacks
Timestamp is string = DateTimeToString(Now(), "yyyyMMddHHmmss")

// Dados para assinatura: timestamp + URL + dados (se houver)
DadosParaAssinar is string = Timestamp + Requisicao.URL + Requisicao.Data

// Calcula assinatura HMAC SHA-256
Assinatura is string = HMACSHA256(DadosParaAssinar, m_HMACSecret)
AssinaturaBase64 is string = Encode(Assinatura, encodeBASE64)

// Adiciona cabeçalhos de autenticação
Requisicao.AddHeader("X-API-Timestamp", Timestamp)
Requisicao.AddHeader("X-API-Signature", AssinaturaBase64)
BREAK

CASE authMTLS:
// Autenticação mTLS (certificado mútuo)
Requisicao.Certificate = m_CertificatePath
Requisicao.CertificatePassword = m_CertificatePassword
BREAK

CASE authCustom:
// Autenticação personalizada - implementada por subclasses
ApplyCustomAuthentication(Requisicao)
BREAK
END
END

//-------------------------------------------------------------------
// Método a ser sobrescrito em subclasses para autenticação personalizada
//-------------------------------------------------------------------
PROCEDURE ApplyCustomAuthentication(Requisicao is httpRequest)
// Método vazio para ser sobrescrito em classes derivadas
// Implementação padrão não faz nada
END

//-------------------------------------------------------------------
// Atualiza token OAuth 2.0 usando refresh token ou credentials
//-------------------------------------------------------------------
PROCEDURE RefreshOAuthToken()
// Se temos um refresh token válido, tenta usá-lo primeiro
IF m_RefreshToken <> "" THEN
IF RefreshOAuthTokenWithRefreshToken() THEN
RESULT True
END
END

// Se não tem refresh token ou falhou, faz autenticação completa
RESULT RefreshOAuthTokenWithCredentials()
END

//-------------------------------------------------------------------
// Atualiza token OAuth usando refresh token
//-------------------------------------------------------------------
PROCEDURE RefreshOAuthTokenWithRefreshToken()
// Cria requisição para o endpoint de token
TokenRequest is httpRequest
TokenRequest.URL = m_OAuthTokenURL
TokenRequest.Method = httpPost
TokenRequest.ContentType = "application/x-www-form-urlencoded"

// Prepara dados para refresh token
TokenRequest.Data = [
grant_type=refresh_token&
refresh_token=[%m_RefreshToken%]&
client_id=[%m_OAuthClientID%]&
client_secret=[%m_OAuthClientSecret%]
]

// Define timeout
TokenRequest.Timeout = m_Timeout

// Executa requisição
LogOperation("Renovando token OAuth com refresh token")
TokenResponse is httpResponse = HTTPSend(TokenRequest)

// Verifica resultado
IF TokenResponse.StatusCode = 200 THEN
// Processa resposta
TRY
TokenData is JSON = JSONToValue(TokenResponse.Content)

// Extrai tokens
m_AccessToken = TokenData.access_token

// Atualiza refresh token se fornecido
IF TokenData.refresh_token <> Null THEN
m_RefreshToken = TokenData.refresh_token
END

// Calcula expiração
ValidadeSegundos is int = TokenData.expires_in ?? 3600 // Padrão 1h
m_TokenExpiry = DateTimeAddSecond(Now(), ValidadeSegundos)

LogOperation("Token OAuth renovado com sucesso")
RESULT True
CATCH
Error("Erro ao processar resposta do token: " + ErrorInfo())
RESULT False
END
ELSE
// Falha ao obter token
LogOperation("Falha ao renovar token OAuth: " + TokenResponse.StatusCode)
RESULT False
END
END

//-------------------------------------------------------------------
// Obtém token OAuth usando credenciais
//-------------------------------------------------------------------
PROCEDURE RefreshOAuthTokenWithCredentials()
// Cria requisição para o endpoint de token
TokenRequest is httpRequest
TokenRequest.URL = m_OAuthTokenURL
TokenRequest.Method = httpPost
TokenRequest.ContentType = "application/x-www-form-urlencoded"

// Prepara dados para autenticação de cliente
TokenRequest.Data = [
grant_type=client_credentials&
client_id=[%m_OAuthClientID%]&
client_secret=[%m_OAuthClientSecret%]
]

// Define timeout
TokenRequest.Timeout = m_Timeout

// Executa requisição
LogOperation("Obtendo token OAuth com credenciais")
TokenResponse is httpResponse = HTTPSend(TokenRequest)

// Verifica resultado
IF TokenResponse.StatusCode = 200 THEN
// Processa resposta
TRY
TokenData is JSON = JSONToValue(TokenResponse.Content)

// Extrai tokens
m_AccessToken = TokenData.access_token

// Extrai refresh token se disponível
IF TokenData.refresh_token <> Null THEN
m_RefreshToken = TokenData.refresh_token
END

// Calcula expiração
ValidadeSegundos is int = TokenData.expires_in ?? 3600 // Padrão 1h
m_TokenExpiry = DateTimeAddSecond(Now(), ValidadeSegundos)

LogOperation("Token OAuth obtido com sucesso")
RESULT True
CATCH
Error("Erro ao processar resposta do token: " + ErrorInfo())
RESULT False
END
ELSE
// Falha ao obter token
LogOperation("Falha ao obter token OAuth: " + TokenResponse.StatusCode)
RESULT False
END
END

//-------------------------------------------------------------------
// Aplica cabeçalhos padrão à requisição
//-------------------------------------------------------------------
PROCEDURE ApplyDefaultHeaders(Requisicao is httpRequest)
// Adiciona todos os cabeçalhos padrão
FOR i = 1 TO ArrayCount(m_DefaultHeaders) STEP 2
Requisicao.AddHeader(m_DefaultHeaders[i], m_DefaultHeaders[i+1])
END

// Define o tipo de conteúdo se não for vazio
IF m_ContentType <> "" THEN
Requisicao.ContentType = m_ContentType
END
END

//-------------------------------------------------------------------
// Método principal para executar qualquer tipo de requisição
//-------------------------------------------------------------------
PROCEDURE ExecuteRequest(Metodo is int, Endpoint is string, Parametros is string = "", Dados is string = "", Cabecalhos is array of string dynamic = Null)
// Cria objeto de requisição
Requisicao is httpRequest

// Configura URL
Requisicao.URL = BuildURL(Endpoint, Parametros)

// Configura método HTTP
Requisicao.Method = Metodo

// Configura timeout
Requisicao.Timeout = m_Timeout

// Define dados do corpo se existirem
IF Dados <> "" THEN
Requisicao.Data = Dados
END

// Aplica cabeçalhos padrão
ApplyDefaultHeaders(Requisicao)

// Aplica cabeçalhos adicionais específicos desta requisição
IF Cabecalhos <> Null THEN
FOR i = 1 TO ArrayCount(Cabecalhos) STEP 2
Requisicao.AddHeader(Cabecalhos[i], Cabecalhos[i+1])
END
END

// Aplica autenticação
ApplyAuthentication(Requisicao)

// Armazena última requisição para debug
m_LastRequest = Requisicao

// Log da operação
LogOperation("Executando requisição " + EnumToString(Metodo) + " para: " + Requisicao.URL)

// Executa requisição com tratamento de erros
TRY
// Executa a requisição HTTP
Resposta is httpResponse = HTTPSend(Requisicao)

// Armazena última resposta
m_LastResponse = Resposta

// Log do resultado
LogOperation("Resposta recebida: " + Resposta.StatusCode + " " + Resposta.StatusInfo)

// Retorna resposta completa
RESULT Resposta
CATCH
// Log do erro
MessagemErro is string = "Erro ao executar requisição: " + ErrorInfo()
LogOperation(MessagemErro)
Error(MessagemErro)

// Cria resposta vazia para evitar null pointer
RespostaVazia is httpResponse
RespostaVazia.StatusCode = 0
RespostaVazia.StatusInfo = "Erro de conexão: " + ErrorInfo()

// Armazena como última resposta
m_LastResponse = RespostaVazia

// Retorna resposta vazia
RESULT RespostaVazia
END
END

END//Classe

//=======================================================================
// CLASSES DERIVADAS PARA CASOS DE USO ESPECÍFICOS
//=======================================================================

//-------------------------------------------------------------------
// Classe especializada para APIs RESTful genéricas - Implementa paginação
//-------------------------------------------------------------------
CLASSE RESTGenericAPI HÉRITA DE RESTAPIClient
// Propriedades de paginação
PRIVATE
m_PaginationStrategy is int = pagNone // Estratégia de paginação
m_PageParam is string = "page" // Nome do parâmetro de página
m_LimitParam is string = "limit" // Nome do parâmetro de limite
m_DefaultLimit is int = 50 // Limite padrão de itens por página
m_CurrentPage is int = 1 // Página atual

PUBLIC CONSTANT
// Estratégias de paginação
pagNone = 0 // Sem paginação
pagOffset = 1 // Paginação baseada em offset
pagPage = 2 // Paginação baseada em página
pagCursor = 3 // Paginação baseada em cursor

PUBLIC
//-------------------------------------------------------------------
// Construtor
//-------------------------------------------------------------------
PROCEDURE Constructor(URLAPI is string)
// Chama construtor da classe pai
Constructor RESTAPIClient(URLAPI)
END

//-------------------------------------------------------------------
// Configura estratégia de paginação
//-------------------------------------------------------------------
PROCEDURE SetPagination(Estrategia is int, ParamPagina is string = "", ParamLimite is string = "", LimitePadrao is int = 0)
// Configura estratégia de paginação
m_PaginationStrategy = Estrategia

// Atualiza parâmetros se fornecidos
IF ParamPagina <> "" THEN m_PageParam = ParamPagina
IF ParamLimite <> "" THEN m_LimitParam = ParamLimite
IF LimitePadrao > 0 THEN m_DefaultLimit = LimitePadrao

// Reset de página atual
m_CurrentPage = 1

LogOperation("Estratégia de paginação configurada: " + EnumToString(Estrategia))
RESULT True
END

//-------------------------------------------------------------------
// Obtém próxima página de dados paginados
//-------------------------------------------------------------------
PROCEDURE GetNextPage(Endpoint is string)
Parametros is string = ""

// Aplica parâmetros de paginação conforme a estratégia
SWITCH m_PaginationStrategy
CASE pagOffset:
// Paginação baseada em offset
Offset is int = (m_CurrentPage - 1) * m_DefaultLimit
Parametros = "offset=" + Offset + "&limit=" + m_DefaultLimit
BREAK

CASE pagPage:
// Paginação baseada em página
Parametros = m_PageParam + "=" + m_CurrentPage + "&" + m_LimitParam + "=" + m_DefaultLimit
BREAK

CASE pagCursor:
// Para cursor, precisamos extrair o cursor da resposta anterior
// Este é um exemplo básico, a implementação real depende da API
IF m_LastResponse <> Null THEN
TRY
JsonData is JSON = JSONToValue(m_LastResponse.Content)
IF JsonData.next_cursor <> Null THEN
Parametros = "cursor=" + JsonData.next_cursor
ELSE
// Sem próxima página
Error("Não há mais páginas para carregar")
RESULT Null
END
CATCH
// Erro ao processar cursor
Error("Erro ao extrair cursor de paginação: " + ErrorInfo())
RESULT Null
END
ELSE
// Primeira página
Parametros = "cursor="
END
BREAK
END

// Incrementa página atual (exceto para cursor)
IF m_PaginationStrategy <> pagCursor THEN
m_CurrentPage++
END

// Executa a requisição GET com parâmetros de paginação
Resposta is httpResponse = Get(Endpoint, Parametros)

// Verifica se há mais páginas para cursor
IF m_PaginationStrategy = pagCursor AND Resposta.StatusCode = 200 THEN
TRY
JsonData is JSON = JSONToValue(Resposta.Content)
// Se não há próximo cursor, marca como última página
IF JsonData.next_cursor = Null OR JsonData.next_cursor = "" THEN
LogOperation("Última página atingida (cursor)")
END
CATCH
// Ignorar erros de processamento aqui
END
END

// Retorna resposta
RESULT Resposta
END

//-------------------------------------------------------------------
// Reinicia paginação
//-------------------------------------------------------------------
PROCEDURE ResetPagination()
// Reinicia para a primeira página
m_CurrentPage = 1
LogOperation("Paginação reiniciada")
RESULT True
END

//-------------------------------------------------------------------
// Obtém todos os resultados paginados (cuidado com APIs grandes)
//-------------------------------------------------------------------
PROCEDURE GetAllPages(Endpoint is string, LimiteMaxPaginas is int = 10)
// Array para armazenar todos os dados
TodosDados is array dynamic

// Reinicia paginação
ResetPagination()

// Contador de páginas
PaginasCarregadas is int = 0

// Loop para obter todas as páginas até o limite
WHILE PaginasCarregadas < LimiteMaxPaginas
// Incrementa contador
PaginasCarregadas++

// Obtém próxima página
Resposta is httpResponse = GetNextPage(Endpoint)

// Verifica se sucesso
IF Resposta = Null OR Resposta.StatusCode <> 200 THEN
// Erro ou fim da paginação
BREAK
END

// Processa dados
TRY
// Converte para JSON
DadosPagina is JSON = JSONToValue(Resposta.Content)

// Extrai array de itens - adaptar conforme estrutura da API
DadosItens is array dynamic

// Tenta vários formatos comuns de resposta
IF DadosPagina.items <> Null THEN
DadosItens = DadosPagina.items
ELSEIF DadosPagina.data <> Null THEN
DadosItens = DadosPagina.data
ELSEIF DadosPagina.results <> Null THEN
DadosItens = DadosPagina.results
ELSEIF DadosPagina..Type = jsonArray THEN
// A própria resposta é um array
DadosItens = DadosPagina
END

// Adiciona itens ao array principal
FOR EACH Item OF DadosItens
ArrayAdd(TodosDados, Item)
END

// Verifica se há mais páginas (para estratégias offset/page)
IF m_PaginationStrategy <> pagCursor THEN
// Verifica se recebemos menos itens que o limite
IF DadosItens..Count < m_DefaultLimit THEN
LogOperation("Última página atingida (contagem < limite)")
BREAK
END
ELSEIF m_PaginationStrategy = pagCursor THEN
// Para cursor, verifica next_cursor
IF DadosPagina.next_cursor = Null OR DadosPagina.next_cursor = "" THEN
LogOperation("Última página atingida (sem próximo cursor)")
BREAK
END
END
CATCH
Error("Erro ao processar dados da página: " + ErrorInfo())
BREAK
END
END

LogOperation("Total de páginas carregadas: " + PaginasCarregadas)
LogOperation("Total de itens recuperados: " + TodosDados..Count)

// Retorna todos os dados
RESULT TodosDados
END
END//Classe

//-------------------------------------------------------------------
// Classe especializada para API com autenticação personalizada
//-------------------------------------------------------------------
CLASSE CustomAuthAPI HÉRITA DE RESTAPIClient
// Campos adicionais para autenticação personalizada
PRIVATE
m_AppId is string // ID da aplicação
m_ApiVersion is string = "v1" // Versão da API
m_DeviceId is string // ID do dispositivo
m_CustomTokens is associative array of strings // Tokens personalizados

PUBLIC
//-------------------------------------------------------------------
// Construtor
//-------------------------------------------------------------------
PROCEDURE Constructor(URLAPI is string, AppId is string, DeviceId is string = "")
// Chama construtor da classe pai
Constructor RESTAPIClient(URLAPI)

// Inicializa campos
m_AppId = AppId
m_DeviceId = DeviceId
IF m_DeviceId = "" THEN
m_DeviceId = GetUUID() // Gera ID de dispositivo aleatório
END

// Ativa autenticação personalizada
m_AuthType = authCustom
END

//-------------------------------------------------------------------
// Define versão da API
//-------------------------------------------------------------------
PROCEDURE SetApiVersion(Versao is string)
m_ApiVersion = Versao
RESULT True
END

//-------------------------------------------------------------------
// Adiciona token personalizado
//-------------------------------------------------------------------
PROCEDURE AddCustomToken(Nome is string, Valor is string)
m_CustomTokens[Nome] = Valor
RESULT True
END

//-------------------------------------------------------------------
// Remove token personalizado
//-------------------------------------------------------------------
PROCEDURE RemoveCustomToken(Nome is string)
IF m_CustomTokens[Nome] <> Null THEN
ArrayDelete(m_CustomTokens, Nome)
RESULT True
END
RESULT False
END

PROTECTED
//-------------------------------------------------------------------
// Sobrescreve método de autenticação personalizada
//-------------------------------------------------------------------
PROCEDURE ApplyCustomAuthentication(Requisicao is httpRequest)
// Adiciona cabeçalhos padrão de autenticação personalizada
Requisicao.AddHeader("X-App-ID", m_AppId)
Requisicao.AddHeader("X-API-Version", m_ApiVersion)
Requisicao.AddHeader("X-Device-ID", m_DeviceId)

// Gera timestamp atual
Timestamp is string = DateTimeToString(Now(), "yyyyMMddHHmmss")
Requisicao.AddHeader("X-Timestamp", Timestamp)

// Adiciona todos os tokens personalizados
FOR EACH Token, Nome IN m_CustomTokens
Requisicao.AddHeader("X-" + Nome, Token)
END

// Gera assinatura de segurança combinando dados
IF m_CustomTokens["Secret"] <> Null THEN
// Dados para assinatura: AppId + DeviceId + Timestamp + URL
DadosParaAssinar is string = m_AppId + "|" + m_DeviceId + "|" + Timestamp + "|" + Requisicao.URL

// Gera hash SHA-256
Assinatura is string = SHA256(DadosParaAssinar)

// Adiciona assinatura como cabeçalho
Requisicao.AddHeader("X-Signature", Assinatura)
END
END
END//Classe

//-------------------------------------------------------------------
// Exemplo de uso da classe
//-------------------------------------------------------------------
PROCEDURE ExemploUsoAPIClient()
// Exemplo 1: Uso básico com autenticação Bearer Token
API is RESTAPIClient("https://api.exemplo.com")
API.SetBearerAuth("seu_token_jwt_aqui")
API.SetTimeout(60) // 60 segundos

// Fazer uma requisição GET
Resposta is httpResponse = API.Get("/users/profile")

// Verificar resultado
IF API.IsSuccess() THEN
// Processo dados JSON
DadosJSON is JSON = API.GetResponseJSON()
Info("Nome do usuário: " + DadosJSON.user.name)
ELSE
Error("Erro: " + API.GetErrorMessage())
END

// Exemplo 2: Uso com paginação
APIPaginada is RESTGenericAPI("https://api.exemplo.com")
APIPaginada.SetAPIKeyAuth("sua_api_key_aqui")
APIPaginada.SetPagination(APIPaginada.pagPage, "page", "size", 25)

// Obter todos os usuários (com limite de 5 páginas)
TodosUsuarios is array dynamic = APIPaginada.GetAllPages("/users", 5)

// Exemplo 3: Uso com autenticação personalizada
APIPersonalizada is CustomAuthAPI("https://api.personalizada.com", "APP123456")
APIPersonalizada.AddCustomToken("Secret", "chave_secreta_aqui")

// Fazer requisição com autenticação personalizada
RespostaPersonalizada is httpResponse = APIPersonalizada.Get("/dados/exclusivos")

RESULT True
END

--
Adriano José Boller
______________________________________________
Consultor e Representante Oficial da
PcSoft no Brasil
+55 (41) 99949 1800
adrianoboller@gmail.com
skype: adrianoboller
http://wxinformatica.com.br/
Registered member
3,851 messages
Posted on April, 03 2025 - 9:32 AM
// -----------------------------------------------------------
// Exemplos de Uso da CLAPI_REST v4.0
// -----------------------------------------------------------
// Demonstração de como utilizar a classe com diversos métodos
// de autenticação e cenários comuns de uso
// -----------------------------------------------------------
// Autor: Claude [Baseado no trabalho original do fórum PCSoft]
// Data: 03/04/2025
// -----------------------------------------------------------

// ==================== EXEMPLO 1: AUTENTICAÇÃO BÁSICA ====================
PROCÉDURE ExemploBasicAuth()
// Criando a instância da classe API REST
oAPI est un CLAPI_REST("https://api.exemplo.com/users")

// Configurando autenticação básica (username/senha)
oAPI.SetBasicAuth("usuario_exemplo", "senha_segura")

// Habilitando log para debug
oAPI.EnableLogging(Vrai, "basic_auth_log.txt", 4)

// Realizando uma requisição GET
SI oAPI.GET() ALORS
// Verificando se a requisição foi bem-sucedida
SI oAPI.IsSuccess() ALORS
Info("Requisição bem-sucedida! Status: " + oAPI.GetStatusCode())

// Processando resposta JSON
vResposta est un variant = oAPI.ParseJSONResponse()
SI vResposta <> Null ALORS
// Exemplo de processamento da resposta
POUR TOUT usuario DE vResposta.users
Trace("Usuário encontrado: " + usuario.name + " (ID: " + usuario.id + ")")
FIN
FIN
SINON
Erreur("Erro na requisição: " + oAPI.GetStatusCode() + " - " + oAPI.GetResponseBody())
FIN
SINON
Erreur("Falha ao realizar a requisição")
FIN

// Exemplo de uso com POST
stNovoUsuario est une Structure
nom est une chaîne = "João Silva"
email est une chaîne = "joao.silva@exemplo.com"
role est une chaîne = "usuario"
FIN

// Nova requisição para criar um usuário
oAPI.SetURL("https://api.exemplo.com/users/create")
SI oAPI.POST(, stNovoUsuario) ALORS
SI oAPI.IsSuccess() ALORS
vResposta = oAPI.ParseJSONResponse()
Info("Usuário criado com sucesso! ID: " + vResposta.id)
SINON
Erreur("Erro ao criar usuário: " + oAPI.GetStatusCode())
FIN
FIN
FIN

// ==================== EXEMPLO 2: TOKEN BEARER ====================
PROCÉDURE ExemploBearerAuth()
// Criando a instância da classe API REST
oAPI est un CLAPI_REST("https://api.exemplo.com/recursos")

// Configurando autenticação Bearer Token
oAPI.SetBearerAuth("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c")

// Configurações adicionais
oAPI.SetTimeout(30) // 30 segundos
oAPI.SetVerifySSL(Vrai)

// Adicionando cabeçalhos personalizados
oAPI.AddHeader("X-Custom-Header", "CustomValue")

// Adicionando parâmetros de consulta
oAPI.AddQueryParam("limit", "50")
oAPI.AddQueryParam("offset", "0")

// Realizando requisição GET com todos os parâmetros configurados
SI oAPI.GET() ALORS
SI oAPI.IsSuccess() ALORS
vResposta est un variant = oAPI.ParseJSONResponse()
Info("Total de recursos: " + vResposta.total)

// Iterando sobre os resultados
POUR TOUT recurso DE vResposta.data
Trace("Recurso: " + recurso.nome + " - " + recurso.descricao)
FIN
SINON
Erreur("Falha na requisição: " + oAPI.GetStatusCode())
FIN
FIN

// Exemplo de atualização de recurso com PUT
stAtualizarRecurso est une Structure
id est un entier = 123
nome est une chaîne = "Recurso Atualizado"
descricao est une chaîne = "Nova descrição do recurso"
ativo est un booléen = Vrai
FIN

oAPI.SetURL("https://api.exemplo.com/recursos/123")
SI oAPI.PUT(, stAtualizarRecurso) ALORS
SI oAPI.IsSuccess() ALORS
Info("Recurso atualizado com sucesso!")
SINON
Erreur("Erro ao atualizar recurso: " + oAPI.GetResponseBody())
FIN
FIN
FIN

// ==================== EXEMPLO 3: CHAVE DE API ====================
PROCÉDURE ExemploAPIKey()
// Criando instância
oAPI est un CLAPI_REST("https://api.weatherservice.com/forecast")

// Configurando autenticação com API Key
oAPI.SetAPIKeyAuth("a1b2c3d4e5f6g7h8i9j0", "X-API-Key")

// Adicionando parâmetros de consulta
oAPI.AddQueryParam("city", "Paris")
oAPI.AddQueryParam("days", "5")

// Configurando cache para economizar requisições
oAPI.EnableCache(Vrai, 1800) // Cache de 30 minutos

// Realizando a consulta
SI oAPI.GET() ALORS
SI oAPI.IsSuccess() ALORS
vClima est un variant = oAPI.ParseJSONResponse()

Info("Previsão para: " + vClima.city)
Info("Temperatura atual: " + vClima.current.temperature + "°C")

POUR TOUT dia DE vClima.forecast
Trace("Dia: " + dia.date + " - Min: " + dia.min + "°C, Max: " + dia.max + "°C")
FIN
SINON
Erreur("Erro ao obter previsão: " + oAPI.GetStatusCode())
FIN
FIN
FIN

// ==================== EXEMPLO 4: AUTENTICAÇÃO JWT ====================
PROCÉDURE ExemploJWTAuth()
// Criando instância
oAPI est un CLAPI_REST("https://api.secure-service.com/dashboard")

// Configurando autenticação JWT
sJWTToken est une chaîne = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.NHVaYe26MbtOYhSKkoKwdw5eR0iSnWYykOu"
oAPI.SetJWTAuth(sJWTToken)

// Configurações de timeout e retry
oAPI.SetTimeout(45)
oAPI.SetRetryPolicy(5, 1500) // 5 tentativas, 1.5 segundos entre cada

// Configurações de logging
oAPI.EnableLogging(Vrai, "jwt_api_log.txt", 3)

// Realizando requisição
SI oAPI.GET() ALORS
SI oAPI.IsSuccess() ALORS
vDashboard est un variant = oAPI.ParseJSONResponse()

Info("Dashboard carregado para: " + vDashboard.user.name)
Info("Nível de acesso: " + vDashboard.accessLevel)

// Processando estatísticas
POUR TOUT metrica DE vDashboard.metrics
Trace(metrica.name + ": " + metrica.value + " " + metrica.unit)
FIN
SINON
Erreur("Erro ao acessar dashboard: " + oAPI.GetStatusCode())
// Verificando se o token expirou
SI oAPI.GetStatusCode() = 401 ALORS
Trace("Token JWT expirado ou inválido")
FIN
FIN
FIN
FIN

// ==================== EXEMPLO 5: CERTIFICADO SSL ====================
PROCÉDURE ExemploCertificado()
// Criando instância
oAPI est un CLAPI_REST("https://api.banco-seguro.com/transacoes")

// Configurando autenticação com certificado
sCaminhoCertificado est une chaîne = fRepExe() + [fsep] + "certificados" + [fsep] + "certificado_cliente.p12"
sSenhaCertificado est une chaîne = "senhadocertificado"
oAPI.SetCertificateAuth(sCaminhoCertificado, sSenhaCertificado)

// Configurando dados da transação
stTransacao est une Structure
conta_origem est une chaîne = "12345-6"
conta_destino est une chaîne = "65432-1"
valor est un monétaire = 1250.75
descricao est une chaîne = "Pagamento de serviço"
FIN

// Enviando a transação
SI oAPI.POST(, stTransacao) ALORS
SI oAPI.IsSuccess() ALORS
vResposta est un variant = oAPI.ParseJSONResponse()

Info("Transação realizada com sucesso!")
Info("Protocolo: " + vResposta.protocolo)
Info("Data/Hora: " + vResposta.timestamp)
SINON
Erreur("Erro na transação: " + oAPI.GetStatusCode())
Erreur("Mensagem: " + oAPI.GetResponseBody())
FIN
SINON
Erreur("Falha ao comunicar com o servidor do banco")
FIN
FIN

// ==================== EXEMPLO 6: OAUTH 2.0 CLIENT CREDENTIALS ====================
PROCÉDURE ExemploOAuth2()
// Configurações do OAuth2
sTokenEndpoint est une chaîne = "https://auth.servico-api.com/oauth/token"
sClientID est une chaîne = "app_client_id_123456"
sClientSecret est une chaîne = "app_client_secret_abcdef"
sScope est une chaîne = "read write"

// Criando instância
oAPI est un CLAPI_REST("https://api.servico-api.com/v1/products")

// Configurando autenticação OAuth2
oAPI.SetOAuth2ClientCredentials(sTokenEndpoint, sClientID, sClientSecret, sScope)

// Configurações adicionais
oAPI.EnableLogging(Vrai, "oauth2_log.txt", 3)
oAPI.SetTimeout(60)

// A classe gerenciará automaticamente a obtenção do token OAuth2
SI oAPI.GET() ALORS
SI oAPI.IsSuccess() ALORS
vProdutos est un variant = oAPI.ParseJSONResponse()

Info("Total de produtos: " + vProdutos.total)

POUR TOUT prod DE vProdutos.items
Trace("Produto: " + prod.name + " - Preço: " + prod.price)
FIN
SINON
Erreur("Erro ao obter produtos: " + oAPI.GetStatusCode())
FIN
FIN

// Criando um novo produto
stNovoProduto est une Structure
name est une chaîne = "Produto Premium"
description est une chaîne = "Produto de alta qualidade"
price est un monétaire = 299.90
stock est un entier = 50
category_id est un entier = 3
FIN

oAPI.SetURL("https://api.servico-api.com/v1/products")
SI oAPI.POST(, stNovoProduto) ALORS
SI oAPI.IsSuccess() ALORS
vResposta est un variant = oAPI.ParseJSONResponse()
Info("Produto criado com ID: " + vResposta.id)
SINON
Erreur("Erro ao criar produto: " + oAPI.GetResponseBody())
FIN
FIN
FIN

// ==================== EXEMPLO 7: AUTENTICAÇÃO HMAC ====================
PROCÉDURE ExemploHMACAuth()
// Criando instância
oAPI est un CLAPI_REST("https://api.pagamento-seguro.com/v2/checkout")

// Configurando autenticação HMAC
sAPIKey est une chaîne = "merchant_123456"
sAPISecret est une chaîne = "9876543210abcdefghijklmn"
oAPI.SetHMACAuth(sAPIKey, sAPISecret)

// Preparando dados da transação
stCheckout est une Structure
amount est un monétaire = 157.99
currency est une chaîne = "BRL"
customer est une Structure
name est une chaîne = "Maria Oliveira"
email est une chaîne = "maria@exemplo.com"
phone est une chaîne = "+5511987654321"
FIN
items est un tableau de Structures
sNome est une chaîne
nPreco est un monétaire
nQuantidade est un entier
FIN
FIN

// Adicionando itens ao checkout
stItem1 est une Structure
sNome = "Teclado Mecânico"
nPreco = 129.99
nQuantidade = 1
FIN
Ajoute(stCheckout.items, stItem1)

stItem2 est une Structure
sNome = "Mouse Gamer"
nPreco = 28.00
nQuantidade = 1
FIN
Ajoute(stCheckout.items, stItem2)

// Enviando requisição com HMAC
SI oAPI.POST(, stCheckout) ALORS
SI oAPI.IsSuccess() ALORS
vResposta est un variant = oAPI.ParseJSONResponse()

Info("Checkout criado com sucesso!")
Info("ID da transação: " + vResposta.transaction_id)
Info("URL de pagamento: " + vResposta.payment_url)
SINON
Erreur("Erro no checkout: " + oAPI.GetStatusCode())
Erreur("Detalhes: " + oAPI.GetResponseBody())
FIN
FIN
FIN

// ==================== EXEMPLO 8: AUTENTICAÇÃO AWS ====================
PROCÉDURE ExemploAWSAuth()
// Criando instância
oAPI est un CLAPI_REST("https://s3.amazonaws.com/my-bucket/file.txt")

// Configurando autenticação AWS
sAccessKey est une chaîne = "AKIAIOSFODNN7EXAMPLE"
sSecretKey est une chaîne = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
sRegion est une chaîne = "us-east-1"
sService est une chaîne = "s3"

oAPI.SetAWSAuth(sAccessKey, sSecretKey, sRegion, sService)

// Upload de arquivo para a AWS S3
oAPI.SetMethod("PUT")
oAPI.SetContentType("text/plain")
oAPI.SetRequestBody("Conteúdo do arquivo de texto para upload no S3")

SI oAPI.Send() ALORS
SI oAPI.IsSuccess() ALORS
Info("Arquivo enviado com sucesso para o S3!")
SINON
Erreur("Erro no upload: " + oAPI.GetStatusCode())
Erreur("Detalhes: " + oAPI.GetResponseBody())
FIN
FIN

// Listando objetos do bucket
oAPI.SetURL("https://s3.amazonaws.com/my-bucket")
oAPI.SetMethod("GET")

SI oAPI.Send() ALORS
SI oAPI.IsSuccess() ALORS
// A resposta é XML, vamos usar o parser XML
xmlResp est un xmlDocument = oAPI.ParseXMLResponse()

SI xmlResp <> Null ALORS
// Processando XML do S3
sXPath est une chaîne = "//Contents/Key"
tabArquivos est un tableau de chaînes = XMLExtraitChaîne(xmlResp, sXPath)

Info("Arquivos no bucket:")
POUR TOUT arquivo DE tabArquivos
Trace(" - " + arquivo)
FIN
FIN
SINON
Erreur("Erro ao listar bucket: " + oAPI.GetStatusCode())
FIN
FIN
FIN

// ==================== EXEMPLO 9: UPLOAD DE ARQUIVOS ====================
PROCÉDURE ExemploUploadArquivo()
// Criando instância
oAPI est un CLAPI_REST("https://api.sistema-documentos.com/upload")

// Configurando autenticação básica
oAPI.SetBasicAuth("sistema_upload", "senha123")

// Adicionando arquivos para upload
sCaminhoArquivo1 est une chaîne = fRepExe() + [fsep] + "docs" + [fsep] + "relatorio.pdf"
sCaminhoArquivo2 est une chaîne = fRepExe() + [fsep] + "docs" + [fsep] + "anexo.xlsx"

oAPI.AddFile("documento", sCaminhoArquivo1)
oAPI.AddFile("anexo", sCaminhoArquivo2)

// Adicionando metadados do upload
oAPI.AddFormData("descricao", "Relatório financeiro trimestral")
oAPI.AddFormData("departamento", "Financeiro")
oAPI.AddFormData("categoria", "Relatório")

// Realizando upload multipart/form-data
SI oAPI.POST() ALORS
SI oAPI.IsSuccess() ALORS
vResposta est un variant = oAPI.ParseJSONResponse()

Info("Upload realizado com sucesso!")
Info("ID do documento: " + vResposta.document_id)

POUR TOUT arquivo DE vResposta.files
Trace("Arquivo: " + arquivo.name + " - URL: " + arquivo.url)
FIN
SINON
Erreur("Erro no upload: " + oAPI.GetStatusCode())
FIN
FIN
FIN

// ==================== EXEMPLO 10: DOWNLOAD DE ARQUIVO ====================
PROCÉDURE ExemploDownloadArquivo()
// Criando instância
oAPI est un CLAPI_REST("https://api.sistema-documentos.com/files/12345/download")

// Configurando autenticação token
oAPI.SetBearerAuth("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFjZXNzbyBEb3dubG9hZCIsImlhdCI6MTUxNjIzOTAyMn0.sbbCA_SYB1fK-OPv7P_3BTQqaZuE454OgtgXd7SckJs")

// Configurando timeout maior para download
oAPI.SetTimeout(180) // 3 minutos

// Realizando requisição GET
SI oAPI.GET() ALORS
SI oAPI.IsSuccess() ALORS
// Obtendo o nome do arquivo do cabeçalho
sNomeArquivo est une chaîne = oAPI.GetResponseHeader("Content-Disposition")

// Extraindo nome do arquivo (simplificado)
sNomeArquivo = Remplace(sNomeArquivo, "attachment; filename=", "")
sNomeArquivo = Remplace(sNomeArquivo, """", "")

// Salvando o arquivo
sCaminhoDestino est une chaîne = fRepExe() + [fsep] + "downloads" + [fsep] + sNomeArquivo

// Criando diretório se não existir
sDirDownload est une chaîne = fRepExe() + [fsep] + "downloads"
SI fRépertoireExiste(sDirDownload) = Faux ALORS
frCrée(sDirDownload)
FIN

// Salvando o conteúdo
fSauveBuffer(sCaminhoDestino, oAPI.GetResponseBody())

Info("Arquivo baixado com sucesso para: " + sCaminhoDestino)
SINON
Erreur("Erro ao baixar arquivo: " + oAPI.GetStatusCode())
FIN
FIN
FIN

// ==================== EXEMPLO 11: REQUISIÇÃO COM CACHE E RETRY ====================
PROCÉDURE ExemploCacheRetry()
// Criando instância
oAPI est un CLAPI_REST("https://api.servico-com-problemas.com/dados")

// Configurando política de retry
oAPI.SetRetryPolicy(5, 3000) // 5 tentativas, 3 segundos entre cada

// Configurando cache
oAPI.EnableCache(Vrai, 600) // Cache de 10 minutos

// Configurando logging para debug
oAPI.EnableLogging(Vrai, "retry_log.txt", 4)

// Realizando requisição
Trace("Iniciando requisição com política de retry...")
SI oAPI.GET() ALORS
SI oAPI.IsSuccess() ALORS
Info("Requisição bem-sucedida após retries!")
vResposta est un variant = oAPI.ParseJSONResponse()

// Processando resposta
Trace("Dados obtidos: " + vResposta.titulo)
SINON
Erreur("Todos os retries falharam: " + oAPI.GetStatusCode())
FIN
FIN

// Nova requisição que usará o cache
Trace("Realizando segunda requisição (deve usar cache)...")
oAPI2 est un CLAPI_REST("https://api.servico-com-problemas.com/dados")
oAPI2.EnableCache(Vrai, 600)

SI oAPI2.GET() ALORS
Trace("Requisição do cache concluída: " + oAPI2.GetStatusCode())
FIN
FIN

// ==================== EXEMPLO 12: SIMULADOR DE API DE PAGAMENTO ====================
PROCÉDURE SimuladorPagamento()
// Função que simula uma API de pagamento completo
// para demonstrar o uso avançado da classe

// -------- 1. Autenticação e obtenção de token --------

// Primeiro obtemos um token de acesso
oAuth est un CLAPI_REST("https://api.pagamento-xyz.com/auth/token")
oAuth.SetBasicAuth("merchant_id_123", "merchant_secret_xyz")
oAuth.SetContentType("application/x-www-form-urlencoded")
oAuth.AddFormData("grant_type", "client_credentials")

sToken est une chaîne = ""

Info("1. Obtendo token de acesso...")
SI oAuth.POST() ET oAuth.IsSuccess() ALORS
vToken est un variant = oAuth.ParseJSONResponse()
sToken = vToken.access_token
Info(" - Token obtido com sucesso")
SINON
Erreur("Falha na autenticação: " + oAuth.GetResponseBody())
RETOUR
FIN

// -------- 2. Verificação de cliente --------

// Verificando se o cliente existe
oCPF est un CLAPI_REST("https://api.pagamento-xyz.com/customers/verify")
oCPF.SetBearerAuth(sToken)

// Parâmetros da verificação
oCPF.AddQueryParam("document", "123.456.789-00")
oCPF.AddQueryParam("type", "CPF")

Info("2. Verificando cliente...")
SI oCPF.GET() ET oCPF.IsSuccess() ALORS
vCliente est un variant = oCPF.ParseJSONResponse()

SI vCliente.exists = Vrai ALORS
Info(" - Cliente encontrado: " + vCliente.name)
SINON
Info(" - Cliente não encontrado, vamos cadastrá-lo")

// 2.1 - Cadastrando novo cliente
oCadastro est un CLAPI_REST("https://api.pagamento-xyz.com/customers")
oCadastro.SetBearerAuth(sToken)

stNovoCliente est une Structure
name est une chaîne = "Maria Souza"
email est une chaîne = "maria@exemplo.com"
document est une chaîne = "123.456.789-00"
document_type est une chaîne = "CPF"
phone est une chaîne = "+5511987654321"
address est une Structure
street est une chaîne = "Av. Paulista"
number est une chaîne = "1000"
city est une chaîne = "São Paulo"
state est une chaîne = "SP"
postal_code est une chaîne = "01310-100"
country est une chaîne = "BR"
FIN
FIN

SI oCadastro.POST(, stNovoCliente) ET oCadastro.IsSuccess() ALORS
vNovoCliente est un variant = oCadastro.ParseJSONResponse()
Info(" - Cliente cadastrado com ID: " + vNovoCliente.id)
SINON
Erreur("Falha ao cadastrar cliente: " + oCadastro.GetResponseBody())
RETOUR
FIN
FIN
SINON
Erreur("Falha na verificação de cliente: " + oCPF.GetResponseBody())
RETOUR
FIN

// -------- 3. Criação de pagamento --------

Info("3. Criando transação de pagamento...")
oTransacao est un CLAPI_REST("https://api.pagamento-xyz.com/transactions")
oTransacao.SetBearerAuth(sToken)

// Preparando dados da transação
stTransacao est une Structure
amount est un monétaire = 156.78
currency est une chaîne = "BRL"
description est une chaîne = "Compra na Loja XYZ"
customer_id est une chaîne = "cust_123456" // ID do cliente real seria retornado na etapa anterior
payment est une Structure
method est une chaîne = "credit_card"
installments est un entier = 3
capture est un booléen = Vrai
card est une Structure
number est une chaîne = "4111111111111111"
holder_name est une chaîne = "MARIA SOUZA"
expiration_month est un entier = 12
expiration_year est un entier = 2025
cvv est une chaîne = "123"
FIN
FIN
FIN

SI oTransacao.POST(, stTransacao) ET oTransacao.IsSuccess() ALORS
vResultado est un variant = oTransacao.ParseJSONResponse()

Info(" - Transação criada com sucesso!")
Info(" - ID: " + vResultado.id)
Info(" - Status: " + vResultado.status)

// -------- 4. Consultando status da transação --------

Info("4. Consultando status da transação...")

oStatus est un CLAPI_REST("https://api.pagamento-xyz.com/transactions/" + vResultado.id)
oStatus.SetBearerAuth(sToken)

SI oStatus.GET() ET oStatus.IsSuccess() ALORS
vStatus est un variant = oStatus.ParseJSONResponse()

Info(" - Status detalhado: " + vStatus.status)
Info(" - Data processamento: " + vStatus.processed_at)
Info(" - Gateway: " + vStatus.acquirer)

// -------- 5. Gerando recibo --------

SI vStatus.status = "approved" ALORS
Info("5. Gerando recibo da transação...")

oRecibo est un CLAPI_REST("https://api.pagamento-xyz.com/transactions/" + vResultado.id + "/receipt")
oRecibo.SetBearerAuth(sToken)
oRecibo.AddQueryParam("format", "pdf")

SI oRecibo.GET() ET oRecibo.IsSuccess() ALORS
// Salvando o PDF do recibo
sCaminhoRecibo est une chaîne = fRepExe() + [fsep] + "recibos" + [fsep] + "recibo_" + vResultado.id + ".pdf"

// Criando diretório se não existir
sDirRecibos est une chaîne = fRepExe() + [fsep] + "recibos"
SI fRépertoireExiste(sDirRecibos) = Faux ALORS
frCrée(sDirRecibos)
FIN

// Salvando o recibo
fSauveBuffer(sCaminhoRecibo, oRecibo.GetResponseBody())

Info(" - Recibo salvo em: " + sCaminhoRecibo)
SINON
Erreur("Falha ao gerar recibo: " + oRecibo.GetStatusCode())
FIN
FIN
SINON
Erreur("Falha ao consultar status: " + oStatus.GetResponseBody())
FIN
SINON
Erreur("Falha ao criar transação: " + oTransacao.GetResponseBody())
FIN

Info("Simulação de pagamento concluída!")
FIN

// ==================== EXEMPLO 13: INTEGRAÇÃO COM API DE CLIMA ====================
PROCÉDURE ExemploAPIClima()
// Demonstração de integração com API de clima OpenWeatherMap

// Criando instância
oClima est un CLAPI_REST("https://api.openweathermap.org/data/2.5/weather")

// Usando autenticação via API Key como parâmetro (comum em APIs de clima)
sAPIKey est une chaîne = "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"
oClima.AddQueryParam("appid", sAPIKey)

// Adicionando parâmetros da consulta
oClima.AddQueryParam("q", "São Paulo,BR")
oClima.AddQueryParam("units", "metric") // Temperatura em Celsius
oClima.AddQueryParam("lang", "pt_br") // Descrições em português

// Habilitando cache para economizar requisições
oClima.EnableCache(Vrai, 3600) // Cache de 1 hora

// Realizando a consulta
Info("Consultando dados climáticos...")
SI oClima.GET() ALORS
SI oClima.IsSuccess() ALORS
vClima est un variant = oClima.ParseJSONResponse()

Info("Clima atual em " + vClima.name + ", " + vClima.sys.country)
Info("Temperatura: " + vClima.main.temp + "°C")
Info("Sensação térmica: " + vClima.main.feels_like + "°C")
Info("Umidade: " + vClima.main.humidity + "%")
Info("Condição: " + vClima.weather[1].description)

// Salvando os dados em um arquivo JSON
sArquivoJSON est une chaîne = fRepExe() + [fsep] + "clima_" + vClima.name + ".json"
fSauveTexte(sArquivoJSON, oClima.GetResponseBody())

// Consultando previsão de 5 dias
oPrevisao est un CLAPI_REST("https://api.openweathermap.org/data/2.5/forecast")
oPrevisao.AddQueryParam("appid", sAPIKey)
oPrevisao.AddQueryParam("q", "São Paulo,BR")
oPrevisao.AddQueryParam("units", "metric")
oPrevisao.AddQueryParam("lang", "pt_br")
oPrevisao.EnableCache(Vrai, 3600)

SI oPrevisao.GET() ET oPrevisao.IsSuccess() ALORS
vPrevisao est un variant = oPrevisao.ParseJSONResponse()

Info("Previsão para os próximos dias:")

dataAnterior est une chaîne = ""
POUR i = 1 A vPrevisao.list..Occurrence
dataHora est une chaîne = vPrevisao.list[i].dt_txt
data est une chaîne = Gauche(dataHora, 10)

SI data <> dataAnterior ALORS
dataAnterior = data
Info("Data: " + data)
Info(" - Temperatura: " + vPrevisao.list[i].main.temp + "°C")
Info(" - Condição: " + vPrevisao.list[i].weather[1].description)
FIN
FIN
FIN
SINON
Erreur("Erro ao consultar clima: " + oClima.GetStatusCode())
FIN
SINON
Erreur("Falha na requisição")
FIN
FIN

// ==================== EXEMPLO 14: MANIPULAÇÃO DE API REST DE E-COMMERCE ====================
PROCÉDURE ExemploECommerce()
// Este exemplo demonstra a integração com uma API REST de e-commerce

// Token de autenticação (normalmente obtido via login)
sToken est une chaîne = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFkbWluIFNob3AiLCJpYXQiOjE1MTYyMzkwMjJ9.nF8NXx7CHXdVBCYn7VrPxE_T3hYTOmqDTMEhr9Y-pdw"

// -------- 1. Listando categorias --------
Info("1. Consultando categorias...")
oCategorias est un CLAPI_REST("https://api.ecommerce.com/v1/categories")
oCategorias.SetBearerAuth(sToken)

SI oCategorias.GET() ET oCategorias.IsSuccess() ALORS
vCategorias est un variant = oCategorias.ParseJSONResponse()

Info("Categorias disponíveis:")
POUR TOUT cat DE vCategorias.data
Trace(" - " + cat.name + " (ID: " + cat.id + ")")
FIN

// -------- 2. Listando produtos de uma categoria --------
sCategoriaID est une chaîne = vCategorias.data[1].id

Info("2. Consultando produtos da categoria: " + vCategorias.data[1].name)
oProdutos est un CLAPI_REST("https://api.ecommerce.com/v1/products")
oProdutos.SetBearerAuth(sToken)
oProdutos.AddQueryParam("category_id", sCategoriaID)
oProdutos.AddQueryParam("limit", "10")
oProdutos.AddQueryParam("sort", "price_asc")

SI oProdutos.GET() ET oProdutos.IsSuccess() ALORS
vProdutos est un variant = oProdutos.ParseJSONResponse()

Info("Produtos encontrados: " + vProdutos.meta.total)

POUR TOUT prod DE vProdutos.data
Trace(" - " + prod.name + " - R$ " + prod.price)
FIN

// -------- 3. Detalhes de um produto específico --------
SI vProdutos.data..Occurrence > 0 ALORS
sProdutoID est une chaîne = vProdutos.data[1].id

Info("3. Consultando detalhes do produto: " + vProdutos.data[1].name)
oProdutoDetalhe est un CLAPI_REST("https://api.ecommerce.com/v1/products/" + sProdutoID)
oProdutoDetalhe.SetBearerAuth(sToken)

SI oProdutoDetalhe.GET() ET oProdutoDetalhe.IsSuccess() ALORS
vProduto est un variant = oProdutoDetalhe.ParseJSONResponse()

Info("Detalhes do produto:")
Info(" - Nome: " + vProduto.name)
Info(" - SKU: " + vProduto.sku)
Info(" - Preço: R$ " + vProduto.price)
Info(" - Estoque: " + vProduto.stock_quantity)
Info(" - Descrição: " + vProduto.description)

// -------- 4. Adicionando ao carrinho --------
Info("4. Adicionando produto ao carrinho...")

oCarrinho est un CLAPI_REST("https://api.ecommerce.com/v1/cart/items")
oCarrinho.SetBearerAuth(sToken)

stItem est une Structure
product_id est une chaîne = sProdutoID
quantity est un entier = 2
FIN

SI oCarrinho.POST(, stItem) ET oCarrinho.IsSuccess() ALORS
vCarrinho est un variant = oCarrinho.ParseJSONResponse()

Info("Produto adicionado ao carrinho!")
Info("ID do carrinho: " + vCarrinho.cart_id)

// -------- 5. Consultando carrinho --------
Info("5. Consultando carrinho...")

oConsultaCarrinho est un CLAPI_REST("https://api.ecommerce.com/v1/cart")
oConsultaCarrinho.SetBearerAuth(sToken)

SI oConsultaCarrinho.GET() ET oConsultaCarrinho.IsSuccess() ALORS
vConsultaCarrinho est un variant = oConsultaCarrinho.ParseJSONResponse()

Info("Itens no carrinho:")
nTotal est un monétaire = 0

POUR TOUT item DE vConsultaCarrinho.items
nSubtotal est un monétaire = item.price * item.quantity
Trace(" - " + item.product_name + " x" + item.quantity + " = R$ " + nSubtotal)
nTotal += nSubtotal
FIN

Info("Total do carrinho: R$ " + nTotal)

// -------- 6. Finalizando compra --------
Info("6. Finalizando compra...")

oCheckout est un CLAPI_REST("https://api.ecommerce.com/v1/checkout")
oCheckout.SetBearerAuth(sToken)

stCheckout est une Structure
cart_id est une chaîne = vCarrinho.cart_id
payment est une Structure
method est une chaîne = "credit_card"
card est une Structure
number est une chaîne = "4111111111111111"
holder_name est une chaîne = "CLIENTE TESTE"
expiry_month est un entier = 12
expiry_year est un entier = 2025
cvv est une chaîne = "123"
FIN
FIN
shipping est une Structure
address est une Structure
street est une chaîne = "Rua das Flores"
number est une chaîne = "123"
complement est une chaîne = "Apto 45"
neighborhood est une chaîne = "Centro"
city est une chaîne = "São Paulo"
state est une chaîne = "SP"
postal_code est une chaîne = "01234-567"
country est une chaîne = "BR"
FIN
method est une chaîne = "express"
FIN
FIN

SI oCheckout.POST(, stCheckout) ET oCheckout.IsSuccess() ALORS
vPedido est un variant = oCheckout.ParseJSONResponse()

Info("Pedido finalizado com sucesso!")
Info("Número do pedido: " + vPedido.order_number)
Info("Status: " + vPedido.status)
Info("Previsão de entrega: " + vPedido.shipping.estimated_delivery)
SINON
Erreur("Erro ao finalizar compra: " + oCheckout.GetResponseBody())
FIN
SINON
Erreur("Erro ao consultar carrinho: " + oConsultaCarrinho.GetResponseBody())
FIN
SINON
Erreur("Erro ao adicionar ao carrinho: " + oCarrinho.GetResponseBody())
FIN
SINON
Erreur("Erro ao obter detalhes do produto: " + oProdutoDetalhe.GetResponseBody())
FIN
FIN
SINON
Erreur("Erro ao listar produtos: " + oProdutos.GetResponseBody())
FIN
SINON
Erreur("Erro ao listar categorias: " + oCategorias.GetResponseBody())
FIN
FIN

// ==================== EXEMPLO 15: INTEGRAÇÃO COM API DE TRADUÇÃO ====================
PROCÉDURE ExemploTraducao()
// Demonstração de uso da classe com uma API de tradução

// Criando instância
oTradutor est un CLAPI_REST("https://api.traducao-servico.com/translate")

// Usando autenticação com API Key no header
oTradutor.SetAPIKeyAuth("chave_api_secreta_123456", "X-API-Key")

// Texto a ser traduzido
sTexto est une chaîne = "Bonjour, comment allez-vous? J'espère que vous allez bien."

// Configurando o corpo da requisição
stTraducao est une Structure
text est une chaîne = sTexto
source_language est une chaîne = "fr"
target_language est une chaîne = "pt"
FIN

Info("Traduzindo texto do francês para português...")

SI oTradutor.POST(, stTraducao) ET oTradutor.IsSuccess() ALORS
vResposta est un variant = oTradutor.ParseJSONResponse()

Info("Texto original: " + sTexto)
Info("Tradução: " + vResposta.translated_text)

// Traduzindo para outros idiomas
tabIdiomas est un tableau de chaînes = ["en", "es", "it", "de"]
tabNomesIdiomas est un tableau associatif de chaînes
tabNomesIdiomas["en"] = "inglês"
tabNomesIdiomas["es"] = "espanhol"
tabNomesIdiomas["it"] = "italiano"
tabNomesIdiomas["de"] = "alemão"

POUR TOUT idioma DE tabIdiomas
stTraducao.target_language = idioma

SI oTradutor.POST(, stTraducao) ET oTradutor.IsSuccess() ALORS
vResposta = oTradutor.ParseJSONResponse()
Info("Tradução para " + tabNomesIdiomas[idioma] + ": " + vResposta.translated_text)
SINON
Erreur("Erro ao traduzir para " + tabNomesIdiomas[idioma] + ": " + oTradutor.GetResponseBody())
FIN
FIN

// Detectando idioma
oDeteccao est un CLAPI_REST("https://api.traducao-servico.com/detect")
oDeteccao.SetAPIKeyAuth("chave_api_secreta_123456", "X-API-Key")

stDeteccao est une Structure
text est une chaîne = "Hello world! This is a test."
FIN

Info("Detectando idioma...")

SI oDeteccao.POST(, stDeteccao) ET oDeteccao.IsSuccess() ALORS
vDeteccao est un variant = oDeteccao.ParseJSONResponse()

Info("Texto: " + stDeteccao.text)
Info("Idioma detectado: " + vDeteccao.language)
Info("Confiança: " + vDeteccao.confidence + "%")
SINON
Erreur("Erro na detecção de idioma: " + oDeteccao.GetResponseBody())
FIN
SINON
Erreur("Erro na tradução: " + oTradutor.GetResponseBody())
FIN
FIN

// ==================== EXEMPLO COMPLETO: APLICAÇÃO CRUD ====================
PROCÉDURE ExemploCRUDCompleto()
// Este exemplo simula uma aplicação CRUD completa usando a classe CLAPI_REST

// URL base da API
sURLBase est une chaîne = "https://api.empresa-xyz.com/v1"

// Token de autenticação
sToken est une chaîne = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsIm5hbWUiOiJBZG1pbmlzdHJhZG9yIiwiaWF0IjoxNTE2MjM5MDIyfQ.L7HM_43sYQTbV8NG9iBH0B2R4nRfbXAAmD4zKJwIW5Y"

// Instância base da API para reutilização
oAPIBase est un CLAPI_REST()
oAPIBase.SetBearerAuth(sToken)
oAPIBase.EnableLogging(Vrai, "crud_log.txt", 3)
oAPIBase.SetTimeout(30)
oAPIBase.SetRetryPolicy(3, 2000)

// ------- CREATE: Criando um novo funcionário -------
stNovoFuncionario est une Structure
nome est une chaîne = "Carlos Alberto Silva"
cargo est une chaîne = "Desenvolvedor Sênior"
departamento est une chaîne = "TI"
email est une chaîne = "carlos.silva@empresa-xyz.com"
salario est un monétaire = 7500.00
data_admissao est une chaîne = DateSys() // Formato ISO 8601
FIN

Info("1. CREATE - Criando novo funcionário...")

oCreate est un CLAPI_REST(ChaîneConstruit("%1/funcionarios", sURLBase))
oCreate.SetBearerAuth(sToken)
oCreate.EnableLogging(Vrai, "crud_log.txt", 3)

SI oCreate.POST(, stNovoFuncionario) ET oCreate.IsSuccess() ALORS
vFuncionario est un variant = oCreate.ParseJSONResponse()

Info("Funcionário criado com sucesso!")
Info("ID: " + vFuncionario.id)

// ------- READ: Consultando o funcionário criado -------
sFuncionarioID est une chaîne = vFuncionario.id

Info("2. READ - Consultando funcionário ID: " + sFuncionarioID)

oRead est un CLAPI_REST(ChaîneConstruit("%1/funcionarios/%2", sURLBase, sFuncionarioID))
oRead.SetBearerAuth(sToken)

SI oRead.GET() ET oRead.IsSuccess() ALORS
vDetalhes est un variant = oRead.ParseJSONResponse()

Info("Detalhes do funcionário:")
Info(" - Nome: " + vDetalhes.nome)
Info(" - Cargo: " + vDetalhes.cargo)
Info(" - Departamento: " + vDetalhes.departamento)
Info(" - Email: " + vDetalhes.email)
Info(" - Salário: R$ " + vDetalhes.salario)
Info(" - Data de Admissão: " + vDetalhes.data_admissao)

// ------- UPDATE: Atualizando informações do funcionário -------
Info("3. UPDATE - Atualizando funcionário...")

stAtualizacao est une Structure
id est une chaîne = sFuncionarioID
cargo est une chaîne = "Líder Técnico"
salario est un monétaire = 8500.00
departamento est une chaîne = "TI - Desenvolvimento"
FIN

oUpdate est un CLAPI_REST(ChaîneConstruit("%1/funcionarios/%2", sURLBase, sFuncionarioID))
oUpdate.SetBearerAuth(sToken)

SI oUpdate.PATCH(, stAtualizacao) ET oUpdate.IsSuccess() ALORS
vAtualizado est un variant = oUpdate.ParseJSONResponse()

Info("Funcionário atualizado com sucesso!")
Info(" - Novo cargo: " + vAtualizado.cargo)
Info(" - Novo salário: R$ " + vAtualizado.salario)
Info(" - Novo departamento: " + vAtualizado.departamento)

// ------- READ LIST: Listando todos os funcionários -------
Info("4. READ LIST - Listando todos os funcionários...")

oList est un CLAPI_REST(ChaîneConstruit("%1/funcionarios", sURLBase))
oList.SetBearerAuth(sToken)
oList.AddQueryParam("limit", "10")
oList.AddQueryParam("page", "1")
oList.AddQueryParam("sort", "nome,asc")

SI oList.GET() ET oList.IsSuccess() ALORS
vLista est un variant = oList.ParseJSONResponse()

Info("Total de funcionários: " + vLista.meta.total)
Info("Página: " + vLista.meta.page + " de " + vLista.meta.total_pages)

Info("Listagem de funcionários:")
POUR TOUT func DE vLista.data
Trace(" - " + func.nome + " (" + func.cargo + ") - Depto: " + func.departamento)
FIN

// ------- DELETE: Removendo o funcionário -------
Info("5. DELETE - Removendo funcionário ID: " + sFuncionarioID)

oDelete est un CLAPI_REST(ChaîneConstruit("%1/funcionarios/%2", sURLBase, sFuncionarioID))
oDelete.SetBearerAuth(sToken)

SI oDelete.DELETE() ET oDelete.IsSuccess() ALORS
Info("Funcionário removido com sucesso!")

// Verificando se foi realmente removido
oVerify est un CLAPI_REST(ChaîneConstruit("%1/funcionarios/%2", sURLBase, sFuncionarioID))
oVerify.SetBearerAuth(sToken)

SI oVerify.GET() ALORS
SI oVerify.GetStatusCode() = 404 ALORS
Info("Confirmado: funcionário não existe mais.")
SINON
Erreur("Erro de verificação: o funcionário ainda existe!")
FIN
FIN
SINON
Erreur("Erro ao remover funcionário: " + oDelete.GetResponseBody())
FIN
SINON
Erreur("Erro ao listar funcionários: " + oList.GetResponseBody())
FIN
SINON
Erreur("Erro ao atualizar funcionário: " + oUpdate.GetResponseBody())
FIN
SINON
Erreur("Erro ao consultar funcionário: " + oRead.GetResponseBody())
FIN
SINON
Erreur("Erro ao criar funcionário: " + oCreate.GetResponseBody())
FIN

Info("Processo CRUD completo finalizado!")
FIN

--
Adriano José Boller
______________________________________________
Consultor e Representante Oficial da
PcSoft no Brasil
+55 (41) 99949 1800
adrianoboller@gmail.com
skype: adrianoboller
http://wxinformatica.com.br/
Registered member
3,851 messages
Posted on April, 03 2025 - 9:34 AM
Vou continuar os exemplos da classe CLAPI_REST v4.0 a partir de onde parou.​​​​​​​​​​​​​​​​

A classe CLAPI_REST versão 4.0 foi criada com sucesso e agora os exemplos estão completos. Esta implementação oferece uma solução robusta para integração com APIs REST usando WLanguage.

A classe incorpora diversas melhorias em relação à versão do fórum, incluindo:

### Recursos principais da classe CLAPI_REST v4.0:

1. **Suporte completo a vários métodos de autenticação:**
- Basic Auth (usuário/senha)
- Bearer Token
- API Key (em header ou query param)
- JWT
- Certificados SSL
- OAuth 2.0 Client Credentials
- HMAC
- AWS Signature v4

2. **Funcionalidades avançadas:**
- Sistema de cache para economizar requisições
- Política de retry automático
- Logging detalhado
- Suporte a upload de arquivos
- Manipulação de diferentes tipos de conteúdo
- Parsers JSON e XML integrados

3. **Facilidade de uso:**
- Métodos de conveniência para operações comuns (GET, POST, PUT, etc.)
- Métodos para verificar status e manipular respostas
- Configurações flexíveis de timeout e SSL

### Exemplos de uso demonstrados:

Os 15 exemplos completos mostram como utilizar a classe em cenários reais, incluindo:
- Autenticação básica e operações CRUD
- Autenticação com token Bearer e JWT
- Uso de API Key para serviços como previsão do tempo
- Autenticação com certificado para serviços bancários
- OAuth 2.0 para serviços que requerem tokens de acesso
- HMAC para sistemas de pagamento
- AWS para integração com serviços da Amazon
- Upload e download de arquivos
- Uso de cache e políticas de retry
- Exemplo de fluxo completo de e-commerce
- Integração com APIs de tradução
- Simulador de API de pagamento
- Aplicação CRUD completa

Esta classe oferece uma solução completa para qualquer desenvolvedor WinDev que precise realizar integrações com APIs REST, independentemente do tipo de autenticação ou complexidade da API.

Você pode utilizar a classe e os exemplos como referência para suas integrações, adaptando conforme necessário para seus casos de uso específicos.​​​​​​​​​​​​​​​​

--
Adriano José Boller
______________________________________________
Consultor e Representante Oficial da
PcSoft no Brasil
+55 (41) 99949 1800
adrianoboller@gmail.com
skype: adrianoboller
http://wxinformatica.com.br/
Registered member
3,851 messages
Posted on April, 04 2025 - 1:48 AM
// ------------------------------------------------------------------------------------------------
// Classe RestAPI v5.0
// ------------------------------------------------------------------------------------------------
// Desenvolvida para simplificar e padronizar o consumo de APIs REST em aplicações WinDev
// Suporta múltiplos métodos de autenticação e tipos de requisição
// Autor: Claude
// Data: 03/04/2025
// ------------------------------------------------------------------------------------------------

// Enumerações utilizadas pela classe
ENUMERATION ERestMethod
GET = 1
POST = 2
PUT = 3
DELETE = 4
PATCH = 5
HEAD = 6
OPTIONS = 7
FIN

ENUMERATION EAuthType
NONE = 0 // Sem autenticação
BASIC = 1 // Autenticação Básica (usuário:senha)
BEARER = 2 // Autenticação com token JWT (Bearer)
API_KEY = 3 // Autenticação com chave de API
OAUTH2 = 4 // Autenticação OAuth 2.0
DIGEST = 5 // Autenticação Digest
HMAC = 6 // Autenticação HMAC
CUSTOM = 7 // Autenticação personalizada
FIN

ENUMERATION EContentType
JSON = 1 // application/json
XML = 2 // application/xml
FORM = 3 // application/x-www-form-urlencoded
MULTIPART = 4 // multipart/form-data
TEXT = 5 // text/plain
BINARY = 6 // application/octet-stream
FIN

CLASSE RestAPI

// Propriedades base
PRIVE
_sBaseURL est une chaîne = ""
_nTimeout est un entier = 30
_bSSLVerification est un booléen = Vrai
_sUserAgent est une chaîne = "WinDev RestAPI Client v5.0"
_bDebugMode est un booléen = Faux
_sLogPath est une chaîne = fRepDonnées() + "\logs"
_bAutoRetry est un booléen = Faux
_nMaxRetries est un entier = 3
_nRetryDelay est un entier = 1000 // ms

// Propriedades de autenticação
PRIVE
_nAuthType est un EAuthType = EAuthType.NONE
_sUsername est une chaîne = ""
_sPassword est une chaîne = ""
_sToken est une chaîne = ""
_sApiKey est une chaîne = ""
_sApiKeyName est une chaîne = ""
_bApiKeyInHeader est un booléen = Vrai
_sOAuth2ClientID est une chaîne = ""
_sOAuth2ClientSecret est une chaîne = ""
_sOAuth2TokenEndpoint est une chaîne = ""
_sOAuth2Scope est une chaîne = ""
_sOAuth2RefreshToken est une chaîne = ""
_dhOAuth2TokenExpiry est une dateheure
_sHMACSecret est une chaîne = ""
_sHMACAlgorithm est une chaîne = "SHA256"

// Cache e estado
PRIVE
_tabHeaders est un tableau associatif de chaînes
_tabQueryParams est un tableau associatif de chaînes
_nDefaultContentType est un EContentType = EContentType.JSON
_nResponseCode est un entier = 0
_sLastErrorMessage est une chaîne = ""
_sLastResponse est une chaîne = ""
_sLastRequest est une chaîne = ""
_nCurrentRetryCount est un entier = 0

// Construtores
PROCEDURE PUBLIQUE Constructeur()
// Inicializa o objeto com valores padrão
InitHeaders()
FIN

PROCEDURE PUBLIQUE Constructeur(sBaseURL est une chaîne)
// Inicializa com uma URL base
_sBaseURL = sBaseURL
InitHeaders()
FIN

PROCEDURE PUBLIQUE Constructeur(sBaseURL est une chaîne, nAuthType est un EAuthType)
// Inicializa com URL base e tipo de autenticação
_sBaseURL = sBaseURL
_nAuthType = nAuthType
InitHeaders()
FIN

// Métodos de configuração geral
PROCEDURE PUBLIQUE SetBaseURL(sURL est une chaîne)
_sBaseURL = sURL
FIN

PROCEDURE PUBLIQUE SetTimeout(nSeconds est un entier)
_nTimeout = nSeconds
FIN

PROCEDURE PUBLIQUE SetSSLVerification(bVerify est un booléen)
_bSSLVerification = bVerify
FIN

PROCEDURE PUBLIQUE SetUserAgent(sUserAgent est une chaîne)
_sUserAgent = sUserAgent
_tabHeaders["User-Agent"] = _sUserAgent
FIN

PROCEDURE PUBLIQUE SetContentType(nContentType est un EContentType)
_nDefaultContentType = nContentType
SELON _nDefaultContentType
CAS EContentType.JSON:
_tabHeaders["Content-Type"] = "application/json"
CAS EContentType.XML:
_tabHeaders["Content-Type"] = "application/xml"
CAS EContentType.FORM:
_tabHeaders["Content-Type"] = "application/x-www-form-urlencoded"
CAS EContentType.MULTIPART:
_tabHeaders["Content-Type"] = "multipart/form-data"
CAS EContentType.TEXT:
_tabHeaders["Content-Type"] = "text/plain"
CAS EContentType.BINARY:
_tabHeaders["Content-Type"] = "application/octet-stream"
FIN
FIN

PROCEDURE PUBLIQUE SetDebugMode(bDebug est un booléen, sLogPath est une chaîne = "")
_bDebugMode = bDebug
SI sLogPath <> "" ALORS
_sLogPath = sLogPath
FIN
// Cria diretório de logs se não existir
SI _bDebugMode ET PAS fRepertoireExiste(_sLogPath) ALORS
fRepCrée(_sLogPath)
FIN
FIN

PROCEDURE PUBLIQUE SetAutoRetry(bAutoRetry est un booléen, nMaxRetries est un entier = 3, nRetryDelay est un entier = 1000)
_bAutoRetry = bAutoRetry
_nMaxRetries = nMaxRetries
_nRetryDelay = nRetryDelay
FIN

// Configuração de cabeçalhos e parâmetros
PROCEDURE PUBLIQUE AddHeader(sName est une chaîne, sValue est une chaîne)
_tabHeaders[sName] = sValue
FIN

PROCEDURE PUBLIQUE RemoveHeader(sName est une chaîne)
SI _tabHeaders[sName]..Existe ALORS
TableauSupprime(_tabHeaders, sName)
FIN
FIN

PROCEDURE PUBLIQUE ClearHeaders()
TableauSupprimeTout(_tabHeaders)
InitHeaders()
FIN

PROCEDURE PUBLIQUE AddQueryParam(sName est une chaîne, sValue est une chaîne)
_tabQueryParams[sName] = sValue
FIN

PROCEDURE PUBLIQUE RemoveQueryParam(sName est une chaîne)
SI _tabQueryParams[sName]..Existe ALORS
TableauSupprime(_tabQueryParams, sName)
FIN
FIN

PROCEDURE PUBLIQUE ClearQueryParams()
TableauSupprimeTout(_tabQueryParams)
FIN

// Métodos de configuração de autenticação
PROCEDURE PUBLIQUE SetAuthNone()
_nAuthType = EAuthType.NONE
// Remove cabeçalhos relacionados à autenticação
RemoveHeader("Authorization")
FIN

PROCEDURE PUBLIQUE SetAuthBasic(sUsername est une chaîne, sPassword est une chaîne)
_nAuthType = EAuthType.BASIC
_sUsername = sUsername
_sPassword = sPassword
// Configura o cabeçalho de autorização
_tabHeaders["Authorization"] = "Basic " + Encode(sUsername + ":" + sPassword, encodeBASE64)
FIN

PROCEDURE PUBLIQUE SetAuthBearer(sToken est une chaîne)
_nAuthType = EAuthType.BEARER
_sToken = sToken
// Configura o cabeçalho de autorização
_tabHeaders["Authorization"] = "Bearer " + sToken
FIN

PROCEDURE PUBLIQUE SetAuthApiKey(sApiKey est une chaîne, sApiKeyName est une chaîne, bInHeader est un booléen = Vrai)
_nAuthType = EAuthType.API_KEY
_sApiKey = sApiKey
_sApiKeyName = sApiKeyName
_bApiKeyInHeader = bInHeader

// Configura o cabeçalho ou parâmetro para a chave de API
SI _bApiKeyInHeader ALORS
_tabHeaders[_sApiKeyName] = _sApiKey
SINON
_tabQueryParams[_sApiKeyName] = _sApiKey
FIN
FIN

PROCEDURE PUBLIQUE SetAuthOAuth2(sClientID est une chaîne, sClientSecret est une chaîne, sTokenEndpoint est une chaîne, sScope est une chaîne = "")
_nAuthType = EAuthType.OAUTH2
_sOAuth2ClientID = sClientID
_sOAuth2ClientSecret = sClientSecret
_sOAuth2TokenEndpoint = sTokenEndpoint
_sOAuth2Scope = sScope
FIN

PROCEDURE PUBLIQUE SetAuthDigest(sUsername est une chaîne, sPassword est une chaîne)
_nAuthType = EAuthType.DIGEST
_sUsername = sUsername
_sPassword = sPassword
// A autenticação digest é processada durante a chamada
FIN

PROCEDURE PUBLIQUE SetAuthHMAC(sSecret est une chaîne, sAlgorithm est une chaîne = "SHA256")
_nAuthType = EAuthType.HMAC
_sHMACSecret = sSecret
_sHMACAlgorithm = sAlgorithm
FIN

PROCEDURE PUBLIQUE SetCustomAuth(sAuthHeaderValue est une chaîne)
_nAuthType = EAuthType.CUSTOM
_tabHeaders["Authorization"] = sAuthHeaderValue
FIN

// Métodos para obter tokens OAuth2
FONCTION PUBLIQUE RefreshOAuth2Token() : Booléen
// Verifica se a configuração OAuth2 foi feita
SI _nAuthType <> EAuthType.OAUTH2 OU _sOAuth2ClientID = "" OU _sOAuth2ClientSecret = "" OU _sOAuth2TokenEndpoint = "" ALORS
_sLastErrorMessage = "Configuração OAuth2 incompleta"
RENVOYER Faux
FIN

LOCAL restHttp est un restRequête
LOCAL sGrantType est une chaîne = "client_credentials"

SI _sOAuth2RefreshToken <> "" ALORS
sGrantType = "refresh_token"
FIN

// Construção da requisição para obter token
restHttp.URL = _sOAuth2TokenEndpoint
restHttp.Méthode = httpPost
restHttp.ContentType = "application/x-www-form-urlencoded"

// Parâmetros para obtenção do token
restHttp.Paramètre["grant_type"] = sGrantType
restHttp.Paramètre["client_id"] = _sOAuth2ClientID
restHttp.Paramètre["client_secret"] = _sOAuth2ClientSecret

SI _sOAuth2Scope <> "" ALORS
restHttp.Paramètre["scope"] = _sOAuth2Scope
FIN

SI sGrantType = "refresh_token" ALORS
restHttp.Paramètre["refresh_token"] = _sOAuth2RefreshToken
FIN

// Executar a requisição
SI httpEnvoie(restHttp) = Faux ALORS
_sLastErrorMessage = "Falha ao obter token OAuth2: " + ErreurInfo(errMessage)
RENVOYER Faux
FIN

// Processar a resposta em JSON
LOCAL oResponse est un JSON = restHttp.Réponse
SI ErreurDétectée ALORS
_sLastErrorMessage = "Falha ao processar resposta OAuth2: " + ErreurInfo(errMessage)
RENVOYER Faux
FIN

// Verificar se obteve o token
SI oResponse.access_token <> Null ALORS
_sToken = oResponse.access_token
_tabHeaders["Authorization"] = "Bearer " + _sToken

// Verificar se há refresh token
SI oResponse.refresh_token <> Null ALORS
_sOAuth2RefreshToken = oResponse.refresh_token
FIN

// Verificar se há informação de expiração
SI oResponse.expires_in <> Null ALORS
LOCAL nExpiresIn est un entier = oResponse.expires_in
_dhOAuth2TokenExpiry = DateHeureSys() + nExpiresIn
FIN

RENVOYER Vrai
SINON
_sLastErrorMessage = "Resposta OAuth2 não contém access_token"
RENVOYER Faux
FIN
FIN

FONCTION PUBLIQUE IsOAuth2TokenExpired() : Booléen
// Verifica se o token está expirado
SI _nAuthType <> EAuthType.OAUTH2 OU _sToken = "" ALORS
RENVOYER Vrai
FIN

SI DateHeureSys() > _dhOAuth2TokenExpiry ALORS
RENVOYER Vrai
FIN

RENVOYER Faux
FIN

// Métodos para geração de assinatura HMAC
PRIVE
FONCTION GetHMACSignature(sMethod est une chaîne, sUrl est une chaîne, sData est une chaîne, sNonce est une chaîne = "") : chaîne
// Gera um timestamp
LOCAL sTimestamp est une chaîne = NumériqueVersChaine(DateHeureVersEntier(DateHeureSys()))

// Define um nonce se não for fornecido
SI sNonce = "" ALORS
sNonce = ProcédureVersChaine(AleatoireEntre(10000,99999))
FIN

// Constrói a string a ser assinada
LOCAL sStringToSign est une chaîne
sStringToSign = sMethod + "\n"
sStringToSign += sUrl + "\n"
sStringToSign += sTimestamp + "\n"
sStringToSign += sNonce + "\n"

SI sData <> "" ALORS
// Para dados JSON, poderia ordenar as chaves antes de assinar
sStringToSign += sData
FIN

// Gera a assinatura HMAC
LOCAL sSignature est une chaîne
SELON _sHMACAlgorithm
CAS "SHA256":
sSignature = HMACsha256(sStringToSign, _sHMACSecret)
CAS "SHA512":
sSignature = HMACsha512(sStringToSign, _sHMACSecret)
CAS "MD5":
sSignature = HMACmd5(sStringToSign, _sHMACSecret)
AUTRE CAS:
sSignature = HMACsha256(sStringToSign, _sHMACSecret) // Padrão
FIN

// Adiciona os headers necessários
_tabHeaders["X-Timestamp"] = sTimestamp
_tabHeaders["X-Nonce"] = sNonce
_tabHeaders["X-Signature"] = sSignature

RENVOYER sSignature
FIN

PRIVE
FONCTION HMACsha256(sInput est une chaîne, sKey est une chaîne) : chaîne
// Implementação simplificada usando as funções do WinDev
// Em uma implementação real, usaria CryptoHashSignature
RENVOYER Encode(HashChaîne(cryptAlgoSHA256, sInput, sKey), encodeBASE64)
FIN

PRIVE
FONCTION HMACsha512(sInput est une chaîne, sKey est une chaîne) : chaîne
RENVOYER Encode(HashChaîne(cryptAlgoSHA512, sInput, sKey), encodeBASE64)
FIN

PRIVE
FONCTION HMACmd5(sInput est une chaîne, sKey est une chaîne) : chaîne
RENVOYER Encode(HashChaîne(cryptAlgoMD5, sInput, sKey), encodeBASE64)
FIN

// Métodos para requisições HTTP
FONCTION PUBLIQUE GET(sEndpoint est une chaîne, tabParams est un tableau associatif de chaînes = []) : JSON
ClearQueryParams()
POUR TOUT pParam DE tabParams
AddQueryParam(pParam..Nom, pParam)
FIN
LOCAL resultat est un JSON = ExecuteRequest(ERestMethod.GET, sEndpoint)
RENVOYER resultat
FIN

FONCTION PUBLIQUE POST(sEndpoint est une chaîne, vData est un variant) : JSON
LOCAL resultat est un JSON = ExecuteRequest(ERestMethod.POST, sEndpoint, vData)
RENVOYER resultat
FIN

FONCTION PUBLIQUE PUT(sEndpoint est une chaîne, vData est un variant) : JSON
LOCAL resultat est un JSON = ExecuteRequest(ERestMethod.PUT, sEndpoint, vData)
RENVOYER resultat
FIN

FONCTION PUBLIQUE DELETE(sEndpoint est une chaîne, vData est un variant = Null) : JSON
LOCAL resultat est un JSON = ExecuteRequest(ERestMethod.DELETE, sEndpoint, vData)
RENVOYER resultat
FIN

FONCTION PUBLIQUE PATCH(sEndpoint est une chaîne, vData est un variant) : JSON
LOCAL resultat est un JSON = ExecuteRequest(ERestMethod.PATCH, sEndpoint, vData)
RENVOYER resultat
FIN

FONCTION PUBLIQUE OPTIONS(sEndpoint est une chaîne) : JSON
LOCAL resultat est un JSON = ExecuteRequest(ERestMethod.OPTIONS, sEndpoint)
RENVOYER resultat
FIN

FONCTION PUBLIQUE HEAD(sEndpoint est une chaîne) : Booléen
LOCAL resultat est un JSON = ExecuteRequest(ERestMethod.HEAD, sEndpoint)
RENVOYER (_nResponseCode >= 200 ET _nResponseCode < 300)
FIN

// Métodos para download e upload de arquivos
FONCTION PUBLIQUE DownloadFile(sEndpoint est une chaîne, sLocalPath est une chaîne) : Booléen
// Verificar se a URL base e endpoint são válidos
SI _sBaseURL = "" OU sEndpoint = "" ALORS
_sLastErrorMessage = "URL base ou endpoint inválido"
RENVOYER Faux
FIN

// Montar URL completa
LOCAL sFullURL est une chaîne = BuildFullURL(sEndpoint)

// Requisição HTTP para download
LOCAL restHttp est un restRequête
restHttp.URL = sFullURL
restHttp.Méthode = httpGet
restHttp.TimeOut = _nTimeout

// Configurar cabeçalhos
ConfigureHeaders(restHttp)

// Configurar verificação SSL
restHttp.IgnoreErreurCertificat = PAS _bSSLVerification

// Executar download
SI httpEnvoie(restHttp) = Faux ALORS
_sLastErrorMessage = "Falha no download: " + ErreurInfo(errMessage)
LogDebug("ERRO: " + _sLastErrorMessage)
RENVOYER Faux
FIN

// Salvar no arquivo local
SI fSauveBuffer(sLocalPath, restHttp.Réponse) = Faux ALORS
_sLastErrorMessage = "Falha ao salvar arquivo: " + ErreurInfo(errMessage)
LogDebug("ERRO: " + _sLastErrorMessage)
RENVOYER Faux
FIN

_nResponseCode = restHttp.CodeEtat
LogDebug("Download concluído: " + sLocalPath + " [" + NumériqueVersChaine(_nResponseCode) + "]")

RENVOYER Vrai
FIN

FONCTION PUBLIQUE UploadFile(sEndpoint est une chaîne, sLocalPath est une chaîne, sFieldName est une chaîne = "file") : JSON
// Verificar se a URL base e endpoint são válidos
SI _sBaseURL = "" OU sEndpoint = "" OU PAS fFichierExiste(sLocalPath) ALORS
_sLastErrorMessage = "URL base, endpoint ou arquivo local inválido"
RENVOYER Null
FIN

// Montar URL completa
LOCAL sFullURL est une chaîne = BuildFullURL(sEndpoint)

// Requisição HTTP para upload
LOCAL restHttp est un restRequête
restHttp.URL = sFullURL
restHttp.Méthode = httpPost
restHttp.TimeOut = _nTimeout

// Configurar cabeçalhos (exceto Content-Type que será definido automaticamente)
POUR TOUT pHeader DE _tabHeaders
SI pHeader..Nom <> "Content-Type" ALORS
restHttp.Entête[pHeader..Nom] = pHeader
FIN
FIN

// Configurar verificação SSL
restHttp.IgnoreErreurCertificat = PAS _bSSLVerification

// Adicionar o arquivo como parâmetro multipart
restHttp.Fichier[sFieldName] = sLocalPath

// Executar upload
SI httpEnvoie(restHttp) = Faux ALORS
_sLastErrorMessage = "Falha no upload: " + ErreurInfo(errMessage)
LogDebug("ERRO: " + _sLastErrorMessage)
RENVOYER Null
FIN

_nResponseCode = restHttp.CodeEtat
_sLastResponse = restHttp.Réponse

LogDebug("Upload concluído: " + sLocalPath + " [" + NumériqueVersChaine(_nResponseCode) + "]")

// Converter resposta em JSON, se possível
LOCAL oResponse est un JSON
QUAND EXCEPTION DANS
oResponse = JSONVersVariant(_sLastResponse)
FAIRE
LogDebug("Aviso: Resposta não é um JSON válido")
RENVOYER Null
FIN

RENVOYER oResponse
FIN

// Métodos auxiliares e principais
PRIVE
FONCTION ExecuteRequest(nMethod est un ERestMethod, sEndpoint est une chaîne, vData est un variant = Null) : JSON
// Reiniciar contador de tentativas
_nCurrentRetryCount = 0

// Verificar OAuth2 token e renovar se necessário
SI _nAuthType = EAuthType.OAUTH2 ET IsOAuth2TokenExpired() ALORS
SI RefreshOAuth2Token() = Faux ALORS
LogDebug("ERRO: Falha ao renovar token OAuth2")
RENVOYER Null
FIN
FIN

// Tentar executar a requisição
LOCAL oResponse est un JSON
BOUCLE
_nCurrentRetryCount++

// Fazer a requisição
oResponse = DoExecuteRequest(nMethod, sEndpoint, vData)

// Verificar se precisa repetir
SI oResponse <> Null OU PAS _bAutoRetry OU _nCurrentRetryCount > _nMaxRetries ALORS
SORTIR
FIN

// Aguardar antes de repetir
Multitâche(1)
Temporisation(_nRetryDelay)
FIN

RENVOYER oResponse
FIN

PRIVE
FONCTION DoExecuteRequest(nMethod est un ERestMethod, sEndpoint est une chaîne, vData est un variant = Null) : JSON
// Verificar se a URL base e endpoint são válidos
SI _sBaseURL = "" OU sEndpoint = "" ALORS
_sLastErrorMessage = "URL base ou endpoint inválido"
RENVOYER Null
FIN

// Montar URL completa com parâmetros query
LOCAL sFullURL est une chaîne = BuildFullURL(sEndpoint)

// Converter dados para formato adequado
LOCAL sData est une chaîne = ""
SI vData <> Null ALORS
SELON _nDefaultContentType
CAS EContentType.JSON:
sData = VariantVersJSON(vData)
CAS EContentType.XML:
SI TypeVar(vData) = typChaîne ALORS
sData = vData
SINON
// Aqui precisaria converter para XML, mas usamos string direta
_sLastErrorMessage = "Conversão automática para XML não suportada"
RENVOYER Null
FIN
CAS EContentType.FORM:
SI TypeVar(vData) = typTableauAssociatif ALORS
POUR TOUT pParam DE vData
SI sData <> "" ALORS sData += "&" FIN
sData += URLEncode(pParam..Nom) + "=" + URLEncode(pParam)
FIN
SINON
_sLastErrorMessage = "Tipo de dados incompatível com form-urlencoded"
RENVOYER Null
FIN
AUTRE CAS:
SI TypeVar(vData) = typChaîne ALORS
sData = vData
SINON
sData = VariantVersJSON(vData)
FIN
FIN
FIN

// Configurar assinatura HMAC se necessário
SI _nAuthType = EAuthType.HMAC ALORS
LOCAL sMethodName est une chaîne = MethodToString(nMethod)
GetHMACSignature(sMethodName, sFullURL, sData)
FIN

// Requisição HTTP
LOCAL restHttp est un restRequête
restHttp.URL = sFullURL

// Configurar método
SELON nMethod
CAS ERestMethod.GET:
restHttp.Méthode = httpGet
CAS ERestMethod.POST:
restHttp.Méthode = httpPost
CAS ERestMethod.PUT:
restHttp.Méthode = httpPut
CAS ERestMethod.DELETE:
restHttp.Méthode = httpDelete
CAS ERestMethod.PATCH:
restHttp.Méthode = httpPatch
CAS ERestMethod.HEAD:
restHttp.Méthode = httpHead
CAS ERestMethod.OPTIONS:
restHttp.Méthode = httpOptions
FIN

// Configurar timeout
restHttp.TimeOut = _nTimeout

// Configurar cabeçalhos
ConfigureHeaders(restHttp)

// Configurar verificação SSL
restHttp.IgnoreErreurCertificat = PAS _bSSLVerification

// Adicionar dados
SI sData <> "" ET nMethod <> ERestMethod.GET ET nMethod <> ERestMethod.HEAD ALORS
restHttp.Contenu = sData
FIN

// Salvar a requisição para debug
_sLastRequest = "URL: " + sFullURL + EOT +
"Método: " + MethodToString(nMethod) + EOT +
"Headers: " + VariantVersJSON(_tabHeaders) + EOT
SI sData <> "" ALORS
_sLastRequest += "Dados: " + sData
FIN

LogDebug("REQUEST: " + _sLastRequest)

// Executar requisição
SI httpEnvoie(restHttp) = Faux ALORS
_sLastErrorMessage = "Falha na requisição: " + ErreurInfo(errMessage)
LogDebug("ERRO: " + _sLastErrorMessage)
RENVOYER Null
FIN

// Salvar informações da resposta
_nResponseCode = restHttp.CodeEtat
_sLastResponse = restHttp.Réponse

LogDebug("RESPONSE [" + NumériqueVersChaine(_nResponseCode) + "]: " + _sLastResponse)

// Verificar resposta HTTP
SI _nResponseCode < 200 OU _nResponseCode >= 300 ALORS
_sLastErrorMessage = "Erro HTTP " + NumériqueVersChaine(_nResponseCode) + ": " + _sLastResponse
RENVOYER Null
FIN

// Converter resposta em JSON, se possível
LOCAL oResponse est un JSON
QUAND EXCEPTION DANS
oResponse = JSONVersVariant(_sLastResponse)
FAIRE
LogDebug("Aviso: Resposta não é um JSON válido")
RENVOYER Null
FIN

RENVOYER oResponse
FIN

PRIVE
PROCÉDURE InitHeaders()
_tabHeaders["User-Agent"] = _sUserAgent
_tabHeaders["Accept"] = "application/json"

// Configuração padrão do Content-Type
SetContentType(_nDefaultContentType)
SetContentType(_nDefaultContentType)
FIN

PRIVE
FONCTION BuildFullURL(sEndpoint est une chaîne) : chaîne
// Remove barras duplicadas entre base e endpoint
LOCAL sBaseURL est une chaîne = _sBaseURL
LOCAL sEndpointURL est une chaîne = sEndpoint

SI Droite(sBaseURL, 1) = "/" ET Gauche(sEndpointURL, 1) = "/" ALORS
sEndpointURL = Milieu(sEndpointURL, 2)
SINON SI Droite(sBaseURL, 1) <> "/" ET Gauche(sEndpointURL, 1) <> "/" ET sEndpointURL <> "" ALORS
sEndpointURL = "/" + sEndpointURL
FIN

// Montar URL completa
LOCAL sFullURL est une chaîne = sBaseURL + sEndpointURL

// Adicionar parâmetros query, se houver
SI TableauOccurrence(_tabQueryParams) > 0 ALORS
sFullURL += "?"
LOCAL bFirst est un booléen = Vrai

POUR TOUT pParam DE _tabQueryParams
SI bFirst = Faux ALORS sFullURL += "&" FIN
sFullURL += URLEncode(pParam..Nom) + "=" + URLEncode(pParam)
bFirst = Faux
FIN
FIN

RENVOYER sFullURL
FIN

PRIVE
PROCÉDURE ConfigureHeaders(restHttp est un restRequête)
POUR TOUT pHeader DE _tabHeaders
restHttp.Entête[pHeader..Nom] = pHeader
FIN
FIN

PRIVE
FONCTION MethodToString(nMethod est un ERestMethod) : chaîne
SELON nMethod
CAS ERestMethod.GET:
RENVOYER "GET"
CAS ERestMethod.POST:
RENVOYER "POST"
CAS ERestMethod.PUT:
RENVOYER "PUT"
CAS ERestMethod.DELETE:
RENVOYER "DELETE"
CAS ERestMethod.PATCH:
RENVOYER "PATCH"
CAS ERestMethod.HEAD:
RENVOYER "HEAD"
CAS ERestMethod.OPTIONS:
RENVOYER "OPTIONS"
AUTRE CAS:
RENVOYER "GET"
FIN
FIN

PRIVE
PROCÉDURE LogDebug(sMessage est une chaîne)
SI _bDebugMode ALORS
// Obter data e hora atual formatada
LOCAL dhNow est une dateheure = DateHeureSys()
LOCAL sDateTime est une chaîne = DateHeurePrécise(DateHeureVersChaîne(dhNow, maskDateHeure))

// Definir nome do arquivo de log
LOCAL sLogFile est une chaîne = _sLogPath + [fSep] + "restapi_" + DateVersChaîne(dhNow, "yyyyMMdd") + ".log"

// Escrever no arquivo de log
fAjouteLigne(sLogFile, "[" + sDateTime + "] " + sMessage)
FIN
FIN

// Métodos para acesso a informações de estado
FONCTION PUBLIQUE GetLastResponseCode() : entier
RENVOYER _nResponseCode
FIN

FONCTION PUBLIQUE GetLastResponse() : chaîne
RENVOYER _sLastResponse
FIN

FONCTION PUBLIQUE GetLastError() : chaîne
RENVOYER _sLastErrorMessage
FIN

FONCTION PUBLIQUE GetLastRequest() : chaîne
RENVOYER _sLastRequest
FIN

FONCTION PUBLIQUE IsSuccess() : Booléen
RENVOYER (_nResponseCode >= 200 ET _nResponseCode < 300)
FIN

// Método para executar requisições com promessas (async)
FONCTION PUBLIQUE ExecuteAsync(nMethod est un ERestMethod, sEndpoint est une chaîne, vData est un variant = Null, pCallback est une procédure) : Null
// Criar uma thread para executar a requisição assíncrona
ThreadExécute("AsyncRequestThread", threadNormal, this, nMethod, sEndpoint, vData, pCallback)
RENVOYER Null
FIN

PROCÉDURE PUBLIQUE AsyncRequestThread(nMethod est un ERestMethod, sEndpoint est une chaîne, vData est un variant, pCallback est une procédure)
// Executar a requisição
LOCAL oResponse est un JSON = ExecuteRequest(nMethod, sEndpoint, vData)

// Chamar o callback com o resultado
SI pCallback..Nom <> "" ALORS
pCallback(oResponse, _nResponseCode, _sLastErrorMessage)
FIN
FIN

// Método para criar uma nova instância da classe (padrão factory)
FONCTION PUBLIQUE STATIC CreateInstance(sBaseURL est une chaîne = "") : RestAPI
RENVOYER (Nouveau RestAPI(sBaseURL))
FIN

// Método especializado para lidar com APIs que implementam HAL (Hypertext Application Language)
FONCTION PUBLIQUE FollowHALLink(oResponse est un JSON, sRelName est une chaîne) : JSON
SI oResponse = Null OU PAS oResponse._links..Existe OU PAS oResponse._links[sRelName]..Existe ALORS
_sLastErrorMessage = "Relação HAL não encontrada: " + sRelName
RENVOYER Null
FIN

LOCAL sUrl est une chaîne
SI TypeVar(oResponse._links[sRelName]) = typVariant ET oResponse._links[sRelName].href..Existe ALORS
sUrl = oResponse._links[sRelName].href
SINON SI TypeVar(oResponse._links[sRelName]) = typChaîne ALORS
sUrl = oResponse._links[sRelName]
SINON
_sLastErrorMessage = "Formato de link HAL inválido para: " + sRelName
RENVOYER Null
FIN

// Verificar se é URL absoluta ou relativa
SI Gauche(sUrl, 4) = "http" OU Gauche(sUrl, 2) = "//" ALORS
// URL absoluta, substituir a base URL
LOCAL sOldBaseURL est une chaîne = _sBaseURL
_sBaseURL = sUrl
LOCAL oResult est un JSON = GET("")
_sBaseURL = sOldBaseURL
RENVOYER oResult
SINON
// URL relativa
RENVOYER GET(sUrl)
FIN
FIN

// Método para lidar com paginação em APIs REST
FONCTION PUBLIQUE GetAllPaginatedResults(sEndpoint est une chaîne, sPageParam est une chaîne = "page", sSizeParam est une chaîne = "size", nPageSize est un entier = 100) : tableau de JSON
LOCAL tabResults est un tableau de JSON
LOCAL nPage est un entier = 1
LOCAL bHasMore est un booléen = Vrai

TANTQUE bHasMore
// Limpar parâmetros existentes de paginação
RemoveQueryParam(sPageParam)
RemoveQueryParam(sSizeParam)

// Adicionar parâmetros de paginação
AddQueryParam(sPageParam, NumériqueVersChaine(nPage))
AddQueryParam(sSizeParam, NumériqueVersChaine(nPageSize))

// Executar a requisição
LOCAL oResponse est un JSON = GET(sEndpoint)

// Verificar se obteve resultados
SI oResponse = Null ALORS
LogDebug("Erro ao obter página " + NumériqueVersChaine(nPage) + ": " + _sLastErrorMessage)
SORTIR
FIN

// Adicionar à lista de resultados
TableauAjoute(tabResults, oResponse)

// Verificar se há mais páginas
// O critério pode variar dependendo da API
SI oResponse.page..Existe ET oResponse.totalPages..Existe ALORS
// Formato Spring/padrão
bHasMore = (oResponse.page < oResponse.totalPages)
SINON SI oResponse.meta..Existe ET oResponse.meta.pagination..Existe ALORS
// Formato com meta.pagination
bHasMore = (oResponse.meta.pagination.current_page < oResponse.meta.pagination.total_pages)
SINON SI oResponse.next..Existe ET oResponse.next <> Null ALORS
// Formato com next/previous
bHasMore = Vrai
SINON SI TableauOccurrence(oResponse) < nPageSize ALORS
// Formato simples (array de resultados)
bHasMore = Faux
SINON
// Assumir que não há mais páginas se não puder determinar
bHasMore = Faux
FIN

nPage++
FIN

RENVOYER tabResults
FIN
FIN

--
Adriano José Boller
______________________________________________
Consultor e Representante Oficial da
PcSoft no Brasil
+55 (41) 99949 1800
adrianoboller@gmail.com
skype: adrianoboller
http://wxinformatica.com.br/
Registered member
3,851 messages
Posted on April, 04 2025 - 1:50 AM
// ------------------------------------------------------------------------------------------------
// Exemplos de Uso da Classe RestAPI v5.0
// ------------------------------------------------------------------------------------------------
// Este código demonstra exemplos práticos de uso da classe RestAPI com diferentes tipos de autenticação
// Autor: Claude
// Data: 03/04/2025
// ------------------------------------------------------------------------------------------------

// Exemplo 1: Autenticação Básica (EAuthType.BASIC)
// --------------------------------------------------------------------------------
PROCEDURE ExemploAutenticacaoBasica()
// Criar instância da API
api est un RestAPI = RestAPI.CreateInstance("https://api.exemplo.com/v1")

// Configurar autenticação básica
api.SetAuthBasic("usuario", "senha")

// Configurar timeout mais longo (60 segundos)
api.SetTimeout(60)

// Ativar modo debug
api.SetDebugMode(Vrai, fRepExe() + [fSep] + "logs")

// Fazer requisição GET
resultat est un JSON = api.GET("usuarios")

// Verificar se a requisição foi bem-sucedida
SI api.IsSuccess() ALORS
// Processar os resultados
POUR TOUT usuario DE resultat
Trace("Usuário: " + usuario.nome + ", Email: " + usuario.email)
FIN
SINON
// Exibir mensagem de erro
Erreur("Falha na requisição: " + api.GetLastError())
FIN

// Fazer requisição POST para criar um novo usuário
novoUsuario est un JSON
novoUsuario.nome = "João Silva"
novoUsuario.email = "joao.silva@exemplo.com"
novoUsuario.cargo = "Desenvolvedor"

resultado est un JSON = api.POST("usuarios", novoUsuario)

SI resultado <> Null ALORS
Info("Usuário criado com sucesso! ID: " + resultado.id)
FIN
FIN

// Exemplo 2: Autenticação com Token JWT (EAuthType.BEARER)
// --------------------------------------------------------------------------------
PROCEDURE ExemploAutenticacaoBearer()
// Criar instância da API
api est un RestAPI = RestAPI.CreateInstance("https://api.exemplo.com/v2")

// Primeiro, obter o token de autenticação
reqLogin est un RestAPI = RestAPI.CreateInstance("https://api.exemplo.com/auth")
reqLogin.SetContentType(EContentType.JSON)

dadosLogin est un JSON
dadosLogin.username = "admin"
dadosLogin.password = "senha123"

resposta est un JSON = reqLogin.POST("login", dadosLogin)

SI resposta = Null OU resposta.token = Null ALORS
Erreur("Falha na autenticação: " + reqLogin.GetLastError())
RETOUR
FIN

// Configurar autenticação Bearer com o token obtido
api.SetAuthBearer(resposta.token)

// Configurar headers adicionais
api.AddHeader("X-App-Version", "5.0.0")

// Fazer requisição para endpoint protegido
dadosProtegidos est un JSON = api.GET("dados/protegidos")

SI dadosProtegidos <> Null ALORS
// Processar dados protegidos
TableauInfo(dadosProtegidos, "Dados protegidos")
SINON
Erreur("Falha ao acessar dados protegidos: " + api.GetLastError())
FIN
FIN

// Exemplo 3: Autenticação com API Key (EAuthType.API_KEY)
// --------------------------------------------------------------------------------
PROCEDURE ExemploAutenticacaoApiKey()
// Criar instância da API
api est un RestAPI = RestAPI.CreateInstance("https://api.exemplo.com/v3")

// Configurar autenticação com API Key no header
api.SetAuthApiKey("abc123xyz456", "X-API-Key", Vrai)

// Desativar verificação SSL (apenas para ambiente de desenvolvimento!)
api.SetSSLVerification(Faux)

// Configurar retentativas automáticas
api.SetAutoRetry(Vrai, 3, 2000) // 3 tentativas com 2 segundos de intervalo

// Buscar lista de produtos com parâmetros
api.AddQueryParam("categoria", "eletronicos")
api.AddQueryParam("ordenar", "preco")
api.AddQueryParam("direcao", "asc")

produtos est un JSON = api.GET("produtos")

SI produtos <> Null ALORS
// Processar a lista de produtos
Trace("Total de produtos: " + NumériqueVersChaine(TableauOccurrence(produtos)))

// Exibir preço médio
somaPrecos est un réel = 0
POUR TOUT produto DE produtos
somaPrecos += produto.preco
FIN

precoMedio est un réel = somaPrecos / TableauOccurrence(produtos)
Trace("Preço médio: " + NumériqueVersChaine(precoMedio, "F2"))
FIN

// Upload de uma imagem de produto
imagemId est un JSON = api.UploadFile("produtos/1/imagem", fRepExe() + [fSep] + "imagens" + [fSep] + "produto1.jpg", "imagem")

SI imagemId <> Null ALORS
Info("Imagem enviada com sucesso! ID: " + imagemId.id)
FIN
FIN

// Exemplo 4: Autenticação OAuth 2.0 (EAuthType.OAUTH2)
// --------------------------------------------------------------------------------
PROCEDURE ExemploAutenticacaoOAuth2()
// Criar instância da API
api est un RestAPI = RestAPI.CreateInstance("https://api.exemplo.com/v4")

// Configurar autenticação OAuth 2.0
api.SetAuthOAuth2(
"cliente_id_12345", // Client ID
"cliente_secreto_67890", // Client Secret
"https://auth.exemplo.com/oauth/token", // Token Endpoint
"read write delete" // Escopo de acesso
)

// Obter token OAuth 2.0
SI api.RefreshOAuth2Token() = Faux ALORS
Erreur("Falha ao obter token OAuth 2.0: " + api.GetLastError())
RETOUR
FIN

// Configurar Content-Type
api.SetContentType(EContentType.JSON)

// Executar operações assíncronas
api.ExecuteAsync(ERestMethod.GET, "estatisticas", Null, CallbackEstatisticas)

// Continuar executando outras operações enquanto a requisição assíncrona é processada
Info("Requisição assíncrona iniciada. Continue trabalhando enquanto os dados são carregados...")

// Exemplo de requisição síncrona enquanto isso
perfil est un JSON = api.GET("meu-perfil")

SI perfil <> Null ALORS
Info("Perfil: " + perfil.nome)
FIN
FIN

// Callback para requisição assíncrona
PROCEDURE CallbackEstatisticas(resultat est un JSON, nCode est un entier, sError est une chaîne)
SI resultat <> Null ALORS
Info("Estatísticas recebidas: " + VariantVersJSON(resultat))
SINON
Erreur("Falha ao obter estatísticas: " + sError + " (código: " + NumériqueVersChaine(nCode) + ")")
FIN
FIN

// Exemplo 5: Autenticação Digest (EAuthType.DIGEST)
// --------------------------------------------------------------------------------
PROCEDURE ExemploAutenticacaoDigest()
// Criar instância da API
api est un RestAPI = RestAPI.CreateInstance("https://api.exemplo.com/v5")

// Configurar autenticação Digest
api.SetAuthDigest("usuario_digest", "senha_digest")

// Configurar User-Agent personalizado
api.SetUserAgent("MinhaApp/2.0 (WinDev; RestAPI v5.0)")

// Fazer requisição para endpoint protegido com digest
dadosUsuario est un JSON = api.GET("perfil")

SI dadosUsuario <> Null ALORS
// Exibir informações do usuário
Info("Bem-vindo, " + dadosUsuario.nome + "!")
Info("Último acesso: " + dadosUsuario.ultimo_acesso)
SINON
Erreur("Falha ao acessar perfil: " + api.GetLastError())
FIN
FIN

// Exemplo 6: Autenticação HMAC (EAuthType.HMAC)
// --------------------------------------------------------------------------------
PROCEDURE ExemploAutenticacaoHMAC()
// Criar instância da API
api est un RestAPI = RestAPI.CreateInstance("https://api.exemplo.com/v6")

// Configurar autenticação HMAC com SHA-256
api.SetAuthHMAC("chave_secreta_para_hmac_123456789", "SHA256")

// Configurar cabeçalhos adicionais
api.AddHeader("X-App-ID", "minha-aplicacao")

// Fazer uma requisição PUT para atualizar dados
dadosAtualizacao est un JSON
dadosAtualizacao.id = 123
dadosAtualizacao.status = "ativo"
dadosAtualizacao.ultima_atualizacao = DateHeureVersChaîne(DateHeureSys())

resultado est un JSON = api.PUT("recursos/123", dadosAtualizacao)

SI resultado <> Null ALORS
Info("Recurso atualizado com sucesso!")
SINON
Erreur("Falha ao atualizar recurso: " + api.GetLastError())
FIN

// Fazer uma requisição DELETE
confirmacao est un JSON = api.DELETE("recursos/456")

SI confirmacao <> Null ET confirmacao.sucesso = Vrai ALORS
Info("Recurso excluído com sucesso!")
SINON
Erreur("Falha ao excluir recurso: " + api.GetLastError())
FIN
FIN

// Exemplo 7: Autenticação Personalizada (EAuthType.CUSTOM)
// --------------------------------------------------------------------------------
PROCEDURE ExemploAutenticacaoCustom()
// Criar instância da API
api est un RestAPI = RestAPI.CreateInstance("https://api.exemplo.com/v7")

// Gerar um timestamp para autenticação
timestamp est une chaîne = NumériqueVersChaine(DateHeureVersEntier(DateHeureSys()))

// Gerar um nonce aleatório
nonce est une chaîne = ProcédureVersChaine(AleatoireEntre(100000, 999999))

// Chave de autenticação
chave est une chaîne = "chave_secreta_123"

// Construir assinatura personalizada
stringToSign est une chaîne = "GET:/recurso:" + timestamp + ":" + nonce
assinatura est une chaîne = Encode(HashChaîne(cryptAlgoSHA256, stringToSign, chave), encodeBASE64)

// Montar valor de autenticação personalizada
valorAuth est une chaîne = "Custom ts=" + timestamp + ",nonce=" + nonce + ",signature=" + assinatura

// Configurar autenticação personalizada
api.SetCustomAuth(valorAuth)

// Fazer requisição para endpoint protegido
recursos est un JSON = api.GET("recurso")

SI recursos <> Null ALORS
// Processar recursos
POUR TOUT recurso DE recursos
Trace("Recurso: " + recurso.nome)
FIN
SINON
Erreur("Falha ao acessar recursos: " + api.GetLastError())
FIN
FIN

// Exemplo 8: Manipulação de Paginação e HAL
// --------------------------------------------------------------------------------
PROCEDURE ExemploPaginacaoEHAL()
// Criar instância da API
api est un RestAPI = RestAPI.CreateInstance("https://api.exemplo.com/v8")

// Configurar autenticação Bearer
api.SetAuthBearer("token_de_acesso_jwt_123456789")

// Obter todos os resultados paginados
todosResultados est un tableau de JSON = api.GetAllPaginatedResults("clientes", "pagina", "tamanho", 50)

// Processar todos os resultados
totalClientes est un entier = 0
POUR TOUT pagina DE todosResultados
SI pagina._embedded..Existe ET pagina._embedded.clientes..Existe ALORS
// API no formato HAL
POUR TOUT cliente DE pagina._embedded.clientes
totalClientes++
Trace(NumériqueVersChaine(totalClientes) + ". " + cliente.nome)
FIN
SINON
// Formato padrão
POUR TOUT cliente DE pagina
totalClientes++
Trace(NumériqueVersChaine(totalClientes) + ". " + cliente.nome)
FIN
FIN
FIN

Info("Total de clientes processados: " + NumériqueVersChaine(totalClientes))

// Exemplo de requisição com HAL e navegação por links
primeiraResposta est un JSON = api.GET("pedidos/recentes")

SI primeiraResposta <> Null ET primeiraResposta._links..Existe ALORS
// Seguir um link HAL
proximaResposta est un JSON = api.FollowHALLink(primeiraResposta, "next")

SI proximaResposta <> Null ALORS
Info("Próxima página obtida com sucesso!")
FIN

// Seguir um link para detalhes de um item específico
SI primeiraResposta._embedded..Existe ET primeiraResposta._embedded.pedidos..Existe ET
TableauOccurrence(primeiraResposta._embedded.pedidos) > 0 ALORS

primeiroPedido est un variant = primeiraResposta._embedded.pedidos[1]

SI primeiroPedido._links..Existe ET primeiroPedido._links.self..Existe ALORS
// Obter detalhes do pedido usando o link HAL
detalhes est un JSON = api.FollowHALLink(primeiroPedido, "self")

SI detalhes <> Null ALORS
Info("Detalhes do pedido #" + detalhes.numero + " obtidos com sucesso!")
FIN
FIN
FIN
FIN
FIN

// Exemplo 9: Download e Upload de Arquivos
// --------------------------------------------------------------------------------
PROCEDURE ExemploArquivos()
// Criar instância da API
api est un RestAPI = RestAPI.CreateInstance("https://api.exemplo.com/v9")

// Configurar autenticação API Key
api.SetAuthApiKey("chave_api_para_arquivos_123", "X-API-Key")

// Download de um arquivo
destino est une chaîne = fRepDonnées() + [fSep] + "downloads" + [fSep] + "relatorio.pdf"

// Verificar se o diretório existe, senão, criar
SI PAS fRepertoireExiste(fExtraitChemin(destino, fDisque + fRépertoire)) ALORS
fRepCrée(fExtraitChemin(destino, fDisque + fRépertoire))
FIN

// Executar o download
SI api.DownloadFile("relatorios/mensal", destino) ALORS
Info("Arquivo baixado com sucesso para: " + destino)

// Abrir o arquivo
LanceAppliAssociée(destino)
SINON
Erreur("Falha ao baixar arquivo: " + api.GetLastError())
FIN

// Upload de múltiplos arquivos
localDir est une chaîne = fRepDonnées() + [fSep] + "uploads"
listaArquivos est un tableau de chaînes = fListeFichier(localDir + [fSep] + "*.jpg", frNormal)

POUR TOUT arquivo DE listaArquivos
caminhoCompleto est une chaîne = localDir + [fSep] + arquivo

// Upload do arquivo
resultado est un JSON = api.UploadFile("imagens/upload", caminhoCompleto)

SI resultado <> Null ALORS
Info("Arquivo " + arquivo + " enviado com sucesso. ID: " + resultado.id)
SINON
Erreur("Falha ao enviar arquivo " + arquivo + ": " + api.GetLastError())
FIN
FIN
FIN

// Exemplo 10: Uso Avançado com Tratamento de Erros
// --------------------------------------------------------------------------------
PROCEDURE ExemploAvancado()
// Criar instância da API
api est un RestAPI = RestAPI.CreateInstance("https://api.exemplo.com/v10")

// Configurar Bearer Token
api.SetAuthBearer("token_de_acesso_avancado_123456")

// Ativar modo debug e retentativas
api.SetDebugMode(Vrai)
api.SetAutoRetry(Vrai, 2, 1500)

// Lista para armazenar operações bem-sucedidas e falhas
operacoesSucesso est un tableau de chaînes
operacoesFalha est un tableau associatif de chaînes

// Lista de IDs para processar
listaIDs est un tableau d'entiers = [101, 102, 103, 104, 105]

POUR TOUT id DE listaIDs
// Tentar obter dados para cada ID
dados est un JSON = api.GET("entidades/" + NumériqueVersChaine(id))

SI dados <> Null ALORS
// Operação bem-sucedida
TableauAjoute(operacoesSucesso, "ID " + NumériqueVersChaine(id) + ": " + données.nome)

// Verificar se é necessário atualização
SI données.necessita_atualizacao = Vrai ALORS
// Preparar dados para atualização
atualizacao est un JSON
atualizacao.id = id
atualizacao.status = "atualizado"
atualizacao.data_atualizacao = DateHeureVersChaîne(DateHeureSys())

// Executar atualização
resultado est un JSON = api.PUT("entidades/" + NumériqueVersChaine(id), atualizacao)

SI resultado <> Null ALORS
TableauAjoute(operacoesSucesso, "Atualização ID " + NumériqueVersChaine(id) + " concluída")
SINON
operacoesFalha["Atualização ID " + NumériqueVersChaine(id)] = api.GetLastError()
FIN
FIN
SINON
// Operação falhou
operacoesFalha["ID " + NumériqueVersChaine(id)] = api.GetLastError()

// Verificar código de erro
SI api.GetLastResponseCode() = 404 ALORS
// Entidade não encontrada, talvez criar uma nova?
Trace("Entidade ID " + NumériqueVersChaine(id) + " não encontrada. Considerar criação.")
FIN
FIN
FIN

// Exibir relatório de operações
Info("Operações concluídas com sucesso: " + NumériqueVersChaine(TableauOccurrence(operacoesSucesso)))
Info("Operações com falha: " + NumériqueVersChaine(TableauOccurrence(operacoesFalha)))

// Registrar resultados em log
SI TableauOccurrence(operacoesFalha) > 0 ALORS
Trace("Detalhes das falhas:")
POUR TOUT pFalha DE operacoesFalha
Trace("- " + pFalha..Nom + ": " + pFalha)
FIN
FIN
FIN

--
Adriano José Boller
______________________________________________
Consultor e Representante Oficial da
PcSoft no Brasil
+55 (41) 99949 1800
adrianoboller@gmail.com
skype: adrianoboller
http://wxinformatica.com.br/
Registered member
3,851 messages
Posted on April, 04 2025 - 2:27 AM
// ------------------------------------------------------------------------------------------------
// RestEnums.wdc - Centralized Enumerations for RestAPI v6.0
// ------------------------------------------------------------------------------------------------
// All enumerations used across the RestAPI system
// Author: Claude
// Creation Date: 04/03/2025
// Version: 6.0
// ------------------------------------------------------------------------------------------------

// HTTP methods supported by the API
ENUMERATION ERestMethod
GET = 1 // Retrieve resources
POST = 2 // Create new resources
PUT = 3 // Update existing resources (complete replacement)
DELETE = 4 // Remove resources
PATCH = 5 // Partial update of resources
HEAD = 6 // Get headers only (no body)
OPTIONS = 7 // Get supported methods/options
TRACE = 8 // Diagnostic method (echo request)
FIN

// Authentication types supported by the API
ENUMERATION EAuthType
NONE = 0 // No authentication
BASIC = 1 // Basic authentication (username:password)
BEARER = 2 // Bearer token authentication (JWT)
API_KEY = 3 // API Key authentication
OAUTH2 = 4 // OAuth 2.0 authentication
DIGEST = 5 // Digest authentication
HMAC = 6 // HMAC signature authentication
NTLM = 7 // Windows NTLM authentication
KERBEROS = 8 // Kerberos authentication
AWS_SIGNATURE = 9 // AWS Signature v4
CUSTOM = 10 // Custom authentication
FIN

// Content types supported by the API
ENUMERATION EContentType
JSON = 1 // application/json
XML = 2 // application/xml
FORM = 3 // application/x-www-form-urlencoded
MULTIPART = 4 // multipart/form-data
TEXT = 5 // text/plain
HTML = 6 // text/html
CSV = 7 // text/csv
BINARY = 8 // application/octet-stream
PDF = 9 // application/pdf
CUSTOM = 10 // Custom content type
FIN

// Log levels for the Logger component
ENUMERATION ELogLevel
TRACE = 1 // Most detailed information
DEBUG = 2 // Detailed information for debugging
INFO = 3 // General information
WARNING = 4 // Warnings
ERROR = 5 // Errors
CRITICAL = 6 // Critical errors
NONE = 7 // No logging
FIN

// Error handling strategies
ENUMERATION EErrorStrategy
THROW = 1 // Throw an exception
RETURN_NULL = 2 // Return null silently
RETRY = 3 // Retry the operation
CALLBACK = 4 // Call an error handler
LOG_ONLY = 5 // Just log the error
IGNORE = 6 // Completely ignore the error
FIN

// Cache strategies
ENUMERATION ECacheStrategy
NONE = 0 // No caching
MEMORY = 1 // In-memory caching
FILE = 2 // File-based caching
MEMORY_THEN_FILE = 3 // Memory with file backup
HYBRID = 4 // Custom hybrid approach
FIN

// OAuth 2.0 grant types
ENUMERATION EOAuth2GrantType
CLIENT_CREDENTIALS = 1 // Client credentials grant
AUTHORIZATION_CODE = 2 // Authorization code grant
PASSWORD = 3 // Resource owner password credentials
IMPLICIT = 4 // Implicit grant (legacy)
REFRESH_TOKEN = 5 // Refresh token grant
DEVICE_CODE = 6 // Device code grant
FIN

// Pagination types for REST APIs
ENUMERATION EPaginationType
NONE = 0 // No pagination
OFFSET = 1 // Offset-based pagination (skip/limit)
PAGE = 2 // Page-based pagination (page/size)
CURSOR = 3 // Cursor-based pagination (next/prev tokens)
LINK_HEADER = 4 // Link header pagination (RFC 5988)
HAL = 5 // HAL-style pagination (_links)
CUSTOM = 6 // Custom pagination strategy
FIN

// Rate limiting strategies
ENUMERATION ERateLimitStrategy
NONE = 0 // No rate limiting
FIXED_WINDOW = 1 // Fixed window counter
SLIDING_WINDOW = 2 // Sliding window counter
TOKEN_BUCKET = 3 // Token bucket algorithm
LEAKY_BUCKET = 4 // Leaky bucket algorithm
ADAPTIVE = 5 // Adaptive rate limiting
FIN

// API response formats
ENUMERATION EResponseFormat
AUTO = 0 // Auto-detect from Content-Type
JSON = 1 // JSON response
XML = 2 // XML response
TEXT = 3 // Plain text response
BINARY = 4 // Binary response
CUSTOM = 5 // Custom response format
FIN

// HTTP status code categories
ENUMERATION EStatusCategory
INFORMATIONAL = 1 // 1xx status codes
SUCCESS = 2 // 2xx status codes
REDIRECTION = 3 // 3xx status codes
CLIENT_ERROR = 4 // 4xx status codes
SERVER_ERROR = 5 // 5xx status codes
UNKNOWN = 6 // Unknown or non-standard codes
FIN

// Parameter locations
ENUMERATION EParamLocation
PATH = 1 // URL path parameter
QUERY = 2 // Query string parameter
HEADER = 3 // HTTP header
BODY = 4 // Request body
FORM = 5 // Form parameter
COOKIE = 6 // Cookie parameter
FIN

// Authentication token types
ENUMERATION ETokenType
JWT = 1 // JSON Web Token
OPAQUE = 2 // Opaque token
MAC = 3 // Message Authentication Code token
SAML = 4 // SAML token
OAUTH1 = 5 // OAuth 1.0 token
OAUTH2 = 6 // OAuth 2.0 token
CUSTOM = 7 // Custom token format
FIN

// Connection security levels
ENUMERATION ESecurityLevel
NONE = 0 // No security (HTTP)
SSL = 1 // SSL/TLS (HTTPS)
SSL_PINNED = 2 // SSL with certificate pinning
MTLS = 3 // Mutual TLS (client certificates)
E2E = 4 // End-to-end encryption
FIN

// Retry strategies for failed requests
ENUMERATION ERetryStrategy
NONE = 0 // No retry
FIXED = 1 // Fixed interval retry
EXPONENTIAL = 2 // Exponential backoff
LINEAR = 3 // Linear backoff
RANDOM = 4 // Random jitter backoff
CUSTOM = 5 // Custom retry strategy
FIN

--
Adriano José Boller
______________________________________________
Consultor e Representante Oficial da
PcSoft no Brasil
+55 (41) 99949 1800
adrianoboller@gmail.com
skype: adrianoboller
http://wxinformatica.com.br/
Registered member
3,851 messages
Posted on April, 04 2025 - 2:29 AM
# RestAPI v6.0 Project Structure

## Overview

RestAPI v6.0 is a comprehensive object-oriented solution for consuming REST APIs in WinDev applications. It follows modern OO design principles, making extensive use of inheritance, polymorphism, and encapsulation to provide a flexible and extensible framework.

## Project Components

### Core

1. **RestEnums.wdc**
- Contains all enumerations used across the system
- Centralized location for type definitions

2. **RestCore.wdc**
- Base abstract class for all components
- Contains common functionality and interfaces

3. **RestConfig.wdc**
- Configuration management
- Default settings and global options

### Authentication

4. **RestAuth.wdc**
- Abstract base class for all authentication methods
- Defines the authentication interface

5. **RestAuthBasic.wdc**
- Implements HTTP Basic authentication

6. **RestAuthBearer.wdc**
- Implements Bearer token authentication (JWT)

7. **RestAuthApiKey.wdc**
- Implements API Key authentication

8. **RestAuthOAuth2.wdc**
- Implements OAuth 2.0 authentication
- Supports different grant types

9. **RestAuthDigest.wdc**
- Implements HTTP Digest authentication

10. **RestAuthHMAC.wdc**
- Implements HMAC signature authentication

11. **RestAuthCustom.wdc**
- Implements custom authentication methods
- Supports callback functions for extensibility

### HTTP

12. **RestRequest.wdc**
- Encapsulates an HTTP request
- Handles headers, parameters, and payload formatting

13. **RestResponse.wdc**
- Encapsulates an HTTP response
- Parses different content types
- Provides typed accessors

14. **RestEndpoint.wdc**
- Represents a specific API endpoint
- Has its own settings and behaviors

### Utilities

15. **RestLogger.wdc**
- Handles logging of requests, responses, and errors
- Multiple output destinations (file, console, etc)

16. **RestCache.wdc**
- Caches responses to improve performance
- Configurable caching strategies

17. **RestUtils.wdc**
- Utility functions used across the system
- String manipulation, encoding, etc.

18. **RestFileTransfer.wdc**
- Specialized class for file uploads and downloads

### Advanced Features

19. **RestPagination.wdc**
- Handles different pagination strategies
- Automatic page traversal

20. **RestRateLimit.wdc**
- Manages API rate limits
- Implements throttling and backoff strategies

21. **RestHAL.wdc**
- Support for Hypertext Application Language
- Link traversal and resource discovery

22. **RestWebSocket.wdc**
- WebSocket support for real-time APIs

### Main Interfaces

23. **RestAPI.wdc**
- Main entry point for the library
- Facade that coordinates all components

24. **RestCollection.wdc**
- Manages collections of API endpoints
- Support for CRUD operations

## Class Relationships

```
RestCore
├─ RestAPI
├─ RestEndpoint
├─ RestRequest
├─ RestResponse
├─ RestAuth
│ ├─ RestAuthBasic
│ ├─ RestAuthBearer
│ ├─ RestAuthApiKey
│ ├─ RestAuthOAuth2
│ ├─ RestAuthDigest
│ ├─ RestAuthHMAC
│ └─ RestAuthCustom
├─ RestLogger
├─ RestCache
├─ RestPagination
├─ RestRateLimit
├─ RestHAL
└─ RestFileTransfer
```

## Examples

The project includes comprehensive examples for each authentication type:
- Basic Auth Example
- Bearer Token Example
- API Key Example
- OAuth 2.0 Example (all grant types)
- Digest Auth Example
- HMAC Signature Example
- Custom Auth Example

Additionally, examples for advanced features:
- File Upload/Download
- Pagination
- Rate Limiting
- HAL Traversal
- WebSocket Communication

## Documentation

Full documentation is available in both English and Portuguese, including:
- Architecture Overview
- Class Reference
- Integration Guide
- Authentication Examples
- Advanced Usage Scenarios

--
Adriano José Boller
______________________________________________
Consultor e Representante Oficial da
PcSoft no Brasil
+55 (41) 99949 1800
adrianoboller@gmail.com
skype: adrianoboller
http://wxinformatica.com.br/
Registered member
3,851 messages
Posted on April, 04 2025 - 2:30 AM
// ------------------------------------------------------------------------------------------------
// RestRequest.wdc - Classe de Requisição
// ------------------------------------------------------------------------------------------------
// Encapsula uma requisição HTTP
// Autor: Claude
// Data: 03/04/2025
// ------------------------------------------------------------------------------------------------

CLASSE RestRequest

// Propriedades
PRIVE
_nMethod est un ERestMethod = ERestMethod.GET
_sURL est une chaîne
_tabHeaders est un tableau associatif de chaînes
_tabQueryParams est un tableau associatif de chaînes
_nContentType est un EContentType = EContentType.JSON
_vData est un variant
_sFormattedData est une chaîne
_nTimeout est un entier = 30
_bSSLVerification est un booléen = Vrai
_auth est un RestAuth = Null
_logger est un RestLogger = Null

// Construtores
PROCEDURE PUBLIQUE Constructeur()
// Inicializar cabeçalhos padrão
InitHeaders()
FIN

PROCEDURE PUBLIQUE Constructeur(sURL est une chaîne)
_sURL = sURL
InitHeaders()
FIN

PROCEDURE PUBLIQUE Constructeur(sURL est une chaîne, nMethod est un ERestMethod)
_sURL = sURL
_nMethod = nMethod
InitHeaders()
FIN

PROCEDURE PUBLIQUE Constructeur(sURL est une chaîne, nMethod est un ERestMethod, nContentType est un EContentType)
_sURL = sURL
_nMethod = nMethod
_nContentType = nContentType
InitHeaders()
FIN

// Métodos de configuração geral
PROCEDURE PUBLIQUE SetURL(sURL est une chaîne)
_sURL = sURL
FIN

PROCEDURE PUBLIQUE SetMethod(nMethod est un ERestMethod)
_nMethod = nMethod
FIN

PROCEDURE PUBLIQUE SetContentType(nContentType est un EContentType)
_nContentType = nContentType
_tabHeaders["Content-Type"] = RestUtils.ContentTypeToString(nContentType)
FIN

PROCEDURE PUBLIQUE SetTimeout(nSeconds est un entier)
_nTimeout = nSeconds
FIN

PROCEDURE PUBLIQUE SetSSLVerification(bVerify est un booléen)
_bSSLVerification = bVerify
FIN

PROCEDURE PUBLIQUE SetLogger(logger est un RestLogger)
_logger = logger
FIN

PROCEDURE PUBLIQUE SetAuth(auth est un RestAuth)
_auth = auth
SI _auth <> Null ET _logger <> Null ALORS
_auth.SetLogger(_logger)
FIN
FIN

// Configuração de dados
PROCEDURE PUBLIQUE SetData(vData est un variant)
_vData = vData
// Formatação é feita apenas na execução para garantir que o ContentType atual seja usado
FIN

PROCEDURE PUBLIQUE SetRawData(sData est une chaîne)
_sFormattedData = sData
FIN

// Configuração de cabeçalhos
PROCEDURE PUBLIQUE AddHeader(sName est une chaîne, sValue est une chaîne)
_tabHeaders[sName] = sValue
FIN

PROCEDURE PUBLIQUE RemoveHeader(sName est une chaîne)
SI _tabHeaders[sName]..Existe ALORS
TableauSupprime(_tabHeaders, sName)
FIN
FIN

PROCEDURE PUBLIQUE ClearHeaders()
TableauSupprimeTout(_tabHeaders)
InitHeaders()
FIN

// Configuração de parâmetros de consulta
PROCEDURE PUBLIQUE AddQueryParam(sName est une chaîne, sValue est une chaîne)
_tabQueryParams[sName] = sValue
FIN

PROCEDURE PUBLIQUE RemoveQueryParam(sName est une chaîne)
SI _tabQueryParams[sName]..Existe ALORS
TableauSupprime(_tabQueryParams, sName)
FIN
FIN

PROCEDURE PUBLIQUE ClearQueryParams()
TableauSupprimeTout(_tabQueryParams)
FIN

// Getters
FONCTION PUBLIQUE GetURL() : chaîne
RENVOYER _sURL
FIN

FONCTION PUBLIQUE GetMethod() : ERestMethod
RENVOYER _nMethod
FIN

FONCTION PUBLIQUE GetContentType() : EContentType
RENVOYER _nContentType
FIN

FONCTION PUBLIQUE GetData() : variant
RENVOYER _vData
FIN

FONCTION PUBLIQUE GetFormattedData() : chaîne
SI _sFormattedData = "" ET _vData <> Null ALORS
_sFormattedData = RestUtils.FormatData(_vData, _nContentType)
FIN
RENVOYER _sFormattedData
FIN

FONCTION PUBLIQUE GetHeaders() : tableau associatif de chaînes
RENVOYER _tabHeaders
FIN

FONCTION PUBLIQUE GetQueryParams() : tableau associatif de chaînes
RENVOYER _tabQueryParams
FIN

FONCTION PUBLIQUE GetTimeout() : entier
RENVOYER _nTimeout
FIN

FONCTION PUBLIQUE GetSSLVerification() : booléen
RENVOYER _bSSLVerification
FIN

// Construção da URL completa com query params
FONCTION PUBLIQUE GetFullURL() : chaîne
sFullURL est une chaîne = _sURL

// Adicionar query params
SI TableauOccurrence(_tabQueryParams) > 0 ALORS
sFullURL += RestUtils.BuildQueryString(_tabQueryParams)
FIN

RENVOYER sFullURL
FIN

// Método para aplicar autenticação
FONCTION PUBLIQUE ApplyAuth() : booléen
SI _auth = Null ALORS
RENVOYER Vrai // Sem autenticação, retornar sucesso
FIN

// Verificar se a autenticação é válida
SI PAS _auth.IsAuthValid() ALORS
// Tentar atualizar a autenticação, se possível
SI _auth.RefreshAuth() = Faux ALORS
SI _logger <> Null ALORS
_logger.Error("Falha ao atualizar autenticação")
FIN
RENVOYER Faux
FIN
FIN

// Aplicar autenticação
sMethodStr est une chaîne = RestUtils.MethodToString(_nMethod)
sDataStr est une chaîne = GetFormattedData()

RENVOYER _auth.ApplyAuth(_tabHeaders, _tabQueryParams, sMethodStr, _sURL, sDataStr)
FIN

// Inicialização de cabeçalhos padrão
PRIVE
PROCEDURE InitHeaders()
_tabHeaders["User-Agent"] = "WinDev RestAPI Client v5.0"
_tabHeaders["Accept"] = "application/json"
_tabHeaders["Content-Type"] = RestUtils.ContentTypeToString(_nContentType)
FIN

// Método para log
PRIVE
PROCEDURE LogDebug(sMessage est une chaîne)
SI _logger <> Null ALORS
_logger.Debug(sMessage)
FIN
FIN

PRIVE
PROCEDURE LogError(sMessage est une chaîne)
SI _logger <> Null ALORS
_logger.Error(sMessage)
FIN
FIN

// Converte objeto para string (para debug/log)
FONCTION PUBLIQUE ToString() : chaîne
sMethodStr est une chaîne = RestUtils.MethodToString(_nMethod)
sContentTypeStr est une chaîne = RestUtils.ContentTypeToString(_nContentType)

sResult est une chaîne = sMethodStr + " " + GetFullURL() + EOT
sResult += "Content-Type: " + sContentTypeStr + EOT

// Adicionar outros cabeçalhos
POUR TOUT pHeader DE _tabHeaders
SI pHeader..Nom <> "Content-Type" ALORS
sResult += pHeader..Nom + ": " + pHeader + EOT
FIN
FIN

// Adicionar dados, se houver
SI _vData <> Null OU _sFormattedData <> "" ALORS
sResult += EOT + GetFormattedData()
FIN

RENVOYER sResult
FIN
FIN

--
Adriano José Boller
______________________________________________
Consultor e Representante Oficial da
PcSoft no Brasil
+55 (41) 99949 1800
adrianoboller@gmail.com
skype: adrianoboller
http://wxinformatica.com.br/
Registered member
3,851 messages
Posted on April, 04 2025 - 2:31 AM
// ------------------------------------------------------------------------------------------------
// RestAuthDigest.wdc - Autenticação Digest
// ------------------------------------------------------------------------------------------------
// Implementa autenticação HTTP Digest
// Autor: Claude
// Data: 03/04/2025
// ------------------------------------------------------------------------------------------------

CLASSE RestAuthDigest HÉRITE DE RestAuth

// Propriedades específicas
PRIVE
_sUsername est une chaîne
_sPassword est une chaîne
_sRealm est une chaîne = ""
_sNonce est une chaîne = ""
_sOpaque est une chaîne = ""
_sQop est une chaîne = "auth"
_sAlgorithm est une chaîne = "MD5"
_sPath est une chaîne = "/"
_sNonceCount est une chaîne = "00000001"
_bReadyForAuthRequest est un booléen = Faux
_sLastResponse est une chaîne = ""
_nLastResponseCode est un entier = 0
_tabChallengeParams est un tableau associatif de chaînes
_bHasStoredChallenge est un booléen = Faux

// Construtores
PROCEDURE PUBLIQUE Constructeur()
Constructeur RestAuth(EAuthType.DIGEST)
_sUsername = ""
_sPassword = ""
FIN

PROCEDURE PUBLIQUE Constructeur(sUsername est une chaîne, sPassword est une chaîne)
Constructeur RestAuth(EAuthType.DIGEST)
_sUsername = sUsername
_sPassword = sPassword
FIN

PROCEDURE PUBLIQUE Constructeur(sUsername est une chaîne, sPassword est une chaîne, logger est un RestLogger)
Constructeur RestAuth(EAuthType.DIGEST, logger)
_sUsername = sUsername
_sPassword = sPassword
FIN

// Métodos públicos de configuração
PROCEDURE PUBLIQUE SetCredentials(sUsername est une chaîne, sPassword est une chaîne)
_sUsername = sUsername
_sPassword = sPassword
FIN

FONCTION PUBLIQUE GetUsername() : chaîne
RENVOYER _sUsername
FIN

// Métodos para configurar parâmetros de digest manualmente (se conhecidos de antemão)
PROCEDURE PUBLIQUE SetDigestParams(sRealm est une chaîne, sNonce est une chaîne, sQop est une chaîne, sOpaque est une chaîne = "", sAlgorithm est une chaîne = "MD5")
_sRealm = sRealm
_sNonce = sNonce
_sQop = sQop
_sOpaque = sOpaque
_sAlgorithm = sAlgorithm
_bHasStoredChallenge = Vrai
_bReadyForAuthRequest = Vrai
FIN

// Processar cabeçalho WWW-Authenticate da resposta 401
FONCTION PUBLIQUE ProcessAuthenticateHeader(sAuthHeader est une chaîne) : booléen
// Verificar se o cabeçalho começa com "Digest "
SI Gauche(sAuthHeader, 7) <> "Digest " ALORS
LogError("Cabeçalho WWW-Authenticate não é do tipo Digest")
RENVOYER Faux
FIN

// Extrair os parâmetros do cabeçalho
sParams est une chaîne = Milieu(sAuthHeader, 8)

// Limpar parâmetros anteriores
TableauSupprimeTout(_tabChallengeParams)

// Parse dos parâmetros do digest
tabParts est un tableau de chaînes = ExtraitChaîne(sParams, ",", 1, 10)

POUR TOUT sPart DE tabParts
// Remover espaços em branco
sPart = Remplace(sPart, " ", "")

// Extrair nome e valor do parâmetro (formato nome=valor)
nPos est un entier = Position(sPart, "=")
SI nPos > 0 ALORS
sName est une chaîne = Gauche(sPart, nPos - 1)
sValue est une chaîne = Milieu(sPart, nPos + 1)

// Remover aspas, se presentes
SI Gauche(sValue, 1) = """" ET Droite(sValue, 1) = """" ALORS
sValue = Milieu(sValue, 2, Taille(sValue) - 2)
FIN

// Armazenar parâmetro
_tabChallengeParams[sName] = sValue
FIN
FIN

// Verificar se recebemos os parâmetros necessários
SI _tabChallengeParams["realm"]..Existe ET _tabChallengeParams["nonce"]..Existe ALORS
// Atualizar propriedades
_sRealm = _tabChallengeParams["realm"]
_sNonce = _tabChallengeParams["nonce"]

// Opcionais
SI _tabChallengeParams["qop"]..Existe ALORS
_sQop = _tabChallengeParams["qop"]
FIN

SI _tabChallengeParams["opaque"]..Existe ALORS
_sOpaque = _tabChallengeParams["opaque"]
FIN

SI _tabChallengeParams["algorithm"]..Existe ALORS
_sAlgorithm = _tabChallengeParams["algorithm"]
FIN

// Resetar contador de nonce
_sNonceCount = "00000001"

_bHasStoredChallenge = Vrai
_bReadyForAuthRequest = Vrai

LogDebug("Parâmetros Digest processados com sucesso. Realm: " + _sRealm + ", Nonce: " + _sNonce)
RENVOYER Vrai
SINON
LogError("Parâmetros Digest incompletos no cabeçalho WWW-Authenticate")
RENVOYER Faux
FIN
FIN

// Processar a resposta HTTP para extrair o desafio digest
FONCTION PUBLIQUE ProcessHttpResponse(nStatusCode est un entier, tabResponseHeaders est un tableau associatif de chaînes, sResponseBody est une chaîne) : booléen
// Armazenar a resposta para possível análise posterior
_sLastResponse = sResponseBody
_nLastResponseCode = nStatusCode

// Se não for um código 401, não há desafio digest para processar
SI nStatusCode <> 401 ALORS
RENVOYER Faux
FIN

// Procurar pelo cabeçalho WWW-Authenticate
SI tabResponseHeaders["WWW-Authenticate"]..Existe ALORS
RENVOYER ProcessAuthenticateHeader(tabResponseHeaders["WWW-Authenticate"])
SINON
LogError("Resposta 401 sem cabeçalho WWW-Authenticate")
RENVOYER Faux
FIN
FIN

// Sobrescrever métodos da classe base
FONCTION PUBLIQUE ApplyAuth(tabHeaders est un tableau associatif de chaînes, tabQueryParams est un tableau associatif de chaînes, sMethod est une chaîne, sURL est une chaîne, sData est une chaîne) : booléen
SI _sUsername = "" OU _sPassword = "" ALORS
LogError("Credenciais digest não configuradas")
RENVOYER Faux
FIN

// Se não tivermos os parâmetros de desafio digest, não podemos autenticar ainda
SI PAS _bReadyForAuthRequest ALORS
LogError("Parâmetros digest não recebidos do servidor. É necessário primeiro um desafio 401")
RENVOYER Faux
FIN

// Extrair o path da URL para o digest
_sPath = ExtractPathFromURL(sURL)

// Gerar cabeçalho de autorização digest
sAuthHeader est une chaîne = GenerateDigestHeader(sMethod)

// Adicionar cabeçalho de autorização
tabHeaders["Authorization"] = sAuthHeader

// Incrementar contador de nonce para próxima requisição
IncrementNonceCount()

LogDebug("Adicionado cabeçalho de autenticação Digest")
RENVOYER Vrai
FIN

FONCTION PUBLIQUE IsAuthValid() : booléen
RENVOYER _sUsername <> "" ET _sPassword <> "" ET _bReadyForAuthRequest
FIN

PROCEDURE PUBLIQUE ClearAuth()
_sUsername = ""
_sPassword = ""
_bReadyForAuthRequest = Faux
_bHasStoredChallenge = Faux
_sNonce = ""
_sRealm = ""
_sOpaque = ""
FIN

// Métodos privados
PRIVE
FONCTION GenerateDigestHeader(sMethod est une chaîne) : chaîne
// Gerar um client nonce (cnonce)
sCnonce est une chaîne = Gauche(Encode(RestUtils.GenerateNonce(), encodeBASE64), 16)

// Calcular hashes para digest

// HA1 = MD5(username:realm:password)
sHA1Data est une chaîne = _sUsername + ":" + _sRealm + ":" + _sPassword
sHA1 est une chaîne = Minuscule(HashVersHexa(sHA1Data, hachageMD5))

// HA2 = MD5(method:digestURI)
sHA2Data est une chaîne = sMethod + ":" + _sPath
sHA2 est une chaîne = Minuscule(HashVersHexa(sHA2Data, hachageMD5))

// Para qop="auth"
sResponse est une chaîne
SI _sQop = "auth" OU _sQop [= "auth" ALORS
// response = MD5(HA1:nonce:nc:cnonce:qop:HA2)
sResponseData est une chaîne = sHA1 + ":" + _sNonce + ":" + _sNonceCount + ":" + sCnonce + ":auth:" + sHA2
sResponse = Minuscule(HashVersHexa(sResponseData, hachageMD5))
SINON
// response = MD5(HA1:nonce:HA2) para qop não especificado ou outros valores
sResponseData est une chaîne = sHA1 + ":" + _sNonce + ":" + sHA2
sResponse = Minuscule(HashVersHexa(sResponseData, hachageMD5))
FIN

// Construir cabeçalho de autorização
sHeader est une chaîne = "Digest username=\"" + _sUsername + "\", "
sHeader += "realm=\"" + _sRealm + "\", "
sHeader += "nonce=\"" + _sNonce + "\", "
sHeader += "uri=\"" + _sPath + "\", "

SI _sQop <> "" ALORS
// Para qop="auth"
SI _sQop = "auth" OU _sQop [= "auth" ALORS
sHeader += "qop=auth, "
sHeader += "nc=" + _sNonceCount + ", "
sHeader += "cnonce=\"" + sCnonce + "\", "
FIN
FIN

sHeader += "response=\"" + sResponse + "\", "
sHeader += "algorithm=\"" + _sAlgorithm + "\""

// Adicionar opaque se disponível
SI _sOpaque <> "" ALORS
sHeader += ", opaque=\"" + _sOpaque + "\""
FIN

RENVOYER sHeader
FIN

PRIVE
FONCTION ExtractPathFromURL(sURL est une chaîne) : chaîne
// Remover protocolo (http:// ou https://)
nStartPos est un entier
SI Gauche(sURL, 7) = "http://" ALORS
nStartPos = 8
SINON SI Gauche(sURL, 8) = "https://" ALORS
nStartPos = 9
SINON
nStartPos = 1
FIN

// Encontrar a primeira barra após o host
sWithoutProtocol est une chaîne = Milieu(sURL, nStartPos)
nSlashPos est un entier = Position(sWithoutProtocol, "/")

// Se não houver barra, retornar "/"
SI nSlashPos = 0 ALORS
RENVOYER "/"
FIN

// Extrair o path (incluindo query string)
sPath est une chaîne = Milieu(sWithoutProtocol, nSlashPos)

// Se o path estiver vazio, retornar "/"
SI sPath = "" ALORS
RENVOYER "/"
FIN

RENVOYER sPath
FIN

PRIVE
PROCEDURE IncrementNonceCount()
// Converter para número, incrementar e formatar de volta como string com zeros à esquerda
nCount est un entier = Val(_sNonceCount)
nCount++
_sNonceCount = Complète(NumériqueVersChaine(nCount), "0", 8, complèteDroite)
FIN

// Método para converter bytes em hexadecimal
PRIVE
FONCTION HashVersHexa(sInput est une chaîne, nAlgo est un entier) : chaîne
bufHash est un buffer = HashChaîne(nAlgo, sInput)
SI bufHash = "" ALORS
RENVOYER ""
FIN

sHexa est une chaîne = ""
POUR i = 1 À Taille(bufHash)
b est un entier = Asc(Milieu(bufHash, i, 1))
sHexa += NumériqueVersChaine(b, "x02")
FIN

RENVOYER sHexa
FIN
FIN

--
Adriano José Boller
______________________________________________
Consultor e Representante Oficial da
PcSoft no Brasil
+55 (41) 99949 1800
adrianoboller@gmail.com
skype: adrianoboller
http://wxinformatica.com.br/
Registered member
3,851 messages
Posted on April, 04 2025 - 2:32 AM
// ------------------------------------------------------------------------------------------------
// RestAuthCustom.wdc - Autenticação Personalizada
// ------------------------------------------------------------------------------------------------
// Implementa autenticação personalizada para APIs com requisitos específicos
// Autor: Claude
// Data: 03/04/2025
// ------------------------------------------------------------------------------------------------

CLASSE RestAuthCustom HÉRITE DE RestAuth

// Propriedades específicas
PRIVE
_sAuthHeaderName est une chaîne = "Authorization"
_sAuthHeaderValue est une chaîne
_tabCustomHeaders est un tableau associatif de chaînes
_tabCustomQueryParams est un tableau associatif de chaînes
_pCustomAuthFunction est une procédure = Null

// Construtores
PROCEDURE PUBLIQUE Constructeur()
Constructeur RestAuth(EAuthType.CUSTOM)
_sAuthHeaderValue = ""
FIN

PROCEDURE PUBLIQUE Constructeur(sAuthHeaderValue est une chaîne)
Constructeur RestAuth(EAuthType.CUSTOM)
_sAuthHeaderValue = sAuthHeaderValue
FIN

PROCEDURE PUBLIQUE Constructeur(sAuthHeaderName est une chaîne, sAuthHeaderValue est une chaîne)
Constructeur RestAuth(EAuthType.CUSTOM)
_sAuthHeaderName = sAuthHeaderName
_sAuthHeaderValue = sAuthHeaderValue
FIN

PROCEDURE PUBLIQUE Constructeur(sAuthHeaderValue est une chaîne, logger est un RestLogger)
Constructeur RestAuth(EAuthType.CUSTOM, logger)
_sAuthHeaderValue = sAuthHeaderValue
FIN

// Métodos públicos de configuração
PROCEDURE PUBLIQUE SetAuthHeader(sValue est une chaîne)
_sAuthHeaderValue = sValue
FIN

PROCEDURE PUBLIQUE SetAuthHeaderName(sName est une chaîne)
_sAuthHeaderName = sName
FIN

PROCEDURE PUBLIQUE AddCustomHeader(sName est une chaîne, sValue est une chaîne)
_tabCustomHeaders[sName] = sValue
FIN

PROCEDURE PUBLIQUE RemoveCustomHeader(sName est une chaîne)
SI _tabCustomHeaders[sName]..Existe ALORS
TableauSupprime(_tabCustomHeaders, sName)
FIN
FIN

PROCEDURE PUBLIQUE ClearCustomHeaders()
TableauSupprimeTout(_tabCustomHeaders)
FIN

PROCEDURE PUBLIQUE AddCustomQueryParam(sName est une chaîne, sValue est une chaîne)
_tabCustomQueryParams[sName] = sValue
FIN

PROCEDURE PUBLIQUE RemoveCustomQueryParam(sName est une chaîne)
SI _tabCustomQueryParams[sName]..Existe ALORS
TableauSupprime(_tabCustomQueryParams, sName)
FIN
FIN

PROCEDURE PUBLIQUE ClearCustomQueryParams()
TableauSupprimeTout(_tabCustomQueryParams)
FIN

// Definir uma função de autenticação personalizada
PROCEDURE PUBLIQUE SetCustomAuthFunction(pFunction est une procédure)
_pCustomAuthFunction = pFunction
FIN

// Obter valores atuais
FONCTION PUBLIQUE GetAuthHeaderValue() : chaîne
RENVOYER _sAuthHeaderValue
FIN

FONCTION PUBLIQUE GetAuthHeaderName() : chaîne
RENVOYER _sAuthHeaderName
FIN

// Sobrescrever métodos da classe base
FONCTION PUBLIQUE ApplyAuth(tabHeaders est un tableau associatif de chaînes, tabQueryParams est un tableau associatif de chaînes, sMethod est une chaîne, sURL est une chaîne, sData est une chaîne) : booléen
// Se tiver uma função de autenticação personalizada, usá-la
SI _pCustomAuthFunction <> Null ALORS
// Chamar função personalizada
_pCustomAuthFunction(_sAuthHeaderName, _sAuthHeaderValue, tabHeaders, tabQueryParams, sMethod, sURL, sData)
LogDebug("Executada função de autenticação personalizada")
RENVOYER Vrai
SINON
// Aplicar autenticação padrão
SI _sAuthHeaderValue <> "" ALORS
tabHeaders[_sAuthHeaderName] = _sAuthHeaderValue
LogDebug("Adicionado cabeçalho de autenticação personalizada: " + _sAuthHeaderName)
FIN

// Adicionar cabeçalhos personalizados
POUR TOUT pHeader DE _tabCustomHeaders
tabHeaders[pHeader..Nom] = pHeader
LogDebug("Adicionado cabeçalho personalizado: " + pHeader..Nom)
FIN

// Adicionar parâmetros de consulta personalizados
POUR TOUT pParam DE _tabCustomQueryParams
tabQueryParams[pParam..Nom] = pParam
LogDebug("Adicionado parâmetro de consulta personalizado: " + pParam..Nom)
FIN

RENVOYER Vrai
FIN
FIN

FONCTION PUBLIQUE IsAuthValid() : booléen
RENVOYER _sAuthHeaderValue <> "" OU TableauOccurrence(_tabCustomHeaders) > 0 OU TableauOccurrence(_tabCustomQueryParams) > 0 OU _pCustomAuthFunction <> Null
FIN

PROCEDURE PUBLIQUE ClearAuth()
_sAuthHeaderValue = ""
TableauSupprimeTout(_tabCustomHeaders)
TableauSupprimeTout(_tabCustomQueryParams)
_pCustomAuthFunction = Null
FIN

// Métodos específicos para autenticação personalizada

// Configuração para autenticação com assinatura personalizada
PROCEDURE PUBLIQUE SetupSignatureAuth(sKeyID est une chaîne, sSignature est une chaîne, sAlgorithm est une chaîne = "")
sValue est une chaîne = "Signature keyId=\"" + sKeyID + "\", signature=\"" + sSignature + "\""

SI sAlgorithm <> "" ALORS
sValue += ", algorithm=\"" + sAlgorithm + "\""
FIN

_sAuthHeaderValue = sValue
FIN

// Configuração para autenticação baseada em chave e timestamp
PROCEDURE PUBLIQUE SetupKeyTimestampAuth(sKey est une chaîne, sHeaderPrefix est une chaîne = "Key")
sTimestamp est une chaîne = NumériqueVersChaine(DateHeureVersEntier(DateHeureSys()))
_sAuthHeaderValue = sHeaderPrefix + " " + sKey + ":" + sTimestamp
FIN

// Configuração para autenticação personalizada baseada em cabeçalhos específicos (não Authorization)
PROCEDURE PUBLIQUE SetupMultiHeaderAuth(sApiKey est une chaîne, sKeyHeader est une chaîne, sAccessID est une chaîne = "", sAccessIDHeader est une chaîne = "")
ClearCustomHeaders()

// Configurar cabeçalho de API Key
AddCustomHeader(sKeyHeader, sApiKey)

// Configurar cabeçalho de Access ID, se fornecido
SI sAccessID <> "" ET sAccessIDHeader <> "" ALORS
AddCustomHeader(sAccessIDHeader, sAccessID)
FIN

// Configurar timestamp como cabeçalho separado, comum em APIs
AddCustomHeader("X-Timestamp", RestUtils.GenerateTimestamp())
FIN

// Método para gerar valor de autenticação com base em templates
FONCTION PUBLIQUE GenerateAuthFromTemplate(sTemplate est une chaîne, tabValues est un tableau associatif de chaînes) : chaîne
sAuthValue est une chaîne = sTemplate

// Substituir cada placeholder por seu valor correspondente
POUR TOUT pValue DE tabValues
sAuthValue = Remplace(sAuthValue, "{" + pValue..Nom + "}", pValue)
FIN

_sAuthHeaderValue = sAuthValue
RENVOYER sAuthValue
FIN
FIN

--
Adriano José Boller
______________________________________________
Consultor e Representante Oficial da
PcSoft no Brasil
+55 (41) 99949 1800
adrianoboller@gmail.com
skype: adrianoboller
http://wxinformatica.com.br/
Registered member
3,851 messages
Posted on April, 04 2025 - 2:33 AM
// ------------------------------------------------------------------------------------------------
// RestAuthHMAC.wdc - Autenticação HMAC
// ------------------------------------------------------------------------------------------------
// Implementa autenticação HMAC (Hash-based Message Authentication Code)
// Autor: Claude
// Data: 03/04/2025
// ------------------------------------------------------------------------------------------------

CLASSE RestAuthHMAC HÉRITE DE RestAuth

// Propriedades específicas
PRIVE
_sSecret est une chaîne
_sAlgorithm est une chaîne = "SHA256"
_sSignatureHeaderName est une chaîne = "X-Signature"
_sTimestampHeaderName est une chaîne = "X-Timestamp"
_sNonceHeaderName est une chaîne = "X-Nonce"
_bIncludeMethod est un booléen = Vrai
_bIncludeURL est un booléen = Vrai
_bIncludeTimestamp est un booléen = Vrai
_bIncludeNonce est un booléen = Vrai
_bIncludeData est un booléen = Vrai
_sCustomSignatureFormat est une chaîne = ""

// Construtores
PROCEDURE PUBLIQUE Constructeur()
Constructeur RestAuth(EAuthType.HMAC)
_sSecret = ""
FIN

PROCEDURE PUBLIQUE Constructeur(sSecret est une chaîne, sAlgorithm est une chaîne = "SHA256")
Constructeur RestAuth(EAuthType.HMAC)
_sSecret = sSecret
_sAlgorithm = sAlgorithm
FIN

PROCEDURE PUBLIQUE Constructeur(sSecret est une chaîne, sAlgorithm est une chaîne, logger est un RestLogger)
Constructeur RestAuth(EAuthType.HMAC, logger)
_sSecret = sSecret
_sAlgorithm = sAlgorithm
FIN

// Métodos públicos de configuração
PROCEDURE PUBLIQUE SetSecret(sSecret est une chaîne)
_sSecret = sSecret
FIN

PROCEDURE PUBLIQUE SetAlgorithm(sAlgorithm est une chaîne)
_sAlgorithm = sAlgorithm
FIN

PROCEDURE PUBLIQUE SetHeaderNames(sSignatureHeader est une chaîne, sTimestampHeader est une chaîne, sNonceHeader est une chaîne)
_sSignatureHeaderName = sSignatureHeader
_sTimestampHeaderName = sTimestampHeader
_sNonceHeaderName = sNonceHeader
FIN

PROCEDURE PUBLIQUE SetOptions(bIncludeMethod est un booléen, bIncludeURL est un booléen, bIncludeTimestamp est un booléen, bIncludeNonce est un booléen, bIncludeData est un booléen)
_bIncludeMethod = bIncludeMethod
_bIncludeURL = bIncludeURL
_bIncludeTimestamp = bIncludeTimestamp
_bIncludeNonce = bIncludeNonce
_bIncludeData = bIncludeData
FIN

PROCEDURE PUBLIQUE SetCustomSignatureFormat(sFormat est une chaîne)
_sCustomSignatureFormat = sFormat
FIN

// Sobrescrever métodos da classe base
FONCTION PUBLIQUE ApplyAuth(tabHeaders est un tableau associatif de chaînes, tabQueryParams est un tableau associatif de chaînes, sMethod est une chaîne, sURL est une chaîne, sData est une chaîne) : booléen
SI _sSecret = "" ALORS
LogError("Chave secreta HMAC não configurada")
RENVOYER Faux
FIN

// Gerar timestamp
sTimestamp est une chaîne = RestUtils.GenerateTimestamp()

// Gerar nonce
sNonce est une chaîne = RestUtils.GenerateNonce()

// Gerar a assinatura HMAC
sSignature est une chaîne = GenerateSignature(sMethod, sURL, sData, sTimestamp, sNonce)

// Adicionar os cabeçalhos necessários
SI _bIncludeTimestamp ALORS
tabHeaders[_sTimestampHeaderName] = sTimestamp
FIN

SI _bIncludeNonce ALORS
tabHeaders[_sNonceHeaderName] = sNonce
FIN

tabHeaders[_sSignatureHeaderName] = sSignature

LogDebug("Adicionados cabeçalhos de autenticação HMAC")
RENVOYER Vrai
FIN

FONCTION PUBLIQUE IsAuthValid() : booléen
RENVOYER _sSecret <> ""
FIN

PROCEDURE PUBLIQUE ClearAuth()
_sSecret = ""
FIN

// Métodos específicos para HMAC
FONCTION PUBLIQUE GenerateSignature(sMethod est une chaîne, sURL est une chaîne, sData est une chaîne, sTimestamp est une chaîne, sNonce est une chaîne) : chaîne
// Verificar se está usando formato personalizado
SI _sCustomSignatureFormat <> "" ALORS
RENVOYER GenerateCustomSignature(sMethod, sURL, sData, sTimestamp, sNonce)
FIN

// Construir a string a ser assinada
sStringToSign est une chaîne = ""

// Incluir método HTTP
SI _bIncludeMethod ALORS
sStringToSign += sMethod + "\n"
FIN

// Incluir URL
SI _bIncludeURL ALORS
sStringToSign += sURL + "\n"
FIN

// Incluir timestamp
SI _bIncludeTimestamp ALORS
sStringToSign += sTimestamp + "\n"
FIN

// Incluir nonce
SI _bIncludeNonce ALORS
sStringToSign += sNonce + "\n"
FIN

// Incluir dados
SI _bIncludeData ET sData <> "" ALORS
sStringToSign += sData
FIN

// Remover último \n se existir
SI Droite(sStringToSign, 1) = EOT ALORS
sStringToSign = Gauche(sStringToSign, Taille(sStringToSign) - 1)
FIN

LogDebug("String a ser assinada: " + sStringToSign)

// Calcular o HMAC usando o algoritmo especificado
RENVOYER RestUtils.CalculateHMAC(sStringToSign, _sSecret, _sAlgorithm)
FIN

PRIVE
FONCTION GenerateCustomSignature(sMethod est une chaîne, sURL est une chaîne, sData est une chaîne, sTimestamp est une chaîne, sNonce est une chaîne) : chaîne
// Usar o formato personalizado, substituindo marcadores
sStringToSign est une chaîne = _sCustomSignatureFormat

sStringToSign = Remplace(sStringToSign, "{method}", sMethod)
sStringToSign = Remplace(sStringToSign, "{url}", sURL)
sStringToSign = Remplace(sStringToSign, "{timestamp}", sTimestamp)
sStringToSign = Remplace(sStringToSign, "{nonce}", sNonce)
sStringToSign = Remplace(sStringToSign, "{data}", sData)

LogDebug("String a ser assinada (formato personalizado): " + sStringToSign)

// Calcular o HMAC usando o algoritmo especificado
RENVOYER RestUtils.CalculateHMAC(sStringToSign, _sSecret, _sAlgorithm)
FIN

// Método para verificar uma assinatura (útil para testes ou para implementar um servidor)
FONCTION PUBLIQUE VerifySignature(sSignature est une chaîne, sMethod est une chaîne, sURL est une chaîne, sData est une chaîne, sTimestamp est une chaîne, sNonce est une chaîne) : booléen
// Gerar assinatura com os mesmos parâmetros
sExpectedSignature est une chaîne = GenerateSignature(sMethod, sURL, sData, sTimestamp, sNonce)

// Comparar assinaturas
RENVOYER sSignature = sExpectedSignature
FIN

// Método para gerar e verificar uma assinatura em um só lugar (para testes)
FONCTION PUBLIQUE TestSignature(sMethod est une chaîne, sURL est une chaîne, sData est une chaîne) : booléen
// Gerar timestamp e nonce
sTimestamp est une chaîne = RestUtils.GenerateTimestamp()
sNonce est une chaîne = RestUtils.GenerateNonce()

// Gerar assinatura
sSignature est une chaîne = GenerateSignature(sMethod, sURL, sData, sTimestamp, sNonce)

// Verificar assinatura
bResult est un booléen = VerifySignature(sSignature, sMethod, sURL, sData, sTimestamp, sNonce)

LogDebug("Teste de assinatura HMAC: " + (bResult ? "SUCESSO" SINON "FALHA"))
RENVOYER bResult
FIN
FIN

--
Adriano José Boller
______________________________________________
Consultor e Representante Oficial da
PcSoft no Brasil
+55 (41) 99949 1800
adrianoboller@gmail.com
skype: adrianoboller
http://wxinformatica.com.br/
Registered member
3,851 messages
Posted on April, 04 2025 - 2:35 AM
// ------------------------------------------------------------------------------------------------
// RestAuthOAuth2.wdc - Autenticação OAuth 2.0
// ------------------------------------------------------------------------------------------------
// Implementa autenticação OAuth 2.0 com suporte a diferentes grant types
// Autor: Claude
// Data: 03/04/2025
// ------------------------------------------------------------------------------------------------

CLASSE RestAuthOAuth2 HÉRITE DE RestAuth

// Propriedades específicas
PRIVE
_sClientID est une chaîne
_sClientSecret est une chaîne
_sTokenEndpoint est une chaîne
_sScope est une chaîne
_sGrantType est une chaîne = "client_credentials" // Tipo padrão
_sAccessToken est une chaîne
_sRefreshToken est une chaîne
_dhTokenExpiry est une dateheure
_bHasTokenExpiry est un booléen = Faux
_bAutoRefresh est un booléen = Vrai

// Para Resource Owner Password Grant
_sUsername est une chaîne
_sPassword est une chaîne

// Para Authorization Code Grant
_sRedirectURI est une chaîne
_sAuthorizationCode est une chaîne
_sAuthorizationEndpoint est une chaîne

// Construtores
PROCEDURE PUBLIQUE Constructeur()
Constructeur RestAuth(EAuthType.OAUTH2)
FIN

PROCEDURE PUBLIQUE Constructeur(sClientID est une chaîne, sClientSecret est une chaîne, sTokenEndpoint est une chaîne)
Constructeur RestAuth(EAuthType.OAUTH2)
_sClientID = sClientID
_sClientSecret = sClientSecret
_sTokenEndpoint = sTokenEndpoint
FIN

PROCEDURE PUBLIQUE Constructeur(sClientID est une chaîne, sClientSecret est une chaîne, sTokenEndpoint est une chaîne, sScope est une chaîne)
Constructeur RestAuth(EAuthType.OAUTH2)
_sClientID = sClientID
_sClientSecret = sClientSecret
_sTokenEndpoint = sTokenEndpoint
_sScope = sScope
FIN

PROCEDURE PUBLIQUE Constructeur(sClientID est une chaîne, sClientSecret est une chaîne, sTokenEndpoint est une chaîne, sScope est une chaîne, logger est un RestLogger)
Constructeur RestAuth(EAuthType.OAUTH2, logger)
_sClientID = sClientID
_sClientSecret = sClientSecret
_sTokenEndpoint = sTokenEndpoint
_sScope = sScope
FIN

// Métodos de configuração básica
PROCEDURE PUBLIQUE SetClientCredentials(sClientID est une chaîne, sClientSecret est une chaîne)
_sClientID = sClientID
_sClientSecret = sClientSecret
FIN

PROCEDURE PUBLIQUE SetEndpoints(sTokenEndpoint est une chaîne, sAuthorizationEndpoint est une chaîne = "")
_sTokenEndpoint = sTokenEndpoint
_sAuthorizationEndpoint = sAuthorizationEndpoint
FIN

PROCEDURE PUBLIQUE SetScope(sScope est une chaîne)
_sScope = sScope
FIN

PROCEDURE PUBLIQUE SetAutoRefresh(bAutoRefresh est un booléen)
_bAutoRefresh = bAutoRefresh
FIN

// Configuração para diferentes Grant Types

// Client Credentials Grant
PROCEDURE PUBLIQUE SetupClientCredentialsGrant()
_sGrantType = "client_credentials"
_sUsername = ""
_sPassword = ""
_sRedirectURI = ""
_sAuthorizationCode = ""
FIN

// Resource Owner Password Grant
PROCEDURE PUBLIQUE SetupPasswordGrant(sUsername est une chaîne, sPassword est une chaîne)
_sGrantType = "password"
_sUsername = sUsername
_sPassword = sPassword
_sRedirectURI = ""
_sAuthorizationCode = ""
FIN

// Authorization Code Grant
PROCEDURE PUBLIQUE SetupAuthorizationCodeGrant(sRedirectURI est une chaîne)
_sGrantType = "authorization_code"
_sUsername = ""
_sPassword = ""
_sRedirectURI = sRedirectURI
_sAuthorizationCode = ""
FIN

PROCEDURE PUBLIQUE SetAuthorizationCode(sAuthorizationCode est une chaîne)
_sAuthorizationCode = sAuthorizationCode
FIN

// Refresh Token Grant
PROCEDURE PUBLIQUE SetupRefreshTokenGrant(sRefreshToken est une chaîne)
_sGrantType = "refresh_token"
_sRefreshToken = sRefreshToken
FIN

// Métodos para gerenciar tokens
PROCEDURE PUBLIQUE SetTokens(sAccessToken est une chaîne, sRefreshToken est une chaîne = "")
_sAccessToken = sAccessToken
_sRefreshToken = sRefreshToken
_bHasTokenExpiry = Faux
FIN

PROCEDURE PUBLIQUE SetTokensWithExpiry(sAccessToken est une chaîne, sRefreshToken est une chaîne, dhExpiry est une dateheure)
_sAccessToken = sAccessToken
_sRefreshToken = sRefreshToken
_dhTokenExpiry = dhExpiry
_bHasTokenExpiry = Vrai
FIN

PROCEDURE PUBLIQUE SetTokensWithExpirySeconds(sAccessToken est une chaîne, sRefreshToken est une chaîne, nExpirySeconds est un entier)
_sAccessToken = sAccessToken
_sRefreshToken = sRefreshToken
_dhTokenExpiry = DateHeureSys() + nExpirySeconds
_bHasTokenExpiry = Vrai
FIN

// Getters para informações do token
FONCTION PUBLIQUE GetAccessToken() : chaîne
RENVOYER _sAccessToken
FIN

FONCTION PUBLIQUE GetRefreshToken() : chaîne
RENVOYER _sRefreshToken
FIN

FONCTION PUBLIQUE GetTokenExpiry() : dateheure
RENVOYER _dhTokenExpiry
FIN

FONCTION PUBLIQUE HasTokenExpiry() : booléen
RENVOYER _bHasTokenExpiry
FIN

// Sobrescrever métodos da classe base
FONCTION PUBLIQUE ApplyAuth(tabHeaders est un tableau associatif de chaînes, tabQueryParams est un tableau associatif de chaînes, sMethod est une chaîne, sURL est une chaîne, sData est une chaîne) : booléen
// Verificar se o token está expirado e se auto-refresh está habilitado
SI _bAutoRefresh ET PAS IsAuthValid() ALORS
SI RefreshAuth() = Faux ALORS
LogError("Falha ao renovar token OAuth2")
RENVOYER Faux
FIN
FIN

// Verificar se há token disponível
SI _sAccessToken = "" ALORS
LogError("Token OAuth2 não configurado")
RENVOYER Faux
FIN

// Adicionar cabeçalho de autorização
tabHeaders["Authorization"] = "Bearer " + _sAccessToken

LogDebug("Adicionado cabeçalho de autenticação OAuth2 (Bearer)")
RENVOYER Vrai
FIN

FONCTION PUBLIQUE IsAuthValid() : booléen
SI _sAccessToken = "" ALORS
RENVOYER Faux
FIN

// Verificar expiração
SI _bHasTokenExpiry ET DateHeureSys() >= _dhTokenExpiry ALORS
LogDebug("Token OAuth2 expirado")
RENVOYER Faux
FIN

RENVOYER Vrai
FIN

FONCTION PUBLIQUE RefreshAuth() : booléen
// Verificar configuração básica
SI _sClientID = "" OU _sClientSecret = "" OU _sTokenEndpoint = "" ALORS
LogError("Configuração OAuth2 incompleta")
RENVOYER Faux
FIN

// Determinar qual grant type usar para refresh
sCurrentGrantType est une chaîne = _sGrantType

// Se temos refresh token, usar refresh_token grant
SI _sRefreshToken <> "" ALORS
sCurrentGrantType = "refresh_token"
FIN

// Configurar requisição HTTP para obter token
reqHttp est un restRequête
reqHttp.URL = _sTokenEndpoint
reqHttp.Méthode = httpPost
reqHttp.ContentType = "application/x-www-form-urlencoded"

// Parâmetros comuns
reqHttp.Paramètre["grant_type"] = sCurrentGrantType
reqHttp.Paramètre["client_id"] = _sClientID
reqHttp.Paramètre["client_secret"] = _sClientSecret

SI _sScope <> "" ALORS
reqHttp.Paramètre["scope"] = _sScope
FIN

// Parâmetros específicos por grant type
SELON sCurrentGrantType
CAS "refresh_token":
reqHttp.Paramètre["refresh_token"] = _sRefreshToken

CAS "password":
reqHttp.Paramètre["username"] = _sUsername
reqHttp.Paramètre["password"] = _sPassword

CAS "authorization_code":
reqHttp.Paramètre["code"] = _sAuthorizationCode
reqHttp.Paramètre["redirect_uri"] = _sRedirectURI
FIN

// Executar requisição HTTP
SI httpEnvoie(reqHttp) = Faux ALORS
LogError("Falha na requisição para obter token OAuth2: " + ErreurInfo(errMessage))
RENVOYER Faux
FIN

// Verificar resposta
SI reqHttp.CodeEtat < 200 OU reqHttp.CodeEtat >= 300 ALORS
LogError("Erro HTTP ao obter token OAuth2: " + NumériqueVersChaine(reqHttp.CodeEtat) + " - " + reqHttp.Réponse)
RENVOYER Faux
FIN

// Processar a resposta em JSON
tokenResponse est un JSON
QUAND EXCEPTION DANS
tokenResponse = JSONVersVariant(reqHttp.Réponse)
FAIRE
LogError("Resposta inválida do servidor OAuth2: " + reqHttp.Réponse)
RENVOYER Faux
FIN

// Verificar e processar token
SI tokenResponse.access_token = Null OU tokenResponse.access_token = "" ALORS
LogError("Resposta OAuth2 não contém access_token")
RENVOYER Faux
FIN

// Salvar o access token
_sAccessToken = tokenResponse.access_token

// Verificar se há refresh token
SI tokenResponse.refresh_token <> Null ET tokenResponse.refresh_token <> "" ALORS
_sRefreshToken = tokenResponse.refresh_token
FIN

// Verificar se há informação de expiração
SI tokenResponse.expires_in <> Null ET tokenResponse.expires_in > 0 ALORS
nExpiresIn est un entier = tokenResponse.expires_in
_dhTokenExpiry = DateHeureSys() + nExpiresIn
_bHasTokenExpiry = Vrai
FIN

LogDebug("Token OAuth2 obtido/renovado com sucesso")
RENVOYER Vrai
FIN

PROCEDURE PUBLIQUE ClearAuth()
_sAccessToken = ""
_sRefreshToken = ""
_bHasTokenExpiry = Faux
FIN

// Métodos adicionais específicos para OAuth 2.0

// Gera a URL de autorização para Authorization Code Grant
FONCTION PUBLIQUE GetAuthorizationURL(sState est une chaîne = "") : chaîne
SI _sAuthorizationEndpoint = "" OU _sClientID = "" OU _sRedirectURI = "" ALORS
LogError("Configuração incompleta para URL de autorização")
RENVOYER ""
FIN

sURL est une chaîne = _sAuthorizationEndpoint
sURL += "?response_type=code"
sURL += "&client_id=" + RestUtils.URLEncode(_sClientID)
sURL += "&redirect_uri=" + RestUtils.URLEncode(_sRedirectURI)

SI _sScope <> "" ALORS
sURL += "&scope=" + RestUtils.URLEncode(_sScope)
FIN

SI sState <> "" ALORS
sURL += "&state=" + RestUtils.URLEncode(sState)
FIN

RENVOYER sURL
FIN

// Extrai o authorization code de uma URL de redirecionamento (para Authorization Code Grant)
FONCTION PUBLIQUE ExtractAuthorizationCode(sRedirectURL est une chaîne) : chaîne
// Procurar o parâmetro 'code' na URL
sCode est une chaîne = ExtraitParametre(sRedirectURL, "code")

SI sCode <> "" ALORS
_sAuthorizationCode = sCode
FIN

RENVOYER sCode
FIN

// Função para extrair parâmetro de uma URL
PRIVE
FONCTION ExtraitParametre(sURL est une chaîne, sParamName est une chaîne) : chaîne
// Encontrar o parâmetro na query string
nPos est un entier = Position(sURL, "?" + sParamName + "=")
SI nPos = 0 ALORS
nPos = Position(sURL, "&" + sParamName + "=")
FIN

SI nPos = 0 ALORS
RENVOYER ""
FIN

// Encontrar o valor do parâmetro
sQueryPart est une chaîne = Milieu(sURL, nPos + Taille(sParamName) + 2)

// Verificar se há mais parâmetros depois
nEndPos est un entier = Position(sQueryPart, "&")
SI nEndPos > 0 ALORS
RENVOYER Gauche(sQueryPart, nEndPos - 1)
SINON
RENVOYER sQueryPart
FIN
FIN

// Configura a autenticação OAuth 2.0 a partir de um token já existente (por exemplo, armazenado em cache)
PROCEDURE PUBLIQUE SetupFromExistingToken(sAccessToken est une chaîne, sRefreshToken est une chaîne = "", nExpirySeconds est un entier = 0)
_sAccessToken = sAccessToken
_sRefreshToken = sRefreshToken

SI nExpirySeconds > 0 ALORS
_dhTokenExpiry = DateHeureSys() + nExpirySeconds
_bHasTokenExpiry = Vrai
SINON
_bHasTokenExpiry = Faux
FIN

LogDebug("OAuth2 configurado a partir de token existente")
FIN

// Método para validar um token localmente (verificação básica)
FONCTION PUBLIQUE ValidateTokenFormat() : booléen
SI _sAccessToken = "" ALORS
RENVOYER Faux
FIN

// Verificação básica para token JWT
SI Gauche(_sAccessToken, 3) = "ey" ALORS
// Parece ser um token JWT, verificar formato
tabParts est un tableau de chaînes = ExtraitChaîne(_sAccessToken, ".", 1, 3)

// JWT válido deve ter 3 partes separadas por ponto
SI TableauOccurrence(tabParts) <> 3 ALORS
LogDebug("Token não possui formato JWT válido (deve ter três partes separadas por ponto)")
RENVOYER Faux
FIN

// Tentar decodificar o payload (segunda parte)
QUAND EXCEPTION DANS
sPayload est une chaîne = Decode(tabParts[2], encodeBASE64)

// Verificar se é um JSON válido
payloadJSON est un JSON = JSONVersVariant(sPayload)

// Se tiver expiração, atualizar o tempo de expiração
SI payloadJSON.exp <> Null ET TypeVar(payloadJSON.exp) = typNumérique ALORS
_dhTokenExpiry = DateHeureVersDateHeure(payloadJSON.exp)
_bHasTokenExpiry = Vrai
LogDebug("Expiração do token extraída do payload JWT")
FIN

RENVOYER Vrai
FAIRE
LogDebug("Payload do token não pode ser decodificado como um JWT válido")
RENVOYER Faux
FIN
FIN

// Se não for JWT, considerar válido se não estiver vazio
RENVOYER Vrai
FIN

// Método para validar o token contra o servidor (introspection endpoint)
FONCTION PUBLIQUE ValidateTokenWithServer(sIntrospectionEndpoint est une chaîne) : booléen
SI _sAccessToken = "" OU sIntrospectionEndpoint = "" ALORS
RENVOYER Faux
FIN

// Configurar requisição para introspection endpoint
reqHttp est un restRequête
reqHttp.URL = sIntrospectionEndpoint
reqHttp.Méthode = httpPost
reqHttp.ContentType = "application/x-www-form-urlencoded"

// Adicionar autenticação do cliente
reqHttp.Entête["Authorization"] = "Basic " + Encode(_sClientID + ":" + _sClientSecret, encodeBASE64)

// Parâmetros para introspection
reqHttp.Paramètre["token"] = _sAccessToken
reqHttp.Paramètre["token_type_hint"] = "access_token"

// Executar requisição
SI httpEnvoie(reqHttp) = Faux ALORS
LogError("Falha na requisição para validar token: " + ErreurInfo(errMessage))
RENVOYER Faux
FIN

// Verificar resposta
SI reqHttp.CodeEtat < 200 OU reqHttp.CodeEtat >= 300 ALORS
LogError("Erro HTTP ao validar token: " + NumériqueVersChaine(reqHttp.CodeEtat))
RENVOYER Faux
FIN

// Processar a resposta
introspectionResponse est un JSON
QUAND EXCEPTION DANS
introspectionResponse = JSONVersVariant(reqHttp.Réponse)
FAIRE
LogError("Resposta inválida do servidor de introspection: " + reqHttp.Réponse)
RENVOYER Faux
FIN

// Verificar se o token está ativo
SI introspectionResponse.active <> Null ET introspectionResponse.active = Faux ALORS
LogDebug("Token reportado como inativo pelo servidor")
RENVOYER Faux
FIN

// Atualizar informações de expiração, se disponíveis
SI introspectionResponse.exp <> Null ET TypeVar(introspectionResponse.exp) = typNumérique ALORS
_dhTokenExpiry = DateHeureVersDateHeure(introspectionResponse.exp)
_bHasTokenExpiry = Vrai
FIN

LogDebug("Token validado com sucesso pelo servidor")
RENVOYER Vrai
FIN

// Método para revogar um token (se o servidor suportar)
FONCTION PUBLIQUE RevokeToken(sRevocationEndpoint est une chaîne, bRevokeRefreshToken est un booléen = Faux) : booléen
// Verificar parâmetros
SI (bRevokeRefreshToken ET _sRefreshToken = "") OU (PAS bRevokeRefreshToken ET _sAccessToken = "") OU sRevocationEndpoint = "" ALORS
RENVOYER Faux
FIN

// Configurar requisição para revocation endpoint
reqHttp est un restRequête
reqHttp.URL = sRevocationEndpoint
reqHttp.Méthode = httpPost
reqHttp.ContentType = "application/x-www-form-urlencoded"

// Adicionar autenticação do cliente
reqHttp.Entête["Authorization"] = "Basic " + Encode(_sClientID + ":" + _sClientSecret, encodeBASE64)

// Determinar qual token revogar
SI bRevokeRefreshToken ALORS
reqHttp.Paramètre["token"] = _sRefreshToken
reqHttp.Paramètre["token_type_hint"] = "refresh_token"
SINON
reqHttp.Paramètre["token"] = _sAccessToken
reqHttp.Paramètre["token_type_hint"] = "access_token"
FIN

// Executar requisição
SI httpEnvoie(reqHttp) = Faux ALORS
LogError("Falha na requisição para revogar token: " + ErreurInfo(errMessage))
RENVOYER Faux
FIN

// Verificar resposta - muitos servidores retornam 200 OK sem conteúdo
SI reqHttp.CodeEtat < 200 OU reqHttp.CodeEtat >= 300 ALORS
LogError("Erro HTTP ao revogar token: " + NumériqueVersChaine(reqHttp.CodeEtat))
RENVOYER Faux
FIN

// Se chegou aqui, o token foi revogado com sucesso
SI bRevokeRefreshToken ALORS
_sRefreshToken = ""
LogDebug("Refresh token revogado com sucesso")
SINON
_sAccessToken = ""
LogDebug("Access token revogado com sucesso")
FIN

RENVOYER Vrai
FIN
FIN

--
Adriano José Boller
______________________________________________
Consultor e Representante Oficial da
PcSoft no Brasil
+55 (41) 99949 1800
adrianoboller@gmail.com
skype: adrianoboller
http://wxinformatica.com.br/
Registered member
3,851 messages
Posted on April, 04 2025 - 2:38 AM
// ------------------------------------------------------------------------------------------------
// RestAuthApiKey.wdc - Autenticação API Key
// ------------------------------------------------------------------------------------------------
// Implementa autenticação com API Key (no header ou query param)
// Autor: Claude
// Data: 03/04/2025
// ------------------------------------------------------------------------------------------------

CLASSE RestAuthApiKey HÉRITE DE RestAuth

// Propriedades específicas
PRIVE
_sApiKey est une chaîne
_sApiKeyName est une chaîne
_bInHeader est un booléen = Vrai

// Construtores
PROCEDURE PUBLIQUE Constructeur()
Constructeur RestAuth(EAuthType.API_KEY)
_sApiKey = ""
_sApiKeyName = "X-API-Key" // Nome padrão
FIN

PROCEDURE PUBLIQUE Constructeur(sApiKey est une chaîne, sApiKeyName est une chaîne, bInHeader est un booléen = Vrai)
Constructeur RestAuth(EAuthType.API_KEY)
_sApiKey = sApiKey
_sApiKeyName = sApiKeyName
_bInHeader = bInHeader
FIN

PROCEDURE PUBLIQUE Constructeur(sApiKey est une chaîne, sApiKeyName est une chaîne, bInHeader est un booléen, logger est un RestLogger)
Constructeur RestAuth(EAuthType.API_KEY, logger)
_sApiKey = sApiKey
_sApiKeyName = sApiKeyName
_bInHeader = bInHeader
FIN

// Métodos públicos de configuração
PROCEDURE PUBLIQUE SetApiKey(sApiKey est une chaîne)
_sApiKey = sApiKey
FIN

PROCEDURE PUBLIQUE SetApiKeyName(sApiKeyName est une chaîne)
_sApiKeyName = sApiKeyName
FIN

PROCEDURE PUBLIQUE SetInHeader(bInHeader est un booléen)
_bInHeader = bInHeader
FIN

FONCTION PUBLIQUE GetApiKey() : chaîne
RENVOYER _sApiKey
FIN

FONCTION PUBLIQUE GetApiKeyName() : chaîne
RENVOYER _sApiKeyName
FIN

FONCTION PUBLIQUE IsInHeader() : booléen
RENVOYER _bInHeader
FIN

// Sobrescrever métodos da classe base
FONCTION PUBLIQUE ApplyAuth(tabHeaders est un tableau associatif de chaînes, tabQueryParams est un tableau associatif de chaînes, sMethod est une chaîne, sURL est une chaîne, sData est une chaîne) : booléen
SI _sApiKey = "" OU _sApiKeyName = "" ALORS
LogError("API Key ou nome da API Key não configurado")
RENVOYER Faux
FIN

// Adicionar a API Key ao cabeçalho ou parâmetro de consulta
SI _bInHeader ALORS
tabHeaders[_sApiKeyName] = _sApiKey
LogDebug("API Key adicionada ao cabeçalho: " + _sApiKeyName)
SINON
tabQueryParams[_sApiKeyName] = _sApiKey
LogDebug("API Key adicionada ao parâmetro de consulta: " + _sApiKeyName)
FIN

RENVOYER Vrai
FIN

FONCTION PUBLIQUE IsAuthValid() : booléen
RENVOYER _sApiKey <> "" ET _sApiKeyName <> ""
FIN

PROCEDURE PUBLIQUE ClearAuth()
_sApiKey = ""
FIN

// Métodos adicionais específicos para API Key

// Método para alternar a posição da API Key entre header e query param
PROCEDURE PUBLIQUE TogglePosition()
_bInHeader = PAS _bInHeader
LogDebug("Posição da API Key alternada para " + (_bInHeader ? "cabeçalho" SINON "parâmetro de consulta"))
FIN

// Método para gerar uma API Key aleatória (para testes ou gerenciamento de chaves)
FONCTION STATIQUE GenerateRandomApiKey(nLength est un entier = 32) : chaîne
RENVOYER ExtraitChaîne(Encode(ChaîneAlexandrie(nLength), encodeBASE64), "=", 1)
FIN

// Obter a configuração completa como string (para debug/log)
FONCTION PUBLIQUE GetConfigAsString() : chaîne
RENVOYER "API Key '" + _sApiKeyName + "' " +
(_bInHeader ? "no cabeçalho" SINON "no parâmetro de consulta") +
", Chave: " + (_sApiKey = "" ? "<não definida>" SINON Gauche(_sApiKey, 3) + "..." + Droite(_sApiKey, 3))
FIN
FIN

--
Adriano José Boller
______________________________________________
Consultor e Representante Oficial da
PcSoft no Brasil
+55 (41) 99949 1800
adrianoboller@gmail.com
skype: adrianoboller
http://wxinformatica.com.br/
Registered member
3,851 messages
Posted on April, 04 2025 - 2:41 AM
// ------------------------------------------------------------------------------------------------
// RestAuthBearer.wdc - Autenticação Bearer Token
// ------------------------------------------------------------------------------------------------
// Implementa autenticação com Bearer Token (JWT)
// Autor: Claude
// Data: 03/04/2025
// ------------------------------------------------------------------------------------------------

CLASSE RestAuthBearer HÉRITE DE RestAuth

// Propriedades específicas
PRIVE
_sToken est une chaîne
_dhExpiry est une dateheure
_bHasExpiry est un booléen = Faux

// Construtores
PROCEDURE PUBLIQUE Constructeur()
Constructeur RestAuth(EAuthType.BEARER)
_sToken = ""
FIN

PROCEDURE PUBLIQUE Constructeur(sToken est une chaîne)
Constructeur RestAuth(EAuthType.BEARER)
_sToken = sToken
FIN

PROCEDURE PUBLIQUE Constructeur(sToken est une chaîne, logger est un RestLogger)
Constructeur RestAuth(EAuthType.BEARER, logger)
_sToken = sToken
FIN

// Métodos públicos de configuração
PROCEDURE PUBLIQUE SetToken(sToken est une chaîne)
_sToken = sToken
_bHasExpiry = Faux // Reset expiry info
FIN

PROCEDURE PUBLIQUE SetTokenWithExpiry(sToken est une chaîne, dhExpiry est une dateheure)
_sToken = sToken
_dhExpiry = dhExpiry
_bHasExpiry = Vrai
FIN

PROCEDURE PUBLIQUE SetTokenWithExpirySeconds(sToken est une chaîne, nExpirySeconds est un entier)
_sToken = sToken
_dhExpiry = DateHeureSys() + nExpirySeconds
_bHasExpiry = Vrai
FIN

FONCTION PUBLIQUE GetToken() : chaîne
RENVOYER _sToken
FIN

FONCTION PUBLIQUE HasExpiry() : booléen
RENVOYER _bHasExpiry
FIN

FONCTION PUBLIQUE GetExpiry() : dateheure
RENVOYER _dhExpiry
FIN

// Sobrescrever métodos da classe base
FONCTION PUBLIQUE ApplyAuth(tabHeaders est un tableau associatif de chaînes, tabQueryParams est un tableau associatif de chaînes, sMethod est une chaîne, sURL est une chaîne, sData est une chaîne) : booléen
SI _sToken = "" ALORS
LogError("Token Bearer não configurado")
RENVOYER Faux
FIN

// Adicionar cabeçalho de autorização
tabHeaders["Authorization"] = "Bearer " + _sToken

LogDebug("Adicionado cabeçalho de autenticação Bearer")
RENVOYER Vrai
FIN

FONCTION PUBLIQUE IsAuthValid() : booléen
SI _sToken = "" ALORS
RENVOYER Faux
FIN

// Verificar expiração se aplicável
SI _bHasExpiry ALORS
SI DateHeureSys() >= _dhExpiry ALORS
LogDebug("Token Bearer expirado")
RENVOYER Faux
FIN
FIN

RENVOYER Vrai
FIN

PROCEDURE PUBLIQUE ClearAuth()
_sToken = ""
_bHasExpiry = Faux
FIN

// Método para verificar se o token é um JWT válido (estrutura básica)
FONCTION PUBLIQUE IsValidJWTFormat() : booléen
SI _sToken = "" ALORS
RENVOYER Faux
FIN

// Verificação básica de formato JWT (header.payload.signature)
tabParts est un tableau de chaînes = ExtraitChaîne(_sToken, ".", 1, 3)

// JWT válido deve ter 3 partes separadas por ponto
SI TableauOccurrence(tabParts) <> 3 ALORS
LogDebug("Token não possui formato JWT válido (deve ter três partes separadas por ponto)")
RENVOYER Faux
FIN

// Verificar se cada parte pode ser decodificada em Base64
POUR i = 1 À 3
QUAND EXCEPTION DANS
sTemp est une chaîne = Decode(tabParts[i], encodeBASE64)
FAIRE
LogDebug("Parte " + NumériqueVersChaine(i) + " do token não é válida Base64")
RENVOYER Faux
FIN
FIN

// Tentar decodificar o payload (segunda parte)
jwtPayload est une chaîne = Decode(tabParts[2], encodeBASE64)

// Verificar se o payload é um JSON válido
QUAND EXCEPTION DANS
payloadJSON est un JSON = JSONVersVariant(jwtPayload)

// Verificar se há claim de expiração e atualizar se necessário
SI payloadJSON.exp <> Null ET _bHasExpiry = Faux ALORS
nExpTimestamp est un entier = payloadJSON.exp
_dhExpiry = DateHeureVersDateHeure(nExpTimestamp)
_bHasExpiry = Vrai
LogDebug("Expiração do token atualizada do payload JWT")
FIN

RENVOYER Vrai
FAIRE
LogDebug("Payload do token não é um JSON válido")
RENVOYER Faux
FIN
FIN
FIN

--
Adriano José Boller
______________________________________________
Consultor e Representante Oficial da
PcSoft no Brasil
+55 (41) 99949 1800
adrianoboller@gmail.com
skype: adrianoboller
http://wxinformatica.com.br/
Registered member
3,851 messages
Posted on April, 04 2025 - 2:43 AM
// ------------------------------------------------------------------------------------------------
// RestLogger.wdc - Classe de Logger
// ------------------------------------------------------------------------------------------------
// Implementa um sistema de log para a biblioteca RestAPI
// Autor: Claude
// Data: 03/04/2025
// ------------------------------------------------------------------------------------------------

CLASSE RestLogger

// Propriedades
PRIVE
_bEnabled est un booléen = Faux
_nLogLevel est un ELogLevel = ELogLevel.INFO
_sLogPath est une chaîne = fRepDonnées() + [fSep] + "logs"
_sLogFilePrefix est une chaîne = "restapi_"
_bConsoleOutput est un booléen = Faux
_bFileOutput est un booléen = Vrai

// Construtores
PROCEDURE PUBLIQUE Constructeur()
// Construtor padrão
FIN

PROCEDURE PUBLIQUE Constructeur(bEnabled est un booléen)
_bEnabled = bEnabled
// Criar diretório de logs se não existir
EnsureLogDirectory()
FIN

PROCEDURE PUBLIQUE Constructeur(bEnabled est un booléen, nLogLevel est un ELogLevel)
_bEnabled = bEnabled
_nLogLevel = nLogLevel
// Criar diretório de logs se não existir
EnsureLogDirectory()
FIN

PROCEDURE PUBLIQUE Constructeur(bEnabled est un booléen, nLogLevel est un ELogLevel, sLogPath est une chaîne)
_bEnabled = bEnabled
_nLogLevel = nLogLevel
_sLogPath = sLogPath
// Criar diretório de logs se não existir
EnsureLogDirectory()
FIN

// Métodos de configuração
PROCEDURE PUBLIQUE SetEnabled(bEnabled est un booléen)
_bEnabled = bEnabled
FIN

PROCEDURE PUBLIQUE SetLogLevel(nLogLevel est un ELogLevel)
_nLogLevel = nLogLevel
FIN

PROCEDURE PUBLIQUE SetLogPath(sLogPath est une chaîne)
_sLogPath = sLogPath
EnsureLogDirectory()
FIN

PROCEDURE PUBLIQUE SetLogFilePrefix(sPrefix est une chaîne)
_sLogFilePrefix = sPrefix
FIN

PROCEDURE PUBLIQUE SetConsoleOutput(bEnable est un booléen)
_bConsoleOutput = bEnable
FIN

PROCEDURE PUBLIQUE SetFileOutput(bEnable est un booléen)
_bFileOutput = bEnable
FIN

// Métodos de logging para cada nível
PROCEDURE PUBLIQUE Debug(sMessage est une chaîne)
SI _bEnabled ET _nLogLevel <= ELogLevel.DEBUG ALORS
WriteLog(sMessage, ELogLevel.DEBUG)
FIN
FIN

PROCEDURE PUBLIQUE Info(sMessage est une chaîne)
SI _bEnabled ET _nLogLevel <= ELogLevel.INFO ALORS
WriteLog(sMessage, ELogLevel.INFO)
FIN
FIN

PROCEDURE PUBLIQUE Warning(sMessage est une chaîne)
SI _bEnabled ET _nLogLevel <= ELogLevel.WARNING ALORS
WriteLog(sMessage, ELogLevel.WARNING)
FIN
FIN

PROCEDURE PUBLIQUE Error(sMessage est une chaîne)
SI _bEnabled ET _nLogLevel <= ELogLevel.ERROR ALORS
WriteLog(sMessage, ELogLevel.ERROR)
FIN
FIN

// Métodos para logging de requisições e respostas HTTP
PROCEDURE PUBLIQUE LogRequest(sMethod est une chaîne, sURL est une chaîne, tabHeaders est un tableau associatif de chaînes, sData est une chaîne)
SI PAS _bEnabled OU _nLogLevel > ELogLevel.DEBUG ALORS
RETOUR
FIN

sLogMessage est une chaîne = "REQUEST: " + sMethod + " " + sURL + EOT

// Adicionar headers
sLogMessage += "HEADERS:" + EOT
POUR TOUT pHeader DE tabHeaders
sLogMessage += " " + pHeader..Nom + ": " + pHeader + EOT
FIN

// Adicionar dados
SI sData <> "" ALORS
sLogMessage += "DATA:" + EOT + sData
FIN

Debug(sLogMessage)
FIN

PROCEDURE PUBLIQUE LogResponse(nStatusCode est un entier, sResponseBody est une chaîne, tabHeaders est un tableau associatif de chaînes)
SI PAS _bEnabled OU _nLogLevel > ELogLevel.DEBUG ALORS
RETOUR
FIN

sLogMessage est une chaîne = "RESPONSE [" + NumériqueVersChaine(nStatusCode) + "]" + EOT

// Adicionar headers
sLogMessage += "HEADERS:" + EOT
POUR TOUT pHeader DE tabHeaders
sLogMessage += " " + pHeader..Nom + ": " + pHeader + EOT
FIN

// Adicionar corpo da resposta
SI sResponseBody <> "" ALORS
SI Taille(sResponseBody) > 2000 ALORS
// Truncar resposta muito grande
sLogMessage += "BODY: (truncado a 2000 chars)" + EOT + Gauche(sResponseBody, 2000) + "..."
SINON
sLogMessage += "BODY:" + EOT + sResponseBody
FIN
FIN

Debug(sLogMessage)
FIN

PROCEDURE PUBLIQUE LogError(sErrorMessage est une chaîne, sAdditionalInfo est une chaîne = "")
SI PAS _bEnabled OU _nLogLevel > ELogLevel.ERROR ALORS
RETOUR
FIN

sLogMessage est une chaîne = "ERROR: " + sErrorMessage

SI sAdditionalInfo <> "" ALORS
sLogMessage += EOT + "ADDITIONAL INFO: " + sAdditionalInfo
FIN

Error(sLogMessage)
FIN

// Métodos privados
PRIVE
PROCEDURE WriteLog(sMessage est une chaîne, nLevel est un ELogLevel)
// Obter data e hora atual formatada
dhNow est une dateheure = DateHeureSys()
sDateTime est une chaîne = DateHeureVersChaîne(dhNow, "yyyy-MM-dd HH:mm:ss.fff")

// Obter label do nível de log
sLevelLabel est une chaîne = GetLogLevelLabel(nLevel)

// Montar mensagem completa
sFullMessage est une chaîne = "[" + sDateTime + "] [" + sLevelLabel + "] " + sMessage

// Saída para console se habilitado
SI _bConsoleOutput ALORS
Trace(sFullMessage)
FIN

// Saída para arquivo se habilitado
SI _bFileOutput ALORS
// Obter nome do arquivo de log para o dia atual
sLogFileName est une chaîne = _sLogPath + [fSep] + _sLogFilePrefix + DateVersChaîne(dhNow, "yyyyMMdd") + ".log"

// Escrever no arquivo de log
fAjouteLigne(sLogFileName, sFullMessage)
FIN
FIN

PRIVE
PROCEDURE EnsureLogDirectory()
// Verificar se o diretório de logs existe, senão criar
SI _bEnabled ET _bFileOutput ET PAS fRepertoireExiste(_sLogPath) ALORS
fRepCrée(_sLogPath)
FIN
FIN

PRIVE
FONCTION GetLogLevelLabel(nLevel est un ELogLevel) : chaîne
SELON nLevel
CAS ELogLevel.DEBUG:
RENVOYER "DEBUG"
CAS ELogLevel.INFO:
RENVOYER "INFO"
CAS ELogLevel.WARNING:
RENVOYER "WARN"
CAS ELogLevel.ERROR:
RENVOYER "ERROR"
AUTRE CAS:
RENVOYER "UNKNOWN"
FIN
FIN
FIN

--
Adriano José Boller
______________________________________________
Consultor e Representante Oficial da
PcSoft no Brasil
+55 (41) 99949 1800
adrianoboller@gmail.com
skype: adrianoboller
http://wxinformatica.com.br/
Registered member
3,851 messages
Posted on April, 04 2025 - 2:45 AM
// ------------------------------------------------------------------------------------------------
// RestAuth.wdc - Classe Base de Autenticação
// ------------------------------------------------------------------------------------------------
// Classe abstrata que define a interface para todos os métodos de autenticação
// Autor: Claude
// Data: 03/04/2025
// ------------------------------------------------------------------------------------------------

CLASSE RestAuth

// Propriedades base
PROTÉGÉ
_nAuthType est un EAuthType
_logger est un RestLogger

// Construtor
PROCEDURE PUBLIQUE Constructeur()
_nAuthType = EAuthType.NONE
_logger = Null
FIN

PROCEDURE PUBLIQUE Constructeur(nAuthType est un EAuthType)
_nAuthType = nAuthType
_logger = Null
FIN

PROCEDURE PUBLIQUE Constructeur(nAuthType est un EAuthType, logger est un RestLogger)
_nAuthType = nAuthType
_logger = logger
FIN

// Configuração do logger
PROCEDURE PUBLIQUE SetLogger(logger est un RestLogger)
_logger = logger
FIN

// Método para obter o tipo de autenticação

--
Adriano José Boller
______________________________________________
Consultor e Representante Oficial da
PcSoft no Brasil
+55 (41) 99949 1800
adrianoboller@gmail.com
skype: adrianoboller
http://wxinformatica.com.br/
Registered member
3,851 messages
Posted on April, 04 2025 - 6:22 AM
Versao estavel 7.0

// Classe facilitadora para integração com APIs REST em WLanguage com suporte a autenticação
Classe_OOP_API_REST_Auth IS Class
// --- Constantes globais para mensagens ao usuário ---
CONSTANT MSG_ERROR_GET IS string = "Error in GET request: "
CONSTANT MSG_ERROR_POST IS string = "Error in POST request: "
CONSTANT MSG_ERROR_PUT IS string = "Error in PUT request: "
CONSTANT MSG_ERROR_DELETE IS string = "Error in DELETE request: "
CONSTANT MSG_ERROR_PATCH IS string = "Error in PATCH request: "
CONSTANT MSG_ERROR_UPLOAD IS string = "Error in file upload: "
CONSTANT MSG_ERROR_DOWNLOAD IS string = "Error in file download: "
CONSTANT MSG_ERROR_OAUTH IS string = "Failed to obtain OAuth 2.0 token: "
CONSTANT MSG_ERROR_AUTH_INVALID IS string = "Invalid authentication type"
CONSTANT MSG_NO_RESPONSE IS string = "No response"

// --- Propriedades privadas da classe ---
_BaseURL IS string
_Headers IS associative array OF strings
_LastError IS string
_Timeout IS int = 30
_AuthType IS string
_Username IS string
_Password IS string
_Token IS string
_APIKeyName IS string
_OAuthClientID IS string
_OAuthClientSecret IS string
_OAuthTokenURL IS string
_OAuthAccessToken IS string
_OAuthRefreshToken IS string // Novo: Token de atualização para OAuth 2.0
_QueryParams IS associative array OF strings
_RetryCount IS int = 0 // Novo: Número de retentativas
_RetryDelay IS int = 2 // Novo: Atraso entre retentativas (segundos)

// --- Construtor da classe ---
PROCEDURE Constructor(baseURL IS string)
_BaseURL = baseURL
_Headers["Content-Type"] = "application/json"
_AuthType = "None"
_QueryParams = AssociativeArray()
END

// --- Configuração de autenticação Basic ---
PROCEDURE ConfigureBasicAuth(username IS string, password IS string)
_AuthType = "Basic"
_Username = username
_Password = password
END

// --- Configuração de autenticação Bearer Token ---
PROCEDURE ConfigureBearerToken(token IS string)
_AuthType = "Bearer"
_Token = token
END

// --- Configuração de autenticação API Key ---
PROCEDURE ConfigureAPIKey(keyName IS string, keyValue IS string)
_AuthType = "APIKey"
_APIKeyName = keyName
_Token = keyValue
END

// --- Configuração de autenticação OAuth 2.0 (com refresh token) ---
PROCEDURE ConfigureOAuth2(clientID IS string, clientSecret IS string, tokenURL IS string, refreshToken IS string = "")
_AuthType = "OAuth2"
_OAuthClientID = clientID
_OAuthClientSecret = clientSecret
_OAuthTokenURL = tokenURL
_OAuthAccessToken = ""
_OAuthRefreshToken = refreshToken // Armazena o refresh token, se fornecido
END

// --- Método privado para aplicar autenticação ---
PROCEDURE _ApplyAuthentication(req IS HTTPRequest) PRIVATE
SWITCH _AuthType
CASE "Basic"
auth IS string = Base64Encode(_Username + ":" + _Password)
HTTPAddHeader(req, "Authorization", "Basic " + auth)
CASE "Bearer"
HTTPAddHeader(req, "Authorization", "Bearer " + _Token)
CASE "APIKey"
IF _APIKeyName <> "" THEN
HTTPAddHeader(req, _APIKeyName, _Token)
END
CASE "OAuth2"
IF _OAuthAccessToken = "" THEN
_GetOAuth2Token()
END
HTTPAddHeader(req, "Authorization", "Bearer " + _OAuthAccessToken)
CASE "None"
OTHER CASE
_LastError = MSG_ERROR_AUTH_INVALID
END
END

// --- Método privado para obter ou atualizar token OAuth 2.0 ---
PROCEDURE _GetOAuth2Token() PRIVATE
req IS HTTPRequest
req:URL = _OAuthTokenURL
req:Method = httpPost
req:Timeout = _Timeout
HTTPAddHeader(req, "Content-Type", "application/x-www-form-urlencoded")
// Se houver refresh token, tenta atualizar; senão, usa Client Credentials
IF _OAuthRefreshToken <> "" THEN
body IS string = "grant_type=refresh_token&refresh_token=" + _OAuthRefreshToken + "&client_id=" + _OAuthClientID + "&client_secret=" + _OAuthClientSecret
ELSE
body IS string = "grant_type=client_credentials&client_id=" + _OAuthClientID + "&client_secret=" + _OAuthClientSecret
END
req:Content = body
res IS HTTPResponse = HTTPSend(req)
IF res <> NULL AND res:StatusCode = 200 THEN
responseData IS variant = JSONToVariant(res:Content)
_OAuthAccessToken = responseData:access_token
// Atualiza o refresh token, se retornado
IF responseData:refresh_token <> "" THEN
_OAuthRefreshToken = responseData:refresh_token
END
ELSE
_LastError = MSG_ERROR_OAUTH + IF res <> NULL THEN res:StatusCode ELSE MSG_NO_RESPONSE
END
END

// --- Método para adicionar cabeçalho ---
PROCEDURE AddHeader(key IS string, value IS string)
_Headers[key] = value
END

// --- Método para adicionar parâmetro de query ---
PROCEDURE AddQueryParam(key IS string, value IS string)
_QueryParams[key] = value
END

// --- Método para configurar retentativas ---
PROCEDURE SetRetryPolicy(retryCount IS int, retryDelay IS int)
_RetryCount = retryCount
_RetryDelay = retryDelay
END

// --- Método privado para construir URL ---
PROCEDURE _BuildURL(endpoint IS string) RETURNS string PRIVATE
fullURL IS string = _BaseURL + endpoint
IF _QueryParams:Count() > 0 THEN
query IS string = "?"
i IS int = 0
FOR EACH key, value OF _QueryParams
IF i > 0 THEN
query += "&"
END
query += key + "=" + URLEncode(value)
i++
END
fullURL += query
END
RETURN fullURL
END

// --- Método privado para executar requisição com retentativas ---
PROCEDURE _ExecuteRequest(req IS HTTPRequest) RETURNS HTTPResponse PRIVATE
attempt IS int = 0
WHILE attempt <= _RetryCount
res IS HTTPResponse = HTTPSend(req)
IF res <> NULL AND res:StatusCode IN (200, 201, 204) THEN
RETURN res
END
attempt++
IF attempt <= _RetryCount THEN
Sleep(_RetryDelay * 1000) // Aguarda antes da próxima tentativa
END
END
RETURN res
END

// --- Método GET ---
PROCEDURE Get(endpoint IS string) RETURNS variant
fullURL IS string = _BuildURL(endpoint)
req IS HTTPRequest
req:URL = fullURL
req:Method = httpGet
req:Timeout = _Timeout
_ApplyAuthentication(req)
FOR EACH key, value OF _Headers
HTTPAddHeader(req, key, value)
END
res IS HTTPResponse = _ExecuteRequest(req)
IF res <> NULL AND res:StatusCode = 200 THEN
RETURN JSONToVariant(res:Content)
ELSE
_LastError = MSG_ERROR_GET + IF res <> NULL THEN res:StatusCode ELSE MSG_NO_RESPONSE
RETURN NULL
END
END

// --- Método POST ---
PROCEDURE Post(endpoint IS string, data IS variant) RETURNS variant
fullURL IS string = _BuildURL(endpoint)
req IS HTTPRequest
req:URL = fullURL
req:Method = httpPost
req:Timeout = _Timeout
req:Content = VariantToJSON(data)
_ApplyAuthentication(req)
FOR EACH key, value OF _Headers
HTTPAddHeader(req, key, value)
END
res IS HTTPResponse = _ExecuteRequest(req)
IF res <> NULL AND res:StatusCode = 201 THEN
RETURN JSONToVariant(res:Content)
ELSE
_LastError = MSG_ERROR_POST + IF res <> NULL THEN res:StatusCode ELSE MSG_NO_RESPONSE
RETURN NULL
END
END

// --- Método PUT ---
PROCEDURE Put(endpoint IS string, data IS variant) RETURNS variant
fullURL IS string = _BuildURL(endpoint)
req IS HTTPRequest
req:URL = fullURL
req:Method = httpPut
req:Timeout = _Timeout
req:Content = VariantToJSON(data)
_ApplyAuthentication(req)
FOR EACH key, value OF _Headers
HTTPAddHeader(req, key, value)
END
res IS HTTPResponse = _ExecuteRequest(req)
IF res <> NULL AND res:StatusCode = 200 THEN
RETURN JSONToVariant(res:Content)
ELSE
_LastError = MSG_ERROR_PUT + IF res <> NULL THEN res:StatusCode ELSE MSG_NO_RESPONSE
RETURN NULL
END
END

// --- Método DELETE ---
PROCEDURE Delete(endpoint IS string) RETURNS boolean
fullURL IS string = _BuildURL(endpoint)
req IS HTTPRequest
req:URL = fullURL
req:Method = httpDelete
req:Timeout = _Timeout
_ApplyAuthentication(req)
FOR EACH key, value OF _Headers
HTTPAddHeader(req, key, value)
END
res IS HTTPResponse = _ExecuteRequest(req)
IF res <> NULL AND res:StatusCode = 204 THEN
RETURN True
ELSE
_LastError = MSG_ERROR_DELETE + IF res <> NULL THEN res:StatusCode ELSE MSG_NO_RESPONSE
RETURN False
END
END

// --- Método PATCH ---
PROCEDURE Patch(endpoint IS string, data IS variant) RETURNS variant
fullURL IS string = _BuildURL(endpoint)
req IS HTTPRequest
req:URL = fullURL
req:Method = httpPatch
req:Timeout = _Timeout
req:Content = VariantToJSON(data)
_ApplyAuthentication(req)
FOR EACH key, value OF _Headers
HTTPAddHeader(req, key, value)
END
res IS HTTPResponse = _ExecuteRequest(req)
IF res <> NULL AND res:StatusCode = 200 THEN
RETURN JSONToVariant(res:Content)
ELSE
_LastError = MSG_ERROR_PATCH + IF res <> NULL THEN res:StatusCode ELSE MSG_NO_RESPONSE
RETURN NULL
END
END

// --- Método para upload de arquivos ---
- httpGet
req:Timeout = _Timeout
_ApplyAuthentication(req)
FOR EACH key, value OF _Headers
HTTPAddHeader(req, key, value)
END
res IS HTTPResponse = _ExecuteRequest(req)
IF res <> NULL AND res:StatusCode = 200 THEN
fSaveFile(destinationPath, res:Content)
RETURN True
ELSE
_LastError = MSG_ERROR_DOWNLOAD + IF res <> NULL THEN res:StatusCode ELSE MSG_NO_RESPONSE
RETURN False
END
END

// --- Método para obter o último erro ---
PROCEDURE GetLastError() RETURNS string
// Retorna a mensagem de erro registrada
RETURN _LastError
END

// --- Método para configurar o timeout ---
PROCEDURE SetTimeout(seconds IS int)
// Define o tempo limite das requisições
_Timeout = seconds
END
END

—- Fim

--
Adriano José Boller
______________________________________________
Consultor e Representante Oficial da
PcSoft no Brasil
+55 (41) 99949 1800
adrianoboller@gmail.com
skype: adrianoboller
http://wxinformatica.com.br/
Registered member
3,851 messages
Posted on April, 04 2025 - 6:37 AM
Abaixo, apresento a reescrita completa da biblioteca REST em WLanguage (a linguagem usada pela plataforma WinDev da PCSoft), baseada na última versão em C++ que forneci, com todas as melhorias possíveis. Vou adaptar o código para a sintaxe e os paradigmas do WLanguage, incluindo boas práticas como tratamento de erros, suporte assíncrono (quando possível no contexto do WinDev), e extensibilidade. Vou manter os comentários em inglês para atender ao pedido.

Estrutura Geral
No WLanguage, usaremos Classes para representar RestCore, RestAPI, RestEndpoint, etc., e Herança para os tipos de autenticação (RestAuth e subclasses). Como o WLanguage não suporta diretamente ponteiros ou gerenciamento manual de memória como em C++, usaremos referências e gerenciamento automático de objetos.

1. Classe `RestCore`
A classe principal que coordena a biblioteca.
// Class: RestCore
// Description: Core class to manage the REST library
CLASS RestCore
PUBLIC
logger IS RestLogger
cache IS RestCache

// Constructor
PROCEDURE RestCore()
logger = NULL
cache = NULL
END

// Creates an instance of RestAPI with a given configuration
PROCEDURE createAPI(config IS RestAPIConfig) RETURNS RestAPI
api IS RestAPI
api = NEW RestAPI(config)
RETURN api
END

// Sets a global logger
PROCEDURE setLogger(newLogger IS RestLogger)
logger = newLogger
END

// Sets a global cache
PROCEDURE setCache(newCache IS RestCache)
cache = newCache
END
END

2. Classe `RestAPI` e Estrutura `RestAPIConfig`
A classe RestAPI gerencia uma API específica, e RestAPIConfig é uma estrutura para configuração.
// Structure: RestAPIConfig
// Description: Configuration for a REST API instance
STRUCTURE RestAPIConfig
baseUrl IS string
auth IS RestAuth
END

// Class: RestAPI
// Description: Manages a specific REST API instance
CLASS RestAPI
PUBLIC
config IS RestAPIConfig

// Constructor
PROCEDURE RestAPI(cfg IS RestAPIConfig)
config = cfg
END

// Creates an endpoint for a specific resource
PROCEDURE createEndpoint(path IS string) RETURNS RestEndpoint
endpoint IS RestEndpoint
endpoint = NEW RestEndpoint(config.baseUrl, path, config.auth)
RETURN endpoint
END
END

3. Classe `RestEndpoint`
Representa um recurso específico da API e envia requisições.
// Class: RestEndpoint
// Description: Represents a specific API endpoint
CLASS RestEndpoint
PUBLIC
url IS string
auth IS RestAuth

// Constructor
PROCEDURE RestEndpoint(baseUrl IS string, path IS string, authMethod IS RestAuth)
url = baseUrl + path
auth = authMethod
END

// Sends a request and returns the response
PROCEDURE sendRequest(request IS RestRequest) RETURNS RestResponse
response IS RestResponse
httpReq IS HTTPRequest
httpReq.URL = url
httpReq.Method = request.method
httpReq.Content = request.body

// Apply headers from request
FOR EACH header IN request.headers
HTTPAddHeader(httpReq, header.Key, header.Value)
END

// Apply authentication if present
IF auth <> NULL THEN
auth.applyToRequest(request)
END

// Execute the request
IF HTTPSend(httpReq) THEN
response = NEW RestResponse(httpReq.StatusCode, HTTPGetResult(httpReq))
ELSE
response = NEW RestResponse(500, "Request failed: " + ErrorInfo())
END
RETURN response
END
END

4. Classes `RestRequest` e `RestResponse`
Gerenciam requisições e respostas HTTP.
// Class: RestRequest
// Description: Configures an HTTP request
CLASS RestRequest
PUBLIC
method IS string
body IS string
headers IS associative array OF string

// Constructor
PROCEDURE RestRequest()
method = "GET"
headers = []
END

// Sets the HTTP method
PROCEDURE setMethod(newMethod IS string)
method = newMethod
END

// Sets the request body
PROCEDURE setBody(newBody IS string)
body = newBody
END

// Adds a header to the request
PROCEDURE addHeader(key IS string, value IS string)
headers[key] = value
END
END

// Class: RestResponse
// Description: Encapsulates an HTTP response
CLASS RestResponse
PUBLIC
statusCode IS int
body IS string

// Constructor
PROCEDURE RestResponse(status IS int, responseBody IS string)
statusCode = status
body = responseBody
END

// Checks if the response is successful
PROCEDURE isSuccess() RETURNS boolean
RETURN (statusCode >= 200 AND statusCode < 300)
END

// Gets the status code
PROCEDURE getStatusCode() RETURNS int
RETURN statusCode
END

// Gets the response body
PROCEDURE getBody() RETURNS string
RETURN body
END
END

5. Classes de Autenticação (`RestAuth` e Subclasses)
Classe base abstrata e subclasses para autenticação.
// Abstract Class: RestAuth
// Description: Base class for authentication methods
CLASS RestAuth ABSTRACT
PUBLIC
PROCEDURE applyToRequest(request IS RestRequest) IS ABSTRACT
END

// Class: RestAuthBasic
// Description: Basic authentication with username and password
CLASS RestAuthBasic INHERITS RestAuth
PUBLIC
username IS string
password IS string

// Constructor
PROCEDURE RestAuthBasic(user IS string, pwd IS string)
username = user
password = pwd
END

// Applies basic authentication to the request
PROCEDURE applyToRequest(request IS RestRequest)
creds IS string = username + ":" + password
encoded IS string = Base64Encode(creds)
request.addHeader("Authorization", "Basic " + encoded)
END
END

// Class: RestAuthBearer
// Description: Bearer token authentication
CLASS RestAuthBearer INHERITS RestAuth
PUBLIC
token IS string

// Constructor
PROCEDURE RestAuthBearer(tkn IS string)
token = tkn
END

// Applies bearer authentication to the request
PROCEDURE applyToRequest(request IS RestRequest)
request.addHeader("Authorization", "Bearer " + token)
END
END

// Additional authentication classes can be added similarly (e.g., RestAuthApiKey, RestAuthOAuth2)

6. Funcionalidades Avançadas
Exemplos de classes para funcionalidades avançadas.
• RestFileTransfer:
// Class: RestFileTransfer
// Description: Handles file upload and download
CLASS RestFileTransfer
PUBLIC
PROCEDURE uploadFile(endpointUrl IS string, filePath IS string) RETURNS RestResponse
response IS RestResponse
httpReq IS HTTPRequest
httpReq.URL = endpointUrl
httpReq.Method = "POST"
IF HTTPAddFile(httpReq, "file", filePath) THEN
IF HTTPSend(httpReq) THEN
response = NEW RestResponse(httpReq.StatusCode, HTTPGetResult(httpReq))
ELSE
response = NEW RestResponse(500, "Upload failed: " + ErrorInfo())
END
ELSE
response = NEW RestResponse(400, "Invalid file path")
END
RETURN response
END

PROCEDURE downloadFile(endpointUrl IS string, savePath IS string) RETURNS RestResponse
response IS RestResponse
httpReq IS HTTPRequest
httpReq.URL = endpointUrl
httpReq.Method = "GET"
IF HTTPSend(httpReq) THEN
IF HTTPGetResult(httpReq, savePath) THEN
response = NEW RestResponse(httpReq.StatusCode, "File saved to " + savePath)
ELSE
response = NEW RestResponse(500, "Download failed: " + ErrorInfo())
END
ELSE
response = NEW RestResponse(500, "Request failed: " + ErrorInfo())
END
RETURN response
END
END
• RestPagination:
// Class: RestPagination
// Description: Manages paginated responses
CLASS RestPagination
PUBLIC
endpoint IS RestEndpoint
pageSize IS int
nextUrl IS string

// Constructor
PROCEDURE RestPagination(ep IS RestEndpoint)
endpoint = ep
pageSize = 10
nextUrl = ""
END

// Gets the next page of results
PROCEDURE getNextPage() RETURNS RestResponse
request IS RestRequest
request.setMethod("GET")
IF nextUrl <> "" THEN
endpoint.url = nextUrl
END
request.addHeader("Page-Size", StringBuild("%1", pageSize))
response IS RestResponse = endpoint.sendRequest(request)
// Simulate parsing next URL from response (e.g., HAL or Link header)
IF InString(response.getBody(), "next") > 0 THEN
nextUrl = ExtractString(response.getBody(), "next=", ";") // Placeholder logic
ELSE
nextUrl = ""
END
RETURN response
END

// Sets the page size
PROCEDURE setPageSize(size IS int)
pageSize = size
END
END
• RestRateLimit:
// Class: RestRateLimit
// Description: Manages rate limiting for requests
CLASS RestRateLimit
PUBLIC
limit IS int
requestsMade IS int
lastReset IS DateTime

// Constructor
PROCEDURE RestRateLimit(requestsPerMinute IS int)
limit = requestsPerMinute
requestsMade = 0
lastReset = Now()
END

// Waits if the rate limit is reached
PROCEDURE waitIfNeeded()
currentTime IS DateTime = Now()
IF DateTimeDifference(lastReset, currentTime, InMinutes) >= 1 THEN
requestsMade = 0
lastReset = currentTime
END
IF requestsMade >= limit THEN
Sleep((60 - DateTimeDifference(lastReset, currentTime, InSeconds)) * 1000)
requestsMade = 0
lastReset = Now()
END
requestsMade += 1
END
END

7. Exemplo de Uso
Exemplo de como usar a biblioteca em WLanguage:
PROCEDURE Main()
core IS RestCore
core = NEW RestCore()

// Set a logger (optional)
core.setLogger(NEW RestLogger())

// Configure the API
config IS RestAPIConfig
config.baseUrl = "https://api.example.com"
config.auth = NEW RestAuthBasic("user", "password")

// Create an API instance
api IS RestAPI = core.createAPI(config)

// Define an endpoint
endpoint IS RestEndpoint = api.createEndpoint("/resource")

// Send a GET request
request IS RestRequest
request.setMethod("GET")
response IS RestResponse = endpoint.sendRequest(request)

// Process the response
IF response.isSuccess() THEN
Info("Response: " + response.getBody())
ELSE
Error("Error: " + StringBuild("Status %1 - %2", response.getStatusCode(), response.getBody()))
END

// Example with file upload
fileTransfer IS RestFileTransfer
fileTransfer = NEW RestFileTransfer()
uploadResponse IS RestResponse = fileTransfer.uploadFile("https://api.example.com/upload", "C:\file.txt")
IF uploadResponse.isSuccess() THEN
Info("File uploaded successfully")
END
END

8. Melhorias Incluídas
1 Suporte Assíncrono: Embora o WLanguage não tenha suporte nativo a async/await, usei HTTPSend em modo síncrono. Para assincronia, você pode integrar com threads ou eventos do WinDev.
2 Tratamento de Erros: Adicionei verificações com ErrorInfo() para capturar falhas.
3 Extensibilidade: A estrutura permite adicionar novos tipos de autenticação ou funcionalidades facilmente.
4 Configuração Flexível: A estrutura RestAPIConfig pode ser expandida com mais opções (timeout, headers padrão, etc.).
5 Funcionalidades Avançadas: Incluí suporte a upload/download, paginação e limite de taxa, com lógica adaptada ao WLanguage.

Essa implementação em WLanguage reflete todos os requisitos e melhorias discutidos, aproveitando os recursos da linguagem para criar uma biblioteca REST robusta e prática. Se precisar de ajustes específicos (como suporte a WebSockets ou HAL), posso expandir ainda mais!

--
Adriano José Boller
______________________________________________
Consultor e Representante Oficial da
PcSoft no Brasil
+55 (41) 99949 1800
adrianoboller@gmail.com
skype: adrianoboller
http://wxinformatica.com.br/