AJS_Validator User Guide

Introduction

AJS_Validator is a singleton JavaScript-object that is a client of the AJS proxy-function-based method-call interception object, and which is instantiated by a call to a function called createAJS_Validator. In short, it is a powerful and versatile debugging tool that supports the use of sophisticated design-by-contract techniques, which allow you to detect defects in your applications swiftly, accurately and automatically, thus expediting their resolution.

It possesses three methods by which run-time type and value constraints may be applied to method-arguments and -returns, and it supports fully the use of bespoke, user-defined validation functions. It also possesses a broad collection of ancillary methods that those validation functions may use to apply pre- and post-conditions to a given method's execution, thus allowing you to validate system state arbitrarily.

Additionally, and by delegating exception management to a discrete handler of your choosing, it permits you to test applications and diagnose problems both locally and on remote-clients. That is: when a real user of your software, located in some far-flung corner of the planet, does something that causes a bug to manifest, AJS_Validator can send a detailed report back to you that will tell you exactly what went wrong. With such ends in mind, the AspectJS distribution set furnishes you therefore with ThrowException.js and ThrowException_Remote.js, and you are entirely free, moreover, to define and use your own exception handlers that implement arbitrary behaviour.

In combination, these features allow comprehensive, flexible and fine-grained policing of an application's execution, thus aiding the development of quality code that implements robust and correct software systems.

It makes no use of host types, and so it is not limited to running only in a browser context, and will operate equally well on ES3- and ES5-compliant run-times. This allows you to validate and therefore debug legacy code on older platforms. Note also that it is optimised for performance in order to reduce any latency that its use introduces (which, in practice, is minimal).

If you are uncertain of the value of AJS_Validator's role in JavaScript development, read the AJS_Validator Motivation page before continuing with this user-guide.

Contents Introduction
Importing AJS_Validator
ValidationDefs
ValidationDef_Maintenance
AJS_Validator Exceptions
Enabling/Disabling AJS_Validator

 // -- AJS_Validator-Object Interface Snapshot (pseudo code) -------------------------
 //
 //    See the API documentation for formal property-definitions.
 //    Optional method-arguments delimited by square brackets.
 //

 var AJS_Validator =
    {
    //-- Principal Methods --------------------------------------------------------------------------

    applyValidationDef      : function (MethodOwner, ValidationDef[, MethodOwnerName])                      { ... },
    pushValidationDef       : function (MethodOwner, ValidationDef[, MethodOwnerName])                      { ... },

    applyValidationDefQueue : function ()                                                                   { ... },

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

    suspend                 : function ()                                                                   { ... },
    resume                  : function ()                                                                   { ... },

    //-- Ancillary Methods --------------------------------------------------------------------------

    getClass                : getClass,

    isOfClass               : function (Obj,      ClassName, MsgPrefix[, StackOffset])                      { ... },
    isOfClass_Not           : function (Obj,      ClassName, MsgPrefix[, StackOffset])                      { ... },

    isString                : function (Obj,                 MsgPrefix[, StackOffset[, NeverUndefined]])    { ... },
    isString_Not            : function (Obj,                 MsgPrefix[, StackOffset[, NeverUndefined]])    { ... },

    isBoolean               : function (Obj,                 MsgPrefix[, StackOffset[, NeverUndefined]])    { ... },
    isBoolean_Not           : function (Obj,                 MsgPrefix[, StackOffset[, NeverUndefined]])    { ... },

    isNumber                : function (Obj,                 MsgPrefix[, StackOffset[, NeverUndefined]])    { ... },
    isNumber_Not            : function (Obj,                 MsgPrefix[, StackOffset[, NeverUndefined]])    { ... },

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

    isDefined               : function (Obj,                 MsgPrefix[, StackOffset)                       { ... },
    isNull_Not              : function (Obj,                 MsgPrefix[, StackOffset[, NeverUndefined]])    { ... },

    isInteger               : function (Num,                 MsgPrefix[, StackOffset[, NeverUndefined]])    { ... },
    isRegExMatch            : function (Str,      RegEx,     MsgPrefix[, StackOffset[, NeverUndefined]])    { ... },

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

    isIn                    : function (Property, Obj,       MsgPrefix[, StackOffset])                      { ... },
    isIn_Not                : function (Property, Obj,       MsgPrefix[, StackOffset])                      { ... },

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

    isIn_Array              : function (Property, ArrayObj,  MsgPrefix[, StackOffset])                      { ... },
    isIn_Array_Not          : function (Property, ArrayObj,  MsgPrefix[, StackOffset])                      { ... },

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

    isConformant            : function (Obj,      Test,      MsgPrefix[, StackOffset[, MinSize[, MaxSize]]) { ... },
    isConformant_Array      : function (ArrayObj, Test,      MsgPrefix[, StackOffset[, MinLen [, MaxLen ]]) { ... },

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

    Version                 : "1.0",
    AJS_Validator_Tag       : "AJS_Validator"

    };
            

Importing AJS_Validator

To use AJS_Validator, import its code into your application, along with the code that defines the AJS object, and a file called AJS_DefMgr.js. To validate your application's code locally (i.e. on your own machine), you should also import ThrowException.js.

Alternatively, if you wish to validate your application remotely, where server-side code receives exception notifications from the client, you should import ThrowException_Remote.js instead (which requires ThrowException.js too).

To instantiate the AJS_Validator object, call the function called createAJS_Validator as early as possible in the application's run, as Examples 1 and 2 demonstrate. If you are using AJS_Validator locally, you should pass-in a reference to the AJS object, a reference to the function called createAJS_DefMgr, and a reference to the throwException function held in ThrowException.js.

Example 1 demonstrates these points.

If you are validating application execution remotely, you should pass the same arguments, except for the throwException reference. Here, you should call the createThrowException_Remote function instead, passing it the URL to which you wish exception notifications to be sent. You should then pass the function-reference that function returns to createAJS_Validator (see Example 2). Note that createThrowException_Remote requires the presence of the throwException function.

If neither of these options suit your requirements, you can pass a proprietary exception-handling function, which may do whatever you wish, as long as it implements the same calling signature as throwException and the function created by calling createThrowException_Remote.

The reference that createAJS_Validator returns should be assigned to a variable called AJS_Validator (because that is the name that it gives itself in any exception notifications that it generates), and that variable should reside within a scope that is visible to any functions of yours that call AJS_Validator's methods.

Following these steps, your application is then free to create and apply ValidationDefs (explored below) to whatever object-methods you choose. It may also avail itself of the ancillary methods that the AJS_Validator object implements.

Note that it is impossible to create multiple AJS_Validator objects (because doing so would be redundant), and any attempt to do so will cause createAJS_Validator to call the exception handler.


 <!-- Example 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->

 <html>
    <head>
       <title> ... </title>

       <script src = "ThrowException.js"></script> <!-- Or your own exception-handler implementation. -->
       <script src = "AJS.js"           ></script>
       <script src = "AJS_DefMgr.js"    ></script>
       <script src = "AJS_Validator.js" ></script>
       <script>

       "use strict";

       var AJS_Validator = createAJS_Validator  (AJS, createAJS_DefMgr, throwException);


       // Other code, including including ValidationDef instantiations,
       // and calls to AJS_Validator methods occur hereon.


       </script>

    </head>

    <body> ... </body>

 </html>
            

 <!-- Example 2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->

 <html>
    <head>
       <title> ... </title>

       <script src = "ThrowException.js"       ></script>
       <script src = "ThrowException_Remote.js"></script> <!-- Note that ThrowException_Remote.js requires ThrowException.js -->
       <script src = "AJS.js"                  ></script>
       <script src = "AJS_DefMgr.js"           ></script>
       <script src = "AJS_Validator.js"        ></script>
       <script>

       "use strict";

       var throwException_Remote = createThrowException_Remote ("OnClientSideException.php");
       var AJS_Validator         = createAJS_Validator         (AJS, createAJS_DefMgr, throwException_Remote);


       // Other code, including including ValidationDef instantiations,
       // and calls to AJS_Validator methods occur hereon.


       </script>

    </head>

    <body> ... </body>

 </html>
            

ValidationDefs

AJS_Validator defines the concept of a 'ValidationDef' (Validation DEFinition), which is an object that specifies the constraints that should be applied to one or more methods of a given object.

ValidationDefs are very similar to the LogDefs that one works with when using AJS_Logger objects, and the LoadDefs that pertain to the use of AJS_ODL. That is: you create a ValidationDef object, and then submit it to AJS_Validator, passing also as the first argument a reference to an object (the 'MethodOwner') to which the ValidationDef should be applied. It is by this mechanism that a given ValidationDef may be applied to multiple objects in an application.

A ValidationDef may possess the following case-sensitive properties (which are also given in pseudo-ish code in Example 3):

  • An optional MethodNames property that, if present, must contain at least one string-member containing the name of a method in the object in question. If the MethodNames property is present, AJS_Validator will apply the constraints prescribed in the rest of the ValidationDef to all of the methods to which it refers. If a ValidationDef possesses no MethodNames property, AJS_Validator will apply the constraints defined within that ValidationDef to all methods in the object in question.
  • An optional 'CallDef' (CALL DEFinition) object; an array that defines the required-properties of the calling arguments of the method(s) to which the enclosing ValidationDef is applied. CallDef arrays must comprise solely zero or more 'ArgDef' (ARGument DEFinition) objects.
  • An optional 'RtnDef' (ReTurN DEFinition) object, which defines the parameters to which the value or reference (if any) returned from the method(s) in question must conform. A RtnDef is, in essence, a lone ArgDef-object, with five differences that are covered in a later section of this guide.
  • An optional AllowDiffThis property. Unless this is present with a value of true, AJS_Validator will invoke the exception handler when a method that is covered by the ValidationDef in question is called in the context of an object other than the method-owner that was stipulated when that ValidationDef was applied (i.e. if this !== MethodOwner).
  • An optional 'preValidator' function-reference, which points to a user-defined function that performs additional processing in addition to any argument checking that AJS_Validator performs on the method(s) in question.
  • An optional 'postValidator' function-reference, which points to a user-defined function that performs additional processing in addition to any return-value scrutinisation that AJS_Validator performs on the method(s) in question (and which can refer to the same function-body as the corresponding preValidator).

Note that AJS_Validator validates the composition of ValidationDefs to the maximum degree possible, and will call the exception handler if a given property is malformed in some way. But note too that ValidationDefs may also contain additional user-defined properties, which AJS_Validator will ignore. This is useful because you may wish a given definition-object to carry proprietary information, or functions that enable Pre- and Post- Validators to operate.

Naturally, it is also possible to define all components of a given ValidationDef externally, thus allowing them to be shared among a number of ValidationDefs. Depending on the application, this permits large-scale method-call validation with great economy, and you should see the Arg-/Rtn-Def Re-Use section in this guide for a detailed exposition on this.

It is also possible, given the optional nature of the six ValidationDef-components that AJS_Validator recognises, for a given ValidationDef to be empty, in which case that definition-object will have no effect. However, ValidationDefs may be created dynamically (rather than statically, using object-literal syntax), therefore allowing empty ValidationDefs permits the simplification of any algorithm that you might implement to generate ValidationDefs in this way.


 // -- Example 3: ValidationDef Composition (pseudo code) ----------------------------

 var ValidationDef =
    {
    MethodNames   :                            // Optional.
       [
       String-Literal/String,                  // Mandatory method-name 0.
        .
        .
        .
       String-Literal/String                   // Optional method-name n.

       ],

    CallDef       :                            // Optional.
       [
       { ... },                                // Optional ArgDef-object 0.
        .
        .
        .
       { ... }                                 // Optional ArgDef-object n.

       ],

    RtnDef        : { ... },                   // Optional ArgDef-like object.

    AllowDiffThis : Boolean/BooleanLiteral,    // Optional.

    // Optional function-reference

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

    // Optional function-reference

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

    };
            

ValidationDef Maintenance

However tedious it may seem, it is better to create and modify ValidationDefs as you create and modify your code. It is silly to put such work off until the latter stages of a project, because AJS_Validator can help you from the beginning of development by catching problems that otherwise, and at best, would protract the development process.

This is a serious issue because it is so tempting to eschew creating or modifying a ValidationDef in the moment, simply because you are, say, tired and impatient to see the result of some new or modified functionality that you have implemented. We are all prone to working in this way, and we can all be forgiven for it on occasion, purely because software development can be such a hard task-mistress. The fact remains, however, that the less disciplined you are in your use of AJS_Validator the less it will be able to help you.

AJS_Validator Exceptions

AJS_Validator generates detailed, human-readable exception-notifications that enable you to address a 'contract violation' within an application immediately and without, perhaps, days of soul-destroying debugging as you attempt to trace the source of the problem.

If, when creating the AJS_Validator object, you pass throwException or the function returned from a call to createThrowException_Remote, the exception objects they generate will have the properties shown in Example 4 (throwException_Remote generates a JSON object, of the same composition).

Note that the message and name properties mirror the properties of the standard exception-classes, and that the name property will always have a value of "AJS_Validator_Exception" or "User_Defined_Validation_Exception". Note too that the stack property is an array of strings (one string per function in the call-chain), rather than a single string, as it is in the language-standard exception classes.

Typically, the exception notifications that AJS_Validator generates look like the samples given in Example 5, which were taken from a run of the AJS_Validator test-suite on Chrome). Note that different JS run-times yield minor variations in phrases that describe an application-object, although the meaning is always the same.

Note also that the argument-position information that is reported when the value of the argument in question contravenes the strictures in a corresponding ValidationDef operates on a zero-offset basis. This is to say that 'position 0' means the first argument in the method's signature, and so on.

Note too that the information in these notifications that refers to AJS_Validator_Tags relates to the use of the ApplyTag property in RtnDefs, and is of value when debugging an application (see the AllowTags/DisallowTags section for further details).

Be aware also that the output texts that are included in most of the examples in this guide omit the file and line-number information that is given normally, as that would give only the location within the test file that was used to test the code examples, not the location of the offending-line in the example code as you see it on-screen. In practice, and assuming that you are using throwException or throwException_Remote, all Arg- and Rtn-Def contraventions are reported with accurate file and line-number information (where the run-time in question provides stack traces).

Finally in this section, the AJS_Validator methods applyValidationDef and pushValidationDef both accept an optional third argument, which should be a string that represents the name of the method-owner object to which the ValidationDef in question should be applied. If you avail yourself of this argument, the contents of the string are incorporated into all relevant exception-notifications, which can assist greatly in diagnosing problems in your application. However, exception notifications are worded differently (i.e. less helpfully) if you do not use this argument.


 -- Example 4 (pseudo code) ---------------------------------------------------

    {
    message    : StringLiteral,

    name       : StringLiteral,                      // Always set to 'AJS_Validator_Exception'
                                                     // or 'User_Defined_Validation_Exception'.
    fileName   : StringLiteral,
    lineNumber : StringLiteral,

    stack      : [StringLiteral, ... StringLiteral]  // Note, a far-more-useful array of strings,
                                                     // rather than the single, concatenated string
    }                                                // that native exception-objects carry.
            

 -- Example 5: Exception-Notification Gallery ---------------------------------

 AJS_Validator_Exception: ArgDef-member 0 of the CallDef property of a
 ValidationDef passed to AJS_Validator.applyValidationDef possesses a
 NeverNegative property that is of class NumberLiteral not BooleanLiteral.

 Exception occurred during execution of
 Object.getValidationDefMalformationsTests.TestFunc, at line 2828, column 33,
 in //localhost/AspectJS_Test/Test_AJS_Validator.js

 ------------------------------------------------------------------------------

 AJS_Validator_Exception: ArgDef-member 0 of the CallDef property of a
 ValidationDef passed to AJS_Validator.applyValidationDef possesses a MaxLen
 property, where the corresponding AllowClasses property does not include
 arrays, strings or string-literals.

 Exception occurred during execution of
 Object.getPropertyCollisionsTests.TestFunc, at line 4587, column 30, in
 //localhost/AspectJS_Test/Test_AJS_Validator.js

 ------------------------------------------------------------------------------

 AJS_Validator_Exception: argument 0 (NumberObj) in call to TestObj.Method_E is
 a number with the value of 42, which the corresponding NeverPositive property
 of the corresponding ValidationDef disallows (Method-Owner's Class: Object,
 Method-Owner's AJS_Validator_Tag: TestObj).

 Exception occurred during execution of
 Object.getArgAndRtnDefContraventionsTests.TestFunc, at line 7530, column 24,
 in //localhost/Site_AspectJS_Test/Test_AJS_Validator.js

 ------------------------------------------------------------------------------

 AJS_Validator_Exception: argument 0 (ArrayObj) in call to TestObj.Method_K is
 an array of length 2, which is greater than the maximum of 1 that is permitted
 by the corresponding MaxLen property in the corresponding ValidationDef
 (Method-Owner's Class: Object, Method-Owner's AJS_Validator_Tag: TestObj).

 Exception occurred during execution of
 Object.getArgAndRtnDefContraventionsTests.TestFunc, at line 7638, column 24,
 in //localhost/AspectJS_Test/Test_AJS_Validator.js

 ------------------------------------------------------------------------------

 AJS_Validator_Exception: argument 0 (StrObj) in call to TestObj.Method_O is of
 class StringLiteral (value: 'A String'), which is precluded by the
 corresponding ArgDef's DisallowClasses property in the corresponding
 ValidationDef. The classes that are disallowed are: StringLiteral. (Object's
 AJS_Validator_Tag : undefined, Method-Owner's Class: Object, Method-Owner's
 AJS_Validator_Tag: TestObj).

 Exception occurred during execution of
 Object.getArgAndRtnDefContraventionsTests.TestFunc, at line 7722, column 24,
 in //localhost/AspectJS_Test/Test_AJS_Validator.js
            

Enabling/Disabling AJS_Validator

AJS_Validator is a development tool, and so it follows that you need at times to enable and disable its functionality, as it is easier to step through method calls using a debugger when those methods are not subject to the AJS interception mechanism.

Enabling and disabling AJS_Validator is trivial; simply issue a call to its suspend method after the component has been instantiated, and before your application makes any other validation-related calls. Subsequent calls to the methods that you use to apply ValidationDefs will then have no effect.

Given this, it follows that you can keep the suspend call in your code, where you comment it out when you do not need call validation, and where you remove the commenting when you do.