AJS_Validator User Guide

Disallowing Arguments

As the ValidationDefs section shows, a ValidationDef may contain, among other properties, a CallDef object. A CallDef is simply an array that contains zero or more ArgDef objects (see below), where each ArgDef defines the constraints that AJS_Validator should apply to a given argument on a call to the method(s) referred to by the ValidationDef's MethodNames property (or to all methods of the object in question, if no MethodNames property is present).

Contents Disallowing Arguments
ArgDefs
RtnDefs
Disallowing Value/Reference Return
Arg-/Rtn-Def Re-Use

It follows that an empty CallDef implies that the corresponding method should take no arguments, and indeed this is the simplest constraint that you can place on a method; AJS_Validator will call its exception handler if a caller passes arguments to a method to which such a ValidationDef has been applied, and Example 16 demonstrates this.

Given that methods that take no arguments will ignore any arguments that callers pass to them, you might argue that using an empty CallDef to disallow arguments explicitly for a given method is redundant. This, however, is not the case. It is possible to change a given call to a given method that takes arguments to a call to a different method that does not, but to forget to remove from the calling syntax the arguments that are passed during that call. Alternatively, you can change a given method's calling signature to cause it to accept no arguments, while you still pass arguments to that method elsewhere in the application.

Either way, cursory inspection of the code at the call-site can then lead you to think that the arguments passed are correct, at which point your cognitive model will be at odds with the truth. This can cause confusion subsequently, which will impinge on efficient use of your time, so it follows that being rigorous in one's use of AJS_Validator, such that you disallow arguments explicitly in a given ValidationDef will guard against such needless confusion.

Given this, an empty CallDef constrains callers of the corresponding method(s), such that they cannot pass arguments to that/those method(s).


 // -- Example 16 -------------------------------------------------------------

 var MyObj      =
    {
    method_A    : function () { }
    };

 AJS_Validator.applyValidationDef (MyObj,
    {
    MethodNames : ["method_A"],  // As in previous examples this is redundant, but is present to clarify matters.
    CallDef     : [          ]   // An empty CallDef means that the method accepts no arguments.
    });

 MyObj.method_A (42);            // Wrong, method_A should be passed nothing.

 -- Output --------------------------------------------------------------------

 AJS_Validator_Exception: the caller of method_A supplied 1 argument(s) more
 than there are ArgDefs in the corresponding CallDef (Method-Owner's Class:
 Object, Method-Owner's AJS_Validator_Tag: undefined).
            

ArgDefs

To define constraints for methods that do take arguments, the relevant CallDef should be populated with one object (an ArgDef) per argument, in the order that the arguments appear in the method's calling-signature. To develop this concept, Example 17 shows the use of ArgDefs for a method that takes three arguments (note that the ellipses they contain indicate the presence of zero or more constraints).


 // -- Example 17 (pseudo code) -----------------------------------------------

 var MyObj =
    {
    method_A    : function (Arg_0, Arg_1, Arg_2)
       {

       ...                       // Assume that method_A does something of interest.

       }

    };

 AJS_Validator.applyValidationDef (MyObj,
    {
    MethodNames : ["method_A"],  // Again, not needed, but included for clarity.
    CallDef     :                // Note: ArgDef properties are omitted here for
       [                         // the purpose of clarity too, hence the ellipses.
       { ... },                  // Arg_0 contraints.
       { ... },                  // Arg_1 contraints.
       { ... }                   // Arg_2 contraints.
       ]

    });
            

All possible properties that an ArgDef can carry are optional, and the complete list is presented in the (somewhat pseudo-) code in Example 18.

Note that, within the scope of this guide, the Never..., Mix.../Max... and RegExp properties are referred to as 'simple constraints'. This is because, mostly, they can be used independently of each other when the ArgDef (or RtnDef, see below) concerned does not carry any of the Allow.../Disallow... properties. The semantics that pertain here are explained in the Validation Model page.

Equally, the reasons for the various defaults and strictures to which ArgDef properties are subject, along with the degree of permissivity that AJS_Validator observes when applying those properties are also explained in the Validation Model section of that page.

Note too that the value of certain properties must concur with each other, if they are present within the same ArgDef, and that some cannot exist simultaneously in the same ArgDef, because they would then contradict each other. This is also covered in detail in the Property Concurrence and Collisions section.

Note that an empty ArgDef does not mean that the argument must be undefined, instead it causes AJS_Validator to require a defined, non-null value. That is to say: if all you want is to mandate that something be passed as a given argument, where you do not wish to bother with constraining that argument's class and/or value, all you have to do is to provide a simple, empty ArgDef. I.e.:

   { }

It follows that, to require that callers of a given method pass three arguments, you should state the following within a CallDef:

  [ { }, { }, { } ]

This means that a ValidationDef that states that the methods to which it is applied must all be passed three arguments would look like this:

   var MyValidationDef =
      {
      MethodNames : [ ... ],
      CallDef     :
         [
         { },
         { },
         { }
         ]

      };

If, however, you wish to force AJS_Validator to allow undefined and null values, you should use the NeverUndefined and NeverNull properties respectively, giving them values of false. The reason for AJS_Validator's default behaviour is explained in the sections of this guide that cover these two properties, and is covered also in the section on the validation model that AJS_Validator implements.


 // -- Example 18: ArgDef Composition (pseudo code) ---------------------------

    {
    NeverUndefined  : Bool,      // Optional, applies to all argument types      (default :  true).
    NeverNull       : Bool,      // Optional, applies to all argument types      (default :  true).

    NeverNegative   : Bool,      // Optional, applies to numeric arguments       (default :  false).
    NeverZero       : Bool,      // Optional, applies to numeric arguments       (default :  false).
    NeverPositive   : Bool,      // Optional, applies to numeric arguments       (default :  false).
    NeverFractional : Bool,      // Optional, applies to numeric arguments       (default :  false).

    NeverExtensible : Bool,      // Optional, applies to all except bool/num/str (default :  false).
    NeverNotSealed  : Bool,      // Optional, applies to all except bool/num/str (default :  false).
    NeverNotFrozen  : Bool,      // Optional, applies to all except bool/num/str (default :  false).

    MaxVal          : Num,       // Optional, applies to numeric arguments       (default :  infinity).
    MinVal          : Num,       // Optional, applies to numeric arguments       (default : -infinity).

    MaxLen          : Num,       // Optional, applies to array/str arguments     (default :  infinity).
    MinLen          : Num,       // Optional, applies to array/str arguments     (default :  zero).

    RegExp          : RegExp,    // Optional, applies to Str arguments.

    AllowClasses    :            // Optional array, applies to all argument types.
       [
       String/StringLiteral,     // AllowClasses property must contain at least one class-name string - it cannot be empty.
       ...
       String/StringLiteral      // Second and third etc. class-name strings are optional.
       ],

    DisallowClasses :            // Optional array, applies to all argument types.
       [
       String/StringLiteral,     // DisallowClasses members are optional.
       ...
       String/StringLiteral      // Second and third etc. class-name strings are optional.
       ],

    AllowTags       :            // Optional array, applies to all argument types that can carry user-defined properties.
       [
       String/StringLiteral,     // AllowTags members are optional - an AllowTags can be empty.
       ...
       String/StringLiteral
       ],

    DisallowTags    :            // Optional array, applies to all argument types that can carry user-defined properties.
       [
       String/StringLiteral,     // DisallowTags members are optional - a DisallowTags can be empty.
       ...
       String/StringLiteral
       ]

    };
            

RtnDefs

As noted previously, the RtnDef that a ValidationDef may possess can be viewed as a singleton ArgDef (within the scope of the relevant ValidationDef), with five exceptions, as Example 19 shows.

First: an empty RtnDef (see the next section) has a different meaning to an empty ArgDef, in that it signifies that nothing should be returned, not that the return value must be defined and non-null. That is to say: the default values for NeverUndefined and NeverNull are both false rather than true (which is the default for ArgDefs, empty or otherwise). However, the presence of any other properties in a RtnDef reverses this, at which point the RtnDef concerned becomes equivalent to an ArgDef.

RtnDefs may also carry an ApplyTag property, which will cause AJS_Validator to add an arbitrary identifier-string to any objects returned by the method in question, and you should see the tag-related sections of this guide for more information on the ApplyTag feature.

RtnDefs also support the PreventExt, Seal and Freeze properties, precisely because it supports the ApplyTag property. That is: a given method may use the preventExt, seal or freeze methods of the Object constructor in order to confer a degree of immutablity on the objects that it returns, and this would clash with the presence of an ApplyTag in a corresponding RtnDef (immutability would render AJS_Validator unable to add the tag to the object). See the Preventing Extensions, Sealing and Freezing section for more information in respect of this.


 // -- Example 19: RtnDef Composition (pseudo code) ---------------------------

 var MyValidationDef =
    {
    ...

    RtnDef :
       {
       ...                                  // Same as ArgDef except that it may carry the following...

       ApplyTag   : String/StringLiteral,   // Optional, can be applied only to a return type that
                                            // can carry user-defined properties.

       PreventExt : Boolean/BooleanLiteral, // Optional, cannot be applied to Bool-/Num-/Str-literals (default : false).
       Seal       : Boolean/BooleanLiteral, // Optional, cannot be applied to Bool-/Num-/Str-literals (default : false).
       Freeze     : Boolean/BooleanLiteral  // Optional, cannot be applied to Bool-/Num-/Str-literals (default : false).

       }

    };
            

Disallowing Value/Reference Return

In the case of preventing the return of values from a given method, one might argue that one need ensure only that the method in question possess no return statements. Moreover, method-callers can simply-ignore any value returned from the method in question, and these points suggest that disallowing object-returns formally, by means of AJS_Validator, is redundant.

However, many systems generate methods and attach them to a given object dynamically (meaning that a given method's origin may not be apparent during debugging). Moreover, an established design-parameter in such a system could be that such methods must never return anything. It follows that, were there a bug in such a system, where a given object gained possession of the wrong method, and that method happened to return a value, you could trap that defect by stipulating in a ValidationDef that the relevant method(s) should return nothing.

Given this, an empty RtnDef for a given method causes AJS_Validator to call its exception handler if the method in question dare return something other than undefined, and Example 20 demonstrates this point.


 // -- Example 20 -------------------------------------------------------------

 var MyObj   =
    {
    method_A : function ()
       {
       return "This time I'm gonna make it out of this scope";
       }

    };


 AJS_Validator.applyValidationDef (MyObj,
    {
    MethodNames : ["method_A"],  // Again, redundant, but present in order to clarify.
    RtnDef      : { }            // method_A must not return anything.
    });

 MyObj.method_A ();              // Problem: method_A tries to return a string.

 -- Output --------------------------------------------------------------------

 AJS_Validator_Exception: method_A has returned an object of class
 StringLiteral, where its RtnDef disallows the return of anything
 (Method-Owner's Class: Object, Method-Owner's AJS_Validator_Tag: undefined).
            

Arg-/Rtn-Def Re-Use

One very useful technique that you can apply when managing large collections of ValidationDefs is to re-use, where possible, the relevant Arg- and Rtn-Defs. This can yield dramatic reductions in ValidationDef code-volume.

To expand on this anecdotally: AJS_Validator polices most of the arguments passed in calls to its ancillary methods by means of internal ValidationDefs rather than by means of hard-coded logic (this bears witness internal consistency of the tool and, from there, its quality, and more information on this can be found on the Ancillaries page). During development, these ValidationDefs grew to well over 100 lines of code in extent, but common-ArgDef re-use then reduced the line-count to just 16 (excluding comments), which constitutes a decrease of at least 84%. Indeed, one such internal ValidationDef is applied to eight methods.

Given that AJS_Validator supports 19 ancillary methods, this amounts effectively to just one line of ValidationDef-code per method – that's pretty-good economy.

To re-use an Arg- or Rtn-Def, you should instantiate it as a free-standing object (i.e. a property of nothing that resides within the function-scope in question – a plain-old var), and then cite that object's name at the relevant locations in the ValidationDefs that, normally, would instantiate in-line the Arg-/Rtn-Def in question.

Example 21 demonstrates this idea by means of a 'before and after' comparision. In the Before scenario, we see two ValidationDefs that cover two methods each. Each ValidationDef carries a CallDef, where a variety of ArgDefs define the arguments that those methods accept. The second ValidationDef specifies a RtnDef as well.

In the Before scenario, this code extends to 23 lines (including whitespace), whereas it runs to just 5 in the After scenario, which is a reduction of around 78%, weighing-in at just over one line of validation code per method. Given this, and under optimum conditions, a given set of ValidationDefs that re-use Arg-/Rtn-Defs heavily will result in far less than one line of ValidationDef-code per method policed – again, that's pretty-good economy.

Remember here, that Arg- and Rtn-Def components that can be re-used only within a given ValidationDef, can be defined within that ValidationDef (AJS_Validator ignores ValidationDef properties that it does not recognise), which will avoid polluting the scope in question with unecessary definitions. Example 22 demonstrates this idea.


 // -- Example 21: Before-After Comparison ------------------------------------

 // -- Before ------------------------------------------

 var ValidationDef_A  =
    {
    MethodNames       : ["method_A", "method_B"],
    CallDef           :
       [
       { AllowClasses : ["String"], MinLen        : 30   },         // Arg must be a string object of exactly 30 characters.
       { AllowClasses : ["Number"], NeverNegative : true }          // Arg must be a non-negative number object.
       ]

    };

 var ValidationDef_B  =
    {
    MethodNames       : ["method_C", "Method_D"],
    CallDef           :
       [
       { AllowClasses : ["Number"], NeverNegative : true },         // Identical to the second ArgDef in the CallDef above.
       { AllowClasses : ["String"], MinLen        : 30   }          // Identical to the first ArgDef in the CallDef above.
       ],

    RtnDef            : { AllowClasses : ["String"], MinLen : 30 }, // The value returned must be the same as the second
                                                                    // argument.
    };


 // -- After -------------------------------------------

 var NumArg           = { AllowClasses : ["Number"], NeverNegative : true };
 var StrArg           = { AllowClasses : ["String"], MinLen        : 30   };

 var ValidationDef_A  = { MethodNames  : ["method_A", "method_B"], CallDef : [ NumArg, StrArg ]                  };
 var ValidationDef_B  = { MethodNames  : ["method_C", "Method_D"], CallDef : [ StrArg, NumArg ], RtnDef : StrArg };
            

 // -- Example 22 -------------------------------------------------------------

 var MyValidationDef =
    {
    ResusableDef     : { AllowClasses : ['StringLiteral'], MinLen : 30, MaxLen : 30 },

    MethodNames      : [ ... ],
    CallDef          :
       [
       MyValidationDef.ResusableDef,
       MyValidationDef.ResusableDef,
       MyValidationDef.ResusableDef
       ],

    RtnDef           : MyValidationDef.ResusableDef

    };