Skip to content

Commit

Permalink
Enhance object.pattern() matches option. Closes #2072. Closes #2073. C…
Browse files Browse the repository at this point in the history
…loses #2074
  • Loading branch information
hueniverse committed Aug 26, 2019
1 parent 71463d0 commit e1acb11
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 9 deletions.
4 changes: 3 additions & 1 deletion lib/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const Compile = require('./compile');
const Errors = require('./errors');
const Extend = require('./extend');
const Manifest = require('./manifest');
const Messages = require('./messages');
const Modify = require('./modify');
const Ref = require('./ref');
const Trace = require('./trace');
Expand Down Expand Up @@ -657,7 +658,8 @@ internals.Base = class {
$_createError(code, value, local, state, prefs, options = {}) {

const flags = options.flags !== false ? this._flags : {};
return new Errors.Report(code, value, local, flags, this._definition.messages, state, prefs);
const messages = options.messages ? Messages.merge(this._definition.messages, options.messages) : this._definition.messages;
return new Errors.Report(code, value, local, flags, messages, state, prefs);
}

$_getFlag(name) {
Expand Down
4 changes: 4 additions & 0 deletions lib/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ internals.schema = function (Joi, config) {
return Joi.valid(config);
}

if (typeof config === 'function') {
return Joi.custom(config);
}

Assert(typeof config === 'object', 'Invalid schema content:', typeof config);

if (Common.isResolvable(config)) {
Expand Down
11 changes: 8 additions & 3 deletions lib/types/keys.js
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,10 @@ module.exports = Any.extend({
const config = { [isRegExp ? 'regex' : 'schema']: pattern, rule: schema };
if (options.matches) {
config.matches = this.$_compile(options.matches);
if (config.matches.type !== 'array') {
config.matches = config.matches.$_root.array().items(config.matches);
}

obj.$_mutateRegister(config.matches);
}

Expand Down Expand Up @@ -851,7 +855,7 @@ internals.unknown = function (schema, value, unprocessed, errors, state, prefs)
}
});

const ancestors = [value];
const ancestors = [value, ...state.ancestors];

for (const key of unprocessed) {
const item = value[key];
Expand Down Expand Up @@ -896,8 +900,9 @@ internals.unknown = function (schema, value, unprocessed, errors, state, prefs)
continue;
}

const localState = state.localize(state.path, ancestors, schema.$_terms.patterns[i].matches);
const result = schema.$_terms.patterns[i].matches.$_validate(match, localState, prefs);
const stpm = schema.$_terms.patterns[i].matches;
const localState = state.localize(state.path, ancestors, stpm);
const result = stpm.$_validate(match, localState, prefs);
if (result.errors) {
const details = Errors.details(result.errors, { override: false });
details.matches = match;
Expand Down
3 changes: 2 additions & 1 deletion lib/validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,8 @@ exports.validate = function (value, schema, state, prefs) {
schema,
state,
error: createError,
warn: (code, local, localState) => state.mainstay.warnings.push(createError(code, local, localState))
warn: (code, local, localState) => state.mainstay.warnings.push(createError(code, local, localState)),
message: (messages, local) => schema.$_createError('custom', value, local, state, prefs, { messages })
};

// Prepare
Expand Down
56 changes: 52 additions & 4 deletions test/types/object.js
Original file line number Diff line number Diff line change
Expand Up @@ -3114,10 +3114,8 @@ describe('object', () => {

expect(() => {

Joi.object().pattern(/.*/, () => {

});
}).to.throw('Invalid schema content: function');
Joi.object().pattern(/.*/, Symbol('x'));
}).to.throw('Invalid schema content: symbol');

});

Expand Down Expand Up @@ -3537,6 +3535,56 @@ describe('object', () => {
expect(err.details).to.have.length(2);
});

it('matches matching keys to grandparents', () => {

const hasMatchingGrandparent = (value, { message, state }) => {

// 0: [ 'b' ]
// 1: { b: true }
// 2: { match: { b: true } }
// 3: { a: { match: { b: true } }, b: { match: { a: true } } }

if (state.ancestors[3][value] === undefined) {
return message('{{#label}} does not have a matching grandparent');
}

return value;
};

const schema = Joi.object()
.pattern(/.*/, Joi.object({
match: Joi.object()
.pattern(/.*/, Joi.boolean(), { matches: hasMatchingGrandparent })
}));

Helper.validate(schema, [
[{ a: { match: { b: true } }, b: { match: { a: true } } }, true],
[{ a: { match: { b: true } } }, false, null, {
message: '"a.match" keys failed to match pattern requirements',
details: [{
message: '"a.match" keys failed to match pattern requirements',
path: ['a', 'match'],
type: 'object.pattern.match',
context: {
key: 'match',
label: 'a.match',
matches: ['b'],
message: 'a.match[0] does not have a matching grandparent',
value: { b: true },
details: [
{
context: { key: 0, label: 'a.match[0]', value: 'b' },
message: 'a.match[0] does not have a matching grandparent',
path: ['a', 'match', 0],
type: 'custom'
}
]
}
}]
}]
]);
});

it('works with keys()', () => {

const schema = Joi.object()
Expand Down

0 comments on commit e1acb11

Please sign in to comment.