Skip to main content
Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.

Considering An isError() Decision Function In ColdFusion

By Ben Nadel on
Tags: ColdFusion

As I mentioned earlier today, I'm looking to use Rollbar's Java SDK in my Adobe ColdFusion 2021 app (namely, this blog). The Rollbar SDK exposes a fairly simple API. However, that simple API uses a data-type that I almost never think about in my code: java.lang.Throwable. To be clear, I deal with error objects all the time in ColdFusion; but, I'm usually serializing them to the "Standard Error" stream (where they get slurped-up into our log aggregator) - I'm never worrying about the actual data-type and what impact it may have on Java method signatures. It got me thinking about decision functions; and, why there is no isError() built-in function (BIF).

ColdFusion errors are somewhat dynamic. When I catch an error in ColdFusion, I'm pretty sure (but not 100% sure) that the following properties always exist:

  • type
  • message
  • detail
  • extendedInfo
  • stackTrace
  • tagContext

On top of that, different types of errors may have additional properties, such as sql and sqlState for database errors and missingFileName for missing include errors.

Perhaps it's this dynamic format that makes an isError() function somewhat irrelevant. Meaning, even if you could tell that you had an error, the shape of that error would change depending on the type. As such, would knowing that you have an "error" really even help you that much?

As I started to dig into this a bit more, I discovered that Adobe ColdFusion and Lucee CFML use completely different base-classes for their errors:

  • Adobe ColdFusion: java.lang.Throwable

  • Lucee CFML: lucee.runtime.exp.CatchBlockImpl

So, even if ColdFusion had an isError() decision function, I still wouldn't be able to pass a Lucee CFML error into one of the Rollbar SDK methods that expects a java.lang.Throwable. At best, I'd have to treat Lucee CFML errors as "error like" - not true errors in the Java sense.

That said, I wanted to play around with the errors in ColdFusion to see if I could create an isError() decision function. Though, in the end, I don't think I would even use it if it existed. In the following demo, I'm also including an isErrorLike() function to detect objects that look like errors but are not true errors (mostly for funzies).

This demo tests "control cases" and "error cases" against my two user-defined functions (UDFs):

<cfscript>

	// Control cases.
	echoLn( "Boolean:", isError( true ), isErrorLike( true ) );
	echoLn( "String:", isError( "woot" ), isErrorLike( "woot" ) );
	echoLn( "Struct:", isError( [:] ), isErrorLike( [:] ) );
	echoLn( "Array:", isError( [] ), isErrorLike( [] ) );
	echoLn( "Null:", isError( javaCast( "null", "" ) ), isErrorLike( javaCast( "null", "" ) ) );

	// Experiments.
	try {
		throw( type = "Oops" );
	} catch ( any error ) {
		echoLn( "Throw:", isError( error ), isErrorLike( error ) );
	}
	try {
		x = y;
	} catch ( any error ) {
		echoLn( "Expression:", isError( error ), isErrorLike( error ) );
	}
	try {
		include "./missing.cfm";
	} catch ( any error ) {
		echoLn( "Missing Include:", isError( error ), isErrorLike( error ) );
	}
	try {
		queryExecute( "SELECT 1", {}, { datasource: "foo" } );
	} catch ( any error ) {
		echoLn( "SQL:", isError( error ), isErrorLike( error ) );
	}

	errorish = {
		type: "Oops",
		message: "I did it again",
		detail: "I played with your heart",
		extendedInfo: "Got lost in the game",
		stackTrace: "",
		tagContext: callStackGet()
	};

	echoLn( "Errorish:", isError( errorish ), isErrorLike( errorish ) );

	throwable = createObject( "java", "java.lang.Throwable" )
		.init( "Something went wrong" )
	;

	echoLn( "Throwable:", isError( throwable ), isErrorLike( throwable ) );

	// ------------------------------------------------------------------------------- /
	// ------------------------------------------------------------------------------- /

	/**
	* I determine if the given value is an "official" error object in the ColdFusion
	* runtime platform.
	*/
	public boolean function isError( any value ) {

		if ( isNull( value ) ) {

			return( false );

		}

		return(
			// Adobe ColdFusion 2021.
			isInstanceOf( value, "java.lang.Throwable" ) ||
			// Lucee CFML.
			isInstanceOf( value, "lucee.runtime.exp.CatchBlockImpl" )
		);

	}


	/**
	* I determine if the given value LOOKS LIKE an error in the ColdFusion runtime
	* platform.
	*/
	public boolean function isErrorLike( any value ) {

		if ( isNull( value ) ) {

			return( false );

		}

		// Check for the core exception information that all errors should contain. Note
		// that some errors may contain more than just these properties (such as the SQL
		// statement in a database error). But, every error should have these (I think).
		return(
			( isStruct( value ) || isObject( value ) ) &&
			isSimpleValue( value?.type ) &&
			isSimpleValue( value?.message ) &&
			isSimpleValue( value?.detail ) &&
			isSimpleValue( value?.extendedInfo ) &&
			isSimpleValue( value?.stackTrace ) &&
			isArray( value?.tagContext )
		);

	}


	/**
	* Utility function that prints the values on a new HTML line.
	*/
	public void function echoLn() {

		var label = arrayFirst( arguments );
		var rest = arraySlice( arguments, 2, ( arrayLen( arguments ) - 1 ) );

		writeOutput( "<strong>#label#</strong> " );
		writeOutput( arrayToList( rest, " " ) );
		writeOutput( "<br />" );

	}

</cfscript>

When we run this in Adobe ColdFusion 2021, we get the following output:

Boolean: NO NO
String: NO NO
Struct: NO NO
Array: NO NO
Null: false false
Throw: YES YES
Expression: YES YES
Missing Include: YES YES
SQL: YES YES
Errorish: NO YES
Throwable: YES YES

When we run this in Lucee CFML 5.3.8, we get the following output:

Boolean: false false
String: false false
Struct: false false
Array: false false
Null: false false
Throw: true true
Expression: true true
Missing Include: true true
SQL: true true
Errorish: false true
Throwable: true false

It's interesting that the manually created instance of java.lang.Throwable is "error like" in Adobe ColdFusion but not in Lucee CFML. It looks like Adobe ColdFusion wraps manually-created error objects in standard properties somehow. Magic!

Ultimately, even if ColdFusion had an isError() decision function, I don't think I would use it. Instead, when logging errors, I'd likely transform the error object into another data-structure, like a Struct, before I then consumed them (such a serializing them as JSON and writing them to my application container's output stream).

Minor Note on onError() ColdFusion Application.cfc Event Handler

In the ColdFusion application framework, you can define a global onError() event handler that will be called for unhandled exceptions in the application. The error object passed to the onError() handler contains unusual properties:

  • .cause
  • .rootCause

Interestingly enough, these two properties are both instance of java.lang.Throwable in Adobe ColdFusion; but, are only instances of StructImpl in Lucee CFML.



Reader Comments

What has two thumbs and hopes you leave a comment? This Guy! (Ben Nadel).

Post A Comment

You — Get Out Of My Dreams, Get Into My Blog
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.