I am working on Salesforce project heavily depending on use of Tooling API (we are executing tests on remote SF organizations and gathering results).
What bothers me is I cannot use polymporphism when calling Tooling API via SOAP.
Even though I use most of the time one SOAP method query
, for every object I have to create separate method and separate schema response element, for example:
public ToolingApi.QueryResultApexCodeCoverage queryApexCodeCoverage(String queryString) {
if (Test.isRunningTest()) {
Test.setMock(WebServiceMock.class, new MockResponses.queryApexCodeCoverageMock());
}
ToolingApi.query_element request_x = new ToolingApi.query_element();
request_x.queryString = queryString;
ToolingApi.queryResponseApexCodeCoverage_element response_x;
Map<String, ToolingApi.queryResponseApexCodeCoverage_element> response_map_x = new Map<String, ToolingApi.queryResponseApexCodeCoverage_element>();
response_map_x.put('response_x', response_x);
WebServiceCallout.invoke(
this,
request_x,
response_map_x,
new String[]{
endpoint_x,
'',
'urn:tooling.soap.sforce.com',
'query',
'urn:tooling.soap.sforce.com',
'queryResponse',
'ToolingApi.queryResponseApexCodeCoverage_element'
}
);
response_x = response_map_x.get('response_x');
return response_x.result;
}
Schema:
public class queryResponseApexTestResult_element {
public ToolingApi.QueryResultApexTestResult result;
...
}
public class QueryResultApexTestResult {
public Boolean done;
public String entityTypeName;
public String nextRecordsUrl;
public String queryLocator;
public ToolingApi.ApexTestResult[] records;
public Integer size;
public Integer totalSize;
...
}
It would be simpler, better and shorter code to have just one of these for every query. I tried to use different tricks here, for example using generic QueryResult
with public sObject[] records
and then casting it to class that is expected to be return, I also tried calling Tooling API via REST and playing a bit with JSON.deserializeUntyped()
– none of these worked for me. Any experience with such cases? What could be solution to simplify the code?
Best Answer
As you found, if you just use the direct QueryResult against the SOAP API and include anything in the fields beyond the
ID
you get the following response:SOQL:
The problem is that the underlying
WebServiceCallout.invoke
can't unpack the specific sObject into the genericQueryResults.records
sObject as it doesn't have a generic set of fields like the Partner API does.E.g. The Name field here is a specific extension element over
ens:sObject
.It's a pain, but you could carry on with your current approach of breaking out the specific classes that extend ens:sObject (plus the associated *_element to unpack it).
In terms of classes that extend sObject, pretty much everything you can
query
falls into that category. Let's look at ApexClass.So to actually use that from Apex you need the the class to deseralize into. Last I checked the built in version of Wsdl2Apex won't generate these classes for you. It needs to be generated with both the fields from the parent complexType and the child fields.
There are ways to automate the generation of these classes from the WSDL.
One thing you might want to investigate is the apex-toolingapi repo. It has a pretty generic query method implementation returning a QueryResult.
Yet somehow they don't run into the "Apex type not found" issues and can then generically unpack to the required class type.
How have they performed this miraculously feat of deserialization? Are they wizards?
Spoiler: