Commit 7a11a665 authored by Stefan Cameron's avatar Stefan Cameron

Lots of work... isMap and isSet remain, and fixing broken tests...

parent 4c5fd8d9
This diff is collapsed.
////// isAnyObject validator
import {default as _isObject} from 'lodash/isObject';
import types from '../types';
/**
* {@link rtvref.validation.validator Validator} function for the
* {@link rtvref.types.ANY_OBJECT ANY_OBJECT} type.
*
* Determines if a value is _any_ type of object except a primitive.
*
* @function rtvref.validation.isAnyObject
* @param {*} v Value to validate.
* @returns {boolean} `true` if validated; `false` otherwise.
*/
export const validator = function isAnyObject(v) {
return _isObject(v);
};
export const type = types.ANY_OBJECT;
////// isArray validator
import {default as _isArray} from 'lodash/isArray';
import {default as _isFinite} from 'lodash/isFinite';
import {validator as isFinite} from './isFinite';
import types from '../types';
import qualifiers from '../qualifiers';
......@@ -21,15 +22,19 @@ export const validator = function isArray(v, q = qualifiers.REQUIRED, args) {
if (valid) {
if (valid && args) { // then check args
if (_isFinite(args.length) && args.length >= 0) {
if (isFinite(args.length) && args.length >= 0) {
valid = (v.length === args.length);
} else {
if (valid && _isFinite(args.min) && args.min >= 0) {
let min;
if (valid && isFinite(args.min) && args.min >= 0) {
min = args.min;
valid = (v.length >= args.min);
}
if (valid && _isFinite(args.max) && args.max >= 0) {
valid = (v.length <= args.max);
if (valid && isFinite(args.max) && args.max >= 0) {
if (min === undefined || args.max >= min) {
valid = (v.length <= args.max);
} // else, ignore
}
}
}
......
////// isFinite validator
import {default as _isFinite} from 'lodash/isFinite';
import types from '../types';
import qualifiers from '../qualifiers';
/**
* {@link rtvref.validation.validator Validator} function for the
* {@link rtvref.types.FINITE FINITE} type.
*
* Determines if a value is a number literal __only__ (i.e. a
* {@link rtvref.types.primitives primitive}). It does not validate
* `new Number(1)`, which is an object that is a number.
*
* @function rtvref.validation.isFinite
* @param {*} v Value to validate.
* @param {string} [q] Validation qualifier. Defaults to
* {@link rtvref.qualifiers.REQUIRED REQUIRED}.
* @param {rtvref.types.numeric_args} [args] Type arguments.
* @returns {boolean} `true` if validated; `false` otherwise.
*/
export const validator = function isFinite(v, q = qualifiers.REQUIRED, args) {
let valid = _isFinite(v); // eliminates NaN, +/-Infinity
if (valid) {
if (valid && args) { // then check args
if (_isFinite(args.exact)) { // ignore if NaN, +/-Infinity
valid = (v === args.exact);
} else {
let min;
if (valid && _isFinite(args.min)) { // ignore if NaN, +/-Infinity
min = args.min;
valid = (v >= args.min);
}
if (valid && _isFinite(args.max)) { // ignore if NaN, +/-Infinity
if (min === undefined || args.max >= min) {
valid = (v <= args.max);
} // else, ignore
}
}
}
}
return valid;
};
export const type = types.FINITE;
////// isSymbol validator
////// isFunction validator
import {default as _isSymbol} from 'lodash/isSymbol';
import {default as _isFunction} from 'lodash/isFunction';
import types from '../types';
/**
* {@link rtvref.validation.validator Validator} function for the
* {@link rtvref.types.SYMBOL SYMBOL} type.
* @function rtvref.validation.isSymbol
* {@link rtvref.types.FUNCTION FUNCTION} type.
* @function rtvref.validation.isFunction
* @param {*} v Value to validate.
* @returns {boolean} `true` if validated; `false` otherwise.
*/
export const validator = function isSymbol(v) {
return _isSymbol(v);
export const validator = function isFunction(v) {
return _isFunction(v);
};
export const type = types.SYMBOL;
export const type = types.FUNCTION;
////// isMap validator
import {default as _isMap} from 'lodash/isMap';
import {default as _isFinite} from 'lodash/isFinite';
import {validator as isFinite} from './isFinite';
import {validator as isString} from './isString';
import types from '../types';
import qualifiers from '../qualifiers';
import {isTypeset} from './validation';
import {validator as isString} from './isString';
import * as impl from '../impl';
/**
* {@link rtvref.validation.validator Validator} function for the
......@@ -23,24 +25,47 @@ export const validator = function isMap(v, q = qualifiers.REQUIRED, args) {
if (valid) {
if (valid && args) { // then check args
// get the typeset for keys
const tsKeys = isTypeset(args.keys) ? args.keys : undefined;
// get the key expression only if the keys are expected to be strings
const keyExp = (tsKeys === types.STRING && isString(args.keyExp)) ?
// start with the easiest/most efficient test: length
if (valid && isFinite(args.length) && args.length >= 0) {
valid = (v.size >= args.length);
}
// remaining args, if specified, require iterating potentially the entire map
if (valid) {
// get the typeset for keys
const tsKeys = isTypeset(args.keys) ? args.keys : undefined;
// get the key expression only if the keys are expected to be strings
const keyExp = (tsKeys === types.STRING && isString(args.keyExp)) ?
args.keyExp : undefined;
// get the key expression flags only if we have a key expression
const keyFlagSpec = (keyExp && isString(args.keyFlagSpec)) ?
// get the key expression flags only if we have a key expression
const keyFlagSpec = (keyExp && isString(args.keyFlagSpec)) ?
args.keyFlagSpec : undefined;
// get the typeset for values
const tsValues = isTypeset(args.values) ? args.values : undefined;
// get the required length
const length = _isFinite(args.length) ? args.length : -1;
// get the typeset for values
const tsValues = isTypeset(args.values) ? args.values : undefined;
if (valid && length >= 0) {
valid = (v.size >= length);
}
if (tsKeys || tsValues) {
const reKeys = keyExp ? new RegExp(keyExp, keyFlagSpec) : undefined;
const it = v.entries(); // iterator
let next = it.next();
while (valid && !next.done) {
const [key, value] = next.value;
if (valid && tsKeys) {
valid = impl.check(key, tsKeys); // check key against typeset
if (valid && tsKeys === types.STRING && reKeys) {
valid = reKeys.test(key); // check key against regex since it's a string
}
}
// DEBUG HERE...
if (valid && tsValues) {
valid = impl.check(value, tsValues); // check value against typeset
}
next = it.next();
}
}
}
}
}
......
......@@ -31,15 +31,19 @@ export const validator = function isNumber(v, q = qualifiers.REQUIRED, args) {
}
if (valid && args) { // then check args
if (_isNumber(args.exact)) {
valid = (v === args.exact);
if (_isNumber(args.exact)) { // NaN OK for this arg (careful: NaN !== NaN...)
valid = (v === args.exact) || (_isNaN(v) && _isNaN(args.exact));
} else {
if (valid && _isNumber(args.min)) {
let min;
if (valid && _isNumber(args.min) && !_isNaN(args.min)) {
min = args.min;
valid = (v >= args.min);
}
if (valid && _isNumber(args.max)) {
valid = (v <= args.max);
if (valid && _isNumber(args.max) && !_isNaN(args.max)) {
if (min === undefined || args.max >= min) {
valid = (v <= args.max);
} // else, ignore
}
}
}
......
////// isObject validator
import {default as _isObjectLike} from 'lodash/isObjectLike';
import {validator as isArray} from './isArray';
import {validator as isMap} from './isMap';
import {validator as isWeakMap} from './isWeakMap';
import {validator as isSet} from './isSet';
import {validator as isWeakSet} from './isWeakSet';
import {validator as isRegExp} from './isRegExp';
import types from '../types';
/**
* {@link rtvref.validation.validator Validator} function for the
* {@link rtvref.types.OBJECT OBJECT} type.
*
* Determines if a value is an object that extends from `JavaScript.Object` and
* is not a function, array, regex, map, weak map, set, weak set, or primitive.
*
* @function rtvref.validation.isObject
* @param {*} v Value to validate.
* @returns {boolean} `true` if validated; `false` otherwise.
*/
export const validator = function isObject(v) { // no qualifier rules, no args
return _isObjectLike(v) && // excludes primitives and functions
!isArray(v) && // excludes arrays which are otherwise object-like (typeof [] === 'object')
!isMap(v) && !isWeakMap(v) && // excludes weak/maps
!isSet(v) && !isWeakSet(v) && // excludes weak/sets
!isRegExp(v); // excludes regex
};
export const type = types.OBJECT;
////// isString validator
import {default as _isFinite} from 'lodash/isFinite';
import {validator as isFinite} from './isFinite';
import types from '../types';
import qualifiers from '../qualifiers';
......@@ -29,17 +29,23 @@ export const validator = function isString(v, q = qualifiers.REQUIRED, args) {
}
if (valid && args) { // then check args
if (args.exact) {
if (isString(args.exact, qualifiers.EXPECTED)) {
valid = (v === args.exact);
} else if (args.partial) {
valid = v.includes(args.partial);
} else {
if (valid && _isFinite(args.min) && args.min >= 0) {
let min;
if (valid && isFinite(args.min) && args.min >= 0) {
min = args.min;
valid = (v.length >= args.min);
}
if (valid && _isFinite(args.max) && args.max >= 0) {
valid = (v.length <= args.max);
if (valid && isFinite(args.max) && args.max >= 0) {
if (min === undefined || args.max >= min) {
valid = (v.length <= args.max);
} // else, ignore
}
if (valid && args.partial) {
valid = v.includes(args.partial);
}
}
}
......
////// isWeakMap validator
import {default as _isWeakMap} from 'lodash/isWeakMap';
import types from '../types';
/**
* {@link rtvref.validation.validator Validator} function for the
* {@link rtvref.types.WEAK_MAP WEAK_MAP} type.
* @function rtvref.validation.isWeakMap
* @param {*} v Value to validate.
* @returns {boolean} `true` if validated; `false` otherwise.
*/
export const validator = function isWeakMap(v) {
return _isWeakMap(v);
};
export const type = types.WEAK_MAP;
////// isWeakSet validator
import {default as _isWeakSet} from 'lodash/isWeakSet';
import types from '../types';
/**
* {@link rtvref.validation.validator Validator} function for the
* {@link rtvref.types.WEAK_SET WEAK_SET} type.
* @function rtvref.validation.isWeakSet
* @param {*} v Value to validate.
* @returns {boolean} `true` if validated; `false` otherwise.
*/
export const validator = function isWeakSet(v) {
return _isWeakSet(v);
};
export const type = types.WEAK_SET;
////// Validation Module
import {default as _isArray} from 'lodash/isArray';
import {default as _isObject} from 'lodash/isObject';
import {default as _isObjectLike} from 'lodash/isObjectLike';
import {default as _isMap} from 'lodash/isMap';
import {default as _isWeakMap} from 'lodash/isWeakMap';
import {default as _isSet} from 'lodash/isSet';
import {default as _isWeakSet} from 'lodash/isWeakSet';
import {default as _forEach} from 'lodash/forEach';
import {default as _isSet} from 'lodash/isSet';
import {validator as isArray} from './isArray';
import {validator as isObject} from './isObject';
import {validator as isString} from './isString';
import {validator as isBoolean} from './isBoolean';
import {validator as isNumber} from './isNumber';
import {validator as isSymbol} from './isSymbol';
import {validator as isFunction} from './isFunction';
import {default as types, argTypes, objTypes, DEFAULT_OBJECT_TYPE} from './types';
import qualifiers from './qualifiers';
import {default as types, argTypes, objTypes, DEFAULT_OBJECT_TYPE} from '../types';
import qualifiers from '../qualifiers';
/**
* RTV Validation Namespace
......@@ -30,20 +32,27 @@ import qualifiers from './qualifiers';
// use these methods for any type validation needed.
//
// The only functions that should be defined directly in this module are those
// that don't map to a type, such as `isTypeset()` or `isPrimitive()`. Where
// possible, these functions should use the type validators rather than third-party
// code.
// NOTE: Validator modules are essentially precursors to plugins. For the time being,
// the expected interface for a validator module is:
// that don't map to a type as defined in ../types.js, such as `isTypeset()` or
// `isPrimitive()`.
//
// Where possible, these functions should use the other type validators rather
// than third-party code (e.g. isArray.js needs to validate some of its arguments
// as finite numbers, so it should use isFinite.js to do that validation rather
// than 'lodash/isFinite', and let isFinite.js decide whether 'lodash/isFinite'
// is the right way to validate a finite number). This way, it makes it much
// easier later on to change third-party libraries, or add additional logic to
// how a type is validated.
// NOTE: Validator modules are essentially precursors to plugins. For the time
// being, the expected interface for a validator module is:
// - 'validator: function': has the rtvref.validation.validator signature
// - 'type: string': The type the validator validates (e.g. rtvref.types.STRING
// for the STRING type validator)
//
// There can only be one validator for any given type.
//
// Later, if we ever expose a plugin architecture, we might change this to pass
// some type of registration function into the plugin, or the plugin calls a
// TODO: Later, if we ever expose a plugin architecture, we might change this to
// pass some type of registration function into the plugin, or the plugin calls a
// registration method on rtv, or maybe something else. Whatever we do, the basics
// would be to register a new type and provide the function for rtv to call to
// validate values for that type. Perhaps we might even allow overriding the
......@@ -51,6 +60,20 @@ import qualifiers from './qualifiers';
/**
* Type Validator Function.
*
* NOTE: A validator must always give __precedence__ to
* {@link rtvref.types.rules qualifier rules} for the type it's validating over
* any arguments specified.
*
* NOTE: A validator __must not__ attempt to validate values considering basic
* {@link rtvref.qualifiers qualifier} rules like allowing `null` when EXPECTED
* vs not when REQUIRED, unless the type itself allows or disallows these
* special values. A validator should focus on checking for its type. For example,
* the {@link rtvref.validation.isString isString validator} requires the value
* to be a string, excluding `null` and `undefined` regardless of the qualifier.
* It does, however, allow an empty string if the qualifier is not REQUIRED because
* that is one of its {@link rtvref.types.STRING type-specific qualifier rules}.
*
* @function rtvref.validation.validator
* @param {*} value The value to validate.
* @param {string} [qualifier] The validation qualifier from the immediate
......@@ -65,24 +88,6 @@ import qualifiers from './qualifiers';
* and args; `false` otherwise.
*/
/**
* Determines if a value is a map.
* @function rtvref.validation.isMap
* @param {*} v Value to validate.
* @returns {boolean} `true` if it is; `false` otherwise.
* @see {@link rtvref.types.MAP}
*/
export const isMap = _isMap;
/**
* Determines if a value is a weak map.
* @function rtvref.validation.isWeakMap
* @param {*} v Value to validate.
* @returns {boolean} `true` if it is; `false` otherwise.
* @see {@link rtvref.types.WEAK_MAP}
*/
export const isWeakMap = _isWeakMap;
/**
* Determines if a value is a set.
* @function rtvref.validation.isSet
......@@ -90,16 +95,7 @@ export const isWeakMap = _isWeakMap;
* @returns {boolean} `true` if it is; `false` otherwise.
* @see {@link rtvref.types.SET}
*/
export const isSet = _isSet;
/**
* Determines if a value is a weak set.
* @function rtvref.validation.isWeakSet
* @param {*} v Value to validate.
* @returns {boolean} `true` if it is; `false` otherwise.
* @see {@link rtvref.types.WEAK_SET}
*/
export const isWeakSet = _isWeakSet;
export const isSet = _isSet; // DEBUG TODO move to validator after impl isMap
/**
* Determines if a value is a JavaScript {@link rtvref.types.primitives primitive}.
......@@ -108,37 +104,13 @@ export const isWeakSet = _isWeakSet;
* @returns {boolean} `true` if it is; `false` otherwise.
*/
export const isPrimitive = function(v) {
return v === undefined ||
v === null ||
isString(v, {emptyOk: true}) || // empty strings are still strings in this case
return v === undefined || v === null ||
isString(v, qualifiers.EXPECTED) || // empty strings are OK in this case
isBoolean(v) ||
isNumber(v) ||
isSymbol(v);
};
/**
* Determines if a value is _any_ type of object except a primitive.
* @function rtvref.validation.isAnyObject
* @param {*} v Value to validate.
* @returns {boolean} `true` if it is; `false` otherwise.
*/
export const isAnyObject = _isObject;
/**
* Determines if a value is an object that extends from `JavaScript.Object` and
* is not a function, array, regex, map, weak map, set, weak set, or primitive.
* @function rtvref.validation.isObject
* @param {*} v Value to validate.
* @returns {boolean} `true` if it is; `false` otherwise.
*/
export const isObject = function(v) {
return _isObjectLike(v) && // excludes primitives and functions
!isArray(v) && // excludes arrays which are otherwise object-like (typeof [] === 'object')
!isMap(v) && !isWeakMap(v) && // excludes weak/maps
!isSet(v) && !isWeakSet(v) && // excludes weak/sets
!isRegExp(v); // excludes regex
};
/**
* Determines if a value is a typeset.
* @function rtvref.validation.isValidTypeset
......
import {expect} from 'chai';
import * as vtu from './validationTestUtil';
import types from '../../../src/lib/types';
import * as val from '../../../src/lib/validation/isAny';
describe('module: lib/validation/isAny', function() {
describe('validator', function() {
it('type', function() {
expect(val.type).to.equal(types.ANY);
});
it('valid values', function() {
expect(vtu.testValues(val.type, val.validator).failures).to.eql([]);
});
it('other types/values', function() {
// for ANY, _all_ other values should be _valid_ also
expect(vtu.testOtherValues(val.type, val.validator, true)).to.eql([]);
});
});
});
import {expect} from 'chai';
import _ from 'lodash';
import * as vtu from './validationTestUtil';
import types from '../../../src/lib/types';
import * as val from '../../../src/lib/validation/isAnyObject';
describe('module: lib/validation/isAnyObject', function() {
describe('validator', function() {
it('type', function() {
expect(val.type).to.equal(types.ANY_OBJECT);
});
it('should validate any object', function() {
const validValues = vtu.getValidValues(); // @type {Object}
const validTypes = Object.keys(validValues); // @type {Array}
_.pull(validTypes, types.ANY, types.STRING, types.BOOLEAN, types.NUMBER, types.SYMBOL); // remove primitives
let values = [];
_.forEach(validTypes, function(type) {
values = values.concat(validValues[type]);
});
expect(vtu.testValues(val.type, val.validator, values).failures).to.eql([]);
});
});
});
import {expect} from 'chai';
import _ from 'lodash';
import * as vtu from './validationTestUtil';
import types from '../../../src/lib/types';
import * as val from '../../../src/lib/validation/isArray';
describe('module: lib/validation/isArray', function() {
describe('validator', function() {
it('type', function() {
expect(val.type).to.equal(types.ARRAY);
});
it('valid values', function() {
expect(vtu.testValues(val.type, val.validator).failures).to.eql([]);
});
it('other types/values', function() {
expect(vtu.testOtherValues(val.type, val.validator)).to.eql([]);
});
});
describe('qualifiers', function() {
it('empty arrays allowed', function() {
_.forEach(qualifiers, function(qualifier) {
expect(val.validator([], qualifier)).to.equal(true);
});
});
});
describe('arguments', function() {
it('checks for an exact length', function() {
const arr = [7];
expect(val.validator([], undefined, {length: 0})).to.be.true;
expect(val.validator(arr, undefined, {length: 1})).to.be.true;
expect(val.validator(arr, undefined, {length: 2})).to.be.false;
expect(val.validator(arr, undefined, {length: 1.1})).to.be.false;
expect(val.validator(arr, undefined, {length: '1'})).to.be.true; // ignored
expect(val.validator(arr, undefined, {length: -0})).to.be.true; // ignored
expect(val.validator(arr, undefined, {length: -1})).to.be.true; // ignored
expect(val.validator(arr, undefined, {length: NaN})).to.be.true; // ignored
expect(val.validator(arr, undefined, {length: Infinity})).to.be.true; // ignored
expect(val.validator(arr, undefined, {length: -Infinity})).to.be.true; // ignored
expect(val.validator(arr, undefined, {length: Number.POSITIVE_INFINITY})).to.be.true; // ignored
expect(val.validator(arr, undefined, {length: Number.NEGATIVE_INFINITY})).to.be.true; // ignored
});
it('length takes precedence over min/max', function() {
const arr = [7, 8, 9];
expect(val.validator(arr, undefined, {length: 3, min: 4})).to.be.true;
expect(val.validator(arr, undefined, {length: 3, max: 2})).to.be.true;
expect(val.validator(arr, undefined, {length: 3, min: 4, max: 2})).to.be.true;
});
it('checks for a minimum length', function() {
const arr = [7, 8, 9];
expect(val.validator(arr, undefined, {min: 3})).to.be.true;
expect(val.validator(arr, undefined, {min: 0})).to.be.true;
expect(val.validator(arr, undefined, {min: '7'})).to.be.true; // ignored
expect(val.validator(arr, undefined, {min: -7})).to.be.true; // ignored
expect(val.validator(arr, undefined, {min: NaN})).to.be.true; // ignored
expect(val.validator(arr, undefined, {min: Infinity})).to.be.true; // ignored
expect(val.validator(arr, undefined, {min: -Infinity})).to.be.true; // ignored
expect(val.validator(arr, undefined, {min: Number.POSITIVE_INFINITY})).to.be.true; // ignored
expect(val.validator(arr, undefined, {min: Number.NEGATIVE_INFINITY})).to.be.true; // ignored
});
it('checks for a maximum length', function() {
const arr = [7, 8, 9];
expect(val.validator(arr, undefined, {max: 3})).to.be.true;
expect(val.validator(arr, undefined, {max: 0})).to.be.false;
expect(val.validator(arr, undefined, {max: '7'})).to.be.true; // ignored
expect(val.validator(arr, undefined, {max: -7})).to.be.true; // ignored
expect(val.validator(arr, undefined, {max: NaN})).to.be.true; // ignored
expect(val.validator(arr, undefined, {max: Infinity})).to.be.true; // ignored
expect(val.validator(arr, undefined, {max: -Infinity})).to.be.true; // ignored
expect(val.validator(arr, undefined, {max: Number.POSITIVE_INFINITY})).to.be.true; // ignored
expect(val.validator(arr, undefined, {max: Number.NEGATIVE_INFINITY})).to.be.true; // ignored
});
it('max ignored if less than min', function() {
expect(val.validator([7, 8, 9], undefined, {min: 2, max: 1})).to.be.true;
});
});
});
import {expect} from 'chai';
import * as vtu from './validationTestUtil';
import types from '../../../src/lib/types';
import * as val from '../../../src/lib/validation/isBoolean';
describe('module: lib/validation/isBoolean', function() {
describe('validator', function() {
it('type', function() {
expect(val.type).to.equal(types.BOOLEAN);
});
it('valid values', function() {
expect(vtu.testValues(val.type, val.validator).failures).to.eql([]);
});
it('other types/values', function() {
expect(vtu.testOtherValues(val.type, val.validator)).to.eql([]);
});
});
});
import {expect} from 'chai';
import _ from 'lodash';
import * as vtu from './validationTestUtil';
import types from '../../../src/lib/types';
import * as val from '../../../src/lib/validation/isFinite';
describe('module: lib/validation/isFinite', function() {
describe('validator', function() {
it('type', function() {
expect(val.type).to.equal(types.FINITE);