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 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). 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 ? 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 ? diff --git a/.npmrc b/.npmrc deleted file mode 100644 index 43c97e719..000000000 --- a/.npmrc +++ /dev/null @@ -1 +0,0 @@ -package-lock=false diff --git a/API.md b/API.md index 13d5da2b7..ffcead916 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. @@ -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])` @@ -1839,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. @@ -2221,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. @@ -2418,8 +2416,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`. @@ -2933,7 +2930,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. @@ -3105,7 +3102,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 @@ -3924,7 +3921,7 @@ Additional local context properties: message: string, // The combined error messages matches: Array // The matching keys } -`` +``` #### `object.refType` 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 c83a01e80..975cb55ee 100755 --- a/lib/common.js +++ b/lib/common.js @@ -172,51 +172,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/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/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..8db251b76 --- /dev/null +++ b/lib/state.js @@ -0,0 +1,152 @@ +'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); + return; + } + + if (parent) { + 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/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/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/package.json b/package.json index 9ba3a2d2f..9c2c1f1db 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.7", "repository": "git://github.com/hapijs/joi", "main": "lib/index.js", "browser": "dist/joi-browser.min.js", diff --git a/test/base.js b/test/base.js index d31f73b55..7924db6cc 100755 --- a/test/base.js +++ b/test/base.js @@ -2400,15 +2400,172 @@ 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(); }); + + it('strips item inside array item', () => { + + const schema = Joi.array().items( + Joi.array().items( + Joi.number(), + Joi.strip() + ), + Joi.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.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.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, {}] + ]); + }); + + 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()', () => { 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(); }); 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'); }); });