Skip to content

Commit

Permalink
Merge branch 'error' into v8
Browse files Browse the repository at this point in the history
  • Loading branch information
Marsup committed May 7, 2016
2 parents 4f8ef50 + e3e72c5 commit 6ba9191
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 5 deletions.
16 changes: 16 additions & 0 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ Validates a value using the given schema and options where:
- `context` - provides an external data set to be used in [references](#refkey-options). Can only be set as an external option to
`validate()` and not using `any.options()`.
- `noDefaults` - when `true`, do not apply default values. Defaults to `false`.
- `error` - overrides the default joi error as described in [`any.error(err)`](#anyerrorerr). Defaults to no override.
- `callback` - the optional synchronous callback method using the signature `function(err, value)` where:
- `err` - if validation failed, the [error](#errors) reason, otherwise `null`.
- `value` - the validated value with any type conversions and other modifiers applied (the input is left unchanged). `value` can be
Expand Down Expand Up @@ -518,6 +519,21 @@ schema = schema.empty();
schema.validate(''); // returns { error: "value" is not allowed to be empty, value: '' }
```
#### `any.error(err)`
Overrides the default joi error with a custom error if the rule fails where:
- `err` - the override error.
Note that the provided error will be returned as-is, unmodified and undecorated with any of the
normal joi 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`).
```js
let schema = Joi.string().error(new Error('Was REALLY expecting a string'));
schema.validate(3); // returns err.message === 'Was REALLY expecting a string'
```
### `array`
Generates a schema object that matches an array data type. Note that undefined values inside arrays are not allowed by default but can be by using `sparse()`.
Expand Down
21 changes: 18 additions & 3 deletions lib/any.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ internals.checkOptions = function (options) {
raw: 'boolean',
context: 'object',
strip: 'boolean',
noDefaults: 'boolean'
noDefaults: 'boolean',
error: 'object'
};

const keys = Object.keys(options);
Expand Down Expand Up @@ -78,7 +79,8 @@ module.exports = internals.Any = function () {
this._invalids = new internals.Set();
this._tests = [];
this._refs = [];
this._flags = { /*
this._flags = {
/*
presence: 'optional', // optional, required, forbidden, ignore
allowOnly: false,
allowUnknown: undefined,
Expand All @@ -90,7 +92,8 @@ module.exports = internals.Any = function () {
case: undefined, // upper, lower
empty: undefined,
func: false
*/ };
*/
};

this._description = null;
this._unit = null;
Expand Down Expand Up @@ -254,6 +257,17 @@ internals.Any.prototype.raw = function (isRaw) {
};


internals.Any.prototype.error = function (err) {

Hoek.assert(err && err instanceof Error, 'Must provide a valid Error object');

const obj = this.clone();
obj._settings = obj._settings || {};
obj._settings.error = err;
return obj;
};


internals.Any.prototype._allow = function () {

const values = Hoek.flatten(Array.prototype.slice.call(arguments));
Expand Down Expand Up @@ -766,6 +780,7 @@ internals.Any.prototype.describe = function () {
return description;
};


internals.Any.prototype.label = function (name) {

Hoek.assert(name && typeof name === 'string', 'Label name must be a non-empty string');
Expand Down
15 changes: 13 additions & 2 deletions lib/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ exports.process = function (errors, object) {
for (let i = 0; i < localErrors.length; ++i) {
const item = localErrors[i];

if (item.options.error) {
return item.options.error;
}

const detail = {
message: item.toString(),
path: internals.getPath(item),
Expand All @@ -127,16 +131,23 @@ exports.process = function (errors, object) {
}

// Do not push intermediate errors, we're only interested in leafs

if (item.context.reason && item.context.reason.length) {
processErrors(item.context.reason, item.path);
const override = processErrors(item.context.reason, item.path);
if (override) {
return override;
}
}
else {
details.push(detail);
}
}
};

processErrors(errors);
const override = processErrors(errors);
if (override) {
return override;
}

const error = new Error(message);
error.isJoi = true;
Expand Down
119 changes: 119 additions & 0 deletions test/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,125 @@ describe('errors', () => {
});
});

it('returns custom error', (done) => {

const schema = Joi.object({
a: Joi.string(),
b: {
c: Joi.number().strict().error(new Error('Really wanted a number!'))
}
});

Joi.validate({ a: 'abc', b: { c: 'x' } }, schema, (err) => {

expect(err).to.exist();
expect(err.isJoi).to.not.exist();
expect(err.message).to.equal('Really wanted a number!');
done();
});
});

it('returns custom error (validate options)', (done) => {

const schema = Joi.object({
a: Joi.string(),
b: {
c: Joi.number()
}
});

Joi.validate({ a: 'abc', b: { c: 'x' } }, schema, { error: new Error('Really wanted a number!') }, (err) => {

expect(err).to.exist();
expect(err.isJoi).to.not.exist();
expect(err.message).to.equal('Really wanted a number!');
done();
});
});

it('returns first custom error with multiple errors', (done) => {

const schema = Joi.object({
a: Joi.string(),
b: {
c: Joi.number().error(new Error('Really wanted a number!'))
}
}).options({ abortEarly: false });

Joi.validate({ a: 22, b: { c: 'x' } }, schema, (err) => {

expect(err).to.exist();
expect(err.isJoi).to.not.exist();
expect(err.message).to.equal('Really wanted a number!');
done();
});
});

it('returns first error with multiple errors (first not custom)', (done) => {

const schema = Joi.object({
a: Joi.string(),
b: {
c: Joi.number().error(new Error('Really wanted a number!'))
}
});

Joi.validate({ a: 22, b: { c: 'x' } }, schema, (err) => {

expect(err).to.exist();
expect(err.isJoi).to.be.true();
done();
});
});

it('returns custom error (options)', (done) => {

const schema = Joi.object({
a: Joi.string(),
b: {
c: Joi.number().options({ error: new Error('Really wanted a number!') })
}
});

Joi.validate({ a: 'abc', b: { c: 'x' } }, schema, (err) => {

expect(err).to.exist();
expect(err.isJoi).to.not.exist();
expect(err.message).to.equal('Really wanted a number!');
done();
});
});

it('errors on invalid error option', (done) => {

expect(() => {

Joi.object({
a: Joi.string(),
b: {
c: Joi.number().error('Really wanted a number!')
}
});
}).to.throw('Must provide a valid Error object');

done();
});

it('errors on missing error option', (done) => {

expect(() => {

Joi.object({
a: Joi.string(),
b: {
c: Joi.number().error()
}
});
}).to.throw('Must provide a valid Error object');

done();
});

describe('annotate()', () => {

it('annotates error', (done) => {
Expand Down

0 comments on commit 6ba9191

Please sign in to comment.