Our recent Engage presentation of the new 19.6 transactional script steps purposefully focused on the topic of error handling, because once you’ve put the transactional “bookends” in your script, your focus immediately turns to errors: handling, throwing, and passing them back to calling scripts or processes.

The Contract

When you add Set Error Capture On to a script, you’ve entered a contract with FileMaker: “don’t show the  default error dialogs, I’ll handle all the errors.”

At our presentation, I joked that this script step doesn’t prevent errors – if only! –  which got a good laugh. The fact is errors will happen and they aren’t necessarily caused by coding errors. For example, in a multi-user environment a user may attempt to edit a record that is in use by another user. This is not something that can be avoided, although designing the UX to limit direct editing of records can help reduce the opportunities where this would occur. Ideally, you provide a user-friendly message when an edit script encounters this error. Another example of an error that you cannot avoid is receiving a payload for an order from a webhook and not finding the customer record that needs to exist for that order. That may actually be fine, and your scripting logic may branch to create a new customer record.

Your goal with scripting, transactional or not, is not to avoid errors. Your job is to trap them and handle them. Let’s take a look at those terms.

Trapping, Throwing, and Parsing

There are some terms that are used to describe error management that would be helpful to define:

  • Trapping – this is the process of detecting an error. Also called “capturing.” The error.GetLast custom function is useful here.
  • Throwing – this is when you create an error. Perhaps the data is in a state that matches a business rule and if so, that needs to be treated as an error. In our presentation I used the scenario that as per business rules, a new work order cannot be created for a client on credit hold. If that is found to be true, then the new work order script “throws” an error. We’ll see that the Revert Transaction script step is a great way to capture those errors and return them back to the calling script. The error.Set custom function is useful here.
  • Parsing – often an error is bundled together with other useful information. We’ve published error custom functions to help gather all that data and package values into a consistent JSON object. Once you have that structure, you can easily pick and choose, parse, the values from that object to branch a script, show a dialog with a message to the user, or return a code to a calling process. The error.GetCode is useful here.
  • Error guards – Conditional logic that typically translates to, “if there’s not an error…do this.” Often used to skip additional script steps because once you’ve detected an error you typically want to jump to the end and return that error.

FileMaker Errors and Transactional Script Behavior

We are excited about the new transactional script steps because they help to trap errors for you. If the script encounters a scripting engine or database error after an Open Transaction, it will jump right to the Commit Transaction script step, auto-revert the transaction and report the error to the GetLastError flag. Additionally, some errors are only caught at the Commit Transaction step. These include errors that depend on the index being updated, such as validation for Unique Value Only, Existing or validation by calculation since that logic is evaluated on commit. No longer do you need to wrap many script steps with error guards.

Throwing an Error and the Revert Transaction Script Step

As mentioned above, sometimes you’d like to declare an error, and have the script abandon its process and return all data and state back to what it was before you attempted the process. That is the joy of transactional scripting. Let’s look closely at the pattern:

  1. Declare/Throw the error. Here’s a block of code that demonstrates a business rule was found to be true and when true, we’d like the script to skip to the end and revert all attempted record changes.
  • Line 96 receives back the Response from a Customer query.
  • Line 97 parses the response for the property “OnCreditHold”
  • Line 99 preps an error object that we’ll return to the calling script. Notice we include the script step where the logical error occurred, 97. That is where we capture the OnCreditHold fact.
  • Line 101 uses the Revert Transaction script step, that if the Condition is True (or empty), will jump the script to the Commit Transaction and revert. It will deliver our error message that we can use to display to the user as to why the create work order failed.

This pattern is repeated as many times as we need to check our data against a business rule. That is, you can have more than one Revert Transaction in a transaction block and if it has a true or empty condition it will skip to the Commit Transaction.

Helpful Error Custom Functions 

Consistency is very important with error handling because you often pass the error to another script or process and that calling script/process needs a reliable structure so that it can predictably parse the response.

We’ve published our error custom functions here: https://github.com/proofgeist/errorCfx

error.Set

This function is used to create a JSON error object with these properties:

JSONSetElement ( 
		"{}" ; 
		[ "code" ;  ~errorCode ; JSONNumber ] ;
		[ "text" ;  ~errorMessage ; JSONString ] ;
		[ "scriptName" ;  ~scriptName ; JSONString ] ;
		[ "scriptStep" ;  scriptStep ; JSONString ] ;
		[ "environment" ; getScriptEnvironment ; JSONObject ]
	)
{
  "code": 301,
  "text": "a message string",
  "scriptName": "the script that was running",
  "scriptStep": "the name of the script step or logical step where the error occurred",
  "environment": {
    "filename": "the result of Get (FileName),",
    "scriptName": "the result of Get (ScriptName),",
    "layoutName": "the result of Get (LayoutName),",
    "systemPlatform": "the result of Get (SystemPlatform),",
    "systemVersion": "the result of Get (SystemVersion)"
  }
}

error.GetLast

This function is used to capture the last error that occurred. This function includes whatever FileMaker wrote to the GetLastError flag and additional helpful state info. 

JSONSetElement (
			"{}" ;
			[ "code" ; __errorCode ; JSONNumber ] ;
			[ "text" ; __message ; JSONString ] ;
			[ "scriptName" ; Get ( ScriptName ) ; JSONString ] ;
			[ "scriptStep" ; null ; JSONNull ] ;
			[ "lineNumber" ; null ; JSONNull ] ;
			[ "environment" ; getScriptEnvironment ; JSONObject ]
		)

error.GetCode

This function helps parse out from an error object the code property, which is often the key value. By convention, code=0 means no error.

GetAsNumber(  JSONGetElement(errorObject; "code" ) )

This code block below shows our pattern for parsing a script result to examine the code property of the returned error object. We have chosen to include an error object in the response even if the code=0.

Trapping errors at the Commit Transaction Step

As mentioned above, when you use the 19.6 transaction scripts steps, FileMaker will capture any script engine or database engine errors and jump to the Commit Transaction script step, revert the transaction and write the errors to the Get(LastError) flag. That is to say, a lot happens at that step which is why the error.GetLast custom function is designed to capture a bevy of useful information for you if you use this scripting pattern:

As you can see on line 176, immediately after the Commit Transaction script step, we capture the error, if the script is not a subscript to a transaction. If it is a subscript, we simply set the $lastError to itself. Either way, the $lastError is returned to the calling script, packaged in the $result JSON.

New 19.6 Error Functions

Included in the error.GetLast custom function are two new error functions that were released in version 19.6:

Get ( LastErrorDetail )

This function returns different information depending on the context in which it is used. When used within a transaction, it returns the failing script, the failing script step, and the script line number. If the failure occurs in a subscript, the subscript name is given instead of the calling script name. 

Get ( LastErrorLocation )

This function returns different information depending on the context in which it is used. When used within a transaction, it returns the failing script and the script line number. It differs from Get ( LastErrorDetail ) in that it returns the line number of the Commit Transaction script step, and so you’ll see that the error.GetLast() uses the line number from Get ( LastErrorDetail) when it can. 

Additionally, this error function includes any custom string entered in the Revert Transaction script step if the custom error entered is in the 5000 to 5499 range. The error.GetLast custom function accounts for this additional information and returns any custom message – which can be json – if provided. Otherwise, the error object’s message falls back to the message supplied by the developer. If that is also empty, the custom function uses the error.Text() custom function which is a library of the FileMaker error code descriptions. In all cases, the resulting error object will have a value for the property “text.” 

Conclusion

The transaction script steps and error functions introduced in 19.6 simplify not only the schema needed for successful transactions, but also the error handling that you’ll need to do. With the custom functions we’ve released, you can be assured of a consistent shape of the error objects returned from a script.