Skip to content

Commit

Permalink
Joi.link(). Closes #1968
Browse files Browse the repository at this point in the history
  • Loading branch information
hueniverse committed Jul 12, 2019
1 parent 12e3b9a commit 70540f2
Show file tree
Hide file tree
Showing 18 changed files with 471 additions and 445 deletions.
1 change: 1 addition & 0 deletions .eslintignore
@@ -1,3 +1,4 @@
browser
examples
sandbox.js
/benchmarks/node_modules
58 changes: 43 additions & 15 deletions API.md
Expand Up @@ -96,7 +96,7 @@
- [`func.class()`](#funcclass)
- [`func.maxArity(n)`](#funcmaxarityn)
- [`func.minArity(n)`](#funcminarityn)
- [`lazy(fn[, options])` - inherits from `Any`](#lazyfn-options---inherits-from-any)
- [`link(ref[, options])` - inherits from `Any`](#linkref-options---inherits-from-any)
- [`number` - inherits from `Any`](#number---inherits-from-any)
- [`number.greater(limit)`](#numbergreaterlimit)
- [`number.integer()`](#numberinteger)
Expand Down Expand Up @@ -216,7 +216,9 @@
- [`function.class`](#functionclass)
- [`function.maxArity`](#functionmaxarity)
- [`function.minArity`](#functionminarity)
- [`lazy.schema`](#lazyschema)
- [`link.depth`](#linkdepth)
- [`link.loop`](#linkloop)
- [`link.ref`](#linkref)
- [`number.base`](#numberbase)
- [`number.greater`](#numbergreater)
- [`number.integer`](#numberinteger-1)
Expand Down Expand Up @@ -1984,25 +1986,30 @@ const schema = Joi.func().minArity(1);

Possible validation errors: [`function.minArity`](#functionminarity)

### `lazy(fn[, options])` - inherits from `Any`
### `link(ref[, options])` - inherits from `Any`

Generates a placeholder schema for a schema that you would provide where:
- `fn` - is a function returning the actual schema to use for validation.
- `options`:
- `once` - enables or disables the single evaluation behavior. When `false`, the function will be called every time a validation happens, otherwise the schema will be cached for further re-use. Defaults to `true`.
Links to another schema node and reuses it for validation where:
- `ref` - the reference to the linked schema node. Cannot reference itself or its children as well
as other links. Follows the same rules as value references.
- `options` - optional settings:

Supports the same methods of the [`any()`](#any) type.
Supports the methods of the [`any()`](#any) type.

Link is useful for creating recursive schemas:

This is mostly useful for recursive schemas, like :
```js
const Person = Joi.object({
const person = Joi.object({
firstName: Joi.string().required(),
lastName: Joi.string().required(),
children: Joi.array().items(Joi.lazy(() => Person).description('Person schema'))
children: Joi.array()
.items(Joi.link('...'))
// . - the link
// .. - the children array
// ... - the person object
});
```

Possible validation errors: [`lazy.schema`](#lazyschema)
Possible validation errors: [`link.depth`](#linkdepth), [`link.ref`](#linkref), [`link.loop`](#linkloop)

### `number` - inherits from `Any`

Expand Down Expand Up @@ -3659,15 +3666,36 @@ Additional local context properties:
n: number // Minimum expected arity
}
```
#### `link.depth`

The link reference reached outside of the schema root.

Additional local context properties:
```ts
{
ref: Reference // The link reference
}
```

#### `link.loop`

The link reference referenced another link.

Additional local context properties:
```ts
{
ref: Reference // The link reference
}
```

#### `lazy.schema`
#### `link.ref`

The lazy function didn't return a **joi** schema.
The link reference referenced a missing schema node.

Additional local context properties:
```ts
{
schema: any // The value return by the generator function
ref: Reference // The link reference
}
```

Expand Down
1 change: 0 additions & 1 deletion benchmarks/suite.js
Expand Up @@ -73,7 +73,6 @@ module.exports = [
})
.xor('foo', 'bar')
.or('bar', 'baz')
.pattern(/a/, Joi.lazy(() => Joi.any()))
.pattern(/b/, Joi.when('a', {
is: true,
then: Joi.prefs({ messages: { 'any.required': 'oops' } })
Expand Down
38 changes: 18 additions & 20 deletions lib/cast.js
Expand Up @@ -13,9 +13,22 @@ exports.schema = function (Joi, config, options = {}) {

Common.assertOptions(options, ['appendPath']);

if (options.appendPath) {
return internals.appendPath(Joi, config);
try {
return internals.schema(Joi, config);
}
catch (err) {
if (options.appendPath &&
err.path !== undefined) {

err.message = `${err.message}${err.message[err.message.length - 1] === ' ' ? '' : ' '}(${err.path})`;
}

throw err;
}
};


internals.schema = function (Joi, config) {

if (config !== undefined &&
config !== null &&
Expand Down Expand Up @@ -64,24 +77,9 @@ exports.schema = function (Joi, config, options = {}) {
};


internals.appendPath = function (Joi, config) {

try {
return exports.schema(Joi, config);
}
catch (err) {
if (err.path !== undefined) {
err.message = `${err.message}${err.message[err.message.length - 1] === ' ' ? '' : ' '}(${err.path})`;
}

throw err;
}
};


exports.ref = function (id, options) {

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


Expand All @@ -102,14 +100,14 @@ exports.compile = function (root, schema, options = {}) {
if (typeof schema !== 'object' ||
!options.legacy) {

return internals.appendPath(root, schema); // Will error if schema contains other versions
return exports.schema(root, schema, { appendPath: true }); // 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 exports.schema(root, schema, { appendPath: true });
}

return compiler.compile(compiler.root, schema);
Expand Down
8 changes: 4 additions & 4 deletions lib/index.js
Expand Up @@ -21,7 +21,7 @@ const internals = {
binary: require('./types/binary'),
date: require('./types/date'),
func: require('./types/func'),
lazy: require('./types/lazy'),
link: require('./types/link'),
number: require('./types/number'),
object: require('./types/object'),
string: require('./types/string'),
Expand All @@ -37,7 +37,7 @@ const internals = {
'binary',
'date',
'func',
'lazy',
'link',
'number',
'object',
'string',
Expand Down Expand Up @@ -77,7 +77,7 @@ internals.root = function () {
};
}

for (const type of ['alternatives', 'lazy', 'object']) {
for (const type of ['alternatives', 'link', 'object']) {
root[type] = function (...args) {

return Common.callWithDefaults(this, internals[type], args);
Expand Down Expand Up @@ -186,7 +186,7 @@ internals.methods = {

ref: function (...args) {

return new Ref(...args);
return Ref.create(...args);
}
};

Expand Down
32 changes: 17 additions & 15 deletions lib/messages.js
Expand Up @@ -169,7 +169,23 @@ exports.errors = {
'function.maxArity': '"{{#label}}" must have an arity lesser or equal to {{#n}}',
'function.minArity': '"{{#label}}" must have an arity greater or equal to {{#n}}',

'lazy.schema': 'schema error: lazy schema function must return a schema',
'link.depth': '"{{#label}}" contains link reference "{{#ref}}" outside of schema boundaries',
'link.ref': '"{{#label}}" contains link reference to non-existing "{{#ref}}" schema',
'link.loop': '"{{#label}}" contains link reference to another link "{{#ref}}"',

'number.base': '"{{#label}}" must be a number',
'number.unsafe': '"{{#label}}" must be a safe number',
'number.min': '"{{#label}}" must be larger than or equal to {{#limit}}',
'number.max': '"{{#label}}" must be less than or equal to {{#limit}}',
'number.less': '"{{#label}}" must be less than {{#limit}}',
'number.greater': '"{{#label}}" must be greater than {{#limit}}',
'number.integer': '"{{#label}}" must be an integer',
'number.negative': '"{{#label}}" must be a negative number',
'number.positive': '"{{#label}}" must be a positive number',
'number.precision': '"{{#label}}" must have no more than {{#limit}} decimal places',
'number.ref': '"{{#label}}" references "{{#ref}}" which is not a number',
'number.multiple': '"{{#label}}" must be a multiple of {{#multiple}}',
'number.port': '"{{#label}}" must be a valid port',

'object.base': '"{{#label}}" must be an object',
'object.allowUnknown': '"{{#label}}" is not allowed',
Expand All @@ -192,20 +208,6 @@ exports.errors = {
'object.without': '"{{#mainWithLabel}}" conflict with forbidden peer "{{#peerWithLabel}}"',
'object.xor': '"{{#label}}" contains a conflict between exclusive peers {{#peersWithLabels}}',

'number.base': '"{{#label}}" must be a number',
'number.unsafe': '"{{#label}}" must be a safe number',
'number.min': '"{{#label}}" must be larger than or equal to {{#limit}}',
'number.max': '"{{#label}}" must be less than or equal to {{#limit}}',
'number.less': '"{{#label}}" must be less than {{#limit}}',
'number.greater': '"{{#label}}" must be greater than {{#limit}}',
'number.integer': '"{{#label}}" must be an integer',
'number.negative': '"{{#label}}" must be a negative number',
'number.positive': '"{{#label}}" must be a positive number',
'number.precision': '"{{#label}}" must have no more than {{#limit}} decimal places',
'number.ref': '"{{#label}}" references "{{#ref}}" which is not a number',
'number.multiple': '"{{#label}}" must be a multiple of {{#multiple}}',
'number.port': '"{{#label}}" must be a valid port',

'string.base': '"{{#label}}" must be a string',
'string.min': '"{{#label}}" length must be at least {{#limit}} characters long',
'string.max': '"{{#label}}" length must be less than or equal to {{#limit}} characters long',
Expand Down

0 comments on commit 70540f2

Please sign in to comment.