3

My original problem is described here:

ToolingAPI.cls for Apex: How to get CompilerErrors at ContainerAsyncRequest in v31+?

Now I have patched ToolingAPI.cls like this:

// elf-patch
public class DeployDetails {
    public String                 componentFailures;
}

// elf-patch
public class ContainerAsyncRequest extends SObject_x implements ISerialize {
    //public String                  compilerErrors; // elf-patch
    public DeployDetails           deployDetails; // elf-patch
    public String                  errorMsg;
    public boolean                 isCheckOnly;
    public Boolean                 isDeleted;
    public Boolean                 isRunTests;
    public MetadataContainer       metadataContainer;
    public Id                      metadataContainerId;
    public MetadataContainerMember metadataContainerMember;
    public Id                      metadataContainerMemberId;
    public String                  state;
    public ContainerAsyncRequest() {
        super(SObjectType.ContainerAsyncRequest);
    }
    public override void serialize(JSONGenerator jsonGen) {
        super.serialize(jsonGen);
        // if(compilerErrors!=null)                                             // elf-patch
        //     jsonGen.writeStringField('compilerErrors', compilerErrors);      // elf-patch
        if(deployDetails!=null)                                             // elf-patch 
            jsonGen.writeObjectField('deployDetails', deployDetails);       // elf-patch
        if(errorMsg!=null)
            jsonGen.writeStringField('errorMsg', errorMsg);
        if(isCheckOnly!=null)
            jsonGen.writeBooleanField('isCheckOnly', isCheckOnly);
        if(isDeleted!=null)
            jsonGen.writeBooleanField('isDeleted', isDeleted);
        if(isRunTests!=null)
            jsonGen.writeBooleanField('isRunTests', isRunTests);
        if(metadataContainer!=null)
            jsonGen.writeObjectField('metadataContainer', metadataContainer);
        if(metadataContainerId!=null)
            jsonGen.writeStringField('metadataContainerId', metadataContainerId);
        if(metadataContainerMember!=null)
            jsonGen.writeObjectField('metadataContainerMember', metadataContainerMember);
        if(metadataContainerMemberId!=null)
            jsonGen.writeStringField('metadataContainerMemberId', metadataContainerMemberId);
        if(state!=null)
            jsonGen.writeStringField('state', state);
    }        
}

I check for the async request status like this:

    ToolingApi.ContainerAsyncRequest containerAsyncRequest = ((List<ToolingAPI.ContainerAsyncRequest>) tool.query(
                'SELECT Id, State, MetadataContainerId, ErrorMsg, DeployDetails ' + // pre-v31 it was CompilerErrors instead of DeployDetails
                'FROM ContainerAsyncRequest ' + 
                'WHERE Id = \'' + AsyncRequestTooling.Id + '\''
    ).records)[0];

As an unfortunate, this results in an error-message like this:

Illegal value for primitive

Error is in expression '{!checkAsyncTooling}' in page elfcodefusion:elfapexeditor: (System Code)
Class.elfCodeFusion.ToolingAPI.getQueryResultRecords: line 2018, column 1
Class.elfCodeFusion.ToolingAPI.parseQueryResult: line 2000, column 1
Class.elfCodeFusion.ToolingAPI.query: line 125, column 1
Class.elfCodeFusion.elfApexEditor.checkAsyncTooling: line 291, column 1

So my question is: how can I retrieve DeployDetails?

2

I think the problem is the definition of the DeployDetials class.

The example response from the documentation appears as:

<DeployDetails>
<componentFailures>
<lineNumber>5</lineNumber>
<fullName>myApex</fileName> <problem>invalid name 'abc'</problem>
</componentFailures>
<componentFailures>
<lineNumber>10</lineNumber>
<fullName>myApex2</fileName>
<problem>invalid type 'hello'</problem>
</componentFailures>
</DeployDetails>

My version was generated directly from the v31.0 Tooling WSDL in .NET and appears as: (modified for readability)

public partial class DeployDetails {
    public DeployMessage[] componentFailures { get; set; }
    public DeployMessage[] componentSuccesses { get; set; }
    public RunTestsResult runTestResult { get; set; }
}

This corresponds to the WSDL definition:

<xsd:complexType name="DeployDetails">
 <xsd:sequence>
  <xsd:element name="componentFailures" minOccurs="0" maxOccurs="unbounded" type="tns:DeployMessage"/>
  <xsd:element name="componentSuccesses" minOccurs="0" maxOccurs="unbounded" type="tns:DeployMessage"/>
  <xsd:element name="runTestResult" minOccurs="0" type="tns:RunTestsResult"/>
 </xsd:sequence>
</xsd:complexType>
<xsd:complexType name="DeployMessage">
 <xsd:sequence>
  <xsd:element name="changed" type="xsd:boolean"/>
  <xsd:element name="columnNumber" minOccurs="0" type="xsd:int"/>
  <xsd:element name="componentType" minOccurs="0" type="xsd:string"/>
  <xsd:element name="created" type="xsd:boolean"/>
  <xsd:element name="createdDate" type="xsd:dateTime"/>
  <xsd:element name="deleted" type="xsd:boolean"/>
  <xsd:element name="fileName" type="xsd:string"/>
  <xsd:element name="fullName" type="xsd:string"/>
  <xsd:element name="id" minOccurs="0" type="xsd:string"/>
  <xsd:element name="lineNumber" minOccurs="0" type="xsd:int"/>
  <xsd:element name="problem" minOccurs="0" type="xsd:string"/>
  <xsd:element name="problemType" minOccurs="0" type="tns:DeployProblemType"/>
  <xsd:element name="success" type="xsd:boolean"/>
 </xsd:sequence>
</xsd:complexType>

<xsd:complexType name="RunTestsResult">
    <xsd:sequence>
     <xsd:element name="codeCoverage" minOccurs="0" maxOccurs="unbounded" type="tns:CodeCoverageResult"/>
     <xsd:element name="codeCoverageWarnings" minOccurs="0" maxOccurs="unbounded" type="tns:CodeCoverageWarning"/>
     <xsd:element name="failures" minOccurs="0" maxOccurs="unbounded" type="tns:RunTestFailure"/>
     <xsd:element name="numFailures" type="xsd:int"/>
     <xsd:element name="numTestsRun" type="xsd:int"/>
     <xsd:element name="successes" minOccurs="0" maxOccurs="unbounded" type="tns:RunTestSuccess"/>
     <xsd:element name="totalTime" type="xsd:double"/>
    </xsd:sequence>
</xsd:complexType>

I've put a full version of my ToolingAPI v32.0 Apex class in https://gist.github.com/FishOfPrey/083ee0b11db8fbd98807

The relevant parts are (edited by @UweHeim):

public class DeployDetails {
    public ToolingAPI.DeployMessage[] componentFailures;
    public ToolingAPI.DeployMessage[] componentSuccesses;
    public ToolingAPI.RunTestsResult runTestResult;
    private String[] componentFailures_type_info = new String[]{'componentFailures','urn:tooling.soap.sforce.com','DeployMessage','0','-1','false'};
    private String[] componentSuccesses_type_info = new String[]{'componentSuccesses','urn:tooling.soap.sforce.com','DeployMessage','0','-1','false'};
    private String[] runTestResult_type_info = new String[]{'runTestResult','urn:tooling.soap.sforce.com','RunTestsResult','0','1','false'};
    private String[] apex_schema_type_info = new String[]{'urn:tooling.soap.sforce.com','true','false'};
    private String[] field_order_type_info = new String[]{'componentFailures','componentSuccesses','runTestResult'};
}
public class DeployMessage {
    public Boolean changed;
    public Integer columnNumber;
    public String componentType;
    public Boolean created;
    public DateTime createdDate;
    public Boolean deleted;
    public String fileName;
    public String fullName;
    public String id;
    public Integer lineNumber;
    public String problem;
    public String problemType;
    public Boolean success;
    private String[] changed_type_info = new String[]{'changed','urn:tooling.soap.sforce.com','boolean','1','1','false'};
    private String[] columnNumber_type_info = new String[]{'columnNumber','urn:tooling.soap.sforce.com','int','0','1','false'};
    private String[] componentType_type_info = new String[]{'componentType','urn:tooling.soap.sforce.com','string','0','1','false'};
    private String[] created_type_info = new String[]{'created','urn:tooling.soap.sforce.com','boolean','1','1','false'};
    private String[] createdDate_type_info = new String[]{'createdDate','urn:tooling.soap.sforce.com','dateTime','1','1','false'};
    private String[] deleted_type_info = new String[]{'deleted','urn:tooling.soap.sforce.com','boolean','1','1','false'};
    private String[] fileName_type_info = new String[]{'fileName','urn:tooling.soap.sforce.com','string','1','1','false'};
    private String[] fullName_type_info = new String[]{'fullName','urn:tooling.soap.sforce.com','string','1','1','false'};
    private String[] id_type_info = new String[]{'id','urn:tooling.soap.sforce.com','string','0','1','false'};
    private String[] lineNumber_type_info = new String[]{'lineNumber','urn:tooling.soap.sforce.com','int','0','1','false'};
    private String[] problem_type_info = new String[]{'problem','urn:tooling.soap.sforce.com','string','0','1','false'};
    private String[] problemType_type_info = new String[]{'problemType','urn:tooling.soap.sforce.com','DeployProblemType','0','1','false'};
    private String[] success_type_info = new String[]{'success','urn:tooling.soap.sforce.com','boolean','1','1','false'};
    private String[] apex_schema_type_info = new String[]{'urn:tooling.soap.sforce.com','true','false'};
    private String[] field_order_type_info = new String[]{'changed','columnNumber','componentType','created','createdDate','deleted','fileName','fullName','id','lineNumber','problem','problemType','success'};
}
public class RunTestsResult {
    public ToolingAPI.CodeCoverageResult[] codeCoverage;
    public ToolingAPI.CodeCoverageWarning[] codeCoverageWarnings;
    public ToolingAPI.RunTestFailure[] failures;
    public Integer numFailures;
    public Integer numTestsRun;
    public ToolingAPI.RunTestSuccess[] successes;
    public Double totalTime;
    private String[] codeCoverage_type_info = new String[]{'codeCoverage','urn:tooling.soap.sforce.com','CodeCoverageResult','0','-1','false'};
    private String[] codeCoverageWarnings_type_info = new String[]{'codeCoverageWarnings','urn:tooling.soap.sforce.com','CodeCoverageWarning','0','-1','false'};
    private String[] failures_type_info = new String[]{'failures','urn:tooling.soap.sforce.com','RunTestFailure','0','-1','false'};
    private String[] numFailures_type_info = new String[]{'numFailures','urn:tooling.soap.sforce.com','int','1','1','false'};
    private String[] numTestsRun_type_info = new String[]{'numTestsRun','urn:tooling.soap.sforce.com','int','1','1','false'};
    private String[] successes_type_info = new String[]{'successes','urn:tooling.soap.sforce.com','RunTestSuccess','0','-1','false'};
    private String[] totalTime_type_info = new String[]{'totalTime','urn:tooling.soap.sforce.com','double','1','1','false'};
    private String[] apex_schema_type_info = new String[]{'urn:tooling.soap.sforce.com','true','false'};
    private String[] field_order_type_info = new String[]{'codeCoverage','codeCoverageWarnings','failures','numFailures','numTestsRun','successes','totalTime'};
}
public class CodeCoverageResult {
    public String id;
    public ToolingAPI.CodeLocation[] locationsNotCovered;
    public String name;
    public String namespace;
    public Integer numLocations;
    public Integer numLocationsNotCovered;
    public String type_x;
    private String[] id_type_info = new String[]{'id','urn:tooling.soap.sforce.com','ID','1','1','false'};
    private String[] locationsNotCovered_type_info = new String[]{'locationsNotCovered','urn:tooling.soap.sforce.com','CodeLocation','0','-1','false'};
    private String[] name_type_info = new String[]{'name','urn:tooling.soap.sforce.com','string','1','1','false'};
    private String[] namespace_type_info = new String[]{'namespace','urn:tooling.soap.sforce.com','string','1','1','true'};
    private String[] numLocations_type_info = new String[]{'numLocations','urn:tooling.soap.sforce.com','int','1','1','false'};
    private String[] numLocationsNotCovered_type_info = new String[]{'numLocationsNotCovered','urn:tooling.soap.sforce.com','int','1','1','false'};
    private String[] type_x_type_info = new String[]{'type','urn:tooling.soap.sforce.com','string','1','1','false'};
    private String[] apex_schema_type_info = new String[]{'urn:tooling.soap.sforce.com','true','false'};
    private String[] field_order_type_info = new String[]{'id','locationsNotCovered','name','namespace','numLocations','numLocationsNotCovered','type_x'};
}
public class CodeCoverageWarning {
    public String id;
    public String message;
    public String name;
    public String namespace;
    private String[] id_type_info = new String[]{'id','urn:tooling.soap.sforce.com','ID','1','1','false'};
    private String[] message_type_info = new String[]{'message','urn:tooling.soap.sforce.com','string','1','1','false'};
    private String[] name_type_info = new String[]{'name','urn:tooling.soap.sforce.com','string','1','1','true'};
    private String[] namespace_type_info = new String[]{'namespace','urn:tooling.soap.sforce.com','string','1','1','true'};
    private String[] apex_schema_type_info = new String[]{'urn:tooling.soap.sforce.com','true','false'};
    private String[] field_order_type_info = new String[]{'id','message','name','namespace'};
}
public class CodeLocation {
    public Integer column;
    public Integer line;
    public Integer numExecutions;
    public Double time_x;
    private String[] column_type_info = new String[]{'column','urn:tooling.soap.sforce.com','int','1','1','false'};
    private String[] line_type_info = new String[]{'line','urn:tooling.soap.sforce.com','int','1','1','false'};
    private String[] numExecutions_type_info = new String[]{'numExecutions','urn:tooling.soap.sforce.com','int','1','1','false'};
    private String[] time_x_type_info = new String[]{'time','urn:tooling.soap.sforce.com','double','1','1','false'};
    private String[] apex_schema_type_info = new String[]{'urn:tooling.soap.sforce.com','true','false'};
    private String[] field_order_type_info = new String[]{'column','line','numExecutions','time_x'};
}
public class RunTestFailure {
    public String id;
    public String message;
    public String methodName;
    public String name;
    public String namespace;
    public String stackTrace;
    public Double time_x;
    public String type_x;
    private String[] id_type_info = new String[]{'id','urn:tooling.soap.sforce.com','ID','1','1','false'};
    private String[] message_type_info = new String[]{'message','urn:tooling.soap.sforce.com','string','1','1','false'};
    private String[] methodName_type_info = new String[]{'methodName','urn:tooling.soap.sforce.com','string','1','1','true'};
    private String[] name_type_info = new String[]{'name','urn:tooling.soap.sforce.com','string','1','1','false'};
    private String[] namespace_type_info = new String[]{'namespace','urn:tooling.soap.sforce.com','string','1','1','true'};
    private String[] stackTrace_type_info = new String[]{'stackTrace','urn:tooling.soap.sforce.com','string','1','1','true'};
    private String[] time_x_type_info = new String[]{'time','urn:tooling.soap.sforce.com','double','1','1','false'};
    private String[] type_x_type_info = new String[]{'type','urn:tooling.soap.sforce.com','string','1','1','false'};
    private String[] apex_schema_type_info = new String[]{'urn:tooling.soap.sforce.com','true','false'};
    private String[] field_order_type_info = new String[]{'id','message','methodName','name','namespace','stackTrace','time_x','type_x'};
}
public class RunTestSuccess {
    public String id;
    public String methodName;
    public String name;
    public String namespace;
    public Double time_x;
    private String[] id_type_info = new String[]{'id','urn:tooling.soap.sforce.com','ID','1','1','false'};
    private String[] methodName_type_info = new String[]{'methodName','urn:tooling.soap.sforce.com','string','1','1','false'};
    private String[] name_type_info = new String[]{'name','urn:tooling.soap.sforce.com','string','1','1','false'};
    private String[] namespace_type_info = new String[]{'namespace','urn:tooling.soap.sforce.com','string','1','1','true'};
    private String[] time_x_type_info = new String[]{'time','urn:tooling.soap.sforce.com','double','1','1','false'};
    private String[] apex_schema_type_info = new String[]{'urn:tooling.soap.sforce.com','true','false'};
    private String[] field_order_type_info = new String[]{'id','methodName','name','namespace','time_x'};
}
12
  • Cool! This looks way better than what I came up with... – Uwe Heim Nov 30 '14 at 18:33
  • However according to the manual, it should be a plain string, doesn't it? salesforce.com/us/developer/docs/api_tooling/Content/… – Uwe Heim Nov 30 '14 at 18:35
  • How would you translate DeployMessage[] componentFailures into Apex? Just String[] componentFailures or is this again a complex type? – Uwe Heim Nov 30 '14 at 18:42
  • @UweHeim In this case I think the documentation is wrong. I'd go with the WSDL definition where the members of DeployDetails are complex types. – Daniel Ballinger Nov 30 '14 at 18:42
  • @UweHeim Are you trying to call the Tooling API from Apex? I have a generated Apex class for the Tooling API. – Daniel Ballinger Nov 30 '14 at 18:43
1

I found a very ugly solution, which is surly based on my incomplete understanding of ToolingAPI internals. So what I have now brings me results and it goes like this (I know, it hurts):

I have created a praserless ToolingApi.queryAsJSON, because the reason for the issue is the parsing of the result - not the call and the result itself:

public string queryAsJSON(String queryString) {
    HttpResponse response = submitRestCall('/query/?q=' + EncodingUtil.urlEncode(queryString, 'UTF-8'));
    return response.getBody();
}

Then I query twice: first using the tool.query() command like before. If there is an error, I query again using my parserless method:

if(containerAsyncRequest.State == 'Failed') {
    string a_result = tool.queryAsJSON(
        'SELECT Id, State, MetadataContainerId, ErrorMsg, DeployDetails ' + 
        'FROM ContainerAsyncRequest ' + 
        'WHERE Id = \'' + AsyncRequestTooling.Id + '\''
    );
}

Now I've got the error message finally. I simply pipe the entire JSON into a property, which I put on the page ready to parse with JavaScript.

So a followup question remains: is there any way to do it better?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.