From 075c6bba24539a1502fd8d774b2faaf3553467a4 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Tue, 1 Oct 2019 23:59:58 -0700 Subject: [PATCH 01/20] Fix handling of shadow values. Closes #2156 --- lib/base.js | 6 +- lib/common.js | 45 ------------ lib/ref.js | 7 +- lib/state.js | 150 ++++++++++++++++++++++++++++++++++++++ lib/types/alternatives.js | 14 +++- lib/types/array.js | 21 +++++- lib/types/keys.js | 3 +- lib/validator.js | 43 +---------- test/base.js | 143 ++++++++++++++++++++++++++++++++++++ 9 files changed, 338 insertions(+), 94 deletions(-) create mode 100755 lib/state.js diff --git a/lib/base.js b/lib/base.js index 488226576..81398d404 100755 --- a/lib/base.js +++ b/lib/base.js @@ -657,7 +657,11 @@ internals.Base = class { prefs.abortEarly = true; prefs._externals = false; - return !Validator.validate(value, this, state, prefs, overrides).errors; + state.snapshot(); + const result = !Validator.validate(value, this, state, prefs, overrides).errors; + state.restore(); + + return result; } $_modify(options) { diff --git a/lib/common.js b/lib/common.js index 4b1734da6..05cb66e98 100755 --- a/lib/common.js +++ b/lib/common.js @@ -163,51 +163,6 @@ exports.preferences = function (target, source) { }; -exports.State = class { - - constructor(path, ancestors, state) { - - this.path = path; - this.ancestors = ancestors; // [parent, ..., root] - - this.mainstay = state.mainstay; - this.schemas = state.schemas; // [current, ..., root] - this.debug = null; - } - - localize(path, ancestors = null, schema = null) { - - const state = new exports.State(path, ancestors, this); - - if (schema && - state.schemas) { - - state.schemas = [internals.schemas(schema), ...state.schemas]; - } - - return state; - } - - nest(schema, debug) { - - const state = new exports.State(this.path, this.ancestors, this); - state.schemas = state.schemas && [internals.schemas(schema), ...state.schemas]; - state.debug = debug; - return state; - } -}; - - -internals.schemas = function (schema) { - - if (exports.isSchema(schema)) { - return { schema }; - } - - return schema; -}; - - exports.tryWithPath = function (fn, key, options = {}) { try { diff --git a/lib/ref.js b/lib/ref.js index 266464405..035b80a55 100755 --- a/lib/ref.js +++ b/lib/ref.js @@ -149,7 +149,7 @@ internals.Ref = class { state.mainstay.shadow && options.shadow !== false) { - resolved = state.mainstay.shadow.get(this.path); + resolved = state.mainstay.shadow.get(this.absolute(state)); } if (resolved === undefined) { @@ -179,6 +179,11 @@ internals.Ref = class { return this.display; } + absolute(state) { + + return [...state.path.slice(0, -this.ancestor), ...this.path]; + } + clone() { return new internals.Ref(this); diff --git a/lib/state.js b/lib/state.js new file mode 100755 index 000000000..e1b80ac5b --- /dev/null +++ b/lib/state.js @@ -0,0 +1,150 @@ +'use strict'; + +const Clone = require('@hapi/hoek/lib/clone'); +const Reach = require('@hapi/hoek/lib/reach'); + +const Common = require('./common'); + + +const internals = { + value: Symbol('value') +}; + + +module.exports = internals.State = class { + + constructor(path, ancestors, state) { + + this.path = path; + this.ancestors = ancestors; // [parent, ..., root] + + this.mainstay = state.mainstay; + this.schemas = state.schemas; // [current, ..., root] + this.debug = null; + } + + localize(path, ancestors = null, schema = null) { + + const state = new internals.State(path, ancestors, this); + + if (schema && + state.schemas) { + + state.schemas = [internals.schemas(schema), ...state.schemas]; + } + + return state; + } + + nest(schema, debug) { + + const state = new internals.State(this.path, this.ancestors, this); + state.schemas = state.schemas && [internals.schemas(schema), ...state.schemas]; + state.debug = debug; + return state; + } + + shadow(value, reason) { + + this.mainstay.shadow = this.mainstay.shadow || new internals.Shadow(); + this.mainstay.shadow.set(this.path, value, reason); + } + + snapshot() { + + if (this.mainstay.shadow) { + this._snapshot = Clone(this.mainstay.shadow.node(this.path)); + } + } + + restore() { + + if (this.mainstay.shadow) { + this.mainstay.shadow.override(this.path, this._snapshot); + this._snapshot = undefined; + } + } +}; + + +internals.schemas = function (schema) { + + if (Common.isSchema(schema)) { + return { schema }; + } + + return schema; +}; + + +internals.Shadow = class { + + constructor() { + + this._values = null; + } + + set(path, value, reason) { + + if (!path.length) { // No need to store root value + return; + } + + if (reason === 'strip' && + typeof path[path.length - 1] === 'number') { // Cannot store stripped array values (due to shift) + + return; + } + + this._values = this._values || new Map(); + + let node = this._values; + for (let i = 0; i < path.length; ++i) { + const segment = path[i]; + let next = node.get(segment); + if (!next) { + next = new Map(); + node.set(segment, next); + } + + node = next; + } + + node[internals.value] = value; + } + + get(path) { + + const node = this.node(path); + if (node) { + return node[internals.value]; + } + } + + node(path) { + + if (!this._values) { + return; + } + + return Reach(this._values, path, { iterables: true }); + } + + override(path, node) { + + if (!this._values) { + return; + } + + const parents = path.slice(0, -1); + const own = path[path.length - 1]; + const parent = Reach(this._values, parents, { iterables: true }); + + if (node) { + parent.set(own, node); + } + else { + parent.delete(own); + } + } +}; diff --git a/lib/types/alternatives.js b/lib/types/alternatives.js index c05c55679..d237bacc3 100755 --- a/lib/types/alternatives.js +++ b/lib/types/alternatives.js @@ -49,11 +49,17 @@ module.exports = Any.extend({ for (let i = 0; i < schema.$_terms.matches.length; ++i) { const item = schema.$_terms.matches[i]; - const result = item.schema.$_validate(value, state.nest(item.schema, `match.${i}`), prefs); + const localState = state.nest(item.schema, `match.${i}`); + localState.snapshot(); + + const result = item.schema.$_validate(value, localState, prefs); if (!result.errors) { ++hits; matched = result.value; } + else { + localState.restore(); + } } if (!hits) { @@ -76,11 +82,15 @@ module.exports = Any.extend({ // Try if (item.schema) { - const result = item.schema.$_validate(value, state.nest(item.schema, `match.${i}`), prefs); + const localState = state.nest(item.schema, `match.${i}`); + localState.snapshot(); + + const result = item.schema.$_validate(value, localState, prefs); if (!result.errors) { return result; } + localState.restore(); errors.push({ schema: item.schema, reports: result.errors }); continue; } diff --git a/lib/types/array.js b/lib/types/array.js index 50a974a19..25f8473d0 100755 --- a/lib/types/array.js +++ b/lib/types/array.js @@ -80,7 +80,7 @@ module.exports = Any.extend({ obj.$_mutateRegister(schema); return obj; }, - validate(value, { state, prefs, error, schema }, { schema: has }) { + validate(value, { state, prefs, error }, { schema: has }) { const ancestors = [value, ...state.ancestors]; for (let i = 0; i < value.length; ++i) { @@ -219,7 +219,10 @@ module.exports = Any.extend({ const requiredChecks = []; let jl = requireds.length; for (let j = 0; j < jl; ++j) { - const res = requireds[j].$_validate(item, state.localize(path, ancestors, requireds[j]), prefs); + const localState = state.localize(path, ancestors, requireds[j]); + localState.snapshot(); + + const res = requireds[j].$_validate(item, localState, prefs); requiredChecks[j] = res; if (!res.errors) { @@ -240,6 +243,8 @@ module.exports = Any.extend({ break; } + + localState.restore(); } if (isValid) { @@ -261,7 +266,10 @@ module.exports = Any.extend({ res = requiredChecks[previousCheck]; } else { - res = inclusion.$_validate(item, state.localize(path, ancestors, inclusion), prefs); + const localState = state.localize(path, ancestors, inclusion); + localState.snapshot(); + + res = inclusion.$_validate(item, localState, prefs); if (!res.errors) { if (inclusion._flags.result === 'strip') { internals.fastSplice(value, i); @@ -281,6 +289,8 @@ module.exports = Any.extend({ isValid = true; break; } + + localState.restore(); } // Return the actual error if only one inclusion defined @@ -308,7 +318,9 @@ module.exports = Any.extend({ continue; } - if (schema.$_terms._inclusions.length && !isValid) { + if (schema.$_terms._inclusions.length && + !isValid) { + if (stripUnknown) { internals.fastSplice(value, i); --i; @@ -333,6 +345,7 @@ module.exports = Any.extend({ return errors.length ? errors : value; }, + priority: true, manifest: false }, diff --git a/lib/types/keys.js b/lib/types/keys.js index fa0c48190..434cf0ccc 100755 --- a/lib/types/keys.js +++ b/lib/types/keys.js @@ -202,7 +202,8 @@ module.exports = Any.extend({ validate(value, { error, prefs, state }, { subject, schema, message }) { const about = subject.resolve(value, state, prefs); - if (schema.$_match(about, state.localize([], [value, ...state.ancestors], schema), prefs)) { + const path = Ref.isRef(subject) ? subject.absolute(state) : []; + if (schema.$_match(about, state.localize(path, [value, ...state.ancestors], schema), prefs)) { return value; } diff --git a/lib/validator.js b/lib/validator.js index 8891b768e..76deb9935 100755 --- a/lib/validator.js +++ b/lib/validator.js @@ -7,6 +7,7 @@ const Reach = require('@hapi/hoek/lib/reach'); const Common = require('./common'); const Errors = require('./errors'); +const State = require('./state'); const internals = { @@ -124,7 +125,7 @@ internals.entry = function (value, schema, prefs) { const links = schema._ids._schemaChain ? new Map() : null; const mainstay = { externals: [], warnings: [], tracer, debug, links }; const schemas = schema._ids._schemaChain ? [{ schema }] : null; - const state = new Common.State([], [], { mainstay, schemas }); + const state = new State([], [], { mainstay, schemas }); // Validate value @@ -510,8 +511,7 @@ internals.finalize = function (value, errors, helpers) { if (schema._flags.result) { result.value = schema._flags.result === 'strip' ? undefined : /* raw */ helpers.original; state.mainstay.tracer.value(state, schema._flags.result, value, result.value); - state.mainstay.shadow = state.mainstay.shadow || new internals.Shadow(); - state.mainstay.shadow.set(state.path, value); + state.shadow(value, schema._flags.result); } // Cache @@ -605,43 +605,6 @@ internals.trim = function (value, schema) { }; -internals.Shadow = class { - - constructor() { - - this._value = null; - } - - set(path, value) { - - if (!path.length) { // No need to store root value - return; - } - - this._value = this._value || new Map(); - - let node = this._value; - for (let i = 0; i < path.length - 1; ++i) { - const segment = path[i]; - let next = node.get(segment); - if (!next) { - next = new Map(); - node.set(segment, next); - } - - node = next; - } - - node.set(path[path.length - 1], value); - } - - get(path) { - - return Reach(this._value, path, { iterables: true }); - } -}; - - internals.ignore = { active: false, debug: Ignore, diff --git a/test/base.js b/test/base.js index d31f73b55..5d17bd166 100755 --- a/test/base.js +++ b/test/base.js @@ -2409,6 +2409,149 @@ describe('any', () => { const schema = Joi.any().strip().strip(false); expect(schema._flags.result).to.not.exist(); }); + + it('strips item inside array item', () => { + + const schema = Joi.array().items( + Joi.array().items( + Joi.number(), + Joi.any().strip() + ), + Joi.any().strip() + ); + + Helper.validate(schema, [ + [['x', ['x']], true, [[]]], + [[1, 2, [1, 2, 'x']], true, [[1, 2]]] + ]); + }); + + it('strips nested keys', () => { + + const schema = Joi.object({ + a: Joi.object({ + x: Joi.any(), + y: Joi.any().strip() + }) + .strip(), + + b: Joi.ref('a.y') + }); + + Helper.validate(schema, [ + [{}, true, {}], + [{ a: { x: 1 } }, true, {}], + [{ a: { x: 1, y: 2 } }, true, {}], + [{ a: { x: 1, y: 2 }, b: 2 }, true, { b: 2 }], + [{ a: { x: 1, y: 2 }, b: 3 }, false, '"b" must be [ref:a.y]'], + [{ b: 1 }, false, '"b" must be [ref:a.y]'] + ]); + }); + + it('references validated stripped value (after child already stripped)', () => { + + const schema = Joi.object({ + a: Joi.object({ + x: Joi.any(), + y: Joi.any().strip() + }) + .strip(), + + b: Joi.ref('a') + }); + + Helper.validate(schema, [ + [{}, true, {}], + [{ a: { x: 1, y: 2 } }, true, {}], + [{ a: { x: 1, y: 2 }, b: { x: 1 } }, true, { b: { x: 1 } }], + [{ a: { x: 1, y: 2 }, b: { x: 1, y: 2 } }, false, '"b" must be [ref:a]'] + ]); + }); + + it('strips key nested in alternative', () => { + + const schema = Joi.object({ + a: Joi.alternatives([ + Joi.object({ + x: Joi.number().strip(), + y: Joi.any() + }), + Joi.object({ + x: Joi.any(), + z: Joi.any() + }) + ]), + + b: Joi.ref('a.x') + }); + + Helper.validate(schema, [ + [{}, true, {}], + [{ a: { x: 1, y: 1 } }, true, { a: { y: 1 } }], + [{ a: { x: 1, z: 1 } }, true], + [{ a: { x: 1, z: 1 }, b: 1 }, true], + [{ a: { x: 1, z: 1 }, b: 2 }, false, '"b" must be [ref:a.x]'], + [{ a: { x: '2', z: 1 }, b: 2 }, false, '"b" must be [ref:a.x]'], + [{ a: { x: '2', z: 1 }, b: '2' }, true] + ]); + }); + + it('strips key in array item', () => { + + const schema = Joi.array() + .items( + Joi.object({ + a: Joi.number().strip(), + b: Joi.ref('a') + }), + Joi.object({ + a: Joi.string(), + b: Joi.ref('a') + }) + ); + + Helper.validate(schema, [ + [[], true], + [[{ a: 'x', b: 'x' }], true], + [[{ a: 1, b: 1 }], true, [{ b: 1 }]], + [[{ a: 'x', b: 'x' }, { a: 1, b: 1 }], true, [{ a: 'x', b: 'x' }, { b: 1 }]], + [[{ a: '1', b: '1' }], true, [{ a: '1', b: '1' }]] + ]); + }); + + it('ignored in matches', () => { + + const schema = Joi.array() + .items(Joi.object({ + a: { + b: Joi.number().strip(), + c: Joi.number() + } + })) + .has(Joi.object({ + a: { + b: Joi.number().min(10), + c: Joi.boolean().truthy(30).strip() // Does not affect result + } + })); + + Helper.validate(schema, [ + [[{ a: { b: '20', c: '30' } }], true, [{ a: { c: 30 } }]] + ]); + }); + + it('retains shadow after match', () => { + + const schema = Joi.object({ + a: Joi.number().strip() + }) + .assert('.a', Joi.number().cast('string').strip().required(), 'error 1') + .assert('.a', Joi.number().strict().required(), 'error 2'); + + Helper.validate(schema, [ + [{ a: '1' }, true, {}] + ]); + }); }); describe('tag()', () => { From 235bdf4a10939c6ec21ada79126e7b7e46ac3886 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Wed, 2 Oct 2019 00:03:44 -0700 Subject: [PATCH 02/20] Closes #2161 --- API.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/API.md b/API.md index 13d5da2b7..2a2b517d7 100755 --- a/API.md +++ b/API.md @@ -93,7 +93,7 @@ Second, the value is validated against the defined schema: const { error, value } = schema.validate({ a: 'a string' }); ``` -If the input is valid, then the `error` will be `null`. If the input is invalid, `error` is assigned +If the input is valid, then the `error` will be `undefined`. If the input is invalid, `error` is assigned a [`ValidationError`](https://github.com/hapijs/joi/blob/master/API.md#validationerror) object providing more information. From ebc2ecc1fbc322ed45f0031e59e15a7e35640873 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Wed, 2 Oct 2019 00:20:35 -0700 Subject: [PATCH 03/20] Clarify error(). Closes #2158 --- API.md | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/API.md b/API.md index 2a2b517d7..d9589075d 100755 --- a/API.md +++ b/API.md @@ -724,24 +724,22 @@ schema.validate(''); // returns { error: "value" is not allowed to be empty, val Overrides the default **joi** error with a custom error if the rule fails where: - `err` can be: - an instance of `Error` - the override error. - - a function with the signature `function(errors)`, where `errors` is an array of validation - reports and it returns either a single `Error` or an array of validation reports. + - a function with the signature `function(errors)`, where `errors` is an array of validation reports and it returns either a single `Error` or an array of validation reports. -Note that if you provide an `Error`, it will be returned as-is, unmodified and undecorated with any -of the normal error properties. If validation fails and another error is found before the error -override, that error will be returned and the override will be ignored (unless the `abortEarly` -option has been set to `false`). +Do not use this method if you are simply trying to override the error message - use `any.message()` or `any.messages()` instead. This method is designed to override the **joi** validation error and return the exact override provided. It is useful when you want to return the result of validation directly (e.g. when using with a **hapi** server) and want to return a different HTTP error code than 400. + +Note that if you provide an `Error`, it will be returned as-is, unmodified and undecorated with any of the normal error properties. If validation fails and another error is found before the error override, that error will be returned and the override will be ignored (unless the `abortEarly` option has been set to `false`). If you set multiple errors on a single schema, only the last error is used. ```js const schema = Joi.string().error(new Error('Was REALLY expecting a string')); -schema.validate(3); // returns error.message === 'Was REALLY expecting a string' +schema.validate(3); // returns Error('Was REALLY expecting a string') ``` ```js const schema = Joi.object({ foo: Joi.number().min(0).error((errors) => new Error('"foo" requires a positive number')) }); -schema.validate({ foo: -2 }); // returns error.message === '"foo" requires a positive number' +schema.validate({ foo: -2 }); // returns new Error('"foo" requires a positive number') ``` ```js @@ -751,7 +749,7 @@ const schema = Joi.object({ return new Error('found errors with ' + errors.map((err) => `${err.type}(${err.local.limit}) with value ${err.local.value}`).join(' and ')); }) }); -schema.validate({ foo: -2 }); // returns error.message === 'child "foo" fails because [found errors with number.min(0) with value -2]' +schema.validate({ foo: -2 }); // returns new Error('child "foo" fails because [found errors with number.min(0) with value -2]') ``` #### `any.example(example, [options])` From f29129a460eb40a59135b88933b0d974810af5de Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Wed, 2 Oct 2019 00:34:53 -0700 Subject: [PATCH 04/20] 16.1.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9ba3a2d2f..609e29c72 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@hapi/joi", "description": "Object schema validation", - "version": "16.1.4", + "version": "16.1.5", "repository": "git://github.com/hapijs/joi", "main": "lib/index.js", "browser": "dist/joi-browser.min.js", From f56b6e3cb746ad3999a9f86d7cb5e0680d823b36 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Fri, 4 Oct 2019 14:48:34 -0700 Subject: [PATCH 05/20] Closes #2165 --- lib/state.js | 4 +++- test/base.js | 26 ++++++++++++++++++++------ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/lib/state.js b/lib/state.js index e1b80ac5b..8db251b76 100755 --- a/lib/state.js +++ b/lib/state.js @@ -142,8 +142,10 @@ internals.Shadow = class { if (node) { parent.set(own, node); + return; } - else { + + if (parent) { parent.delete(own); } } diff --git a/test/base.js b/test/base.js index 5d17bd166..7924db6cc 100755 --- a/test/base.js +++ b/test/base.js @@ -2400,13 +2400,13 @@ describe('any', () => { it('avoids unnecessary cloning when called twice', () => { - const schema = Joi.any().strip(); + const schema = Joi.strip(); expect(schema.strip()).to.shallow.equal(schema); }); it('cancels strip', () => { - const schema = Joi.any().strip().strip(false); + const schema = Joi.strip().strip(false); expect(schema._flags.result).to.not.exist(); }); @@ -2415,9 +2415,9 @@ describe('any', () => { const schema = Joi.array().items( Joi.array().items( Joi.number(), - Joi.any().strip() + Joi.strip() ), - Joi.any().strip() + Joi.strip() ); Helper.validate(schema, [ @@ -2431,7 +2431,7 @@ describe('any', () => { const schema = Joi.object({ a: Joi.object({ x: Joi.any(), - y: Joi.any().strip() + y: Joi.strip() }) .strip(), @@ -2453,7 +2453,7 @@ describe('any', () => { const schema = Joi.object({ a: Joi.object({ x: Joi.any(), - y: Joi.any().strip() + y: Joi.strip() }) .strip(), @@ -2552,6 +2552,20 @@ describe('any', () => { [{ a: '1' }, true, {}] ]); }); + + it('revers match changes when shadow exists', () => { + + const schema = Joi.object({ + x: Joi.strip(), + y: Joi.object({ + z: Joi.when('$x', { otherwise: Joi.strip() }) + }) + }); + + Helper.validate(schema, [ + [{ x: 1, y: { z: 'x' } }, true, { y: {} }] + ]); + }); }); describe('tag()', () => { From 089463289b3c3e64783fe65d7046d3945130dc58 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Fri, 4 Oct 2019 20:38:45 -0700 Subject: [PATCH 06/20] 16.1.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 609e29c72..8cb58d9e3 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@hapi/joi", "description": "Object schema validation", - "version": "16.1.5", + "version": "16.1.6", "repository": "git://github.com/hapijs/joi", "main": "lib/index.js", "browser": "dist/joi-browser.min.js", From 7dea82bfc57c8ea964357f961c18c84340db4884 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Sat, 5 Oct 2019 10:36:52 -0700 Subject: [PATCH 07/20] Fix date format validation. Closes #2168 --- lib/types/date.js | 3 ++- test/types/date.js | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/types/date.js b/lib/types/date.js index 902c40215..073ede589 100755 --- a/lib/types/date.js +++ b/lib/types/date.js @@ -193,6 +193,7 @@ internals.parse = function (value, format) { // Normalize number string + const original = value; if (typeof value === 'string' && /^[+-]?\d+(\.\d+)?$/.test(value)) { @@ -210,7 +211,7 @@ internals.parse = function (value, format) { return internals.date(1000 * value); } - if (typeof value === 'string') { + if (typeof original === 'string') { return null; } } diff --git a/test/types/date.js b/test/types/date.js index 2f76b8a18..507c68f8e 100755 --- a/test/types/date.js +++ b/test/types/date.js @@ -200,7 +200,21 @@ describe('date', () => { ['x', false, '"value" must be in unknown format'], [now, true, new Date(now)] ]); - Helper.validate(custom.date().format(['unknown']), [['x', false, '"value" must be in [unknown] format']]); + + Helper.validate(custom.date().format(['unknown']), [ + ['x', false, '"value" must be in [unknown] format'] + ]); + }); + + it('enforces format when value is a string', () => { + + const schema = Joi.date().$_setFlag('format', 'MM-DD-YY'); + + // Cannot use Helper since format is set to unknown value + + expect(schema.validate(new Date()).error).to.not.exist(); + expect(schema.validate(Date.now()).error).to.not.exist(); + expect(schema.validate('1').error).to.be.an.error('"value" must be in MM-DD-YY format'); }); }); From 3c9a2296ecfa239ddfc99e29814109398b6facc6 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Sat, 5 Oct 2019 10:36:55 -0700 Subject: [PATCH 08/20] 16.1.7 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8cb58d9e3..9c2c1f1db 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@hapi/joi", "description": "Object schema validation", - "version": "16.1.6", + "version": "16.1.7", "repository": "git://github.com/hapijs/joi", "main": "lib/index.js", "browser": "dist/joi-browser.min.js", From 73515c3237a12ec9d06a4e08d18fe7aadfac7ac3 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Sat, 5 Oct 2019 12:44:24 -0700 Subject: [PATCH 09/20] Delete CONTRIBUTING.md --- .github/CONTRIBUTING.md | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 .github/CONTRIBUTING.md diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md deleted file mode 100644 index 713d376a4..000000000 --- a/.github/CONTRIBUTING.md +++ /dev/null @@ -1,14 +0,0 @@ -# How to contribute -We welcome contributions from the community and are pleased to have them. Please follow this guide when logging issues or making code changes. - -## Logging Issues -All issues should be created using the [new issue form](https://github.com/hapijs/joi/issues/new). Clearly describe the issue including steps to reproduce if there are any. Also, make sure to indicate the earliest version that has the issue being reported. - -## Patching Code -Code changes are welcome and should follow the guidelines below. - -* Fork the repository on GitHub. -* Fix the issue ensuring that your code follows the [style guide](https://github.com/hapijs/contrib/blob/master/Style.md). -* Add tests for your new code ensuring that you have 100% code coverage (we can help you reach 100% but will not merge without it). - * Run `npm test` to generate a report of test coverage -* [Pull requests](http://help.github.com/send-pull-requests/) should be made to the [master branch](https://github.com/hapijs/joi/tree/master). From 7dcaab9a9807440f50aba653ad8e59a989e438bf Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Sat, 5 Oct 2019 13:33:35 -0700 Subject: [PATCH 10/20] Delete bug_report.md --- .github/ISSUE_TEMPLATE/bug_report.md | 31 ---------------------------- 1 file changed, 31 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 89cedc9de..000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve - ---- - - - -#### Context - -* *node version*: -* *joi version*: -* *environment* (node, browser): -* *used with* (hapi, standalone, ...): -* *any other relevant information*: - -#### What are you trying to achieve or the steps to reproduce ? - - - -```js -const schema = Joi.any() -``` - -#### Which result you had ? - -#### What did you expect ? From 5af2267db9e81cc75af8c9cd0b5ed43c017b5af0 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Sat, 5 Oct 2019 13:33:44 -0700 Subject: [PATCH 11/20] Delete feature_request.md --- .github/ISSUE_TEMPLATE/feature_request.md | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index fc90c456f..000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project - ---- - - - -#### Describe the problem you are trying to fix (provide as much context as possible) - -#### Which API (or modification of the current API) do you suggest to solve that problem ? - -#### Are you ready to work on a pull request if your suggestion is accepted ? From 6229deb8aab02dcac38904f529167e1d3dd3c295 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Sat, 5 Oct 2019 19:22:43 -0700 Subject: [PATCH 12/20] Delete .editorconfig --- .editorconfig | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 2ae4771bb..000000000 --- a/.editorconfig +++ /dev/null @@ -1,20 +0,0 @@ -# This file is for unifying the coding style for different editors and IDEs -# editorconfig.org - -root = true - -[*] -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = true -indent_style = space -indent_size = 4 - -[**.{js,json,md}] -insert_final_newline = true - -[**.{json,md}] -indent_size = 2 - -[**.html] -insert_final_newline = false From 2c62d4b04b8c6a87c13a8c5226e6db043033f8be Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Sat, 5 Oct 2019 19:23:04 -0700 Subject: [PATCH 13/20] Delete .npmrc --- .npmrc | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .npmrc diff --git a/.npmrc b/.npmrc deleted file mode 100644 index 43c97e719..000000000 --- a/.npmrc +++ /dev/null @@ -1 +0,0 @@ -package-lock=false From 73c3a279520ab8a4fad480a2e0e92c627139b0eb Mon Sep 17 00:00:00 2001 From: Nicolas Morel Date: Mon, 7 Oct 2019 14:05:29 +0200 Subject: [PATCH 14/20] Fix function signature. Fixes #2170. --- API.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/API.md b/API.md index d9589075d..80b60a0b3 100755 --- a/API.md +++ b/API.md @@ -3103,7 +3103,7 @@ const custom = Joi.extend((joi) => { return { value: Math.round(value) }; } }, - validate(schema, value, helpers) { + validate(value, helpers) { // Base validation regardless of the rules applied From 1b2e44ebbe2a9c03926003c3960269bd1dab1701 Mon Sep 17 00:00:00 2001 From: Stefan-Gabriel Muscalu Date: Mon, 7 Oct 2019 17:55:47 +0300 Subject: [PATCH 15/20] Fix docs missing code block ending at section `date.less(date)` --- API.md | 1 + 1 file changed, 1 insertion(+) diff --git a/API.md b/API.md index 80b60a0b3..e1b360ba8 100755 --- a/API.md +++ b/API.md @@ -1837,6 +1837,7 @@ Specifies that the value must be less than `date` (or a reference). ```js const schema = Joi.date().less('12-31-2020'); +``` Notes: `'now'` can be passed in lieu of `date` so as to always compare relatively to the current date, allowing to explicitly ensure a date is either in the past or in the future. From 31dc9546974d5e57a703b0ef3837b73f008c2e0f Mon Sep 17 00:00:00 2001 From: Nicolas Morel Date: Mon, 7 Oct 2019 16:57:45 +0200 Subject: [PATCH 16/20] End code block --- API.md | 1 + 1 file changed, 1 insertion(+) diff --git a/API.md b/API.md index 80b60a0b3..e1b360ba8 100755 --- a/API.md +++ b/API.md @@ -1837,6 +1837,7 @@ Specifies that the value must be less than `date` (or a reference). ```js const schema = Joi.date().less('12-31-2020'); +``` Notes: `'now'` can be passed in lieu of `date` so as to always compare relatively to the current date, allowing to explicitly ensure a date is either in the past or in the future. From ce89e4dda36bae57470f185dfdde5a0fcfd8e743 Mon Sep 17 00:00:00 2001 From: Stefan-Gabriel Muscalu Date: Mon, 7 Oct 2019 18:00:13 +0300 Subject: [PATCH 17/20] Fix docs malformed code block ending at section `object.pattern.match` --- API.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/API.md b/API.md index e1b360ba8..9d39234bf 100755 --- a/API.md +++ b/API.md @@ -3923,7 +3923,7 @@ Additional local context properties: message: string, // The combined error messages matches: Array // The matching keys } -`` +``` #### `object.refType` From bc461b5591d3d8a5e17ccfb987902f4aacd45b2f Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Tue, 8 Oct 2019 16:38:31 -0700 Subject: [PATCH 18/20] Closes #2172 --- API.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/API.md b/API.md index 9d39234bf..05da327b7 100755 --- a/API.md +++ b/API.md @@ -2417,8 +2417,7 @@ Possible validation errors: [`object.oxor`](#objectoxor) #### `object.pattern(pattern, schema, [options])` Specify validation rules for unknown keys matching a pattern where: -- `pattern` - a pattern that can be either a regular expression or a **joi** schema that will be - tested against the unknown key names. +- `pattern` - a pattern that can be either a regular expression or a **joi** schema that will be tested against the unknown key names. Note that if the pattern is a regular expression, for it to match the entire key name, it must begin with `^` and end with `$`. - `schema` - the schema object matching keys must validate against. - `options` - options settings: - `fallthrough` - if `true`, multiple matching patterns are tested against the key, otherwise once a pattern match is found, no other patterns are compared. Defaults to `false`. @@ -2932,7 +2931,7 @@ Possible validation errors: [`string.normalize`](#stringnormalize) #### `string.pattern(regex, [name | options])` - aliases: `regex` Defines a pattern rule where: -- `regex` - a regular expression object the string value must match against. +- `regex` - a regular expression object the string value must match against. Note that if the pattern is a regular expression, for it to match the entire key name, it must begin with `^` and end with `$`. - `name` - optional name for patterns (useful with multiple patterns). - `options` - an optional configuration object with the following supported properties: - `name` - optional pattern name. From 94d5998075f9ced2fe685f0d74d1ca89155f722a Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Tue, 8 Oct 2019 16:49:23 -0700 Subject: [PATCH 19/20] Improve compile version conflict error message. Closes #2173 --- lib/compile.js | 2 +- test/compile.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/compile.js b/lib/compile.js index a25c636e3..5593b7c90 100755 --- a/lib/compile.js +++ b/lib/compile.js @@ -105,7 +105,7 @@ exports.compile = function (root, schema, options = {}) { const any = schema && schema[Common.symbols.any]; if (any) { - Assert(options.legacy || any.version === Common.version, 'Cannot mix different versions of joi schemas'); + Assert(options.legacy || any.version === Common.version, 'Cannot mix different versions of joi schemas:', any.version, Common.version); return schema; } diff --git a/test/compile.js b/test/compile.js index 6c9e7be56..331f19a75 100755 --- a/test/compile.js +++ b/test/compile.js @@ -169,7 +169,7 @@ describe('cast', () => { it('errors on legacy schema', () => { const schema = Legacy.number(); - expect(() => Joi.compile(schema)).to.throw('Cannot mix different versions of joi schemas'); + expect(() => Joi.compile(schema)).to.throw(`Cannot mix different versions of joi schemas: ${require('@hapi/joi-legacy-test/package.json').version} ${require('../package.json').version}`); expect(() => Joi.compile(schema, { legacy: true })).to.not.throw(); }); From 37b21829f36fd3e643819f717c411ed397d52321 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Fri, 11 Oct 2019 16:59:29 -0700 Subject: [PATCH 20/20] Update API.md --- API.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/API.md b/API.md index 05da327b7..ffcead916 100755 --- a/API.md +++ b/API.md @@ -2220,8 +2220,7 @@ Possible validation errors: [`number.unsafe`](#numberunsafe) ### `object` -Generates a schema object that matches an object data type (as well as JSON strings that parsed into objects). Defaults -to allowing any child key. +Generates a schema object that matches an object data type. Defaults to allowing any child key. Supports the same methods of the [`any()`](#any) type.