AJS_Validator User Guide

Overview

You may think that the range of properties that AJS_Validator offers, such as MaxLen and MinLen, is somewhat limited. Where, for example, is the ability to specify a set of precise values or ranges for numbers? Similarly, would it not be useful to be able to state, say, that a given array must have only an even number of members?

While, in principle, it is possible to extend the range of Arg-/Rtn-Def properties that AJS_Validator allows, doing so would risk over-complicating its implementation, and so would require more documentation, a longer user-guide, a larger test-regimen, and so on. It would also require more learning on the part of users, and all of this could compromise the tool's value.

It would also raise the question of what is actually useful to the majority of developers, and where such extension of the property-range should stop. Some extensions would be of great value to a particular sub-set of the JavaScript development-community, while other sub-sets would view those extensions as utterly redundant.

This is where preValidators and postValidators come into play. By providing the ability to execute a user-defined function prior to execution of a method (a 'preValidator'), and the ability to execute another such function on that method's return (a 'postValidator'), AJS_Validator allows developers to fine-tune the constraints that any corresponding Arg- and Rtn-Defs place upon the classes, types and values of arguments and returns, and to perform whatever additional processing that may be required.

This means that you can check system-state before and after the execution of a method, and this ability takes AJS_Validator beyond the concept of simple run-time class- and value- assertions into the area of pre-conditions and post-conditions, as defined within the concept of Design by Contract programming.

Contents Overview
preValidators and postValidators
Processing Order
Pre-/Post-Validator Examples
Pre-/Post-Validators and Exceptions
External Validators
Pre-/Post-Validators and Suspension/Resumption
Factory Methods
ValidationDefCtrl Management
Pre-/Post-Validators and Argument Injection/Mutation

preValidators and postValidators

Example 49 re-presents Example 3 from the ValidationDefs section of this guide, showing that a Pre-/Post-Validator property must be a reference to a custom function implemented by you. Note that AJS_Validator will detect the provision of anything other than function references for these properties, and will bring such exceptions to your attention.

Upon invocation of a preValidator, AJS_Validator passes:

  1. The arguments array passed to the method in question.
  2. A reference to the owner of the method (i.e. the object to which the corresponding ValidationDef was applied).
  3. A string carrying the name of the method, the calling of which caused the call to the preValidator.
  4. A string carrying the value of the MethodOwnerName argument that may have been supplied when the corresponding ValidationDef was passed to AJS_Validator originally.
  5. A function reference called onException that the PreValidator should treat as an exception handler.
  6. A positive-integer 'stack offset' that is indispensable in the generation of exception-notifications that carry accurate file and line-number information (see below for details).
  7. A reference to the ValidationDef of which the preValidator is a property.

postValidators receive exactly the same arguments, in the same order, along with a seventh argument, which is the value returned by the method in question. This allows 'before-and-after' comparisons, which assist thereon in enforcing pre- and post-conditions. Note also that the first six arguments passed to Pre- and Post-Validators are the same in order to allow the use of a single function as both a Pre- and a Post-Validator.

Note that returning a value from a preValidator is redundant, as it is ignored by AJS_Validator. However, returning a value from a postValidator is of significance. This is because any suffix-function that follows the suffix that executes the postValidator, receives the postValidator's return-value as its second argument. This is explained in detail in the section on Multiple Suffix-Function Signatures in the AJS-object user guide. This is of value in the ValidationDef held in AJS_Logger.val.js, as it allows that code to attach a ValidationDef to the createLoggerFactory function.

Note too that, if you have acquired a copy of AspectJS, and you want to see real-world examples of Pre- and Post-Validators then have a look at the ValidationDefs held in AJS.val.js, AJS_Logger.val.js and AJS_ODL.val.js, which will provide you with rich and meaningful examples.


 // -- Example 49 (pseudo code - re-presentation of Example 3) ----------------

 var ValidationDef =
    {
    MethodNames    : ...                        // Optional string-array.

    CallDef        : ...                        // Optional ArgDef-array.
    RtnDef         : ...                        // Optional Argdef-like object.

    AllowDiffThis  : ...                        // Optional BooleanLiteral/Boolean.


    // -- Optional function-reference ------------------------

    preValidator   : function (Args, MethodOwner, MethodName, MethodOwnerName, onException, StackOffset, ValidationDef)
       {

                                                // Your validation-logic goes here - see the Ancillaries sections
                                                // of this guide for a useful 'Swiss Army Knife' of validation
                                                // methods.

       },


    // -- Optional function-reference ------------------------

    postValidator  : function (Args, MethodOwner, MethodName, MethodOwnerName, onException, StackOffset, ValidationDef, RtnVal)
       {

                                                // Your validation-logic goes here too. Again, see
                                                // the Ancillaries sections for a validation tool-kit.

       }

    };
      

Processing Order

Given these points, the order of processing when a call is made to a method that is covered by a ValidationDef is depicted in the UML Activity Diagram given here. The reasoning behind this processing-order is that it makes sense to check the type and value etc. of an argument before any preValidator gets a crack at things, as the basic class- and value-checking that AJS_Validator imposes ensures that the preValidator will not execute thereon in the context of nonsensical values (assuming exhaustive and correct ArgDefs and RtnDefs etc.).

For the same reason, postValidators execute only after the constraints imposed by the properties in any associated RtnDef have been satisfied.

Do remember to avoid calling Object.preventExtensions, Object.seal or Object.freeze within a postValidator on any object returned from the method in question, where the corresponding RtnDef carries an ApplyTag property. Failure to do this will cause AJS_Validator to call its exception handler when the time comes to apply the tag in question. If you need to freeze, seal or render inextensible an object that a method returns, use the Freeze, Seal and PreventExt properties (see the Preventing-Extensions, Sealing and Freezing section for more information).

Diagram of ValidationDef-property processing order

Pre-/Post-Validator Examples

These points in hand, Example 50 demonstrates the use of a preValidator. Here, the function performs a modulo 2 operation on the first member of the Args array (which corresponds to the first argument passed to the evenNumberAcceptor method). If the result has a remainder of 1 then the number is odd rather than even, and thus the preValidator throws an exception object, using the MethodName argument it received, in order to indicate the location of the trouble.

Likewise, Example 51 shows the (entirely contrived) use of a postValidator. Here, the method called ArrayCreator should return arrays only, where those arrays must possess an even number of members, and so the code applies a ValidationDef to it that carries a postValidator that checks the length of the arrays it returns. This function performs a modulo 2 operation on the array's length, and throws an exception object if it detects an odd number of elements.

Do note that, when working with Pre- and Post-Validators, the this reference will have the value that was set (implicitly or explicitly) at the point of call of the method that is subject to the relevant ValidationDef (see the relevant section in the AJS-object user guide for more information). This means they cannot use the this reference to access the properties of the ValidationDef of which they are properties; moreover, that ValidationDef may not be accessible directly because it may be an anonymous object, or may be hidden within a private scope.

Additionally, you may implement a given Pre- or Post-Validator externally to the ValidationDef in question, which allows multiple ValidationDefs to use the same Pre- and Post-Validator functions, yet some Pre-/postValidators may need to know under which ValidationDef they are executing at a given time.

It is for these two reasons that AJS_Validator passes to Pre-/Post-Validators a fifth argument that refers to the ValidationDef that made use of them in the context in question.

Note here that you are not stuck at this point with implementing Pre-/Post-Validator logic yourself solely. AJS_Validator provides nineteen ancillary methods that client code can use quite independently of its ValidationDef-related functionality, but which are suited especially to the kind of work you need to do within Pre- and Post-Validators. The ancillaries are, in essence, a validation toolkit (a 'Swiss Army knife', if you like) that automates a lot of the work you do typically in a validation function.


 // -- Example 50 -------------------------------------------------------------

 var MyObj =
    {
    evenNumberAcceptor : function (NumericalArg) { }
    };

 AJS_Validator.applyValidationDef (MyObj,
    {
    preValidator : function (Args, MethodOwner, MethodName, MethodOwnerName)
       {
       if (Args[0] % 2)                         // Modulo 2 operation on the first argument passed to evenNumberAcceptor.
          {
          throw new Error ("Numerical argument provided to " + MethodOwnerName + "." + MethodName + " is not even.");
          }

       }

    }, "MyObj");

 MyObj.evenNumberAcceptor (43);                 // No can do, 43 is odd.

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

 Error: Numerical argument provided to MyObj.evenNumberAcceptor is not even.
      

 // -- Example 51 -------------------------------------------------------------

 var MyObj =
    {
    createArray : function ()
       {
       return ["Fred", "Barney", "Wilma"];      // This array has an odd number of members.
       }

    };

 AJS_Validator.applyValidationDef (MyObj,
    {
    postValidator : function (Args, MethodOwner, MethodName, MethodOwnerName, onException, StackOffset, ValidationDef, RtnVal)
       {
       if (RtnVal.length % 2)
          {
          throw new Error ("Array returned from " + MethodOwnerName + "." + MethodName + " has an odd number of elements.");
          }

       }

    }, "MyObj");

 var MyArray = MyObj.createArray ();

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

 Error: Array returned from MyObj.createArray has an odd number of elements.
     

Pre-/Post-Validators and Exceptions

Examples 50 and 51 show that Pre- and Post-Validators can throw homespun exceptions using the throw keyword, which pass transparently through the method-call interception mechanism upon which AJS_Validator pivots. However, this requires proprietary logic in order to acquire from the exception object in question the file-name and line-number information that identifies the location of the problem correctly (assuming the run-time in question provides a stack trace).

To address this, AJS_Validator passes to Pre- and Post-Validators the fourth onException-argument, which is a reference to a function that is internal to AJS_Validator and which accepts a mandatory, arbitrary string as its first argument. Calling this function causes AJS_Validator (after some argument checking) to invoke the exception handler that you passed to createAJS_Validator initially.

If that is one of the stock handlers that form part of the AspectJS distribution set, then that function will throw the kind of object depicted in the AJS_Validator Exceptions section of this guide, where the message property will hold the contents of the string you passed from within your Pre-/Post-Validator.

The use of this is shown in Example 52, but, as you can see, the exception notification reports the location of the problem as being the point at which onException was invoked. Clearly, this is not what we want – we need to know where in the application the problem lies, not the point at which it was detected.

It is for this reason that AJS_Validator passes Pre- and Post-Validators the fifth StackOffset argument, which, in turn, is why the exception handler that is passed should accept a second (optional) StackOffset argument. AJS_Validator calculates the value of StackOffset internally (it is always a positive integer), and its magnitude accounts for the chain of function calls between the invocation of a method to which a ValidationDef has been applied, and the call to the corresponding Pre-/Post-Validator.

Given this, all you have to do is increment StackOffset at the beginning of the validator in question, and then pass it to onException when you call that function. As long as you observe this principle throughout, the exception notifications you receive will always report accurately the file and line-number where an offending method call occurred (assuming that you are using an AspectJS exception handler or an equivalent, and assuming again that the run-time in question provides a stack trace).

(Note here that the ancillary methods that AJS_Validator supports also accept a StackOffset argument that performs the exactly same role as it does in a call to onException.)

AJS_Validator also passes 'User_Defined_Validation_Exception' to its exception handler as the value for that function's name argment, instead of 'AJS_Validator_Exception', which is the value that it passes when it needs to raise an exception. This indicates that your validation-logic trapped the problem, rather than AJS_Validator.

Example 53 illustrates these points by means of a contrived scenario (in order to clarify), where the output shows correctly that the location of the 'troublesome' call is on line 18.

As a tangential (and somewhat poetic) point: note that StackOffset arguments are 'tramp variables', in that they are 'just passing through', although the metaphor varies slightly here, because to increment a StackOffset argument before passing it on is akin to 'giving the tramp a penny' as he goes on his way.

Finally in this section: given that the AJS object supports the application of multiple affixes to a given method, any validating affix that AJS_Validator emplaces could constitute a link at an arbitrary point in a chain of affixes for the same method. However, this does not affect AJS_Validator's calculation of stack-offset values, because the AffixCtrl type that the AJS object defines supports a method called getCardinality, which allows AJS_Validator to 'offset the stack-offset' accordingly.

 01
 02 // -- Example 52 ----------------------------------------------------------
 03
 04 var SomeObj =
 05    {
 06    method_A : function () { }
 07    };
 08
 09 AJS_Validator.applyValidationDef (SomeObj,
 10    {
 11    preValidator : function (Args, MethodOwner, MethodName, MethodOwnerName, onException)
 12       {
 13       // Assume that a problem is detected here
 14
 15       onException ("preValidator not a happy bunny");                  // No StackOffset passed.
 16
 17       }
 18
 19    });
 20
 21 SomeObj.method_A ();
 22

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

 User_Defined_Validation_Exception: preValidator not a happy bunny.

 Exception occurred during execution of Example_52 at line 15, column 8, in:
 Example_52.js
      
 01
 02 // -- Example 53 ----------------------------------------------------------
 03
 04 var SomeObj =
 05    {
 06    method_A : function () { }
 07    };
 08
 09 AJS_Validator.applyValidationDef (SomeObj,
 10    {
 11    preValidator : function (Args, MethodOwner, MethodName, MethodOwnerName, onException, StackOffset)
 12       {
 13       StackOffset++;                                                   // Always increment the StackOffset
 14                                                                        // before you do anything else...
 15       // Assume that a problem is detected here
 16
 17       onException ("preValidator not a happy bunny", StackOffset);     // ...And pass it here.
 18
 19       }
 20
 21    });
 22
 23 SomeObj.method_A ();
 24

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

 User_Defined_Validation_Exception: preValidator not a happy bunny.

 Exception occurred during execution of Example_53 at line 23, column 1, in:
 Example_53.js
      

External Validators

The features explored above expedite problem resolution greatly, but in situations where you have a common complex-task that a number of different Pre- or Post-Validators perform, it is favourable to factor that replicate-complexity into a discrete, stand-alone function that is shared among those validators. Moreover, it follows that that function may call a second, common validation-function, which may call a third, and so on.

This can reduce code-bloat greatly by eliminating redundancy, but, in order that you continue to receive accurate location-information, it also requires you to pass to such validation functions the onException argument that your Pre-/Post-Validators receive, along with the StackOffset argument. This means that your standalone validators should increment StackOffset before doing anything else, according to the same principle that your Pre- and Post-Validators should observe.

It follows that, should such an external validator call another external validator, it too should pass onException and the value of StackOffset to that validator, where the 'Increment Once, Then Pass' rule (or the 'Give the Tramp a Penny' rule, if you prefer a dash of whimsy) applies equally; and if that validator calls a third you should observe the rule again.

As long as you follow this modus operandum, your validators will always yield information that identifies the location of a problem within your application, rather than the point at which the problem was detected (assuming, again, that the run-time in question generates a stack trace). Moreover, this will hold-true even if you change the sequence in which one validator calls another – coding bliss.

Example 54 demonstrates the technique, where the preValidator passes to performValidation a StackOffset value of 1, which performValidation passes to onException. As a result, the notification that arises shows correctly that the offending call occurred on line 38, which is exactly what we want.

 01
 02 // -- Example 54 ----------------------------------------------------------
 03
 04 var SomeObj =
 05    {
 06    method_A : function () { }
 07    };
 08
 09 AJS_Validator.applyValidationDef (SomeObj,
 10    {
 11    preValidator : function (Args, MethodOwner, MethodName, MethodOwnerName, onException, StackOffset)
 12       {
 13       StackOffset++;                                                                                        // Increment...
 14
 15       performValidation (Args[0], MethodOwner, MethodName, MethodOwnerName, onException, StackOffset);      // ...and pass.
 16
 17       }
 18
 19    }, "SomeObj");
 20
 21 function performValidation (SomeValue, MethodOwner, MethodName, MethodOwnerName, onException, StackOffset)
 22    {
 23    StackOffset++                                                                                            // Increment...
 24
 25    onException           ("Bad 'SomeValue' passed to " + MethodOwnerName + "." + MethodName,  StackOffset); // ...and pass.
 26    PerformMoreValidation (SomeValue, MethodOwner, MethodName, MethodOwnerName, onException,   StackOffset);
 27
 28    }
 29
 20 function PerformMoreValidation (SomeValue, MethodOwner, MethodName, MethodOwnerName, onException, StackOffset)
 31    {
 32    StackOffset++;                                                                                           // Increment,
 33                                                                                                             // and so on.
 34    // ...
 35
 36    }
 37
 38 SomeObj.method_A ();
 39

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

 User_Defined_Validation_Exception: Bad 'SomeValue' passed to SomeObj.method_A.

 Exception occurred during execution of Example_54 at line 38, column 1, in:
 Example_54.js
      

Pre-/Post-Validators and Suspension/Resumption

Note that it is possible for Pre- and Post-Validators to call AJS_Validator.suspend and AJS_Validator.resume as they execute. This means that, should a preValidator suspend AJS_Validator operation, it will also prevent subsequent execution of any corresponding postValidator that is carried by the ValidationDef in question. It follows that, if that postValidator possesses a call to AJS_Validator.resume, but the preValidator suspended AJS_Validator, the postValidator will never execute and so will be unable to cause AJS_Validator operations to resume.

Example 55 demonstrates this minor but significant point.

This does not mean, however, that calling AJS_Validator.suspend from within a Pre- or Post-Validator will affect the operation of the exception handler they receive – a Pre-/Post-Validator can call AJS_Validator.suspend and the exception handler will still work if called from within that function (the same applies to AJS_Validator's ancillary methodssuspend and resume do not affect their operation).

If you choose to use the suspend/resume methods of the ValidationDefCtrl object returned from a call to applyValidationDef then this will have the same effect, but will restrict it to the effect of a given ValidationDef only.


 // -- Example 55 -------------------------------------------------------------

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

 AJS_Validator.applyValidationDef (Obj,
    {
    preValidator  : function ()
       {
       console      .log     ("preValidator executed");
       AJS_Validator.suspend ();
       },

    postValidator : function ()
       {
       console      .log     ("postValidator executed");         // This postValidator cannot execute, because its
       }                                                         // corresponding preValidator suspended AJS_Validator.

    });

 Obj.method_A ();

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

 preValidator executed
      

Factory Methods

postValidator functions are indispensable when used in conjunction with methods that act as object factories. It is one thing to apply a set of ValidationDefs to a set of methods when your application initialises, but what happens subsequently when a function generates an object that possesses methods, the execution of which you must police as well?

This is where postValidators take centre stage. On return from an object-creating method, AJS_Validator can invoke a postValidator that uses AJS_Validator reflexively to add constraints to the method(s) of the object that was returned.

Example 56 demonstrates this concept.

Given this, it follows that the ValidationDefs applied to the methods of the object created by createObj could carry their own postValidators, which could apply ValidationDefs to the objects that those objects return (if any) and so on.

This notion brings into view the motivation behind ValidationDefCtrls. As noted in the product overview, the contents of AJS.val.js contain a function that applies ValidationDefs to the methods of the AJS object itself. This allows you to police the calls that your code makes to the methods of that object, and to the methods of the WrapperCtrl and AffixCtrl objects that those methods return.

As the product overview also notes, this constitutes a recursive application of the AJS object, and this means that there are times during the execution of the preValidators in AJS.val.js where the action of the ValidationDefs applied to the AJS object must be suspended, to be resumed again in the corresponding postValidators. Without this, a call to an AJS method would cause infinite recursion.

However, implementing a custom suspension/resumption mechanism to manage this would have added more complexity to an already-complex piece of technology, and so ValidationDefCtrls came into existence in order to avoid such trouble, and to improve performance (which they did superbly). It follows that you may encounter similarly-recursive challenges in developing your own Pre- and Post-Validators, and so ValidationDefCtrls are there to help you retain your sanity in such situations.


 // -- Example 56 -------------------------------------------------------------

 var MyObjFactory  =
    {
    createObject   : function ()
       {
       return {

          method_A : function (StringArg)  { },
          method_B : function (NumericArg) { },
          method_C : function (BooleanArg) { }

          };

       }

    };

 AJS_Validator.applyValidationDef (MyObjFactory,
    {
    postValidator : function (Args, MethodOwner, MethodName, MethodOwnerName, onException, StackOffset, ValidationDef, RtnVal)
       {
       AJS_Validator.applyValidationDef (RtnVal,
           {
           MethodNames : ["method_A"],
           CallDef     : [ { AllowClasses : ["StringLiteral" ] } ]
           });

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

       AJS_Validator.applyValidationDef (RtnVal,
           {
           MethodNames : ["method_B"],
           CallDef     : [ { AllowClasses : ["NumberLiteral" ] } ]
           });

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

       AJS_Validator.applyValidationDef (RtnVal,
           {
           MethodNames : ["method_C"],
           CallDef     : [ { AllowClasses : ["BooleanLiteral"] } ]
           });

       }

    });

 var MyNewObject = MyObjFactory.createObject ();

 MyNewObject.method_B ("Some string");

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

 AJS_Validator_Exception: argument 0 (NumericArg) in call to method_B is of
 class StringLiteral (value: 'Some string'), which is not included in the
 corresponding ArgDef's AllowClasses property in the corresponding
 ValidationDef. Permissible classes are: NumberLiteral. (Object's
 AJS_Validator_Tag : undefined, Method-Owner's Class: Object, Method-Owner's
 AJS_Validator_Tag: undefined).
      

ValidationDefCtrl Management

Note that using ValidationDefCtrls in the development of AJS.val.js uncovered an unexpected and rather useful technique. Given that functions may possess properties of their own, it is possible to add to a method to which a ValidationDef has been applied the ValidationDefCtrl object that was returned when AJS_Validator.applyValidationDef was called to apply the ValidationDef (to be precise, we assign to the proxy function created by the AJS object when we do this – see below).

This means that, when client code needs to suspend or resume the validation that has been applied to such a method, it need only call the suspend or resume method of that methods's ValidationDefCtrl.

Indeed, this beautiful technique proved invaluable during the implementation of AJS.val.js, as it obviated constructing some accursedly-complex, unwieldy and inefficient scheme that would associate a given ValidationDefCtrl with its corresponding method, such that the validation for that method may be managed dynamically, and Example 57 shows the little darling in action.

You will note from the example that there is a minor fly in the coding ointment, in that we must acquire the reference to the ValidationDefCtrl, and then assign it to the method (i.e. it's a two-stage process). This is because attempting to do it the 'inline' way, as in:

Obj.someMethod.ValidationDefCtrl = AJS_Validator.applyValidationDef (Obj, ... );

...will not work, as the reference that such a statement assigns to Obj.someMethod turns out simply to be the proxy function that the AJS object puts in place when it creates the underlying method-call interception mechanism (which is as things should be). That is: dereferencing Obj.someMethod.ValidationDefCtrl subsequently yields only the methods of the proxy function, and any ValidationDefCtrl property is nowhere to be seen.

This must be because because the AJS object changes the reference assigned to Obj.someMethod during the call to applyValidationDef, but that change prevails only after complete execution of the applyValidationDef statement, which means that this behaviour stems from a given JS run-time's implementation.


 // -- Example 57 -------------------------------------------------------------

 var Obj =
    {
    someMethod  : function (Arg) { console.log ("someMethod executed. Arg Value: " + Arg); }
    };

 var ValidationDefCtrl = AJS_Validator.applyValidationDef (Obj,  // Get the reference to the ValidationDefCtrl first (see
    {                                                            // below).
    MethodNames : ["someMethod"],                                // As before, this is redundant, but is present in order to
    CallDef     : [ { AllowClasses : ["Number"] } ]              // clarify matters.
    });

 Obj.someMethod.ValidationDefCtrl = ValidationDefCtrl;           // Only now do we assign the ValidationDefCtrl reference to
                                                                 // to the method, otherwise the run-time will not do what we
 Obj.someMethod.ValidationDefCtrl.suspend ();                    // expect.
 Obj.someMethod                           ();                    // AJS_Validator will not detect the missing argument
                                                                 // here...
 Obj.someMethod.ValidationDefCtrl.resume  ();
 Obj.someMethod                           ();                    // ...But here it will.

 // -- Output -----------------------------------------------------------------

 someMethod executed. Arg Value = undefined

 AJS_Validator_Exception: argument 0 (Arg) in call to someMethod is undefined,
 which the corresponding ArgDef within the corresponding ValidationDef
 disallows (Method-Owner's Class: Object, Method-Owner's AJS_Validator_Tag:
 undefined).
      

Pre-/Post-Validators and Argument Injection/Mutation

As pointed out in the relevant user-guides, AJS_Validator, AJS_Logger and AJS_ODL all use a small utility-component (AJS_DefMgr) to manage their queues of pending definitions, and this component performs essential checks in order to confirm, for example, that a method cited in a given MethodNames property is, in fact, a function. If not, the AJS_DefMgr object in question will invoke the AJS_Validator object's exception handler, and it is here that it needs to pass a correct stack-offset value to the handler, otherwise the location of the offending applyValidationDef call in the application will be reported incorrectly (as explained above).

With this in hand, remember that AJS_Logger and AJS_ODL perform no argument validation internally. Instead, you have the option to delegate this to AJS_Validator, using the ValidationDefs in AJS_ODL.val.js and AJS_Logger.val.js to govern the validation process. AJS_Validator's modus operandum is to attach a prefix and suffix to the various methods in the objects concerned, but this increases the call-chain length between the point where the application calls an AJS_Logger/AJS_ODL method and the point where the relevant AJS_DefMgr object performs its validation.

This means that a given AJS_DefMgr must receive a different stack-offset depending on whether or not you use AJS_Logger.val.js and/or AJS_ODL.val.js, otherwise it will be unable to report reliably the location of the problem in the application-code.

One way to achieve this would be for, say, AJS_ODL to check for interception of its own methods (by testing for the presence of the interceptee methods that a given proxy function possesses), and to adjust the stack-offset accordingly. At the least, however, this would be inelegant – our objective, after all, in using method-call interception is to absolve ourselves from polluting the abstraction that a given method enshrines with logic that manages extraneous concerns, and that principle is as germane within AJS_Logger and AJS_ODL as it is anywhere else. Moreover, that approach would also fail where a given method possessed multiple affixes, because each affix would increase the 'distance' stack-wise from a given point-of-call to the method in question, which would render the stack-offset value inaccurate.

Instead, the AJS_Logger and AJS_ODL methods accept a trailing, undocumented StackOffset argument, a value for which your code should never pass. Given this, and if you choose to use, say, AJS_ODL without applying its ValidationDef, then the value for that argument is undefined, in which case the method in question passes a default value to the functions it calls.

However, if you do apply the ValidationDefs for those components, the preValidators concerned 'inject' a value for the StackOffset argument into the arguments array passed implicitly in the call to the corresponding AJS_ODL method, thus overriding the default. This ensures that the AJS_DefMgr in question always receives the correct stack-offset value, whether or not validation is applied to its client-object's methods.

This is an elegant technique, that may be of use in your own work. Given this, to inject an extra argument (and as the AJS-object user guide explains), you need only increment the length of the arguments array by one, and then fill the trailing slot in the array with the desired value. Example 58 demonstrates this.

This leaves only one issue, which is that AJS_Validator examines the number of ArgDefs that any CallDef holds, and calls its exception handler if the method in question receives a different number of arguments. Given this, if a preValidator injects additional arguments, we must add the same number of ArgDefs to any corresponding CallDef. In the case of the StackOffset discussed above, its ArgDef should specify an optional, never-negative, never-fractional number.

Example 58 reflects this issue too; and clearly it is a good idea to comment the extra ArgDef that notes the debug-only purpose of that extra ArgDef, stating that client code should not pass a value for that argument.

 01
 02 // -- Example 58 -------------------------------------------------------------
 03
 04 function someFunc (StackOffset)
 05     {
 06     // Assume that we always perform a test here, irrespective of whether
 07     // or not a ValidationDef has been applied to method_A, and that this
 08     // function uses throwException to report problems. Assume from there,
 09     // that this function has discovered some exceptional condition.
 10
 11     throwException ("BigBadException", "Run-time monstrosity discovered - run for the hills.", ++StackOffset);
 12
 13     }
 14
 15 var MyObj =
 16     {
 17     method_A : function (Arg_A, Arg_B, Arg_C, StackOffset)
 18        {
 19        StackOffset = (StackOffset || 0) + 1;
 20
 21        someFunc (StackOffset);
 22
 23        }
 24
 25     };
 26
 27 var ArgInjectingValidationDef =
 28     {
 29     CallDef :
 20        [
 31           { },                                         // We do not care, for the purpose of this demonstration, what
 32           { },                                         // the types and values of the first three arguments are, so we
 33           { },                                         // just say that some kind of non-null, defined argument is
 34                                                        // required in each place.
 35           {
 36           AllowClasses    : ["NumberLiteral"],         // But here we allow for the injected argument.
 37           NeverUndefined  : false,
 38           NeverZero       : true,
 39           NeverNegative   : true,
 40           NeverFractional : true
 41           }
 42
 43        ],
 44
 45     preValidator : function (Arguments)                // We must inject a stack-offset of 1 because the presence of
 46        {                                               // the AJS proxy-function between method_A and the second
 47        Arguments.length = 4;                           // call-point below adds an extra function to the call chain.
 48        Arguments[3]     = 1;                           // Note that we do not increment arguments.length, but set it
 49        }                                               // explicitly to 4. This is because we cannot guarantee that
 50                                                        // the caller provided arguments in all three preceding-places.
 51     };
 52
 53 try
 54    {
 55    MyObj.method_A (42, "Forty-Two", 4.2424242);        // Here, at line 55, someFunc is only one stage away in the call
 56    }                                                   // chain...
 57
 58 catch (E) { console.log (E.name + ": " + E.message); }
 59
 60 AJS_Validator.applyValidationDef (MyObj, ArgInjectingValidationDef, "MyObj");
 61
 62 try
 63    {
 64    MyObj.method_A (42, "Forty-Two", 4.2424242);        // ...But here at line 64, it is now two stages away in the call
 65    }                                                   // chain. Nevertheless, injecting a stack-offset argument ensures
 66                                                        // that the line-number of the call is reported correctly.
 67 catch (E) { console.log (E.name + ": " + E.message); }
 68
 69
 70

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

 BigBadException: Run-time monstrosity discovered - run for the hills. Exception occurred during
 execution of Example_58 at line: 55 in: Example_58.js.

 BigBadException: Run-time monstrosity discovered - run for the hills. Exception occurred during
 execution of Example_58 at line: 64 in: Example_58.js.