FWRest Client Generator
Overview
Generate production-ready AdvPL/TLPP code that consumes external REST APIs using the framework FWRest class. FWRest is the HTTP client class — it is the counterpart to the @Get/@Post annotation-based REST server (see tlpp-rest-endpoint-generator for exposing endpoints, not consuming them).
FWRest wraps low-level HTTP socket calls and supports the four standard verbs GET, POST, PUT, DELETE (no native PATCH support). It handles SSL automatically through appserver.ini socket configuration.
When to Use
Use this skill when generating code that:
- Calls a third-party REST API from inside Protheus (integrations with CRMs, payment gateways, ERPs, government services, etc.)
- Sends JSON payloads to external services
- Pulls data from external endpoints into a Protheus routine
- Needs HTTP Basic, Bearer/JWT, or OAuth 2.0 authentication
- Replaces legacy
HTTPCGet/HTTPCPost/HTTPQuotecalls with the framework client
Do NOT use this skill for:
- Exposing endpoints from Protheus → use
../tlpp-rest-endpoint-generator/SKILL.md - Workstation-side HTTP calls that must run on the user's machine → use
HTTPCGet/HTTPCPostwith WebAgent - File downloads from non-REST endpoints → use
HTTPQuoteorWSDownload
FWRest Architecture
Lifecycle
A typical FWRest call follows this five-step lifecycle:
- Instantiate —
oClient := FWRest():New(cHost)wherecHostis the base URL only (scheme + host + optional port), e.g."https://api.example.com". - Configure —
SetPath(),SetPostParams(),SetGetParams(),SetTimeOut(),SetChkStatus(),SetLegacySuccess(). - Build headers — Plain
Arrayof"Key: Value"strings, e.g.{"Content-Type: application/json", "Authorization: Bearer xyz"}. - Invoke verb —
:Get(aHead),:Post(aHead),:Put(aHead, cBody),:Delete(aHead, cBody). All return.T.on success. - Read result —
GetResult()on success (response body as character),GetLastError()on failure,GetHTTPCode()for the numeric status.
Path vs Query Parameters
| Concern | API | Example |
|---|---|---|
| Path segments | SetPath("/api/v1/customers/123") |
URL: /api/v1/customers/123 |
| Query string (inline) | SetPath("/api/v1/customers?page=1") |
URL: /api/v1/customers?page=1 |
| Query string (separate) | SetGetParams("page=1&size=20") |
Appended after path |
| GET param via verb | :Get(aHead, "page=1") |
Appended after path |
Special characters in query values must be URI-encoded via the
Escape()function — otherwise the request will fail or be misinterpreted.
Status Code Semantics
| Method | Behavior |
|---|---|
Get() |
Returns .T. only for HTTP 200 (legacy) or 200–299 (with SetLegacySuccess(.F.)) |
Post() |
Returns .T. for 200 or 201 (legacy) or 200–299 (with SetLegacySuccess(.F.)) |
Put() / Delete() |
Returns .T. for 200 or 201 (legacy) or 200–299 (with SetLegacySuccess(.F.)) |
SetChkStatus(.F.) |
Disables internal HTTP code validation — verb returns .T. if the connection succeeded, regardless of HTTP code. You then call GetHTTPCode() to decide. Use this for APIs that return 204, 207, 3xx, or 4xx as part of the contract. |
No PATCH Support
FWRest does not support the PATCH verb. If the target API requires PATCH, generate code using HTTPQuote() instead and note this limitation explicitly.
Bundled Reference Files
This skill uses progressive disclosure. The SKILL.md body covers the architecture, decision logic, and the generation checklist. Detailed method reference, code templates, and authentication patterns are in the references/ directory — read them on demand based on the scenario:
| Reference File | When to Read | Content |
|---|---|---|
| references/fwrest-api-reference.md | Looking up exact method signatures, parameter types, minimum LIB version per method, or behavior of SetChkStatus/SetLegacySuccess/SetTimeOut/GetHTTPCode |
Complete FWRest method reference table with syntax, parameters, returns, LIB version requirements |
| references/fwrest-client-templates.md | Generating any FWRest call — GET, POST, PUT, DELETE, JSON parsing, error handling, file upload (.gz), header construction | Full code templates for all 4 HTTP verbs, JSON body construction, response parsing, generic error-handling wrapper |
| references/fwrest-authentication-patterns.md | Implementing HTTP Basic, Bearer Token / JWT, API Key, or OAuth 2.0 (client credentials / authorization code) authentication | Header templates for each auth scheme, token-refresh pattern, secret storage guidance |
Also refer to references/sonarqube-rules-reference.md for the complete SonarQube rules reference shared across skills.
Generation Workflow
Step 1: Gather Requirements
Identify from the user's request:
- Target API — base URL, path, and HTTP verb(s)
- Authentication scheme — None, Basic, Bearer/JWT, API Key, or OAuth 2.0
- Payload format — JSON (default), XML, form-encoded, binary/gzip
- Expected response codes — only 2xx, or also 204/3xx/4xx as part of contract
- Timeout requirement — default 120s vs custom (e.g. webhook endpoints with 5s SLA)
- Whether the call is part of a transaction — affects error handling strategy
Step 2: Load Templates
Read references/fwrest-client-templates.md for the verb-specific template. Combine with the auth header pattern from references/fwrest-authentication-patterns.md.
Step 3: Pick the Status-Code Strategy
- Standard CRUD (200/201 only matter): leave defaults.
- API uses full 2xx range (e.g. 202 Accepted, 204 No Content): call
oClient:SetLegacySuccess(.F.)(requires LIB 20240812+). - Need to read body of 4xx/5xx responses: call
oClient:SetChkStatus(.F.)and inspectGetHTTPCode()+GetResult()manually.
Step 4: Wrap in Try/Catch + Logging
Wrap every FWRest invocation in a TLPP Try/Catch block. Log failures via FWLogMsg() including the URL, HTTP code, and the truncated response body. Never log secrets (tokens, passwords).
Step 5: Validate Against Checklist
Use the checklist below to verify the generated code covers all requirements.
FWRest Client Generation Checklist
Structure
-
#include "totvs.ch"(AdvPL) or#include "tlpp-core.th"(TLPP) present, in lowercase - User Function declares
oClient,aHeader,cBody,cResponse,nHttpCodeas locals with explicit types (TLPPas Object,as Array, etc.) -
FWRest():New(cHost)receives ONLY the base URL — path is set viaSetPath()
Request Construction
-
SetPath()called with leading/ - Query parameter values passed through
Escape()when they may contain spaces or special chars - Headers built as an
Arrayof"Key: Value"strings (note the literal space after the colon) -
Content-Typeheader included for POST/PUT bodies (application/json,application/xml, etc.) - Body serialized via
oJson:toJson()— never built by string concatenation when the data is dynamic -
SetPostParams(cBody)called before:Post()(Post body is NOT a parameter of:Post()) - PUT/DELETE bodies passed as the second positional argument of
:Put(aHead, cBody)/:Delete(aHead, cBody)
Authentication
- Secrets read from
GetMV()parameter or environment, never hardcoded - HTTP Basic:
"Authorization: Basic " + Encode64(cUser + ":" + cPass) - Bearer/JWT:
"Authorization: Bearer " + cToken - OAuth 2.0 token-acquisition call is a separate FWRest call to the auth server, cached for the token's
expires_inwindow - Token never logged or echoed in error responses
Response Handling
- Verb result captured in a local
lOk(e.g.lOk := oClient:Post(aHeader)) -
GetHTTPCode()retrieved into a local before any branching -
GetResult()parsed viaJsonObject():New() + :fromJson()and thefromJson()return checked (returnsNilon success, error string otherwise) - Failure branch reads
GetLastError()ANDGetHTTPCode()(both can be informative)
Robustness
-
SetTimeOut()set explicitly (default 120s is often too long for synchronous calls) - Calls wrapped in
Try/Catchto capture transport-layer exceptions -
SetChkStatus(.F.)used when the API returns 204/4xx as part of contract -
SetLegacySuccess(.F.)used when the API uses the full 2xx range -
FreeObj(oClient)after use in long-running routines to release the socket promptly
Logging & Observability
- Successful calls logged at
INFOlevel with URL + HTTP code (no body) - Failed calls logged at
ERRORlevel with URL + HTTP code + truncated response (max ~500 chars) -
FWLogMsg("ERROR", , "REST_CLIENT", FunName(), , "01", cMessage, 0, 0, {})pattern used - No use of
ConOut()for production logging (only acceptable in standalone smoke tests) - No secrets, tokens, passwords, or full request bodies emitted to logs
SonarQube Compliance
- No hardcoded passwords, tokens, or API keys in source code
- No
IIF()— useIf/Else/EndIfblocks -
GetMV()calls outside of loops - No UI functions (
MsgAlert,MsgYesNo,Aviso,Help) inside the call path of a transaction or scheduled job - Includes in lowercase (e.g.,
#include "totvs.ch") - No use of
RpcSetEnvinside REST endpoint handlers that themselves invoke FWRest — environment must already be prepared
Refer to references/sonarqube-rules-reference.md for the complete SonarQube rules reference.
Common Pitfalls
| Pitfall | Symptom | Fix |
|---|---|---|
Full URL passed to New() |
404 or empty result | Pass only https://host:port; use SetPath() for the path |
Body passed to :Post() as argument |
Body ignored, empty POST sent | Use SetPostParams(cBody) before :Post(aHead) |
| 204 response hangs ~2 minutes | Slow integrations | Set SetTimeOut(nSec) to a small value, or use HTTPQuote() as alternative |
| Header missing space after colon | Server rejects request | Always write "Key: Value" with a space |
| Query string not escaped | Garbled parameters | Wrap values with Escape() |
| 4xx body unreadable | GetResult() returns empty |
Call SetChkStatus(.F.) first; then read GetResult() even on failure |
| Hardcoded credentials | SonarQube blocker | Read from GetMV("MV_XYZTOK",,"") parameter |
| PATCH attempted | Compile error / no such method | FWRest does not support PATCH — use HTTPQuote() |