A lightweight Apex utility for executing (Salesforce) REST based APIs
This is very lightweight REST API utility designed to work with Salesforce and Data Cloud REST APIs, yet is flexible enough be used for any type of REST based API call. It is focussed around OAuth 2.0 authenticated APIs with a Bearer token as authentication and should ideally be used in combination with a logging / retry library.
This utility handles the standard Salesforce API status codes 200, 201, 400, 401, 404, 405, 415 and 500 HTTP Response status codes and supports the Data Cloud specifc API status codes 202, 409, 422, 429 as well.
It will handle the error response codes by throwing an utl.RestUtilException
with the error message(s) from the reponse that the consuming code can handle accordingly.
It's designed to work with method chaining so that full callouts can be performed in a single line without the need for verbose class construction on larger configurations.
The following package need to be installed first before installing this package. If you use the managed package you need to installed the managed package dependency and if you use the unlocked version you need to use the unlocked dependency.
Info | Value |
---|---|
Name | Lightweight - Apex Unit Test Util v2 |
Version | 2.4.0-1 |
Managed Installation URL | /packaging/installPackage.apexp?p0=04tP3000000M6OXIA0 |
Unlocked Installation URL | /packaging/installPackage.apexp?p0=04tP3000000M6Q9IAK |
Github URL | https://github.com/jfwberg/lightweight-apex-unit-test-util-v2 |
Info | Value |
---|---|
Name | Lightweight - Apex REST Util |
Version | 0.11.0-1 |
Managed Installation URL | /packaging/installPackage.apexp?p0=04tP3000000M6gHIAS |
Unlocked Installation URL | /packaging/installPackage.apexp?p0=04tP3000000M6htIAC |
- Timeout : 120,000ms
- HTTP Method : GET
- Endpoint : ''
- API Version : 'v60.0' (Salesforce Constructor Only)
- Content-Type: 'application/json;charset=UTF-8'
You cannot execute the Test.setMock()
class for http callouts in namespaced packages.
To test your callouts made though this utility you need to leverage the utl.Mck
class and methods.
Example implementation in your TEST method:
// Setup the mock response
utl.Mck.setResponse(200, '{"SUCCESS": true}');
The basic setup is by constructing a util.Rst()
object and chain methods to set it up, execute it and get the response.
There are several options for calling Salesforce APIs specifically.
// Base URL : The current org (Home org) and the
// BearerToken : The current logged in user's SessionId.(Through a VF page from Aura Context)
// SF Response handling: Enabled
global Rst(Boolean isSalesforce)
// Example
utl.Rst callout = new utl.Rst(true);
// Base URL : From Named Credential
// Bearer Token : From External Credential
// SF Response handling: Enabled
global Rst(String namedCredential, Boolean isSalesforce)
// Example
utl.Rst callout = new utl.Rst('Home_Org', true);
// Base URL : From Named Credential
// Bearer Token : From External Credential
// SF Response handling: Disabled
global Rst(String namedCredential)
// Example
utl.Rst rst = new utl.Rst('namedCredentialName');
// Base URL : From baseUrl variable
// Bearer Token : From bearerToken variable
// SF Response handling: Disabled
global Rst(String baseUrl, String bearerToken)
// Example
utl.Rst rst = new utl.Rst('https://mydomain.my.salesforce.com','[SESSION_ID_VALUE]');
// Base URL : Not set
// Bearer Token : Not set
// SF Response handling: Disabled
global Rst()
// Example
utl.Rst rst = new utl.Rst()
.setBaseUrl('https://prod.pineapple.tools')
.setEndpoint('/oauth2/token')
.setMethod('POST')
.call();
These are all the methods to setup the REST request
// Set the base URL (i.e. https://mydomain.my.salesforce.com)
setBaseUrl(String baseUrl)
// Sets the value for the 'Authorization' Header to the value of 'Bearer bearerToken'
setBearerToken(String bearerToken)
// When true the endpoint value will prefixed with /services/data/[API_VERSION]
setHandleSfEndpoint(Boolean handleSfEndpoint)
// If this is set to true, the code will handle the API Response
// like a response that comes from a Salesforce API and will throw
// an exception on any known error responses
setHandleSfResponse(Boolean handleSfResponse)
// Set the HTTP Method, (defaults to GET)
setMethod(String method)
// Set a custom api version for a Salesforce API like 'v60.0'
setApiVersion(String apiVersion)
// Set the endpoint (i.e. /services/data/v60.0/sobjects/account/describe)
setEndpoint(String endpoint)
// Ability to set a custom timeout value (defaults to 120,000)
setTimeout(Integer timeout)
// Set the value of a PUT, POST or PATCH request type
setBody(String body)
// If you want to remove a header set the value to null
// Customer headers will override default headers
setHeader(String key, String value)
// Method to set a mockResponseIdentifier so you can specify what utl.Mck instance is returned
// This is option for Apex testing and required when you work with multiple calls in the same logic
// Match mockResponseIdentifier in the utl.Mck.addResponse(mockResponseIdentifier,200,payload)
setMockResponseIdentifier(String mockResponseIdentifier)
// Sets the X-PrettyPrint header to value to '1' or '0'
// Should be turned off in production, but can be used for
setPrettyPrintHeader(Boolean prettyPrint)
// Method to set an X-Request-ID header, set to NULL to auto generate a GUID
setRequestIdHeader(String requestId)
// Method to set an X-Correlation-ID header, set to NULL to auto generate a GUID
setCorrelationIdHeader(String correlationId)
// Sets the content type to 'text/xml; charset=UTF-8;' for the use with the SOAP API
setContentTypeHeaderToXml()
// Sets the content type to 'application/x-www-form-urlencoded'
// Sets the method to 'POST'
// Sets the body to a URL Encoded query String based on the parameter value map input
setContentTypeHeaderToFormUrlEncoded(Map<String,String> parameterValueMap)
These methods are run after you have set up your request using the setter methods. It will generate the request and/or execute the REST callout.
// Set up the request, but do not call the endpoint
// Not required when you use the call() method, this is mainly used for debugging
// or if you want to handle the request using a different library
setupRequest()
// Executes the HTTP Request. This method automatically runs setupRequest() so no need to
// call that method separately
call()
These methods are used AFTER you have executed your REST callout or you have setup your request.
// Returns the HttpRequest object, this can be used for debugging or custom execution / logging
HttpRequest getRequest()
// Returns the HttpResponse object, this can be used for handling the response in whatever way required
HttpResponse getResponse()
String getRequestId()
String getCorrelationId()
// The number of milli-seconds the callout took, for debugging / performance purposes
Integer getExecutionTime()
// Method to get a VF API enabled session Id for local session.
// Calls a VF page and extracts the session Id, this is quite a hack but allowed in AppExchange reviews
// It is required for lightning components to communicate with the Salesforce API directy
// Ideally you would always use a Named credential for each connection, even to the local (home) org
String getApiEnabledSessionId()
These methods are static methods that have nothing to do with the Rst Object instance it self. These are truly utility methods that can be used for custom REST related set ups.
// Method for encoding a Blob into a Base64 URL encoded String
// Mainly used for generating a JWS
String utl.Rst.base64UrlEncode(Blob input)
// Method to validate named credential(s) exists in the metadata.
// Throws an utl.Rst.NamedCredentialException in case a NC does not exist
void utl.Rst.validateNamedCredentials(Set<String> developerNames)
// Method to generate a GUID that is compliant with the standards
String utl.Rst.guid()
// Method to convert a key/value map into a URL encoded parameter query string that
// can be used with url-encoded forms or get queries
String utl.Rst.urlParameterQueryString(Map<String,String> parameterValueMap)
/**
* Example 01: Call a Salesforce API with a Named Credential
* - SF Error responses are handled by the utility
* - The base URL is managed by the named credential
* - The endpoint base url is set automatically (/services/data/[apiversion])
* - Authorization header is managed by the named credential
*/
try{
// Constructor with Named Credential Name and a Boolean indicator, it is a Salesforce Org
// The named credential can point to the home org as well.
utl.Rst callout = new utl.Rst('namedCredentialName', true)
.setEndpoint('/sobjects/account/describe')
.setMethod('GET')
.call();
String responseBody = callout.getResponse().getBody();
Integer executionTime = callout.getExecutionTime();
}catch(utl.Rst.RestUtilException e){
System.debug('\n\n' + e.getMessage() + '\n\n');
}
/**
* Example 02: Call a Salesforce API on the Home Org
* - SF Error responses are handled by the utility
* - The base URL is set to the current org's my domain url
* - The endpoint base url is set automatically (/services/data/[apiversion])
* - Authorization header is set to the session Id taken from the logged in user
*/
try{
// Constructor with the Boolean indicator it is the Salesforce Home Org
// Optionally we set the API version to a custom version
utl.Rst callout = new utl.Rst(true)
.setEndpoint('/sobjects/account/describe')
.setApiVersion('v60.0')
.setHeader('X-PrettyPrint', '1')
.call();
String responseBody = callout.getResponse().getBody();
Integer executionTime = callout.getExecutionTime();
}catch(utl.Rst.RestUtilException e){
System.debug('\n\n' + e.getMessage() + '\n\n');
}
/**
* Example 03: Call ANY api without anything configured
* - No response handling
* - Base URL needs to be set manually
* - Authorization header needs to be set manually
*/
try{
// Constructor without any parameters
utl.Rst callout = new utl.Rst()
.setBaseUrl('https://prod.pineapple.tools')
.setEndpoint('/oauth2/token')
.setBearerToken('token_goes_here')
.setMethod('POST')
.setBody('{"apiKey" : "api_key_goes_here"}')
.setHeader('debug', '1')
.call();
String responseBody = callout.getResponse().getBody();
Integer executionTime = callout.getExecutionTime();
}catch(utl.Rst.RestUtilException e){
System.debug('\n\n' + e.getMessage() + '\n\n');
}
/**
* Example 04: Call ANY api using a named credential
* - No response handling
* - Base URL is managed by the named credential
* - Authorization header is managed by the named credential
*/
try{
// Constructor with the Boolean indicator it is the Salesforce Home Org
utl.Rst callout = new utl.Rst('namedCredentialName')
.setEndpoint('/api/echo')
.call();
String responseBody = callout.getResponse().getBody();
Integer executionTime = callout.getExecutionTime();
}catch(utl.Rst.RestUtilException e){
System.debug('\n\n' + e.getMessage() + '\n\n');
}
/**
* Example 05: Call ANY api with an OAuth authrization
* - No response handling
* - Base URL is set through the input variable
* - Authorization header is set through the input variable
*/
try{
// Constructor with the baseURl and bearerToken variables to connect to any enpoinnt that requires a token
utl.Rst callout = new utl.Rst(URL.getOrgDomainUrl().toExternalForm(),UserInfo.getSessionId())
.setEndpoint('/services/data/v60.0/sobjects/accounts/describe')
.call();
String responseBody = callout.getResponse().getBody();
Integer executionTime = callout.getExecutionTime();
}catch(utl.Rst.RestUtilException e){
System.debug('\n\n' + e.getMessage() + '\n\n');
}
/**
* Example 06: Call Salesforce API without error handling and without API prefix
* - SF Error responses need to be handle manually
* - The endpoint needs to be set fully by the user, this allows for calling an OAuth endpoint
*/
try{
utl.Rst callout = new utl.Rst(true)
.setHandleSfEndpoint(false) // Disable the salesforce base endpoint
.setHandleSfResponse(false) // Disable any error handling
.setEndpoint('/services/data/v60.0/sobjects/account/describe')
.setMethod('GET')
.call();
String responseBody = callout.getResponse().getBody();
Integer executionTime = callout.getExecutionTime();
}catch(utl.Rst.RestUtilException e){
System.debug('\n\n' + e.getMessage() + '\n\n');
}
/**
* Example 01: Force a salesforce Error Object response, by calling the token endpoint
* This returns a 400 with an OAuth2 error response object
*/
try{
utl.Rst callout = new utl.Rst(true)
.setHandleSfEndpoint(false) // We do not want set the base URL in this case
.setEndpoint('/services/oauth2/token')
.call();
}catch(utl.Rst.RestUtilException e){
// unsupported_grant_type :: grant type not supported
System.debug(e.getMessage());
}
/**
* Example 02: Force a salesforce Error Object List response, by calling an invalid sObject
*/
try{
utl.Rst callout = new utl.Rst(true)
.setEndpoint('/sobjects/InvalidSObject')
.call();
}catch(utl.Rst.RestUtilException e){
// NOT_FOUND :: The requested resource does not exist
System.debug(e.getMessage());
}
/**
* Example 03: Force a non-JSON Salesforce response
* This returns a 404 with an HTML page as an example
*/
try{
utl.Rst callout = new utl.Rst(true)
.setHandleSfEndpoint(false) // We do not want set the base URL in this case
.setEndpoint('/htmlerrorpage')
.call();
}catch(utl.Rst.RestUtilException e){
// Unexpected character ('<' (code 60)): expected a valid value
System.debug((e.getMessage().length() > 100) ? (e.getMessage().substring(0,100) + '[...]') : e.getMessage());
}
/**
* Example 01: Call the data cloud ingestion api through a named credential
* with Salesforce error handling enabled, endpoint comes from Named credential
*/
// Execute the call out for a specific named credential
utl.Rst callout = new utl.Rst('[NAMED_CREDENTIAL_NAME]')
.setHandleSfResponse(true) // Handle the response automatically
.setEndpoint('/api/v1/ingest/sources/ingestionConnectorName/objectName')
.setMethod('POST')
.setBody('{... payload ...}')
.call();
/**
* Example 02: Call the data cloud query api through a named credential
* with Salesforce error handling enabled, endpoint comes from Named credential
*/
// Execute the call out for a specific named credential
utl.Rst callout = new utl.Rst('[NAMED_CREDENTIAL_NAME]')
.setHandleSfResponse(true) // Handle the response automatically
.setEndpoint('/api/v2/query')
.setMethod('POST')
.setBody('{"sql" : "select 1"}')
.call();