Skip to content

Commit

Permalink
Legacy interop. Closes #1913
Browse files Browse the repository at this point in the history
  • Loading branch information
hueniverse committed Jun 23, 2019
1 parent aea5e6c commit 2706e0a
Show file tree
Hide file tree
Showing 9 changed files with 248 additions and 32 deletions.
32 changes: 21 additions & 11 deletions API.md
Expand Up @@ -8,7 +8,7 @@
- [`version`](#version)
- [`validate(value, schema, [options], [callback])`](#validatevalue-schema-options-callback)
- [`ValidationError`](#validationerror)
- [`compile(schema)`](#compileschema)
- [`compile(schema, [options])`](#compileschema-options)
- [`describe(schema)`](#describeschema)
- [`assert(value, schema, [message], [options])`](#assertvalue-schema-message-options)
- [`attempt(value, schema, [message], [options])`](#attemptvalue-schema-message-options)
Expand All @@ -17,7 +17,7 @@
- [`isRef(ref)`](#isrefref)
- [`template(template, [options])`](#templatetemplate-options)
- [Template syntax](#template-syntax)
- [`isSchema(schema)`](#isschemaschema)
- [`isSchema(schema, [options])`](#isschemaschema-options)
- [`reach(schema, path)`](#reachschema-path)
- [`defaults(fn)`](#defaultsfn)
- [`bind()`](#bind)
Expand Down Expand Up @@ -291,7 +291,7 @@ Property showing the current version of **joi** being used.

Validates a value using the given schema and options where:
- `value` - the value being validated.
- `schema` - the validation schema. Can be a **joi** type object or a plain object where every key is assigned a **joi** type object using [`Joi.compile`](#compileschema) (be careful of the cost of compiling repeatedly the same schemas).
- `schema` - the validation schema. Can be a **joi** type object or a plain object where every key is assigned a **joi** type object using [`Joi.compile`](#compileschema-options) (be careful of the cost of compiling repeatedly the same schemas).
- `options` - an optional object with the following optional keys:
- `abortEarly` - when `true`, stops validation on the first error, otherwise returns all the errors found. Defaults to `true`.
- `allowUnknown` - when `true`, allows object to contain unknown keys which are ignored. Defaults to `false`.
Expand Down Expand Up @@ -363,10 +363,16 @@ Thrown by `assert` when validation fails.

See [Errors](#errors)

### `compile(schema)`
### `compile(schema, [options])`

Converts literal schema definition to **joi** schema object (or returns the same back if already a **joi** schema object) where:
Converts literal schema definition to **joi** schema object (or returns the same back if already a
**joi** schema object) where:
- `schema` - the schema definition to compile.
- `options` - optional settings:
- `legacy` - if `true` and the provided schema is (or contains parts) using an older version of
**joi**, will return a compiled schema that is compatible with the older version. If `false`,
the schema is always compiled using the current version and if older schema components are
found, an error is thrown.

```js
const definition = ['key', 5, { a: true, b: [/^a/, 'boom'] }];
Expand Down Expand Up @@ -411,7 +417,7 @@ Results in:

Validates a value against a schema and [throws](#errors) if validation fails where:
- `value` - the value to validate.
- `schema` - the validation schema. Can be a **joi** type object or a plain object where every key is assigned a **joi** type object using [`Joi.compile`](#compileschema) (be careful of the cost of compiling repeatedly the same schemas).
- `schema` - the validation schema. Can be a **joi** type object or a plain object where every key is assigned a **joi** type object using [`Joi.compile`](#compileschema-options) (be careful of the cost of compiling repeatedly the same schemas).
- `message` - optional message string prefix added in front of the error message. may also be an Error object.
- `options` - optional options object, passed in to [`Joi.validate`](##validatevalue-schema-options-callback)

Expand All @@ -423,7 +429,7 @@ Joi.assert('x', Joi.number());

Validates a value against a schema, returns valid object, and [throws](#errors) if validation fails where:
- `value` - the value to validate.
- `schema` - the validation schema. Can be a **joi** type object or a plain object where every key is assigned a **joi** type object using [`Joi.compile`](#compileschema) (be careful of the cost of compiling repeatedly the same schemas).
- `schema` - the validation schema. Can be a **joi** type object or a plain object where every key is assigned a **joi** type object using [`Joi.compile`](#compileschema-options) (be careful of the cost of compiling repeatedly the same schemas).
- `message` - optional message string prefix added in front of the error message. may also be an Error object.
- `options` - optional options object, passed in to [`Joi.validate`](##validatevalue-schema-options-callback)

Expand Down Expand Up @@ -575,9 +581,13 @@ The variable names can have one of the following prefixes:
provided as an option to the validation function or set using [`any.prefs()`](#anyprefsoptions--aliases-preferences-options).
- any other variable references a key within the current value being validated.

### `isSchema(schema)`
### `isSchema(schema, [options])`

Checks whether or not the provided argument is a **joi** schema.
Checks whether or not the provided argument is a **joi** schema where:
- `schema` - the value being checked.
- `options` - optional settings:
- `legacy` - if `true`, will identify schemas from older versions of joi, otherwise will throw
an error. Defaults to `false`.

```js
const schema = Joi.any();
Expand Down Expand Up @@ -1205,7 +1215,7 @@ conditions merged into the type definition where:
- `condition` - the key name or [reference](#refkey-options), or a schema.
- `options` - an object with:
- `is` - the condition expressed as a **joi** schema. Anything that is not a **joi** schema will be
converted using [Joi.compile](#compileschema). By default, the `is` condition schema allows for
converted using [Joi.compile](#compileschema-options). By default, the `is` condition schema allows for
`undefined` values. Use `.required()` to override. For example, use `is: Joi.number().required()`
to guarantee that a **joi** reference exists and is a number.
- `then` - if the condition is true, the **joi** schema to use.
Expand Down Expand Up @@ -2984,7 +2994,7 @@ schema peeking into the current value, where:
- `condition` - the key name or [reference](#refkey-options), or a schema.
- `options` - an object with:
- `is` - the condition expressed as a **joi** schema. Anything that is not a **joi** schema will be
converted using [Joi.compile](#compileschema).
converted using [Joi.compile](#compileschema-options).
- `then` - if the condition is true, the **joi** schema to use.
- `otherwise` - if the condition is false, the **joi** schema to use.
- `switch` - an array of `{ is, then }` conditions that are evaluated against the `condition`.
Expand Down
68 changes: 67 additions & 1 deletion lib/cast.js
Expand Up @@ -39,6 +39,8 @@ exports.schema = function (Joi, config, options = {}) {
return Joi.date().valid(config);
}

Hoek.assert(Object.getPrototypeOf(config) === Object.getPrototypeOf({}), 'Schema can only contain plain objects');

return Joi.object().keys(config);
}

Expand Down Expand Up @@ -67,7 +69,7 @@ internals.appendPath = function (Joi, config) {
}
catch (err) {
if (err.path !== undefined) {
err.message = `${err.message}(${err.path})`;
err.message = `${err.message}${err.message[err.message.length - 1] === ' ' ? '' : ' '}(${err.path})`;
}

throw err;
Expand All @@ -79,3 +81,67 @@ exports.ref = function (id, options) {

return Ref.isRef(id) ? id : new Ref(id, options);
};


exports.compile = function (root, schema, options = {}) {

// Compiled by any supported version

const any = schema && schema[Common.symbols.any];
if (any) {
Hoek.assert(options.legacy || any.version === Common.version, 'Cannot mix different versions of joi schemas');
return schema;
}

// Uncompiled root

if (typeof schema !== 'object' ||
!options.legacy) {

return internals.appendPath(root, schema); // Will error if schema contains other versions
}

// Scan schema for compiled parts

const compiler = internals.walk(schema);
if (!compiler) {
return internals.appendPath(root, schema);
}

return compiler.compile(compiler.root, schema);
};


internals.walk = function (schema) {

if (typeof schema !== 'object') {
return null;
}

if (Array.isArray(schema)) {
for (const item of schema) {
const compiler = internals.walk(item);
if (compiler) {
return compiler;
}
}

return null;
}

const any = schema[Common.symbols.any];
if (any) {
return { root: schema[any.root], compile: any.compile };
}

Hoek.assert(Object.getPrototypeOf(schema) === Object.getPrototypeOf({}), 'Schema can only contain plain objects');

for (const key in schema) {
const compiler = internals.walk(schema[key]);
if (compiler) {
return compiler;
}
}

return null;
};
15 changes: 13 additions & 2 deletions lib/common.js
Expand Up @@ -3,10 +3,15 @@
const Hoek = require('@hapi/hoek');
const Marker = require('@hapi/marker');

const Pkg = require('../package.json');


const internals = {};


exports.version = Pkg.version;


exports.defaults = {
abortEarly: true,
allowUnknown: false,
Expand Down Expand Up @@ -92,9 +97,15 @@ exports.extend = function (Class, prop, methods) {
};


exports.isSchema = function (schema) {
exports.isSchema = function (schema, options = {}) {

const any = schema[exports.symbols.any];
if (!any) {
return false;
}

return !!schema[exports.symbols.any];
Hoek.assert(options.legacy || any.version === exports.version, 'Cannot mix different versions of joi schemas');
return true;
};


Expand Down
8 changes: 4 additions & 4 deletions lib/index.js
Expand Up @@ -129,9 +129,9 @@ internals.root = function () {
return Ref.isRef(ref);
};

root.isSchema = function (schema) {
root.isSchema = function (schema, options) {

return Common.isSchema(schema);
return Common.isSchema(schema, options);
};

root.validate = function (value, ...args /*, [schema], [options], callback */) {
Expand All @@ -158,9 +158,9 @@ internals.root = function () {
return schema.describe();
};

root.compile = function (schema) {
root.compile = function (schema, options) {

return Cast.schema(this, schema, { appendPath: true });
return Cast.compile(this, schema, options);
};

root.assert = function (...args) {
Expand Down
14 changes: 8 additions & 6 deletions lib/types/any.js
Expand Up @@ -3,16 +3,14 @@
const Hoek = require('@hapi/hoek');

const About = require('../about');
const Cast = require('../cast');
const Common = require('../common');
const Errors = require('../errors');
const Ref = require('../ref');
const Validator = require('../validator');
const Values = require('../values');

const Pkg = require('../../package.json');

let Alternatives = null; // Delay-loaded to prevent circular dependencies
let Cast = null;
let Schemas = null;


Expand Down Expand Up @@ -41,8 +39,6 @@ module.exports = internals.Any = class {

constructor() {

Cast = Cast || require('../cast');

this._type = 'any';
this._preferences = null;
this._refs = new Ref.Manager();
Expand Down Expand Up @@ -774,7 +770,13 @@ module.exports = internals.Any = class {


internals.Any.prototype.isImmutable = true; // Prevents Hoek from deep cloning schema objects
internals.Any.prototype[Common.symbols.any] = Pkg.version;


internals.Any.prototype[Common.symbols.any] = {
version: Common.version,
compile: Cast.compile,
root: '_root'
};


// Aliases
Expand Down
1 change: 1 addition & 0 deletions package.json 100644 → 100755
Expand Up @@ -19,6 +19,7 @@
"devDependencies": {
"@hapi/code": "5.x.x",
"@hapi/lab": "19.x.x",
"@hapi/joi-legacy-test": "npm:@hapi/joi@15.x.x",
"hapitoc": "1.x.x"
},
"scripts": {
Expand Down

0 comments on commit 2706e0a

Please sign in to comment.