question

jeff0rosenberg avatar image
jeff0rosenberg asked

Cloudscript: How do I properly return an error to the client? My errors end up in FunctionResult instead of Error!

I'm creating a system for equipping items to a player. I'm referencing this example for how to do write the CloudScript end of it. I've got the items equipping just fine, but I want to wrap this up by adding in my error handling (invalid item, item isn't owned, item is already equipped, etc).

The example I followed throws an exception when various things happen. One of them is for a null or missing argument:

if(args == null || typeof args.referralCode === undefined || args.referralCode === "")
{
    throw "Failed to redeem. args.referralCode is undefined or blank";
}

This throws an exception which is all wrapped in a try/catch, ending with:

catch(e) {
   var returnObj = {};
   returnObj["errorDetails"] = "Error: " + e;
   return returnObj;
}

In the client code from the referenced example (ln 151), these exceptions appear to end up in result.Error:

if(result.Error != null)
{
      Debug.LogError(string.Format("{0} -- {1}", result.Error, result.Error.Message));
      return;
}

I'm having trouble getting this to work in practice. My exceptions end up in the FunctionResult object instead of in the Error object.

Here's my CloudScript:

// Get an item from an array of items
function GetItem(container, itemId) {
    for(var index in container) {
        if(container[index].ItemId === itemId)
            return container[index];
    }
    return null;
}

// Equip an item to the current player.
// Args:
//    itemId: the string item Id of the item to be equipped
handlers.equipItem = function(args) {
    try {
        // Check to make sure args are correct
        if(args == null || typeof args.itemId === undefined || args.itemId == "") {
            throw "Failed to find item. args.itemId is undefined or blank.";
        }

        // Check if the player owns the item
        var getUserInventoryRequest = {
            PlayFabId: currentPlayerId
        };
        var getUserInventoryResult = server.GetUserInventory(getUserInventoryRequest);
        var item = GetItem(getUserInventoryResult.Inventory, args.itemId);
        if(item == null) {
            throw "Player does not own this item.";
        }

        // Equip the item
        var updateUserReadOnlyDataRequest = {
            PlayFabId: currentPlayerId,
            Data: {}
        };
        updateUserReadOnlyDataRequest.Data[item.ItemClass] = item.ItemId;
        var updateUserReadOnlyDataResult = server.UpdateUserReadOnlyData(updateUserReadOnlyDataRequest);
        log.info("Equipped Item '" + item.ItemId + "'' to Player '" + currentPlayerId + "'' in Slot '" + item.ItemClass + "'.");

        // Return success
        var returnObj = {};
        returnObj["FunctionResult"] = "Successfully equipped Item.";
        return returnObj;
    } catch(e) {
        // Return failure
        var returnObj = {};
        returnObj["errorDetails"] = "Error: " + e;
        return returnObj;
    }
}

I then make the request in Unity like this:

public void EquipItem(string id)
{
    var request = new ExecuteCloudScriptRequest()
    {
        FunctionName = "equipItem",
        FunctionParameter = new {
            itemId = id
        }
    };

    System.Action<ExecuteCloudScriptResult> successHandler = delegate(ExecuteCloudScriptResult obj)
    {
        Debug.LogFormat("Success! Return: {0}, Error: {1}", obj.FunctionResult, obj.Error);
    };

    System.Action<PlayFabError> errorHandler = delegate(PlayFabError obj)
    {
        // General error (execution)
        Toolbox.DialogWindow.ShowPlayFabError(obj);
    };
    PlayFabClientAPI.ExecuteCloudScript(request, successHandler, errorHandler);

}

My client-side logs:

Success! Return: {"errorDetails":"Error: Player does not own this item."}, Error: 
Success! Return: {"FunctionResult":"Successfully equipped Item."}, Error: 
Success! Return: {"errorDetails":"Error: Player does not own this item."}, Error: 

I get the error properly, but not in the right place. In every case the error or success response ends up in the FunctionResult object and the Error object is always null. I could easily differentiate between the results, but I'd rather just have the errors end up in the Error object like they should. How do I have a CloudScript function return an error in ExecuteCloudScriptResult.Error?

I've also got a few small but closely related questions:

  1. Where do I view the CloudScript log created by log.info, log.debug, log.error, etc?
  2. Is there a specific test environment for working on CloudScript? I'd prefer not creating tons of small revisions if I can avoid it.
CloudScript
1 comment
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

jeff0rosenberg avatar image jeff0rosenberg commented ·

I'd like to add, I'm aware that my success return should probably be nothing in this case, or maybe a confirmation of the item's ID and slot. Something like:

var returnObj = {};
returnObj["itemId"] = item.ItemId;
returnObj["slot"] = item.ItemClass;
return returnObj;

It is the way it is since I'm still testing :)

0 Likes 0 ·

1 Answer

·
brendan avatar image
brendan answered

That's an error in the example - I'm opening a bug for the tools team to correct it. If you're catching all errors yourself in the script, the script will complete successfully - so, no top-level Error value. In that case, you would need to write your error info into the function results, which is what the Cloud Script example you've linked to shows (it's the C# handling of the error return which is incorrect). The best way to manage error handling overall is to use try/catch, collect the info on the issues that occurred, and return it in the function results, so that you can parse it there.

For your other questions:

1. All log text is returned in the ExecuteCloudScriptResult - that's specifically where you would see that info. You can also optionally have your scripts generate PlayStream events using the GeneratePlayStreamEvent parameter, but please note that PlayStream events are limited to 1 KB of custom data.

2. For unit testing of Cloud Script, I would recommend reviewing this thread, where this was discussed in more depth: https://community.playfab.com/questions/3854/recommendations-for-unit-testing-cloud-script.html.

8 comments
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

jeff0rosenberg avatar image jeff0rosenberg commented ·

I can get the error in the right place by removing the try/catch and just throwing my error, but it shows up as a generic JavascriptException:

Debug.LogFormat("Error {0}, {1}, {2}", obj.Error.Error, obj.Error.Message, obj.Error.StackTrace);

This yields:

Error JavascriptException, JavascriptException, 

Is there a specific way to throw the error from CloudScript such that the Error object contains the right Error/Message/StackTrace?

0 Likes 0 ·
brendan avatar image brendan jeff0rosenberg commented ·

That's correct - if you remove the try/catch, you're no longer handling the error in your script, so the error populates the Error in the response. That's actually specifically how you get the error into that part of the response.

0 Likes 0 ·
jeff0rosenberg avatar image jeff0rosenberg brendan commented ·

How would I format that? Throwing a string, for example, results in the above Error/Message/StackTrace ("JavascriptException", "JavascriptException", null). Would I throw a ScriptExecutionError?

0 Likes 0 ·
Show more comments
Show more comments
SL Tech avatar image SL Tech commented ·

Hi,

I'm running into a similar case where a cloudscript error is showing in the response.Error (using Unity C# SDK)

Ex: PlayFab.ClientModels.ScriptExecutionError

Using GeneratePlayStreamEvent=true, I see the error as

 "Error": {
            "Error": "CloudScriptNotFound",
            "Message": "No function named ConsumeInventoryItems was found to execute",
            "StackTrace": ""
        }

Are there specific types of errors that show up in response.Error and others that trigger the error callback?

Please advise. Adding a `if(response.Error != null)` check for every cloudscript call seems tedious.

0 Likes 0 ·
brendan avatar image brendan SL Tech commented ·

Well, that error is saying that the handler you tried to run doesn't exist. So in that case, it's not an error coming from Cloud Script, since one never ran. So yes, that would be in data.Error in the response. Similarly, if there's a basic code issue, like a call to a function in the script with the wrong parameter list (or one that doesn't exist), that would show up the same way. Where you might have exceptions you'd need to check for and handle (as shown above) would largely be on Server API calls, since errors cannot be handled via lambda-type operations or callbacks (the Server API calls are synchronous in Cloud Script).

0 Likes 0 ·

Write an Answer

Hint: Notify or tag a user in this post by typing @username.

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.