Considering An isError() Decision Function In 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).
onError()
ColdFusion Application.cfc
Event Handler
Minor Note on 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.
Want to use code from this post? Check out the license.
Reader Comments