(Quick Reference)

2 Usage - Reference Documentation

Authors: Aaron Brown

Version: 1.0.0

2 Usage

This section will describe general usage of Sanity Checkers, as well as usage of the packaged extensions that come with the plugin.

2.1 General Usage

General usage of any Sanity Checker is to run the various checks, and then determining if there were any failures. The scope of how to handle failures if they are detected falls outside of this guide, as that is dependant on the application or circumstances of the failure.

Running Sanity Checks

The BasicSanityChecker is used in these examples, as the SanityChecker is too limited to adiquately demonstrate usage.

The basic methodology for running sanity checks is roughly the following:

  • Instantiate a sanity checker.
  • Provide the entity to check.
  • Run the checks (runChecks).
  • Detect Failures.
  • React to results.

The way this appears in code is as follows:

import me.sudofu.sanitycheck.BasicSanityChecker

// ...

BasicSanityChecker checker = new BasicSanityChecker()

checker.runChecks { check('a', a, 'parameter').isString()

check('b', b) isMap() isNotEmpty()

check('b.age', b.age).isInteger() }

if (checker.hasFailures()) { // Respond to failures. }

// ...

The above shows the closure usage, Notice the repeated use of check, which is used to indicate to the sanity checker what entity is it checking on.

Note that in the first call to check, the classification 'parameter' is used. By default, the classification is hard-coded and configured as 'entity'; it is possible to configure the default value through configuring the default classification. Additionaly, this configuration can be overridden by parameter as shown here.

For BasicSanityChecker and StringCoerciveSanityChecker, and any other extensions which are written appropriately, invoking a sanity check prior to calling check for the first time will result in an IllegalStateException.

This does not need to be put in a try / catch clause, as this is to prevent developer folly.

While the closure-style is recommended, more verbose calling structures are possible, but will not be discussed in this guide.

Allow Pass on null

Sanity checkers outside the ones packaged in this plugin may not be written to implement this feature. If writing such a sanity checker, please indicate such.

All references to sanity checkers is only confidently referring to sanity checkers provided by the plugin.

isNotNull inherently does not follow the behavior outlined in this section. It will always result in a failure if the entity being checked is null.

Generally speaking, null changes the behavior of sanity checks. All sanity checkers are designed to accommodate two methodologies:

  1. Check if the entity is null, and if so report failure.
  2. Check if the entity is null, and if so allow it to pass.

The sanity checkers can be instructed to follow either of these methodologies by various means of granularity.

The highest level of granularity is the default. By default, sanity checkers are hard-coded and configured to disallow pass on null. This default configuration can be changed via allowPassOnNull.

The next level of granularity is in the SanityChecker constructor. This will set the default behavior for that sanity checker.

The next level of granularity is through use of either directly modifying allowPassOnNull, or invoking allowPassOnNull() / disallowPassOnNull(). This will alter the behavior over the course of a sanity checker's life.

The final level of granularity is via the allowPassOnNull parameter at every sanity check. This is the finest granularity and only impacts the particular sanity check performed.

These levels of granularity provide extremely high flexibility for this behavior. Of course, it is best to develop with the mindset of using the highest level necessary, to avoid confusing the behavior.

An example use-case for using both methodologies in one sanity checker is to check optional parameters. In many cases, optional parameters are allowed to be null, and therefore it is undesirable to let that interfere with sanity checks if they are not present, but abide by sanity checks if they are.

Refer to the following example to see this implemention using Groovy Named Parameters. The method first checks the required parameters, assuming that the default behavior is set to disallow null to pass, and then allows null to pass when checking the optional parameters.

public void myMethod(Map options, String a, Date birthday, int… luckyNumbers) {
    BasicSanityChecker checker = new BasicSanityChecker().runChecks {
        check('a', a).isNotEmpty()

check('birthday', myBirthday).isNotNull()

luckyNumbers.eachWithIndex { index, number -> check("luckyNumber[${index}]", number).isNotNull() }

allowPassOnNull()

options.with { check('favoriteColor', myFavoriteColor) isString() isNotEmpty()

check('height', myHeight) isDouble()

check('myEyesAreBlue', myEyesAreBlue) isBoolean() } }

if (checker.hasFailures) { // Handle the result. } }

2.2 BasicSanityChecker

The BasicSanityChecker is a very strict sanity checker. It is used when only strict datatyping needs to be enforced. It is arguably less useful if an application is strictly typed, however it is provided for completeness and for giving mnemonics to certain sanity checks that can otherwise be implemented manually.

However, due to the nature of some extensions, the simplistic nature of the BasicSanityChecker gives it some sanity checks that may not be feasible with other extensions.

Much like the root SanityChecker this checker is likely best used as a more fleshed-out basis for extension.

Sample:

public void myMethod(Map options, String a, Date birthday, int… luckyNumbers) {
    BasicSanityChecker checker = new BasicSanityChecker().runChecks {
        check('a', a).isNotEmpty()

check('birthday', myBirthday).isNotNull()

luckyNumbers.eachWithIndex { index, number -> check("luckyNumber[${index}]", number).isNotNull() }

allowPassOnNull()

options.with { check('favoriteColor', myFavoriteColor) isString() isNotEmpty()

check('height', myHeight) isDouble()

check('myEyesAreBlue', myEyesAreBlue) isBoolean() } }

if (checker.hasFailures) { // Handle the result. } }

2.3 StringCoerciveSanityChecker

The StringCoerciveSanityChecker is designed to perform sanity checks, with the added serendipity of coercing String entities where possible.

This sanity checker is intended for general use, especially where inputs are not strictly typed, and may be normalized as String. For instance, JSON in a JAXRS Resource, or with Groovy's Named Parameters.

The StringCoerciveSanityChecker handles the entity along with determining pass / fail of the check:

  • Run the sanity check.
  • If the entity is a String, and the String is coercible to the type the sanity check is looking for, return the entity coerced to that type.
  • Otherwise, return the entity.

The following example demonstrates the usage:

import me.sudofu.sanitycheck.SanityChecker
import me.sudofu.sanitycheck.BasicSanityChecker
import me.sudofu.sanitycheck.StringCoerciveSanityChecker as SCSanityChecker

public void myMethod(Map options, String a, Date birthday, int… luckyNumbers) { SanityChecker checker = new SCSanityChecker().runChecks { check('a', a).isNotEmpty()

check('birthday', myBirthday).isNotNull()

luckyNumbers.eachWithIndex { index, number -> luckyNumbers[index] = check("luckyNumber[${index}]", number).isNotNull() }

allowPassOnNull()

options.with { check('favoriteColor', myFavoriteColor) isString() isNotEmpty()

myHeight = check('height', myHeight) } }

if (checker.hasFailures) { // Handle the result. }

checker = new BasicSanityChecker() .check('myEyesAreBlue', options.myEyesAreBlue) .isBoolean()

if (checker.hasFailures) { // Handle the result. } }

Notice that not all sanity checks performed are strictly from StringCoerciveSanityChecker. Also note that isBoolean is not within StringCoerciveSanityChecker, so it has to be checked separately. The justification of this is because a String is not very intuitively coercible into Boolean, since both "false" and "puppies" would both resolve to false.

The behavior of StringCoerciveSanityChecker is subject to Groovy's string-coersion algorithms. Therefore, a change in Groovy's string-coersion algorithms will change the behavior of this sanity checker.

For example, "15D" will successfully pass and be coerced from isDouble, but "15I" will not pass nor be coerced from isInteger.