In a Salesforce instance we have a web service to be consumed by an external service. Considering the resource that the ws will consume, it has been designed in such a way that for each request received it manages a JSON with a single object.
For several days the external service has been erroneously sending a JSON that did not conform to the interface agreement. This has caused the ws to return an error on the request. We are currently facing a high volume of items that have been sent to Salesforce through this ws and could not be processed.
These JSON format elements are identified and after their adjustment they are required to be processed by the ws. This process is tedious to perform manually, so it has been decided to elaborate a JavaScript script using the JSForce library.
The library offers a number of utilities including Apex REST. The documentation indicates that it allows us to do the following:
If you have a static Apex class in Salesforce and are exposing it using "Apex REST" feature, you can call it by using
Apex#get(path)
,Apex#post(path, body)
,Apex#put(path, body)
,Apex#patch(path, body)
, andApex#del(path, body)
(or its synonymApex#delete(path, body)
) throughapex
API object in connection object.
In their documentation they give us an example of use. We adapted it and developed the following function.
/**
* Function to process leads through requests to the Salesforce endpoint.
* @param {jsforce.Connection} conn The connection established with Salesforce.
* @param {Object[]} leads Leads to be processed.
* @returns {Promise<PromiseSettledResult[]>} A promise that is resolved by the results of individual promises.
*/
async function processLeads(conn, leads) {
const promises = leads.map((lead) => {
return new Promise((resolve, reject) => {
conn.apex.post(endpoint, lead, (error, data) => {
if (error) {
reject(`${JSON.stringify(lead)};Error;${JSON.stringify(error)}`);
} else {
resolve(`${JSON.stringify(lead)};Success;${JSON.stringify(data)}`);
}
});
});
});
return Promise.allSettled(promises);
}
What is special about the above code snippet?
The functions resolve
and reject
make use of the Template literals functionality to elaborate a text string that will be stored as a result in a csv file.
What is in this string?
That string will contain three elements separated by the ;
character.
The first element (first column) contains the JSON that has been sent to the ws.
The second element (second column) contains the result of the request.
Success when the request has been processed successfully.
Error when the request was not processed successfully.
The third and last element (third column) contains the JSON with the response from the ws.
These three elements are useful for the following:
Know which JSON has been processed successfully and which have not.
Relate the JSON returned by the ws with the JSON that has been sent.
When processed successfully the ws returns the Id of the record.
When not processed successfully the ws returns an error defined in the interface agreement.
Another function in the script (next code fragment) is responsible for 'formatting' the result of the previous function so that the collected information can be dumped to the csv file.
/**
* Function for formatting promise results in a specific format.
* @param {PromiseSettledResult[]} results Results of the promises.
* @returns {string[]} An array of strings with formatted results.
*/
function formatResults(results) {
return results.map((result) => {
return result.status === "fulfilled" ? result.value : result.reason;
});
}
On this point I run into a problem that I have not yet been able to solve.
When the promise status is other than fulfilled (i.e. rejected status), it will pick up the text string conformed by the reject
function from the promise generated in the processLeads
function.
Using the JSON.stringify
method on the error
object in the reject
function, the converted string does not contain the message returned by the ws, but a generic
message with the error code;
{"name":"ERROR_HTTP_400","errorCode":"ERROR_HTTP_400"}
To see the content of this object, I have introduced the console.log(error)
method before the reject
function. Result is different from the one I get after the conversion performed with the JSON.stringify
method.
ERROR_HTTP_400: {"status":"error","offerId":null,"errors":[{"message":"The following required fields are missing: origin","errorCode":"INVALID_FIELD"}],"accountId":null} ... { errorCode: 'ERROR_HTTP_400' }
If the content of the reject
function is replaced by the creation of a new Error
object (reject(new Error(error))
), the content retrieved by the formatResults
function includes the message returned by the ws.
Error: ERROR_HTTP_400: {"status":"error","offerId":null,"errors":[{"message":"The following required fields are missing: origin","errorCode":"INVALID_FIELD"}],"accountId":null} ... }
The difference in what we get with the JSON.stringify
method is due to the fact that this method iterates only over own and enumerable properties as explained here.
So how could I produce a text string containing the three elements listed above and the third element would have the message returned from ws?