mirror of https://gitee.com/bigwinds/arangodb
829 lines
24 KiB
JavaScript
Executable File
829 lines
24 KiB
JavaScript
Executable File
'use strict';
|
|
|
|
// Load modules
|
|
|
|
const Hoek = require('hoek');
|
|
const Topo = require('topo');
|
|
const Any = require('./any');
|
|
const Errors = require('./errors');
|
|
const Cast = require('./cast');
|
|
const Ref = require('./ref');
|
|
|
|
|
|
// Declare internals
|
|
|
|
const internals = {};
|
|
|
|
|
|
internals.Object = class extends Any {
|
|
|
|
constructor() {
|
|
|
|
super();
|
|
this._type = 'object';
|
|
this._inner.children = null;
|
|
this._inner.renames = [];
|
|
this._inner.dependencies = [];
|
|
this._inner.patterns = [];
|
|
}
|
|
|
|
_base(value, state, options) {
|
|
|
|
let target = value;
|
|
const errors = [];
|
|
const finish = () => {
|
|
|
|
return {
|
|
value: target,
|
|
errors: errors.length ? errors : null
|
|
};
|
|
};
|
|
|
|
if (typeof value === 'string' &&
|
|
options.convert) {
|
|
|
|
value = internals.safeParse(value);
|
|
}
|
|
|
|
const type = this._flags.func ? 'function' : 'object';
|
|
if (!value ||
|
|
typeof value !== type ||
|
|
Array.isArray(value)) {
|
|
|
|
errors.push(this.createError(type + '.base', null, state, options));
|
|
return finish();
|
|
}
|
|
|
|
// Skip if there are no other rules to test
|
|
|
|
if (!this._inner.renames.length &&
|
|
!this._inner.dependencies.length &&
|
|
!this._inner.children && // null allows any keys
|
|
!this._inner.patterns.length) {
|
|
|
|
target = value;
|
|
return finish();
|
|
}
|
|
|
|
// Ensure target is a local copy (parsed) or shallow copy
|
|
|
|
if (target === value) {
|
|
if (type === 'object') {
|
|
target = Object.create(Object.getPrototypeOf(value));
|
|
}
|
|
else {
|
|
target = function () {
|
|
|
|
return value.apply(this, arguments);
|
|
};
|
|
|
|
target.prototype = Hoek.clone(value.prototype);
|
|
}
|
|
|
|
const valueKeys = Object.keys(value);
|
|
for (let i = 0; i < valueKeys.length; ++i) {
|
|
target[valueKeys[i]] = value[valueKeys[i]];
|
|
}
|
|
}
|
|
else {
|
|
target = value;
|
|
}
|
|
|
|
// Rename keys
|
|
|
|
const renamed = {};
|
|
for (let i = 0; i < this._inner.renames.length; ++i) {
|
|
const rename = this._inner.renames[i];
|
|
|
|
if (rename.options.ignoreUndefined && target[rename.from] === undefined) {
|
|
continue;
|
|
}
|
|
|
|
if (!rename.options.multiple &&
|
|
renamed[rename.to]) {
|
|
|
|
errors.push(this.createError('object.rename.multiple', { from: rename.from, to: rename.to }, state, options));
|
|
if (options.abortEarly) {
|
|
return finish();
|
|
}
|
|
}
|
|
|
|
if (Object.prototype.hasOwnProperty.call(target, rename.to) &&
|
|
!rename.options.override &&
|
|
!renamed[rename.to]) {
|
|
|
|
errors.push(this.createError('object.rename.override', { from: rename.from, to: rename.to }, state, options));
|
|
if (options.abortEarly) {
|
|
return finish();
|
|
}
|
|
}
|
|
|
|
if (target[rename.from] === undefined) {
|
|
delete target[rename.to];
|
|
}
|
|
else {
|
|
target[rename.to] = target[rename.from];
|
|
}
|
|
|
|
renamed[rename.to] = true;
|
|
|
|
if (!rename.options.alias) {
|
|
delete target[rename.from];
|
|
}
|
|
}
|
|
|
|
// Validate schema
|
|
|
|
if (!this._inner.children && // null allows any keys
|
|
!this._inner.patterns.length &&
|
|
!this._inner.dependencies.length) {
|
|
|
|
return finish();
|
|
}
|
|
|
|
const unprocessed = Hoek.mapToObject(Object.keys(target));
|
|
|
|
if (this._inner.children) {
|
|
for (let i = 0; i < this._inner.children.length; ++i) {
|
|
const child = this._inner.children[i];
|
|
const key = child.key;
|
|
const item = target[key];
|
|
|
|
delete unprocessed[key];
|
|
|
|
const localState = { key, path: (state.path || '') + (state.path && key ? '.' : '') + key, parent: target, reference: state.reference };
|
|
const result = child.schema._validate(item, localState, options);
|
|
if (result.errors) {
|
|
errors.push(this.createError('object.child', { key, child: child.schema._getLabel(key), reason: result.errors }, localState, options));
|
|
|
|
if (options.abortEarly) {
|
|
return finish();
|
|
}
|
|
}
|
|
|
|
if (child.schema._flags.strip || (result.value === undefined && result.value !== item)) {
|
|
delete target[key];
|
|
}
|
|
else if (result.value !== undefined) {
|
|
target[key] = result.value;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Unknown keys
|
|
|
|
let unprocessedKeys = Object.keys(unprocessed);
|
|
if (unprocessedKeys.length &&
|
|
this._inner.patterns.length) {
|
|
|
|
for (let i = 0; i < unprocessedKeys.length; ++i) {
|
|
const key = unprocessedKeys[i];
|
|
const localState = { key, path: (state.path ? state.path + '.' : '') + key, parent: target, reference: state.reference };
|
|
const item = target[key];
|
|
|
|
for (let j = 0; j < this._inner.patterns.length; ++j) {
|
|
const pattern = this._inner.patterns[j];
|
|
|
|
if (pattern.regex.test(key)) {
|
|
delete unprocessed[key];
|
|
|
|
const result = pattern.rule._validate(item, localState, options);
|
|
if (result.errors) {
|
|
errors.push(this.createError('object.child', { key, child: pattern.rule._getLabel(key), reason: result.errors }, localState, options));
|
|
|
|
if (options.abortEarly) {
|
|
return finish();
|
|
}
|
|
}
|
|
|
|
if (result.value !== undefined) {
|
|
target[key] = result.value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
unprocessedKeys = Object.keys(unprocessed);
|
|
}
|
|
|
|
if ((this._inner.children || this._inner.patterns.length) && unprocessedKeys.length) {
|
|
if (options.stripUnknown ||
|
|
options.skipFunctions) {
|
|
|
|
const stripUnknown = options.stripUnknown
|
|
? (options.stripUnknown === true ? true : !!options.stripUnknown.objects)
|
|
: false;
|
|
|
|
|
|
for (let i = 0; i < unprocessedKeys.length; ++i) {
|
|
const key = unprocessedKeys[i];
|
|
|
|
if (stripUnknown) {
|
|
delete target[key];
|
|
delete unprocessed[key];
|
|
}
|
|
else if (typeof target[key] === 'function') {
|
|
delete unprocessed[key];
|
|
}
|
|
}
|
|
|
|
unprocessedKeys = Object.keys(unprocessed);
|
|
}
|
|
|
|
if (unprocessedKeys.length &&
|
|
(this._flags.allowUnknown !== undefined ? !this._flags.allowUnknown : !options.allowUnknown)) {
|
|
|
|
for (let i = 0; i < unprocessedKeys.length; ++i) {
|
|
const unprocessedKey = unprocessedKeys[i];
|
|
errors.push(this.createError('object.allowUnknown', { child: unprocessedKey }, { key: unprocessedKey, path: state.path + (state.path ? '.' : '') + unprocessedKey }, options));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Validate dependencies
|
|
|
|
for (let i = 0; i < this._inner.dependencies.length; ++i) {
|
|
const dep = this._inner.dependencies[i];
|
|
const err = internals[dep.type].call(this, dep.key !== null && target[dep.key], dep.peers, target, { key: dep.key, path: (state.path || '') + (dep.key ? '.' + dep.key : '') }, options);
|
|
if (err instanceof Errors.Err) {
|
|
errors.push(err);
|
|
if (options.abortEarly) {
|
|
return finish();
|
|
}
|
|
}
|
|
}
|
|
|
|
return finish();
|
|
}
|
|
|
|
_func() {
|
|
|
|
const obj = this.clone();
|
|
obj._flags.func = true;
|
|
return obj;
|
|
}
|
|
|
|
keys(schema) {
|
|
|
|
Hoek.assert(schema === null || schema === undefined || typeof schema === 'object', 'Object schema must be a valid object');
|
|
Hoek.assert(!schema || !(schema instanceof Any), 'Object schema cannot be a joi schema');
|
|
|
|
const obj = this.clone();
|
|
|
|
if (!schema) {
|
|
obj._inner.children = null;
|
|
return obj;
|
|
}
|
|
|
|
const children = Object.keys(schema);
|
|
|
|
if (!children.length) {
|
|
obj._inner.children = [];
|
|
return obj;
|
|
}
|
|
|
|
const topo = new Topo();
|
|
if (obj._inner.children) {
|
|
for (let i = 0; i < obj._inner.children.length; ++i) {
|
|
const child = obj._inner.children[i];
|
|
|
|
// Only add the key if we are not going to replace it later
|
|
if (children.indexOf(child.key) === -1) {
|
|
topo.add(child, { after: child._refs, group: child.key });
|
|
}
|
|
}
|
|
}
|
|
|
|
for (let i = 0; i < children.length; ++i) {
|
|
const key = children[i];
|
|
const child = schema[key];
|
|
try {
|
|
const cast = Cast.schema(child);
|
|
topo.add({ key, schema: cast }, { after: cast._refs, group: key });
|
|
}
|
|
catch (castErr) {
|
|
if (castErr.hasOwnProperty('path')) {
|
|
castErr.path = key + '.' + castErr.path;
|
|
}
|
|
else {
|
|
castErr.path = key;
|
|
}
|
|
throw castErr;
|
|
}
|
|
}
|
|
|
|
obj._inner.children = topo.nodes;
|
|
|
|
return obj;
|
|
}
|
|
|
|
unknown(allow) {
|
|
|
|
const obj = this.clone();
|
|
obj._flags.allowUnknown = (allow !== false);
|
|
return obj;
|
|
}
|
|
|
|
length(limit) {
|
|
|
|
Hoek.assert(Hoek.isInteger(limit) && limit >= 0, 'limit must be a positive integer');
|
|
|
|
return this._test('length', limit, function (value, state, options) {
|
|
|
|
if (Object.keys(value).length === limit) {
|
|
return value;
|
|
}
|
|
|
|
return this.createError('object.length', { limit }, state, options);
|
|
});
|
|
}
|
|
|
|
arity(n) {
|
|
|
|
Hoek.assert(Hoek.isInteger(n) && n >= 0, 'n must be a positive integer');
|
|
|
|
return this._test('arity', n, function (value, state, options) {
|
|
|
|
if (value.length === n) {
|
|
return value;
|
|
}
|
|
|
|
return this.createError('function.arity', { n }, state, options);
|
|
});
|
|
}
|
|
|
|
minArity(n) {
|
|
|
|
Hoek.assert(Hoek.isInteger(n) && n > 0, 'n must be a strict positive integer');
|
|
|
|
return this._test('minArity', n, function (value, state, options) {
|
|
|
|
if (value.length >= n) {
|
|
return value;
|
|
}
|
|
|
|
return this.createError('function.minArity', { n }, state, options);
|
|
});
|
|
}
|
|
|
|
maxArity(n) {
|
|
|
|
Hoek.assert(Hoek.isInteger(n) && n >= 0, 'n must be a positive integer');
|
|
|
|
return this._test('maxArity', n, function (value, state, options) {
|
|
|
|
if (value.length <= n) {
|
|
return value;
|
|
}
|
|
|
|
return this.createError('function.maxArity', { n }, state, options);
|
|
});
|
|
}
|
|
|
|
min(limit) {
|
|
|
|
Hoek.assert(Hoek.isInteger(limit) && limit >= 0, 'limit must be a positive integer');
|
|
|
|
return this._test('min', limit, function (value, state, options) {
|
|
|
|
if (Object.keys(value).length >= limit) {
|
|
return value;
|
|
}
|
|
|
|
return this.createError('object.min', { limit }, state, options);
|
|
});
|
|
}
|
|
|
|
max(limit) {
|
|
|
|
Hoek.assert(Hoek.isInteger(limit) && limit >= 0, 'limit must be a positive integer');
|
|
|
|
return this._test('max', limit, function (value, state, options) {
|
|
|
|
if (Object.keys(value).length <= limit) {
|
|
return value;
|
|
}
|
|
|
|
return this.createError('object.max', { limit }, state, options);
|
|
});
|
|
}
|
|
|
|
pattern(pattern, schema) {
|
|
|
|
Hoek.assert(pattern instanceof RegExp, 'Invalid regular expression');
|
|
Hoek.assert(schema !== undefined, 'Invalid rule');
|
|
|
|
pattern = new RegExp(pattern.source, pattern.ignoreCase ? 'i' : undefined); // Future version should break this and forbid unsupported regex flags
|
|
|
|
try {
|
|
schema = Cast.schema(schema);
|
|
}
|
|
catch (castErr) {
|
|
if (castErr.hasOwnProperty('path')) {
|
|
castErr.message = castErr.message + '(' + castErr.path + ')';
|
|
}
|
|
|
|
throw castErr;
|
|
}
|
|
|
|
|
|
const obj = this.clone();
|
|
obj._inner.patterns.push({ regex: pattern, rule: schema });
|
|
return obj;
|
|
}
|
|
|
|
schema() {
|
|
|
|
return this._test('schema', null, function (value, state, options) {
|
|
|
|
if (value instanceof Any) {
|
|
return value;
|
|
}
|
|
|
|
return this.createError('object.schema', null, state, options);
|
|
});
|
|
}
|
|
|
|
with(key, peers) {
|
|
|
|
return this._dependency('with', key, peers);
|
|
}
|
|
|
|
without(key, peers) {
|
|
|
|
return this._dependency('without', key, peers);
|
|
}
|
|
|
|
xor() {
|
|
|
|
const peers = Hoek.flatten(Array.prototype.slice.call(arguments));
|
|
return this._dependency('xor', null, peers);
|
|
}
|
|
|
|
or() {
|
|
|
|
const peers = Hoek.flatten(Array.prototype.slice.call(arguments));
|
|
return this._dependency('or', null, peers);
|
|
}
|
|
|
|
and() {
|
|
|
|
const peers = Hoek.flatten(Array.prototype.slice.call(arguments));
|
|
return this._dependency('and', null, peers);
|
|
}
|
|
|
|
nand() {
|
|
|
|
const peers = Hoek.flatten(Array.prototype.slice.call(arguments));
|
|
return this._dependency('nand', null, peers);
|
|
}
|
|
|
|
requiredKeys(children) {
|
|
|
|
children = Hoek.flatten(Array.prototype.slice.call(arguments));
|
|
return this.applyFunctionToChildren(children, 'required');
|
|
}
|
|
|
|
optionalKeys(children) {
|
|
|
|
children = Hoek.flatten(Array.prototype.slice.call(arguments));
|
|
return this.applyFunctionToChildren(children, 'optional');
|
|
}
|
|
|
|
rename(from, to, options) {
|
|
|
|
Hoek.assert(typeof from === 'string', 'Rename missing the from argument');
|
|
Hoek.assert(typeof to === 'string', 'Rename missing the to argument');
|
|
Hoek.assert(to !== from, 'Cannot rename key to same name:', from);
|
|
|
|
for (let i = 0; i < this._inner.renames.length; ++i) {
|
|
Hoek.assert(this._inner.renames[i].from !== from, 'Cannot rename the same key multiple times');
|
|
}
|
|
|
|
const obj = this.clone();
|
|
|
|
obj._inner.renames.push({
|
|
from,
|
|
to,
|
|
options: Hoek.applyToDefaults(internals.renameDefaults, options || {})
|
|
});
|
|
|
|
return obj;
|
|
}
|
|
|
|
applyFunctionToChildren(children, fn, args, root) {
|
|
|
|
children = [].concat(children);
|
|
Hoek.assert(children.length > 0, 'expected at least one children');
|
|
|
|
const groupedChildren = internals.groupChildren(children);
|
|
let obj;
|
|
|
|
if ('' in groupedChildren) {
|
|
obj = this[fn].apply(this, args);
|
|
delete groupedChildren[''];
|
|
}
|
|
else {
|
|
obj = this.clone();
|
|
}
|
|
|
|
if (obj._inner.children) {
|
|
root = root ? (root + '.') : '';
|
|
|
|
for (let i = 0; i < obj._inner.children.length; ++i) {
|
|
const child = obj._inner.children[i];
|
|
const group = groupedChildren[child.key];
|
|
|
|
if (group) {
|
|
obj._inner.children[i] = {
|
|
key: child.key,
|
|
_refs: child._refs,
|
|
schema: child.schema.applyFunctionToChildren(group, fn, args, root + child.key)
|
|
};
|
|
|
|
delete groupedChildren[child.key];
|
|
}
|
|
}
|
|
}
|
|
|
|
const remaining = Object.keys(groupedChildren);
|
|
Hoek.assert(remaining.length === 0, 'unknown key(s)', remaining.join(', '));
|
|
|
|
return obj;
|
|
}
|
|
|
|
_dependency(type, key, peers) {
|
|
|
|
peers = [].concat(peers);
|
|
for (let i = 0; i < peers.length; ++i) {
|
|
Hoek.assert(typeof peers[i] === 'string', type, 'peers must be a string or array of strings');
|
|
}
|
|
|
|
const obj = this.clone();
|
|
obj._inner.dependencies.push({ type, key, peers });
|
|
return obj;
|
|
}
|
|
|
|
describe(shallow) {
|
|
|
|
const description = Any.prototype.describe.call(this);
|
|
|
|
if (description.rules) {
|
|
for (let i = 0; i < description.rules.length; ++i) {
|
|
const rule = description.rules[i];
|
|
// Coverage off for future-proof descriptions, only object().assert() is use right now
|
|
if (/* $lab:coverage:off$ */rule.arg &&
|
|
typeof rule.arg === 'object' &&
|
|
rule.arg.schema &&
|
|
rule.arg.ref /* $lab:coverage:on$ */) {
|
|
rule.arg = {
|
|
schema: rule.arg.schema.describe(),
|
|
ref: rule.arg.ref.toString()
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this._inner.children &&
|
|
!shallow) {
|
|
|
|
description.children = {};
|
|
for (let i = 0; i < this._inner.children.length; ++i) {
|
|
const child = this._inner.children[i];
|
|
description.children[child.key] = child.schema.describe();
|
|
}
|
|
}
|
|
|
|
if (this._inner.dependencies.length) {
|
|
description.dependencies = Hoek.clone(this._inner.dependencies);
|
|
}
|
|
|
|
if (this._inner.patterns.length) {
|
|
description.patterns = [];
|
|
|
|
for (let i = 0; i < this._inner.patterns.length; ++i) {
|
|
const pattern = this._inner.patterns[i];
|
|
description.patterns.push({ regex: pattern.regex.toString(), rule: pattern.rule.describe() });
|
|
}
|
|
}
|
|
|
|
return description;
|
|
}
|
|
|
|
assert(ref, schema, message) {
|
|
|
|
ref = Cast.ref(ref);
|
|
Hoek.assert(ref.isContext || ref.depth > 1, 'Cannot use assertions for root level references - use direct key rules instead');
|
|
message = message || 'pass the assertion test';
|
|
|
|
try {
|
|
schema = Cast.schema(schema);
|
|
}
|
|
catch (castErr) {
|
|
if (castErr.hasOwnProperty('path')) {
|
|
castErr.message = castErr.message + '(' + castErr.path + ')';
|
|
}
|
|
|
|
throw castErr;
|
|
}
|
|
|
|
const key = ref.path[ref.path.length - 1];
|
|
const path = ref.path.join('.');
|
|
|
|
return this._test('assert', { schema, ref }, function (value, state, options) {
|
|
|
|
const result = schema._validate(ref(value), null, options, value);
|
|
if (!result.errors) {
|
|
return value;
|
|
}
|
|
|
|
const localState = Hoek.merge({}, state);
|
|
localState.key = key;
|
|
localState.path = path;
|
|
return this.createError('object.assert', { ref: localState.path, message }, localState, options);
|
|
});
|
|
}
|
|
|
|
type(constructor, name) {
|
|
|
|
Hoek.assert(typeof constructor === 'function', 'type must be a constructor function');
|
|
name = name || constructor.name;
|
|
|
|
return this._test('type', name, function (value, state, options) {
|
|
|
|
if (value instanceof constructor) {
|
|
return value;
|
|
}
|
|
|
|
return this.createError('object.type', { type: name }, state, options);
|
|
});
|
|
}
|
|
|
|
ref() {
|
|
|
|
return this._test('ref', null, function (value, state, options) {
|
|
|
|
if (Ref.isRef(value)) {
|
|
return value;
|
|
}
|
|
|
|
return this.createError('function.ref', null, state, options);
|
|
});
|
|
}
|
|
};
|
|
|
|
internals.safeParse = function (value) {
|
|
|
|
try {
|
|
return JSON.parse(value);
|
|
}
|
|
catch (parseErr) {}
|
|
|
|
return value;
|
|
};
|
|
|
|
|
|
internals.renameDefaults = {
|
|
alias: false, // Keep old value in place
|
|
multiple: false, // Allow renaming multiple keys into the same target
|
|
override: false // Overrides an existing key
|
|
};
|
|
|
|
|
|
internals.groupChildren = function (children) {
|
|
|
|
children.sort();
|
|
|
|
const grouped = {};
|
|
|
|
for (let i = 0; i < children.length; ++i) {
|
|
const child = children[i];
|
|
Hoek.assert(typeof child === 'string', 'children must be strings');
|
|
const group = child.split('.')[0];
|
|
const childGroup = grouped[group] = (grouped[group] || []);
|
|
childGroup.push(child.substring(group.length + 1));
|
|
}
|
|
|
|
return grouped;
|
|
};
|
|
|
|
|
|
internals.with = function (value, peers, parent, state, options) {
|
|
|
|
if (value === undefined) {
|
|
return value;
|
|
}
|
|
|
|
for (let i = 0; i < peers.length; ++i) {
|
|
const peer = peers[i];
|
|
if (!Object.prototype.hasOwnProperty.call(parent, peer) ||
|
|
parent[peer] === undefined) {
|
|
|
|
return this.createError('object.with', { peer }, state, options);
|
|
}
|
|
}
|
|
|
|
return value;
|
|
};
|
|
|
|
|
|
internals.without = function (value, peers, parent, state, options) {
|
|
|
|
if (value === undefined) {
|
|
return value;
|
|
}
|
|
|
|
for (let i = 0; i < peers.length; ++i) {
|
|
const peer = peers[i];
|
|
if (Object.prototype.hasOwnProperty.call(parent, peer) &&
|
|
parent[peer] !== undefined) {
|
|
|
|
return this.createError('object.without', { peer }, state, options);
|
|
}
|
|
}
|
|
|
|
return value;
|
|
};
|
|
|
|
|
|
internals.xor = function (value, peers, parent, state, options) {
|
|
|
|
const present = [];
|
|
for (let i = 0; i < peers.length; ++i) {
|
|
const peer = peers[i];
|
|
if (Object.prototype.hasOwnProperty.call(parent, peer) &&
|
|
parent[peer] !== undefined) {
|
|
|
|
present.push(peer);
|
|
}
|
|
}
|
|
|
|
if (present.length === 1) {
|
|
return value;
|
|
}
|
|
|
|
if (present.length === 0) {
|
|
return this.createError('object.missing', { peers }, state, options);
|
|
}
|
|
|
|
return this.createError('object.xor', { peers }, state, options);
|
|
};
|
|
|
|
|
|
internals.or = function (value, peers, parent, state, options) {
|
|
|
|
for (let i = 0; i < peers.length; ++i) {
|
|
const peer = peers[i];
|
|
if (Object.prototype.hasOwnProperty.call(parent, peer) &&
|
|
parent[peer] !== undefined) {
|
|
return value;
|
|
}
|
|
}
|
|
|
|
return this.createError('object.missing', { peers }, state, options);
|
|
};
|
|
|
|
|
|
internals.and = function (value, peers, parent, state, options) {
|
|
|
|
const missing = [];
|
|
const present = [];
|
|
const count = peers.length;
|
|
for (let i = 0; i < count; ++i) {
|
|
const peer = peers[i];
|
|
if (!Object.prototype.hasOwnProperty.call(parent, peer) ||
|
|
parent[peer] === undefined) {
|
|
|
|
missing.push(peer);
|
|
}
|
|
else {
|
|
present.push(peer);
|
|
}
|
|
}
|
|
|
|
const aon = (missing.length === count || present.length === count);
|
|
return !aon ? this.createError('object.and', { present, missing }, state, options) : null;
|
|
};
|
|
|
|
|
|
internals.nand = function (value, peers, parent, state, options) {
|
|
|
|
const present = [];
|
|
for (let i = 0; i < peers.length; ++i) {
|
|
const peer = peers[i];
|
|
if (Object.prototype.hasOwnProperty.call(parent, peer) &&
|
|
parent[peer] !== undefined) {
|
|
|
|
present.push(peer);
|
|
}
|
|
}
|
|
|
|
const values = Hoek.clone(peers);
|
|
const main = values.splice(0, 1)[0];
|
|
const allPresent = (present.length === peers.length);
|
|
return allPresent ? this.createError('object.nand', { main, peers: values }, state, options) : null;
|
|
};
|
|
|
|
|
|
module.exports = new internals.Object();
|