'use strict';
|
|
const Hoek = require('@hapi/hoek');
|
|
const Any = require('../any');
|
const Cast = require('../../cast');
|
const Ref = require('../../ref');
|
|
|
const internals = {};
|
|
|
internals.Alternatives = class extends Any {
|
|
constructor() {
|
|
super();
|
this._type = 'alternatives';
|
this._invalids.remove(null);
|
this._inner.matches = [];
|
}
|
|
_init(...args) {
|
|
return args.length ? this.try(...args) : this;
|
}
|
|
_base(value, state, options) {
|
|
const errors = [];
|
const il = this._inner.matches.length;
|
const baseType = this._baseType;
|
|
for (let i = 0; i < il; ++i) {
|
const item = this._inner.matches[i];
|
if (!item.schema) {
|
const schema = item.peek || item.is;
|
const input = item.is ? item.ref(state.reference || state.parent, options) : value;
|
const failed = schema._validate(input, null, options, state.parent).errors;
|
|
if (failed) {
|
if (item.otherwise) {
|
return item.otherwise._validate(value, state, options);
|
}
|
}
|
else if (item.then) {
|
return item.then._validate(value, state, options);
|
}
|
|
if (i === (il - 1) && baseType) {
|
return baseType._validate(value, state, options);
|
}
|
|
continue;
|
}
|
|
const result = item.schema._validate(value, state, options);
|
if (!result.errors) { // Found a valid match
|
return result;
|
}
|
|
errors.push(...result.errors);
|
}
|
|
if (errors.length) {
|
return { errors: this.createError('alternatives.child', { reason: errors }, state, options) };
|
}
|
|
return { errors: this.createError('alternatives.base', null, state, options) };
|
}
|
|
try(...schemas) {
|
|
schemas = Hoek.flatten(schemas);
|
Hoek.assert(schemas.length, 'Cannot add other alternatives without at least one schema');
|
|
const obj = this.clone();
|
|
for (let i = 0; i < schemas.length; ++i) {
|
const cast = Cast.schema(this._currentJoi, schemas[i]);
|
if (cast._refs.length) {
|
obj._refs.push(...cast._refs);
|
}
|
|
obj._inner.matches.push({ schema: cast });
|
}
|
|
return obj;
|
}
|
|
when(condition, options) {
|
|
let schemaCondition = false;
|
Hoek.assert(Ref.isRef(condition) || typeof condition === 'string' || (schemaCondition = condition instanceof Any), 'Invalid condition:', condition);
|
Hoek.assert(options, 'Missing options');
|
Hoek.assert(typeof options === 'object', 'Invalid options');
|
if (schemaCondition) {
|
Hoek.assert(!options.hasOwnProperty('is'), '"is" can not be used with a schema condition');
|
}
|
else {
|
Hoek.assert(options.hasOwnProperty('is'), 'Missing "is" directive');
|
}
|
|
Hoek.assert(options.then !== undefined || options.otherwise !== undefined, 'options must have at least one of "then" or "otherwise"');
|
|
const obj = this.clone();
|
let is;
|
if (!schemaCondition) {
|
is = Cast.schema(this._currentJoi, options.is);
|
|
if (options.is === null || !(Ref.isRef(options.is) || options.is instanceof Any)) {
|
|
// Only apply required if this wasn't already a schema or a ref, we'll suppose people know what they're doing
|
is = is.required();
|
}
|
}
|
|
const item = {
|
ref: schemaCondition ? null : Cast.ref(condition),
|
peek: schemaCondition ? condition : null,
|
is,
|
then: options.then !== undefined ? Cast.schema(this._currentJoi, options.then) : undefined,
|
otherwise: options.otherwise !== undefined ? Cast.schema(this._currentJoi, options.otherwise) : undefined
|
};
|
|
if (obj._baseType) {
|
|
item.then = item.then && obj._baseType.concat(item.then);
|
item.otherwise = item.otherwise && obj._baseType.concat(item.otherwise);
|
}
|
|
if (!schemaCondition) {
|
Ref.push(obj._refs, item.ref);
|
obj._refs.push(...item.is._refs);
|
}
|
|
if (item.then && item.then._refs.length) {
|
obj._refs.push(...item.then._refs);
|
}
|
|
if (item.otherwise && item.otherwise._refs.length) {
|
obj._refs.push(...item.otherwise._refs);
|
}
|
|
obj._inner.matches.push(item);
|
|
return obj;
|
}
|
|
label(name) {
|
|
const obj = super.label(name);
|
obj._inner.matches = obj._inner.matches.map((match) => {
|
|
if (match.schema) {
|
return { schema: match.schema.label(name) };
|
}
|
|
match = Object.assign({}, match);
|
if (match.then) {
|
match.then = match.then.label(name);
|
}
|
|
if (match.otherwise) {
|
match.otherwise = match.otherwise.label(name);
|
}
|
|
return match;
|
});
|
return obj;
|
}
|
|
describe() {
|
|
const description = super.describe();
|
const alternatives = [];
|
for (let i = 0; i < this._inner.matches.length; ++i) {
|
const item = this._inner.matches[i];
|
if (item.schema) {
|
|
// try()
|
|
alternatives.push(item.schema.describe());
|
}
|
else {
|
|
// when()
|
|
const when = item.is ? {
|
ref: item.ref.toString(),
|
is: item.is.describe()
|
} : {
|
peek: item.peek.describe()
|
};
|
|
if (item.then) {
|
when.then = item.then.describe();
|
}
|
|
if (item.otherwise) {
|
when.otherwise = item.otherwise.describe();
|
}
|
|
alternatives.push(when);
|
}
|
}
|
|
description.alternatives = alternatives;
|
return description;
|
}
|
|
};
|
|
|
module.exports = new internals.Alternatives();
|