Skip to content

Commit

Permalink
Refactor empty string. Closes #2005. Closes #2006
Browse files Browse the repository at this point in the history
  • Loading branch information
hueniverse committed Aug 1, 2019
1 parent fed870f commit 5c1840a
Show file tree
Hide file tree
Showing 12 changed files with 265 additions and 267 deletions.
17 changes: 5 additions & 12 deletions API.md
Expand Up @@ -2598,7 +2598,7 @@ const schema = Joi.string().min(1).max(10);
await schema.validate('12345');
```

Possible validation errors: [`string.base`](#stringbase), [`any.empty`](#anyempty)
Possible validation errors: [`string.base`](#stringbase), [`string.empty`](#stringempty)

#### `string.alphanum()`

Expand Down Expand Up @@ -3268,17 +3268,6 @@ Additional local context properties:
}
```

#### `any.empty`

When an empty string is found and denied by invalid values.

Additional local context properties:
```ts
{
invalids: Array<any> // Contains the list of the invalid values that should be rejected
}
```

#### `any.invalid`

The value matched a value listed in the invalid values.
Expand Down Expand Up @@ -4041,6 +4030,10 @@ Additional local context properties:
}
```

#### `string.empty`

When an empty string is found and denied by invalid values.

#### `string.guid`

The string is not a valid GUID.
Expand Down
2 changes: 1 addition & 1 deletion lib/messages.js
Expand Up @@ -118,7 +118,6 @@ exports.errors = {
'alternatives.types': '"{{#label}}" must be one of {{#types}}',

'any.default': '"{{#label}}" threw an error when running default method',
'any.empty': '"{{#label}}" is not allowed to be empty',
'any.failover': '"{{#label}}" threw an error when running failover method',
'any.invalid': '"{{#label}}" contains an invalid value',
'any.only': '"{{#label}}" must be one of {{#valids}}',
Expand Down Expand Up @@ -216,6 +215,7 @@ exports.errors = {
'string.dataUri': '"{{#label}}" must be a valid dataUri string',
'string.domain': '"{{#label}}" must contain a valid domain name',
'string.email': '"{{#label}}" must be a valid email',
'string.empty': '"{{#label}}" is not allowed to be empty',
'string.guid': '"{{#label}}" must be a valid GUID',
'string.hex': '"{{#label}}" must only contain hexadecimal characters',
'string.hexAlign': '"{{#label}}" hex decoded representation must be byte aligned',
Expand Down
12 changes: 8 additions & 4 deletions lib/types/any.js
Expand Up @@ -452,7 +452,7 @@ module.exports = internals.Any = class {
obj._ruleset = source._ruleset === false ? false : source._ruleset + obj._tests.length;
}

// Combine tests
// Rules

for (const test of source._tests) {
if (!test.func &&
Expand All @@ -464,6 +464,8 @@ module.exports = internals.Any = class {
obj._tests.push(test);
}

// Flags

if (obj._flags.empty &&
source._flags.empty) {

Expand All @@ -482,6 +484,8 @@ module.exports = internals.Any = class {
Hoek.merge(obj._flags, source._flags);
}

// Inners

for (const key in source._inners) {
const inners = source._inners[key];
if (!inners) {
Expand Down Expand Up @@ -877,14 +881,14 @@ module.exports = internals.Any = class {
return obj;
}

_state(path, ancestors, state, options = {}) {
_state(path, ancestors, state) {

return {
path,
ancestors,
mainstay: state.mainstay,
flags: options.flags !== false ? this._flags : {},
schemas: options.schemas ? [this, ...state.schemas] : state.schemas
flags: this._flags,
schemas: state.schemas
};
}

Expand Down
4 changes: 3 additions & 1 deletion lib/types/object.js
Expand Up @@ -235,7 +235,9 @@ internals.Object = Any.extend({
const forbidUnknown = !Common.default(this._flags.unknown, prefs.allowUnknown);
if (forbidUnknown) {
for (const unprocessedKey of unprocessed) {
const localState = this._state([...state.path, unprocessedKey], [], state, { flags: false });
const localState = this._state([...state.path, unprocessedKey], [], state);
localState.flags = {};

const report = this.createError('object.unknown', value[unprocessedKey], { child: unprocessedKey }, localState, prefs);
if (prefs.abortEarly) {
return { value, errors: report };
Expand Down
6 changes: 4 additions & 2 deletions lib/types/string/index.js
Expand Up @@ -5,7 +5,6 @@ const Hoek = require('@hapi/hoek');

const Any = require('../any');
const Common = require('../../common');
const Values = require('../../values');

const Ip = require('./ip');
const Uri = require('./uri');
Expand Down Expand Up @@ -61,7 +60,6 @@ internals.String = Any.extend({

initialize: function () {

this._invalids = new Values(['']);
this._inners.replacements = null;
},

Expand Down Expand Up @@ -143,6 +141,10 @@ internals.String = Any.extend({
if (typeof value !== 'string') {
return { value, errors: this.createError('string.base', value, null, state, prefs) };
}

if (value === '') {
return { value, errors: this.createError('string.empty', value, null, state, prefs) };
}
},

// Rules
Expand Down
134 changes: 68 additions & 66 deletions lib/validator.js
Expand Up @@ -25,9 +25,6 @@ exports.entry = function (value, schema, prefs) {

Hoek.assert(!mainstay.externals.length || settings.externals, 'Cannot validate a schema with external rules without the externals flag');

this.value = value;
this.error = error;

const outcome = { value: result.value, error };
if (mainstay.warnings.length) {
outcome.warning = Errors.details(mainstay.warnings);
Expand All @@ -43,56 +40,21 @@ exports.entry = function (value, schema, prefs) {
};


internals.externals = function (externals, outcome, prefs) {

return new Promise(async (resolve, reject) => {

let root = outcome.value;

for (const { method, path, label } of externals) {
let value = root;
let key;
let parent;

if (path.length) {
key = path[path.length - 1];
parent = Hoek.reach(root, path.slice(0, -1));
value = parent[key];
}

try {
var result = await method(value);
}
catch (err) {
err.message += ` (${label})`;
return reject(err); // Change message to include path
}

if (result === undefined ||
result === value) {

continue;
}

if (parent) {
parent[key] = result;
}
else {
root = result;
}
}

resolve(prefs.warnings ? Object.assign(outcome, { value: root }) : root);
});
};


exports.validate = function (value, schema, state, prefs) {

// Setup state and settings

state = schema._state(state.path, state.ancestors, state, { schemas: true });
prefs = internals.prefs(schema, prefs);
state = {
path: state.path,
ancestors: state.ancestors,
mainstay: state.mainstay,
flags: schema._flags,
schemas: [schema, ...state.schemas]
};

if (schema._preferences) {
prefs = internals.prefs(schema, prefs);
}

const original = value;

Expand Down Expand Up @@ -186,7 +148,7 @@ exports.validate = function (value, schema, state, prefs) {

if (schema._invalids) {
if (schema._invalids.has(value, state, prefs, schema._flags.insensitive)) {
const report = schema.createError(value === '' ? 'any.empty' : 'any.invalid', value, { invalids: schema._invalids.values({ stripUndefined: true }) }, state, prefs);
const report = schema.createError('any.invalid', value, { invalids: schema._invalids.values({ stripUndefined: true }) }, state, prefs);
if (prefs.abortEarly) {
return internals.finalize(value, schema, original, [report], state, prefs);
}
Expand Down Expand Up @@ -400,20 +362,16 @@ internals.finalize = function (value, schema, original, errors, state, prefs) {

internals.prefs = function (schema, prefs) {

if (schema._preferences) {
const isDefaultOptions = prefs === Common.defaults;
if (isDefaultOptions &&
schema._preferences[Common.symbols.prefs]) {
const isDefaultOptions = prefs === Common.defaults;
if (isDefaultOptions &&
schema._preferences[Common.symbols.prefs]) {

return schema._preferences[Common.symbols.prefs];
}

prefs = Common.preferences(prefs, schema._preferences);
if (isDefaultOptions) {
schema._preferences[Common.symbols.prefs] = prefs;
}
return schema._preferences[Common.symbols.prefs];
}

return prefs;
prefs = Common.preferences(prefs, schema._preferences);
if (isDefaultOptions) {
schema._preferences[Common.symbols.prefs] = prefs;
}

return prefs;
Expand Down Expand Up @@ -468,13 +426,13 @@ internals.trim = function (value, schema) {
}

const trim = schema._uniqueRules.get('trim');
if (trim &&
trim.args.enabled) {
if (!trim ||
!trim.args.enabled) {

value = value.trim();
return value;
}

return value;
return value.trim();
};


Expand Down Expand Up @@ -506,6 +464,50 @@ internals.Promise = class {
};


internals.externals = function (externals, outcome, prefs) {

return new Promise(async (resolve, reject) => {

let root = outcome.value;

for (const { method, path, label } of externals) {
let value = root;
let key;
let parent;

if (path.length) {
key = path[path.length - 1];
parent = Hoek.reach(root, path.slice(0, -1));
value = parent[key];
}

try {
var result = await method(value);
}
catch (err) {
err.message += ` (${label})`;
return reject(err); // Change message to include path
}

if (result === undefined ||
result === value) {

continue;
}

if (parent) {
parent[key] = result;
}
else {
root = result;
}
}

resolve(prefs.warnings ? Object.assign(outcome, { value: root }) : root);
});
};


internals.Shadow = class {

constructor() {
Expand Down

0 comments on commit 5c1840a

Please sign in to comment.