diff --git a/API.md b/API.md index b5bb4763..332f2947 100755 --- a/API.md +++ b/API.md @@ -252,6 +252,7 @@ The reference names can have one of the following prefixes: The formula syntax also supports built-in functions: - `if(condition, then, otherwise)` - returns `then` when `condition` is truthy, otherwise `otherwise`. +- `length(item)` - reutrn the length of an array or string, the number of keys of an object, otherwise `null`. - `msg(code)` - embeds another error code message. - `number(value)` - cast value to a number. diff --git a/lib/template.js b/lib/template.js index 440e4a10..3858ad17 100755 --- a/lib/template.js +++ b/lib/template.js @@ -385,6 +385,23 @@ internals.functions = { return condition ? then : otherwise; }, + length(item) { + + if (typeof item === 'string') { + return item.length; + } + + if (!item || typeof item !== 'object') { + return null; + } + + if (Array.isArray(item)) { + return item.length; + } + + return Object.keys(item).length; + }, + msg(code) { const [value, state, prefs, local, options] = this; diff --git a/test/template.js b/test/template.js index 1e361910..9c0ca1e4 100755 --- a/test/template.js +++ b/test/template.js @@ -201,5 +201,71 @@ describe('Template', () => { Helper.validate(schema, { context: { x: {} } }, [[4, false, '"value" must be [{number(1) + number(true) + number(false) + number("1") + number($x)}]']]); }); }); + + describe('length()', () => { + + it('calculates object size', () => { + + const schema = Joi.object({ + a: Joi.array().length(Joi.x('{length(b)}')), + b: Joi.object() + }); + + Helper.validate(schema, [ + [{ a: [1, 2], b: { a: true, b: true } }, true], + [{ a: [1, 2, 3], b: { a: true, b: true } }, false, '"a" must contain {length(b)} items'] + ]); + }); + + it('calcualtes array size', () => { + + const schema = Joi.object({ + a: Joi.array().length(Joi.x('{length(b)}')), + b: Joi.array() + }); + + Helper.validate(schema, [ + [{ a: [1, 2], b: [2, 3] }, true], + [{ a: [1, 2, 3], b: [1] }, false, '"a" must contain {length(b)} items'] + ]); + }); + + it('handles null', () => { + + const schema = Joi.object({ + a: Joi.array().length(Joi.x('{length(b)}')), + b: Joi.array().allow(null) + }); + + Helper.validate(schema, [ + [{ a: [1], b: null }, false, '"a" limit references "{length(b)}" which must be a positive integer'] + ]); + }); + + it('handles strings', () => { + + const schema = Joi.object({ + a: Joi.array().length(Joi.x('{length(b)}')), + b: Joi.string() + }); + + Helper.validate(schema, [ + [{ a: [1], b: 'x' }, true], + [{ a: [1], b: 'xx' }, false, '"a" must contain {length(b)} items'] + ]); + }); + + it('handles items without length', () => { + + const schema = Joi.object({ + a: Joi.array().length(Joi.x('{length(b)}')), + b: Joi.number() + }); + + Helper.validate(schema, [ + [{ a: [1], b: 1 }, false, '"a" limit references "{length(b)}" which must be a positive integer'] + ]); + }); + }); }); });