Search code examples
c#google-ads-apijscriptclearscript

Trapping Adwords errors in JScript when called from ClearScript?


Context: VS2015 Community; C#; ClearScript.V8.5.4.5; Google.AdWords.18.25.0

For background on this posting, see an earlier posting (Kudos, by the way, to @BitCortex for solving the first conundrum.)

I now have a working Adwords mutation scripted in JScript via ClearScript and C#. The challenge now is to handle errors.

In the following chunk of code, I'm creating a new BudgetOrder:

var order = new BudgetOrder();
order.billingAccountId = acct.id;
order.startDateTime = "20160801 000000 Australia/Perth";
order.endDateTime = "20160831 235959 Australia/Perth";

var amt = new Money();
amt.microAmount = 10000000;
order.spendingLimit = amt;

var boo = new BudgetOrderOperation();
boo.operator = Operator.ADD;
boo.operand = order;
var mutations = ToTypedArray(BudgetOrderOperation, [boo]);

var response;
try {
  response = bos.mutate(mutations);
  Console.WriteLine(response.value[0].billingAccountId);
  Console.WriteLine(response.value[0].id);
  Console.WriteLine(response.value[0].lastRequest.status.ToString());
} catch (exc) {
  Console.WriteLine(exc.message);
}

...

function ToTypedArray(typ, arr) {
  var T;
  if ("string" === typeof typ) {
    T = host.type(typ);
  } else {
    T = typ;
  }
  var a = host.newArr(T, arr.length);
  for (var i = 0; i < arr.length; i++) {
    a.SetValue(arr[i], i);
  }
  return a;
}

The problem I'm having at the moment is that if there is an error, exc doesn't have anything useful in it apart from

exc
{...}
    description: ""
    message: ""
    name: "Error"
    number: -2146190593

for example, and response is undefined.

The usual data that would be available in a BudgetOrderReturnValue running natively in C# is not stored anywhere I can see.

I did try casting the result of the mutate using

response = host.cast(BudgetOrderReturnValue,bos.mutate(mutations));  

but when the error occurs, response is still set as undefined.

I've been able to capture the XML for the mutate having specified

<add name="AdsClientLibs.DetailedRequestLogs" value="All" />

in App.config which gives me a detailed_logs.log in C:\Logs\Adwords. Thus when an error occurs I've been able to go back to that log and see what the error was, e.g.

        <detail>
            <ns2:ApiExceptionFault xmlns="https://adwords.google.com/api/adwords/cm/v201603" xmlns:ns2="https://adwords.google.com/api/adwords/billing/v201603">
                <message>[BudgetOrderError.INVALID_BUDGET_DATE_RANGE @ operations[0].operand.startDateTime.endDateTime; trigger:'Overlapping budget found']</message>
                <ApplicationException.Type>ApiException</ApplicationException.Type>
                <errors xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="ns2:BudgetOrderError">
                    <fieldPath>operations[0].operand.startDateTime.endDateTime</fieldPath>
                    <trigger>Overlapping budget found</trigger>
                    <errorString>BudgetOrderError.INVALID_BUDGET_DATE_RANGE</errorString>
                    <ApiError.Type>BudgetOrderError</ApiError.Type>
                    <ns2:reason>INVALID_BUDGET_DATE_RANGE</ns2:reason>
                </errors>
            </ns2:ApiExceptionFault>
        </detail>

However, none of that data seems to be available to the script.

Ideas, anyone?

LATER

var response;
var hostException;
var succeeded = host.tryCatch(
    function () {
    response = bos.mutate(mutations);
    return true;
  },
    function (exception) {
    hostException = exception;
    return false;
  });
if (succeeded) {
  // process response
  Console.WriteLine(response.value[0].billingAccountId);
  Console.WriteLine(response.value[0].id);
  Console.WriteLine(response.value[0].lastRequest.status.ToString());

} else {
  // handle host exception
  if (host.isType(BudgetOrderError, hostException)) {
    Console.WriteLine("BudgetOrderException");
  } else if (host.isType(ClientTermsError, hostException)) {
    Console.WriteLine("ClientTermsError");
  }
  //...
}

Unfortunately, this doesn't work. The bos.mutate line causes the script to crash with an uncaught error.

NEXT DAY

The output from the EXE running the script:

Exception has been thrown by the target of an invocation.
    at JScript global code (Script Document [temp]:149:0) -> var succeeded = host.tryCatch(
    function () {
    response = bos.mutate(mutations);
    return true;
  },
    function (exception) {
    hostException = exception;
    return false;
  })

The C# code

        string script = File.ReadAllText(scriptSpec);
        try
        {
            answer = JSengine.Evaluate(script);
        }
        catch (ScriptEngineException see)
        {
            Console.WriteLine(see.ErrorDetails);
            ScriptEngineException next = see.InnerException as ScriptEngineException;
            while (next != null)
            {
                Console.WriteLine(next.ErrorDetails);
                next = next.InnerException as ScriptEngineException;
            }
        }
        catch (Exception exc)
        {
            Console.WriteLine(exc.Message);
        }

The JScript code as above. So the ClearScript engine appears not to be doing well with tryCatch.

A COUPLE OF DAYS LATER

I've learned one thing at least out of this: I don't need to put

WindowsScriptEngineFlags.EnableDebugging | WindowsScriptEngineFlags.EnableJITDebugging

into my C# code when instantiating the JScriptEngine object. If there's a debugger; statement in the script, I'll get prompted to start a debugging session.

But back to the script

    debugger;

    var CFG = new Config(Path.Combine(Settings.Item("EXEPath"), "mutator2.cfg"));

    var config = new AdWordsAppConfig();
    config.DeveloperToken = CFG.Retrieve("DeveloperToken");
    config.UserAgent = CFG.Retrieve("UserAgent");
    config.ClientCustomerId = CFG.Retrieve("CustomerID");
    config.RetryCount = 10;

    var user = new AdWordsUser(config);
    user.OAuthProvider.ClientId = CFG.Retrieve("ClientId");
    user.OAuthProvider.ClientSecret = CFG.Retrieve("ClientSecret");
    //user.OAuthProvider.AccessToken = CFG.Retrieve("AccessToken");
    user.Config.OAuth2RefreshToken = CFG.Retrieve("OAuth2RefreshToken");
    try {
      user.OAuthProvider.RefreshAccessToken();
    } catch (ex) {
      Console.WriteLine("RefreshAccessToken failed.");
      Environment.Exit(1);
    }

    var bos = user.GetService(AdWordsService.v201603.BudgetOrderService);
    bos = host.cast(BudgetOrderService, bos);

    //bos.RequestHeader.clientCustomerId = config.ClientCustomerId;
    //bos.RequestHeader.developerToken = config.DeveloperToken;
    //bos.RequestHeader.userAgent = config.UserAgent;

    bas = bos.getBillingAccounts();

    var order = new BudgetOrder();
    order.billingAccountId = CFG.Retrieve("BillingID");
    order.startDateTime = "20160801 000000 Australia/Perth";
    order.endDateTime = "20160830 000000 Australia/Perth";

    var amt = new Money();
    amt.microAmount = 10000000;
    order.spendingLimit = amt;

    var boo = new BudgetOrderOperation();
    boo.operator = Operator.ADD;
    boo.operand = order;
    var mutations = ToTypedArray(BudgetOrderOperation, [boo]);
    // bos.RequestHeader.validateOnly = true;

    var response;
    var hostException;

    var succeeded = host.tryCatch(
        function () {
        response = bos.mutate(mutations);
      },
        function (exception) {
        hostException = exception;
        return true;
      });
    if (succeeded) {
      // process response
      Console.WriteLine(response.value[0].billingAccountId);
      Console.WriteLine(response.value[0].id);
      Console.WriteLine(response.value[0].lastRequest.status.ToString());

    } else {
      // handle host exception
      if (host.isType(BudgetOrderError, hostException)) {
        Console.WriteLine("BudgetOrderException");
      } else if (host.isType(ClientTermsError, hostException)) {
        Console.WriteLine("ClientTermsError");
      }
      //...
    }

    function qq(v, d) {
      if (null === v) {
        return "undefined" === typeof d ? "" : d;
      } else {
        return v;
      }
    }

    function ToTypedArray(typ, arr) {
      var T;
      if ("string" === typeof typ) {
        T = host.type(typ);
      } else {
        T = typ;
      }
      var a = host.newArr(T, arr.length);
      for (var i = 0; i < arr.length; i++) {
        a.SetValue(arr[i], i);
      }
      return a;
    }

The first time through, it works fine. The second time through, with the dates unchanged, throws an AdWords error (date range already taken) which causes the JScriptEngine to throw an unhandled exception error. I get prompted to start a debugging session which on launch shows a dialog containing

Unhandled exception at line 52, column 2 in JScript - script block
0x8013baff - unknown exception

and the highlight on the line response = bos.mutate(mutations);. And this happens whether I've got a debugger; statement or not.

So I'm giving up on the scripting of AdWords using ClearScript. Maybe I should file this as a bug with the folk at ClearScript.


Solution

  • JScript has limited support for host exception handling, but you could try HostFunctions.tryCatch:

    var hostException;
    var succeeded = host.tryCatch(
        function() {
            response = bos.mutate(mutations);
        },
        function(exception) {
            hostException = exception;
            return true;
        }
    );
    if (succeeded) {
        // process response
        ...
    }
    else {
        // handle host exception
        if (host.isType(BudgetOrderError, hostException)) { ... }
        else if (host.isType(ClientTermsError, hostException)) { ... }
        ...
    }
    

    Obviously for this to work you must expose the host exception types (BudgetOrderError et al) via ScriptEngine.AddHostType.