Sunday 4 May 2014

ColdFusion 11: Adobe goes some way to correctly identifying what constitutes an integer

G'day:
This is a follow-up to all the shenangians around isValid() and integers and what not: "Can we please agree that Adobe is not the arbitor of what constitutes an integer?". Adobe have done some work to improve things here.

They've added a switch in Application.cfc:

component {
    this.name = "testStrictNumberValidation01";
    if (structKeyExists(URL, "strict")){
        this.strictNumberValidation = URL.strict;
    }
}

The docs for this are as follows ("Application variables"):
In ColdFusion 10 and earlier versions, the IsValid function allowed currency symbols at the start and commas inside the number.
Starting from ColdFusion 11, this function evaluates on a more strict basis. Setting this value to false makes the isValid function to behave in the older way. This setting effects cfargument, cfparam and cfform tags wherever integer & numeric validation is used. Based on this setting, the validation reflects in those tags as well.
Here's a test rig which tests isValid(), param, argument and return types; and also in an expression:

for (value in ["1,000","$1,000","$,1,2,$,2352345,$","0,6","1,2,3,4"]){
    writeOutput("<h4>Testing #value#</h4>");
    writeOutput("isValid(): #isValid("integer", "#value#")#<br>");

    writeOutput("Valid in param:");
    safe(function(){
        param integer local.foo=value;
    });

    writeOutput("Valid as numeric argument:");
    safe(function(){
        takesNumeric(value);
    });

    writeOutput("Valid as numeric return:");
    safe(function(){
        returnsNumeric(value);
    });

    writeOutput("Can be <em>used</em> as integer by CFML:");
    safe(function(){
        1 MOD value;
    });
    writeOutput("<br><hr>");
}

function takesNumeric(required numeric n){}
numeric function returnsNumeric(required any n){
    return arguments.n;
}


function safe(f){
    try {
        f();
        writeOutput(" OK");
    }
    catch (any e){
        writeOutput(" FAIL: #e.message#");
    }
    finally{
        writeOutput("<br>");
    }
}

On ColdFusion 10, we get this:


Testing 1,000

isValid(): YES
Valid in param: OK
Valid as numeric argument: OK
Valid as numeric return: OK
Can be used as integer by CFML: OK



Testing $1,000

isValid(): YES
Valid in param: OK

Valid as numeric argument: FAIL: The N argument passed to the takesNumeric function is not of type numeric.
Valid as numeric return: FAIL: The value returned from the returnsNumeric function is not of type numeric.
Can be used as integer by CFML: FAIL: The value $1,000 cannot be converted to a number.



Testing $,1,2,$,2352345,$

isValid(): YES
Valid in param: OK

Valid as numeric argument: FAIL: The N argument passed to the takesNumeric function is not of type numeric.
Valid as numeric return: FAIL: The value returned from the returnsNumeric function is not of type numeric.
Can be used as integer by CFML: FAIL: The value $,1,2,$,2352345,$ cannot be converted to a number.



Testing 0,6

isValid(): YES
Valid in param: OK
Valid as numeric argument: OK
Valid as numeric return: OK
Can be used as integer by CFML: OK



Testing 1,2,3,4

isValid(): YES
Valid in param: OK

Valid as numeric argument: FAIL: The N argument passed to the takesNumeric function is not of type numeric.
Valid as numeric return: FAIL: The value returned from the returnsNumeric function is not of type numeric.
Can be used as integer by CFML: FAIL: The value 1,2,3,4 cannot be converted to a number.




All of these are wrong to some degree or other, as indicated in red. The output when specifying strict=false on ColdFusion 11 is the same.

With strict=true (or just the default setting), we get this:


Testing 1,000

isValid(): NO
Valid in param: FAIL: Invalid parameter type.
Valid as numeric argument: FAIL: The N argument passed to the takesNumeric function is not of type numeric.
Valid as numeric return: FAIL: The value returned from the returnsNumeric function is not of type numeric.

Can be used as integer by CFML: OK



Testing $1,000

isValid(): NO
Valid in param: FAIL: Invalid parameter type.
Valid as numeric argument: FAIL: The N argument passed to the takesNumeric function is not of type numeric.
Valid as numeric return: FAIL: The value returned from the returnsNumeric function is not of type numeric.
Can be used as integer by CFML: FAIL: The value $1,000 cannot be converted to a number.



Testing $,1,2,$,2352345,$

isValid(): NO
Valid in param: FAIL: Invalid parameter type.
Valid as numeric argument: FAIL: The N argument passed to the takesNumeric function is not of type numeric.
Valid as numeric return: FAIL: The value returned from the returnsNumeric function is not of type numeric.
Can be used as integer by CFML: FAIL: The value $,1,2,$,2352345,$ cannot be converted to a number.



Testing 0,6

isValid(): NO
Valid in param: FAIL: Invalid parameter type.
Valid as numeric argument: FAIL: The N argument passed to the takesNumeric function is not of type numeric.
Valid as numeric return: FAIL: The value returned from the returnsNumeric function is not of type numeric.

Can be used as integer by CFML: OK



Testing 1,2,3,4

isValid(): NO
Valid in param: FAIL: Invalid parameter type.
Valid as numeric argument: FAIL: The N argument passed to the takesNumeric function is not of type numeric.
Valid as numeric return: FAIL: The value returned from the returnsNumeric function is not of type numeric.
Can be used as integer by CFML: FAIL: The value 1,2,3,4 cannot be converted to a number.





So most of this is now correct; there's a coupla behaviours which are still wrong, but there's a bigger picture at play here ('According to ColdFusion "0,6" == "6,0". And both are integers to boot'), so that's predictable I guess.

Secondly I checked <cfform> validation. This code is hilarious:

cfform(method="post"){
    writeOutput('<br>Integer: ');
    cfinput(name="integerValue", validate="integer");
    writeOutput('<br>Float: ');
    cfinput(name="floatValue", validate="float");
    writeOutput('<br>');
    cfinput(type="Submit", name="btnSubmit", value="Submit");
}
writeDump(var=form);

So we've found something worse than <cfform>. A <cfform> written in CFScript. [boggle]. Needless to say... do not write code like this. Indeed don't ever use <cfform> or <cfinput> in any way, shape or form ("Oi! You bloody wankers! Stop using ColdFusion UI controls").

Still... it tests the functionality. Which seems to be more than Adobe did. Because any of those test values still pass validation here. So that's a fail.

If, however, I opt to validateAt="onServer", then it does work. So they've done half the job.

All in all it's an improvement. And I am pleased they have defaulted to the correct behaviour, rather than clamouring "backwards compatibility". I'll raise a bug for the <cfform> behaviour (3754589 [update: this has been marked "To Fix"]).

--
Adam