From fef337228bcf966966b74d1cd238d9586eb67a65 Mon Sep 17 00:00:00 2001 From: Nguyen The Thang Date: Sun, 24 Apr 2016 21:30:55 +0700 Subject: [PATCH] initial commit --- README.md | 6 + lib/alternatives.js | 22 +-- lib/any.js | 63 ++++---- lib/array.js | 22 +-- lib/binary.js | 14 +- lib/boolean.js | 6 +- lib/cast.js | 6 +- lib/date.js | 12 +- lib/errors.js | 20 ++- lib/escape.js | 134 ++++++++++++++++ lib/index.js | 8 +- lib/number.js | 18 ++- lib/object.js | 75 ++++----- lib/patch.js | 372 ++++++++++++++++++++++++++++++++++++++++++++ lib/ref.js | 11 +- lib/string.js | 60 +++---- package.json | 19 ++- test/helper.js | 4 +- 18 files changed, 709 insertions(+), 163 deletions(-) create mode 100644 lib/escape.js create mode 100644 lib/patch.js diff --git a/README.md b/README.md index 5f0b8a7..f95f307 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ +# Disclaimer + +This is directly ported from `hapi/joi` but removed `hoek` and some methods to make it works with React Native. + +Some methods that belongs to `Hoek` are moved into `patch.js`. + ![joi Logo](https://raw.github.com/hapijs/joi/master/images/joi.png) Object schema description language and validator for JavaScript objects. diff --git a/lib/alternatives.js b/lib/alternatives.js index 8186f36..8f834a0 100755 --- a/lib/alternatives.js +++ b/lib/alternatives.js @@ -1,8 +1,10 @@ 'use strict'; // Load modules - -const Hoek = require('hoek'); + +const _ = require('lodash'); +const Util = require('util'); +const Patch = require('./patch'); const Any = require('./any'); const Cast = require('./cast'); const Ref = require('./ref'); @@ -21,7 +23,7 @@ internals.Alternatives = function () { this._inner.matches = []; }; -Hoek.inherits(internals.Alternatives, Any); +Util.inherits(internals.Alternatives, Any); internals.Alternatives.prototype._base = function (value, state, options) { @@ -65,8 +67,8 @@ internals.Alternatives.prototype._base = function (value, state, options) { internals.Alternatives.prototype.try = function (/* schemas */) { - const schemas = Hoek.flatten(Array.prototype.slice.call(arguments)); - Hoek.assert(schemas.length, 'Cannot add other alternatives without at least one schema'); + const schemas = _.flatten(Array.prototype.slice.call(arguments)); + Patch.assert(schemas.length, 'Cannot add other alternatives without at least one schema'); const obj = this.clone(); @@ -84,11 +86,11 @@ internals.Alternatives.prototype.try = function (/* schemas */) { internals.Alternatives.prototype.when = function (ref, options) { - Hoek.assert(Ref.isRef(ref) || typeof ref === 'string', 'Invalid reference:', ref); - Hoek.assert(options, 'Missing options'); - Hoek.assert(typeof options === 'object', 'Invalid options'); - Hoek.assert(options.hasOwnProperty('is'), 'Missing "is" directive'); - Hoek.assert(options.then !== undefined || options.otherwise !== undefined, 'options must have at least one of "then" or "otherwise"'); + Patch.assert(Ref.isRef(ref) || typeof ref === 'string', 'Invalid reference:', ref); + Patch.assert(options, 'Missing options'); + Patch.assert(typeof options === 'object', 'Invalid options'); + Patch.assert(options.hasOwnProperty('is'), 'Missing "is" directive'); + Patch.assert(options.then !== undefined || options.otherwise !== undefined, 'options must have at least one of "then" or "otherwise"'); const obj = this.clone(); let is = Cast.schema(options.is); diff --git a/lib/any.js b/lib/any.js index bac0308..80d9b2a 100755 --- a/lib/any.js +++ b/lib/any.js @@ -2,7 +2,10 @@ // Load modules -const Hoek = require('hoek'); + +const _ = require('lodash'); +const Util = require('util'); +const Patch = require('./patch'); const Ref = require('./ref'); const Errors = require('./errors'); let Alternatives = null; // Delay-loaded to prevent circular dependencies @@ -58,10 +61,10 @@ internals.checkOptions = function (options) { values = opt.slice(1); } - Hoek.assert(type, 'unknown key ' + key); - Hoek.assert(typeof options[key] === type, key + ' should be of type ' + type); + Patch.assert(type, 'unknown key ' + key); + Patch.assert(typeof options[key] === type, key + ' should be of type ' + type); if (values) { - Hoek.assert(values.indexOf(options[key]) >= 0, key + ' should be one of ' + values.join(', ')); + Patch.assert(values.indexOf(options[key]) >= 0, key + ' should be one of ' + values.join(', ')); } } }; @@ -116,11 +119,11 @@ internals.Any.prototype.clone = function () { obj.isJoi = true; obj._type = this._type; obj._settings = internals.concatSettings(this._settings); - obj._valids = Hoek.clone(this._valids); - obj._invalids = Hoek.clone(this._invalids); + obj._valids = Patch.clone(this._valids); + obj._invalids = Patch.clone(this._invalids); obj._tests = this._tests.slice(); obj._refs = this._refs.slice(); - obj._flags = Hoek.clone(this._flags); + obj._flags = Patch.clone(this._flags); obj._description = this._description; obj._unit = this._unit; @@ -142,8 +145,8 @@ internals.Any.prototype.clone = function () { internals.Any.prototype.concat = function (schema) { - Hoek.assert(schema && schema.isJoi, 'Invalid schema object'); - Hoek.assert(this._type === 'any' || schema._type === 'any' || schema._type === this._type, 'Cannot merge type', this._type, 'with another type:', schema._type); + Patch.assert(schema && schema.isJoi, 'Invalid schema object'); + Patch.assert(this._type === 'any' || schema._type === 'any' || schema._type === this._type, 'Cannot merge type', this._type, 'with another type:', schema._type); let obj = this.clone(); @@ -166,7 +169,7 @@ internals.Any.prototype.concat = function (schema) { obj._invalids.merge(schema._invalids, schema._valids); obj._tests = obj._tests.concat(schema._tests); obj._refs = obj._refs.concat(schema._refs); - Hoek.merge(obj._flags, schema._flags); + _.merge(obj._flags, schema._flags); obj._description = schema._description || obj._description; obj._unit = schema._unit || obj._unit; @@ -227,7 +230,7 @@ internals.Any.prototype._test = function (name, arg, func) { internals.Any.prototype.options = function (options) { - Hoek.assert(!options.context, 'Cannot override context'); + Patch.assert(!options.context, 'Cannot override context'); internals.checkOptions(options); const obj = this.clone(); @@ -256,11 +259,11 @@ internals.Any.prototype.raw = function (isRaw) { internals.Any.prototype._allow = function () { - const values = Hoek.flatten(Array.prototype.slice.call(arguments)); + const values = _.flatten(Array.prototype.slice.call(arguments)); for (let i = 0; i < values.length; ++i) { const value = values[i]; - Hoek.assert(value !== undefined, 'Cannot call allow/valid/invalid with undefined'); + Patch.assert(value !== undefined, 'Cannot call allow/valid/invalid with undefined'); this._invalids.remove(value); this._valids.add(value, this._refs); } @@ -286,11 +289,11 @@ internals.Any.prototype.valid = internals.Any.prototype.only = internals.Any.pro internals.Any.prototype.invalid = internals.Any.prototype.disallow = internals.Any.prototype.not = function (value) { const obj = this.clone(); - const values = Hoek.flatten(Array.prototype.slice.call(arguments)); + const values = _.flatten(Array.prototype.slice.call(arguments)); for (let i = 0; i < values.length; ++i) { value = values[i]; - Hoek.assert(value !== undefined, 'Cannot call allow/valid/invalid with undefined'); + Patch.assert(value !== undefined, 'Cannot call allow/valid/invalid with undefined'); obj._valids.remove(value); obj._invalids.add(value, this._refs); } @@ -362,7 +365,7 @@ internals.Any.prototype.default = function (value, description) { } if (!this._flags.func) { - Hoek.assert(typeof value.description === 'string' && value.description.length > 0, 'description must be provided when default value is a function'); + Patch.assert(typeof value.description === 'string' && value.description.length > 0, 'description must be provided when default value is a function'); } } @@ -383,8 +386,8 @@ internals.Any.prototype.empty = function (schema) { internals.Any.prototype.when = function (ref, options) { - Hoek.assert(options && typeof options === 'object', 'Invalid options'); - Hoek.assert(options.then !== undefined || options.otherwise !== undefined, 'options must have at least one of "then" or "otherwise"'); + Patch.assert(options && typeof options === 'object', 'Invalid options'); + Patch.assert(options.then !== undefined || options.otherwise !== undefined, 'options must have at least one of "then" or "otherwise"'); const then = options.hasOwnProperty('then') ? this.concat(Cast.schema(options.then)) : undefined; const otherwise = options.hasOwnProperty('otherwise') ? this.concat(Cast.schema(options.otherwise)) : undefined; @@ -400,7 +403,7 @@ internals.Any.prototype.when = function (ref, options) { internals.Any.prototype.description = function (desc) { - Hoek.assert(desc && typeof desc === 'string', 'Description must be a non-empty string'); + Patch.assert(desc && typeof desc === 'string', 'Description must be a non-empty string'); const obj = this.clone(); obj._description = desc; @@ -410,7 +413,7 @@ internals.Any.prototype.description = function (desc) { internals.Any.prototype.notes = function (notes) { - Hoek.assert(notes && (typeof notes === 'string' || Array.isArray(notes)), 'Notes must be a non-empty string or array'); + Patch.assert(notes && (typeof notes === 'string' || Array.isArray(notes)), 'Notes must be a non-empty string or array'); const obj = this.clone(); obj._notes = obj._notes.concat(notes); @@ -420,7 +423,7 @@ internals.Any.prototype.notes = function (notes) { internals.Any.prototype.tags = function (tags) { - Hoek.assert(tags && (typeof tags === 'string' || Array.isArray(tags)), 'Tags must be a non-empty string or array'); + Patch.assert(tags && (typeof tags === 'string' || Array.isArray(tags)), 'Tags must be a non-empty string or array'); const obj = this.clone(); obj._tags = obj._tags.concat(tags); @@ -429,7 +432,7 @@ internals.Any.prototype.tags = function (tags) { internals.Any.prototype.meta = function (meta) { - Hoek.assert(meta !== undefined, 'Meta cannot be undefined'); + Patch.assert(meta !== undefined, 'Meta cannot be undefined'); const obj = this.clone(); obj._meta = obj._meta.concat(meta); @@ -439,9 +442,9 @@ internals.Any.prototype.meta = function (meta) { internals.Any.prototype.example = function (value) { - Hoek.assert(arguments.length, 'Missing example'); + Patch.assert(arguments.length, 'Missing example'); const result = this._validate(value, null, internals.defaults); - Hoek.assert(!result.errors, 'Bad example:', result.errors && Errors.process(result.errors, value)); + Patch.assert(!result.errors, 'Bad example:', result.errors && Errors.process(result.errors, value)); const obj = this.clone(); obj._examples.push(value); @@ -451,7 +454,7 @@ internals.Any.prototype.example = function (value) { internals.Any.prototype.unit = function (name) { - Hoek.assert(name && typeof name === 'string', 'Unit name must be a non-empty string'); + Patch.assert(name && typeof name === 'string', 'Unit name must be a non-empty string'); const obj = this.clone(); obj._unit = name; @@ -513,7 +516,7 @@ internals.Any.prototype._validate = function (value, state, options, reference) if (state.parent !== null && this._flags.default.length > 0) { - arg = Hoek.clone(state.parent); + arg = Patch.clone(state.parent); } const defaultValue = internals._try(this._flags.default, arg); @@ -523,7 +526,7 @@ internals.Any.prototype._validate = function (value, state, options, reference) } } else { - finalValue = Hoek.clone(this._flags.default); + finalValue = Patch.clone(this._flags.default); } } @@ -758,7 +761,7 @@ internals.Any.prototype.describe = function () { delete description.rules; } - const label = Hoek.reach(this._settings, 'language.label'); + const label = Patch.reach(this._settings, 'language.label'); if (label) { description.label = label; } @@ -768,7 +771,7 @@ internals.Any.prototype.describe = function () { internals.Any.prototype.label = function (name) { - Hoek.assert(name && typeof name === 'string', 'Label name must be a non-empty string'); + Patch.assert(name && typeof name === 'string', 'Label name must be a non-empty string'); const obj = this.clone(); const options = { language: { label: name } }; @@ -898,7 +901,7 @@ internals.concatSettings = function (target, source) { obj[key] = source[key]; } else { - obj[key] = Hoek.applyToDefaults(obj[key], source[key]); + obj[key] = Patch.applyToDefaults(obj[key], source[key]); } } } diff --git a/lib/array.js b/lib/array.js index 2f9d0b8..e612be7 100755 --- a/lib/array.js +++ b/lib/array.js @@ -4,8 +4,10 @@ const Any = require('./any'); const Cast = require('./cast'); -const Hoek = require('hoek'); +const _ = require('lodash'); +const Util = require('util'); +const Patch = require('./patch'); // Declare internals @@ -35,7 +37,7 @@ internals.Array = function () { this._flags.sparse = false; }; -Hoek.inherits(internals.Array, Any); +Util.inherits(internals.Array, Any); internals.Array.prototype._base = function (value, state, options) { @@ -290,7 +292,7 @@ internals.fillMissedErrors = function (errors, requireds, state, options) { const knownMisses = []; let unknownMisses = 0; for (let i = 0; i < requireds.length; ++i) { - const label = Hoek.reach(requireds[i], '_settings.language.label'); + const label = Patch.reach(requireds[i], '_settings.language.label'); if (label) { knownMisses.push(label); } @@ -318,7 +320,7 @@ internals.fillOrderedErrors = function (errors, ordereds, state, options) { const requiredOrdereds = []; for (let i = 0; i < ordereds.length; ++i) { - const presence = Hoek.reach(ordereds[i], '_flags.presence'); + const presence = Patch.reach(ordereds[i], '_flags.presence'); if (presence === 'required') { requiredOrdereds.push(ordereds[i]); } @@ -357,7 +359,7 @@ internals.Array.prototype.items = function () { const obj = this.clone(); - Hoek.flatten(Array.prototype.slice.call(arguments)).forEach((type, index) => { + _.flatten(Array.prototype.slice.call(arguments)).forEach((type, index) => { try { type = Cast.schema(type); @@ -394,7 +396,7 @@ internals.Array.prototype.ordered = function () { const obj = this.clone(); - Hoek.flatten(Array.prototype.slice.call(arguments)).forEach((type, index) => { + _.flatten(Array.prototype.slice.call(arguments)).forEach((type, index) => { try { type = Cast.schema(type); @@ -418,7 +420,7 @@ internals.Array.prototype.ordered = function () { internals.Array.prototype.min = function (limit) { - Hoek.assert(Hoek.isInteger(limit) && limit >= 0, 'limit must be a positive integer'); + Patch.assert(_.isInteger(limit) && limit >= 0, 'limit must be a positive integer'); return this._test('min', limit, (value, state, options) => { @@ -433,7 +435,7 @@ internals.Array.prototype.min = function (limit) { internals.Array.prototype.max = function (limit) { - Hoek.assert(Hoek.isInteger(limit) && limit >= 0, 'limit must be a positive integer'); + Patch.assert(_.isInteger(limit) && limit >= 0, 'limit must be a positive integer'); return this._test('max', limit, (value, state, options) => { @@ -448,7 +450,7 @@ internals.Array.prototype.max = function (limit) { internals.Array.prototype.length = function (limit) { - Hoek.assert(Hoek.isInteger(limit) && limit >= 0, 'limit must be a positive integer'); + Patch.assert(_.isInteger(limit) && limit >= 0, 'limit must be a positive integer'); return this._test('length', limit, (value, state, options) => { @@ -484,7 +486,7 @@ internals.Array.prototype.unique = function () { if (/* $lab:coverage:off$ */ records /* $lab:coverage:on$ */) { if (Array.isArray(records)) { for (let j = 0; j < records.length; ++j) { - if (Hoek.deepEqual(records[j], item)) { + if (Patch.deepEqual(records[j], item)) { return this.createError('array.unique', { pos: i, value: item }, state, options); } } diff --git a/lib/binary.js b/lib/binary.js index c6f9c97..f235c48 100755 --- a/lib/binary.js +++ b/lib/binary.js @@ -3,7 +3,9 @@ // Load modules const Any = require('./any'); -const Hoek = require('hoek'); +const Patch = require('./patch'); +const _ = require('lodash'); +const Util = require('util'); // Declare internals @@ -17,7 +19,7 @@ internals.Binary = function () { this._type = 'binary'; }; -Hoek.inherits(internals.Binary, Any); +Util.inherits(internals.Binary, Any); internals.Binary.prototype._base = function (value, state, options) { @@ -43,7 +45,7 @@ internals.Binary.prototype._base = function (value, state, options) { internals.Binary.prototype.encoding = function (encoding) { - Hoek.assert(Buffer.isEncoding(encoding), 'Invalid encoding:', encoding); + Patch.assert(Buffer.isEncoding(encoding), 'Invalid encoding:', encoding); const obj = this.clone(); obj._flags.encoding = encoding; @@ -53,7 +55,7 @@ internals.Binary.prototype.encoding = function (encoding) { internals.Binary.prototype.min = function (limit) { - Hoek.assert(Hoek.isInteger(limit) && limit >= 0, 'limit must be a positive integer'); + Patch.assert(_.isInteger(limit) && limit >= 0, 'limit must be a positive integer'); return this._test('min', limit, (value, state, options) => { @@ -68,7 +70,7 @@ internals.Binary.prototype.min = function (limit) { internals.Binary.prototype.max = function (limit) { - Hoek.assert(Hoek.isInteger(limit) && limit >= 0, 'limit must be a positive integer'); + Patch.assert(_.isInteger(limit) && limit >= 0, 'limit must be a positive integer'); return this._test('max', limit, (value, state, options) => { @@ -83,7 +85,7 @@ internals.Binary.prototype.max = function (limit) { internals.Binary.prototype.length = function (limit) { - Hoek.assert(Hoek.isInteger(limit) && limit >= 0, 'limit must be a positive integer'); + Patch.assert(_.isInteger(limit) && limit >= 0, 'limit must be a positive integer'); return this._test('length', limit, (value, state, options) => { diff --git a/lib/boolean.js b/lib/boolean.js index 6fca4eb..acf433d 100755 --- a/lib/boolean.js +++ b/lib/boolean.js @@ -3,7 +3,9 @@ // Load modules const Any = require('./any'); -const Hoek = require('hoek'); + +const _ = require('lodash'); +const Util = require('util'); // Declare internals @@ -17,7 +19,7 @@ internals.Boolean = function () { this._type = 'boolean'; }; -Hoek.inherits(internals.Boolean, Any); +Util.inherits(internals.Boolean, Any); internals.Boolean.prototype._base = function (value, state, options) { diff --git a/lib/cast.js b/lib/cast.js index 7ec9618..2c3f28f 100755 --- a/lib/cast.js +++ b/lib/cast.js @@ -2,7 +2,9 @@ // Load modules -const Hoek = require('hoek'); +const Patch = require('./patch'); +const _ = require('lodash'); +const Util = require('util'); const Ref = require('./ref'); // Type modules are delay-loaded to prevent circular dependencies @@ -64,7 +66,7 @@ exports.schema = function (config) { return internals.any.valid(config); } - Hoek.assert(config === null, 'Invalid schema content:', config); + Patch.assert(config === null, 'Invalid schema content:', config); return internals.any.valid(null); }; diff --git a/lib/date.js b/lib/date.js index e1a7e09..a4e630a 100755 --- a/lib/date.js +++ b/lib/date.js @@ -4,7 +4,9 @@ const Any = require('./any'); const Ref = require('./ref'); -const Hoek = require('hoek'); +const Patch = require('./patch'); +const _ = require('lodash'); +const Util = require('util'); const Moment = require('moment'); @@ -30,7 +32,7 @@ internals.Date = function () { this._type = 'date'; }; -Hoek.inherits(internals.Date, Any); +Util.inherits(internals.Date, Any); internals.Date.prototype._base = function (value, state, options) { @@ -110,7 +112,7 @@ internals.compare = function (type, compare) { date = internals.toDate(date); } - Hoek.assert(date, 'Invalid date format'); + Patch.assert(date, 'Invalid date format'); return this._test(type, date, (value, state, options) => { @@ -145,7 +147,7 @@ internals.Date.prototype.max = internals.compare('max', (value, date) => value < internals.Date.prototype.format = function (format) { - Hoek.assert(typeof format === 'string' || (Array.isArray(format) && format.every((f) => typeof f === 'string')), 'Invalid format.'); + Patch.assert(typeof format === 'string' || (Array.isArray(format) && format.every((f) => typeof f === 'string')), 'Invalid format.'); const obj = this.clone(); obj._flags.format = format; @@ -164,7 +166,7 @@ internals.Date.prototype.timestamp = function (type) { type = type || 'javascript'; const allowed = ['javascript', 'unix']; - Hoek.assert(allowed.indexOf(type) !== -1, '"type" must be one of "' + allowed.join('", "') + '"'); + Patch.assert(allowed.indexOf(type) !== -1, '"type" must be one of "' + allowed.join('", "') + '"'); const obj = this.clone(); obj._flags.timestamp = type; diff --git a/lib/errors.js b/lib/errors.js index ea2617e..c08f773 100755 --- a/lib/errors.js +++ b/lib/errors.js @@ -2,7 +2,11 @@ // Load modules -const Hoek = require('hoek'); + +const _ = require('lodash'); +const Util = require('util'); +const Patch = require('./patch'); +const Escape = require('./escape'); const Language = require('./language'); @@ -65,7 +69,7 @@ internals.Err.prototype.toString = function () { this.context.key = localized.root || Language.errors.root; } - let format = Hoek.reach(localized, this.type) || Hoek.reach(Language.errors, this.type); + let format = Patch.reach(localized, this.type) || Patch.reach(Language.errors, this.type); const hasKey = /\{\{\!?key\}\}/.test(format); const skipKey = format.length > 2 && format[0] === '!' && format[1] === '!'; @@ -74,19 +78,19 @@ internals.Err.prototype.toString = function () { } if (!hasKey && !skipKey) { - format = (Hoek.reach(localized, 'key') || Hoek.reach(Language.errors, 'key')) + format; + format = (Patch.reach(localized, 'key') || Patch.reach(Language.errors, 'key')) + format; } - let wrapArrays = Hoek.reach(localized, 'messages.wrapArrays'); + let wrapArrays = Patch.reach(localized, 'messages.wrapArrays'); if (typeof wrapArrays !== 'boolean') { wrapArrays = Language.errors.messages.wrapArrays; } const message = format.replace(/\{\{(\!?)([^}]+)\}\}/g, ($0, isSecure, name) => { - const value = Hoek.reach(this.context, name); + const value = Patch.reach(this.context, name); const normalized = internals.stringify(value, wrapArrays); - return (isSecure ? Hoek.escapeHtml(normalized) : normalized); + return (isSecure ? Escape.escapeHtml(normalized) : normalized); }); return message; @@ -152,7 +156,7 @@ internals.getPath = function (item) { const recursePath = (it) => { - const reachedItem = Hoek.reach(it, 'context.reason.0'); + const reachedItem = Patch.reach(it, 'context.reason.0'); if (reachedItem && reachedItem.context) { return recursePath(reachedItem); } @@ -235,7 +239,7 @@ internals.annotate = function () { return this.details[0].message; } - const obj = Hoek.clone(this._object || {}); + const obj = Patch.clone(this._object || {}); const lookup = {}; for (let i = this.details.length - 1; i >= 0; --i) { // Reverse order to process deepest child first diff --git a/lib/escape.js b/lib/escape.js new file mode 100644 index 0000000..0438d5e --- /dev/null +++ b/lib/escape.js @@ -0,0 +1,134 @@ +'use strict'; + +// Declare internals + +const internals = {}; + + +exports.escapeJavaScript = function (input) { + + if (!input) { + return ''; + } + + let escaped = ''; + + for (let i = 0; i < input.length; ++i) { + + const charCode = input.charCodeAt(i); + + if (internals.isSafe(charCode)) { + escaped += input[i]; + } + else { + escaped += internals.escapeJavaScriptChar(charCode); + } + } + + return escaped; +}; + + +exports.escapeHtml = function (input) { + + if (!input) { + return ''; + } + + let escaped = ''; + + for (let i = 0; i < input.length; ++i) { + + const charCode = input.charCodeAt(i); + + if (internals.isSafe(charCode)) { + escaped += input[i]; + } + else { + escaped += internals.escapeHtmlChar(charCode); + } + } + + return escaped; +}; + + +internals.escapeJavaScriptChar = function (charCode) { + + if (charCode >= 256) { + return '\\u' + internals.padLeft('' + charCode, 4); + } + + const hexValue = new Buffer(String.fromCharCode(charCode), 'ascii').toString('hex'); + return '\\x' + internals.padLeft(hexValue, 2); +}; + + +internals.escapeHtmlChar = function (charCode) { + + const namedEscape = internals.namedHtml[charCode]; + if (typeof namedEscape !== 'undefined') { + return namedEscape; + } + + if (charCode >= 256) { + return '&#' + charCode + ';'; + } + + const hexValue = new Buffer(String.fromCharCode(charCode), 'ascii').toString('hex'); + return '&#x' + internals.padLeft(hexValue, 2) + ';'; +}; + + +internals.padLeft = function (str, len) { + + while (str.length < len) { + str = '0' + str; + } + + return str; +}; + + +internals.isSafe = function (charCode) { + + return (typeof internals.safeCharCodes[charCode] !== 'undefined'); +}; + + +internals.namedHtml = { + '38': '&', + '60': '<', + '62': '>', + '34': '"', + '160': ' ', + '162': '¢', + '163': '£', + '164': '¤', + '169': '©', + '174': '®' +}; + + +internals.safeCharCodes = (function () { + + const safe = {}; + + for (let i = 32; i < 123; ++i) { + + if ((i >= 97) || // a-z + (i >= 65 && i <= 90) || // A-Z + (i >= 48 && i <= 57) || // 0-9 + i === 32 || // space + i === 46 || // . + i === 44 || // , + i === 45 || // - + i === 58 || // : + i === 95) { // _ + + safe[i] = null; + } + } + + return safe; +}()); diff --git a/lib/index.js b/lib/index.js index 4f43332..4aa3de2 100755 --- a/lib/index.js +++ b/lib/index.js @@ -2,7 +2,9 @@ // Load modules -const Hoek = require('hoek'); +const Patch = require('./patch'); +const _ = require('lodash'); +const Util = require('util'); const Any = require('./any'); const Cast = require('./cast'); const Ref = require('./ref'); @@ -150,8 +152,8 @@ internals.root = function () { root.reach = function (schema, path) { - Hoek.assert(schema && schema.isJoi, 'you must provide a joi schema'); - Hoek.assert(typeof path === 'string', 'path must be a string'); + Patch.assert(schema && schema.isJoi, 'you must provide a joi schema'); + Patch.assert(typeof path === 'string', 'path must be a string'); if (path === '') { return schema; diff --git a/lib/number.js b/lib/number.js index 6c813bd..b913ade 100755 --- a/lib/number.js +++ b/lib/number.js @@ -4,7 +4,9 @@ const Any = require('./any'); const Ref = require('./ref'); -const Hoek = require('hoek'); +const Patch = require('./patch'); +const _ = require('lodash'); +const Util = require('util'); // Declare internals @@ -20,7 +22,7 @@ internals.Number = function () { this._invalids.add(-Infinity); }; -Hoek.inherits(internals.Number, Any); +Util.inherits(internals.Number, Any); internals.compare = function (type, compare) { @@ -29,7 +31,7 @@ internals.compare = function (type, compare) { const isRef = Ref.isRef(limit); const isNumber = typeof limit === 'number' && !isNaN(limit); - Hoek.assert(isNumber || isRef, 'limit must be a number or reference'); + Patch.assert(isNumber || isRef, 'limit must be a number or reference'); return this._test(type, limit, (value, state, options) => { @@ -91,8 +93,8 @@ internals.Number.prototype.less = internals.compare('less', (value, limit) => va internals.Number.prototype.multiple = function (base) { - Hoek.assert(Hoek.isInteger(base), 'multiple must be an integer'); - Hoek.assert(base > 0, 'multiple must be greater than 0'); + Patch.assert(_.isInteger(base), 'multiple must be an integer'); + Patch.assert(base > 0, 'multiple must be greater than 0'); return this._test('multiple', base, (value, state, options) => { @@ -109,7 +111,7 @@ internals.Number.prototype.integer = function () { return this._test('integer', undefined, (value, state, options) => { - return Hoek.isInteger(value) ? null : this.createError('number.integer', { value }, state, options); + return _.isInteger(value) ? null : this.createError('number.integer', { value }, state, options); }); }; @@ -145,8 +147,8 @@ internals.precisionRx = /(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/; internals.Number.prototype.precision = function (limit) { - Hoek.assert(Hoek.isInteger(limit), 'limit must be an integer'); - Hoek.assert(!('precision' in this._flags), 'precision already set'); + Patch.assert(_.isInteger(limit), 'limit must be an integer'); + Patch.assert(!('precision' in this._flags), 'precision already set'); const obj = this._test('precision', limit, (value, state, options) => { diff --git a/lib/object.js b/lib/object.js index 0dd600e..d48d919 100755 --- a/lib/object.js +++ b/lib/object.js @@ -2,10 +2,13 @@ // Load modules -const Hoek = require('hoek'); + +const _ = require('lodash'); +const Util = require('util'); const Topo = require('topo'); const Any = require('./any'); const Cast = require('./cast'); +const Patch = require('./patch'); // Declare internals @@ -23,7 +26,7 @@ internals.Object = function () { this._inner.patterns = []; }; -Hoek.inherits(internals.Object, Any); +Util.inherits(internals.Object, Any); internals.Object.prototype._base = function (value, state, options) { @@ -79,7 +82,7 @@ internals.Object.prototype._base = function (value, state, options) { return value.apply(this, arguments); }; - target.prototype = Hoek.clone(value.prototype); + target.prototype = Patch.clone(value.prototype); } const valueKeys = Object.keys(value); @@ -143,11 +146,11 @@ internals.Object.prototype._base = function (value, state, options) { return finish(); } - const unprocessed = Hoek.mapToObject(Object.keys(target)); + const unprocessed = Patch.mapToObject(Object.keys(target)); // Children mustn't inherit the current label if it exists const childOptions = options.language && options.language.label ? - Hoek.applyToDefaults(options, { language: { label: null } }, true) : + Patch.applyToDefaults(options, { language: { label: null } }, true) : options; if (this._inner.children) { @@ -268,8 +271,8 @@ internals.Object.prototype._func = function () { internals.Object.prototype.keys = function (schema) { - Hoek.assert(schema === null || schema === undefined || typeof schema === 'object', 'Object schema must be a valid object'); - Hoek.assert(!schema || !schema.isJoi, 'Object schema cannot be a joi schema'); + Patch.assert(schema === null || schema === undefined || typeof schema === 'object', 'Object schema must be a valid object'); + Patch.assert(!schema || !schema.isJoi, 'Object schema cannot be a joi schema'); const obj = this.clone(); @@ -331,7 +334,7 @@ internals.Object.prototype.unknown = function (allow) { internals.Object.prototype.length = function (limit) { - Hoek.assert(Hoek.isInteger(limit) && limit >= 0, 'limit must be a positive integer'); + Patch.assert(_.isInteger(limit) && limit >= 0, 'limit must be a positive integer'); return this._test('length', limit, (value, state, options) => { @@ -345,7 +348,7 @@ internals.Object.prototype.length = function (limit) { internals.Object.prototype.arity = function (n) { - Hoek.assert(Hoek.isInteger(n) && n >= 0, 'n must be a positive integer'); + Patch.assert(_.isInteger(n) && n >= 0, 'n must be a positive integer'); return this._test('arity', n, (value, state, options) => { @@ -359,7 +362,7 @@ internals.Object.prototype.arity = function (n) { internals.Object.prototype.minArity = function (n) { - Hoek.assert(Hoek.isInteger(n) && n > 0, 'n must be a strict positive integer'); + Patch.assert(_.isInteger(n) && n > 0, 'n must be a strict positive integer'); return this._test('minArity', n, (value, state, options) => { @@ -373,7 +376,7 @@ internals.Object.prototype.minArity = function (n) { internals.Object.prototype.maxArity = function (n) { - Hoek.assert(Hoek.isInteger(n) && n >= 0, 'n must be a positive integer'); + Patch.assert(_.isInteger(n) && n >= 0, 'n must be a positive integer'); return this._test('maxArity', n, (value, state, options) => { @@ -388,7 +391,7 @@ internals.Object.prototype.maxArity = function (n) { internals.Object.prototype.min = function (limit) { - Hoek.assert(Hoek.isInteger(limit) && limit >= 0, 'limit must be a positive integer'); + Patch.assert(_.isInteger(limit) && limit >= 0, 'limit must be a positive integer'); return this._test('min', limit, (value, state, options) => { @@ -403,7 +406,7 @@ internals.Object.prototype.min = function (limit) { internals.Object.prototype.max = function (limit) { - Hoek.assert(Hoek.isInteger(limit) && limit >= 0, 'limit must be a positive integer'); + Patch.assert(_.isInteger(limit) && limit >= 0, 'limit must be a positive integer'); return this._test('max', limit, (value, state, options) => { @@ -418,8 +421,8 @@ internals.Object.prototype.max = function (limit) { internals.Object.prototype.pattern = function (pattern, schema) { - Hoek.assert(pattern instanceof RegExp, 'Invalid regular expression'); - Hoek.assert(schema !== undefined, 'Invalid rule'); + Patch.assert(pattern instanceof RegExp, 'Invalid regular expression'); + Patch.assert(schema !== undefined, 'Invalid rule'); pattern = new RegExp(pattern.source, pattern.ignoreCase ? 'i' : undefined); // Future version should break this and forbid unsupported regex flags @@ -455,42 +458,42 @@ internals.Object.prototype.without = function (key, peers) { internals.Object.prototype.xor = function () { - const peers = Hoek.flatten(Array.prototype.slice.call(arguments)); + const peers = _.flatten(Array.prototype.slice.call(arguments)); return this._dependency('xor', null, peers); }; internals.Object.prototype.or = function () { - const peers = Hoek.flatten(Array.prototype.slice.call(arguments)); + const peers = _.flatten(Array.prototype.slice.call(arguments)); return this._dependency('or', null, peers); }; internals.Object.prototype.and = function () { - const peers = Hoek.flatten(Array.prototype.slice.call(arguments)); + const peers = _.flatten(Array.prototype.slice.call(arguments)); return this._dependency('and', null, peers); }; internals.Object.prototype.nand = function () { - const peers = Hoek.flatten(Array.prototype.slice.call(arguments)); + const peers = _.flatten(Array.prototype.slice.call(arguments)); return this._dependency('nand', null, peers); }; internals.Object.prototype.requiredKeys = function (children) { - children = Hoek.flatten(Array.prototype.slice.call(arguments)); + children = _.flatten(Array.prototype.slice.call(arguments)); return this.applyFunctionToChildren(children, 'required'); }; internals.Object.prototype.optionalKeys = function (children) { - children = Hoek.flatten(Array.prototype.slice.call(arguments)); + children = _.flatten(Array.prototype.slice.call(arguments)); return this.applyFunctionToChildren(children, 'optional'); }; @@ -504,12 +507,12 @@ internals.renameDefaults = { internals.Object.prototype.rename = function (from, to, options) { - Hoek.assert(typeof from === 'string', 'Rename missing the from argument'); - Hoek.assert(typeof to === 'string', 'Rename missing the to argument'); - Hoek.assert(to !== from, 'Cannot rename key to same name:', from); + Patch.assert(typeof from === 'string', 'Rename missing the from argument'); + Patch.assert(typeof to === 'string', 'Rename missing the to argument'); + Patch.assert(to !== from, 'Cannot rename key to same name:', from); for (let i = 0; i < this._inner.renames.length; ++i) { - Hoek.assert(this._inner.renames[i].from !== from, 'Cannot rename the same key multiple times'); + Patch.assert(this._inner.renames[i].from !== from, 'Cannot rename the same key multiple times'); } const obj = this.clone(); @@ -517,7 +520,7 @@ internals.Object.prototype.rename = function (from, to, options) { obj._inner.renames.push({ from, to, - options: Hoek.applyToDefaults(internals.renameDefaults, options || {}) + options: Patch.applyToDefaults(internals.renameDefaults, options || {}) }); return obj; @@ -532,7 +535,7 @@ internals.groupChildren = function (children) { for (let i = 0; i < children.length; ++i) { const child = children[i]; - Hoek.assert(typeof child === 'string', 'children must be strings'); + Patch.assert(typeof child === 'string', 'children must be strings'); const group = child.split('.')[0]; const childGroup = grouped[group] = (grouped[group] || []); childGroup.push(child.substring(group.length + 1)); @@ -545,7 +548,7 @@ internals.groupChildren = function (children) { internals.Object.prototype.applyFunctionToChildren = function (children, fn, args, root) { children = [].concat(children); - Hoek.assert(children.length > 0, 'expected at least one children'); + Patch.assert(children.length > 0, 'expected at least one children'); const groupedChildren = internals.groupChildren(children); let obj; @@ -578,7 +581,7 @@ internals.Object.prototype.applyFunctionToChildren = function (children, fn, arg } const remaining = Object.keys(groupedChildren); - Hoek.assert(remaining.length === 0, 'unknown key(s)', remaining.join(', ')); + Patch.assert(remaining.length === 0, 'unknown key(s)', remaining.join(', ')); return obj; }; @@ -588,7 +591,7 @@ internals.Object.prototype._dependency = function (type, key, peers) { peers = [].concat(peers); for (let i = 0; i < peers.length; ++i) { - Hoek.assert(typeof peers[i] === 'string', type, 'peers must be a string or array of strings'); + Patch.assert(typeof peers[i] === 'string', type, 'peers must be a string or array of strings'); } const obj = this.clone(); @@ -707,7 +710,7 @@ internals.nand = function (value, peers, parent, state, options) { } } - const values = Hoek.clone(peers); + const values = Patch.clone(peers); const main = values.splice(0, 1)[0]; const allPresent = (present.length === peers.length); return allPresent ? this.createError('object.nand', { main, peers: values }, state, options) : null; @@ -721,7 +724,7 @@ internals.Object.prototype.describe = function (shallow) { if (description.rules) { for (let i = 0; i < description.rules.length; ++i) { const rule = description.rules[i]; - // Coverage off for future-proof descriptions, only object().assert() is use right now + // Coverage off for future-proof descriptions, only object().Patch.assert() is use right now if (/* $lab:coverage:off$ */rule.arg && typeof rule.arg === 'object' && rule.arg.schema && @@ -745,7 +748,7 @@ internals.Object.prototype.describe = function (shallow) { } if (this._inner.dependencies.length) { - description.dependencies = Hoek.clone(this._inner.dependencies); + description.dependencies = Patch.clone(this._inner.dependencies); } if (this._inner.patterns.length) { @@ -764,7 +767,7 @@ internals.Object.prototype.describe = function (shallow) { internals.Object.prototype.assert = function (ref, schema, message) { ref = Cast.ref(ref); - Hoek.assert(ref.isContext || ref.depth > 1, 'Cannot use assertions for root level references - use direct key rules instead'); + Patch.assert(ref.isContext || ref.depth > 1, 'Cannot use assertions for root level references - use direct key rules instead'); message = message || 'pass the assertion test'; try { @@ -788,7 +791,7 @@ internals.Object.prototype.assert = function (ref, schema, message) { return null; } - const localState = Hoek.merge({}, state); + const localState = _.merge({}, state); localState.key = key; localState.path = path; return this.createError('object.assert', { ref: localState.path, message }, localState, options); @@ -798,7 +801,7 @@ internals.Object.prototype.assert = function (ref, schema, message) { internals.Object.prototype.type = function (constructor, name) { - Hoek.assert(typeof constructor === 'function', 'type must be a constructor function'); + Patch.assert(typeof constructor === 'function', 'type must be a constructor function'); name = name || constructor.name; return this._test('type', name, (value, state, options) => { diff --git a/lib/patch.js b/lib/patch.js new file mode 100644 index 0000000..e3c5303 --- /dev/null +++ b/lib/patch.js @@ -0,0 +1,372 @@ +'use strict'; +const _ = require('lodash'); + +exports.applyToDefaults = function (defaults, options, isNullOverride) { + + exports.assert(defaults && typeof defaults === 'object', 'Invalid defaults value: must be an object'); + exports.assert(!options || options === true || typeof options === 'object', 'Invalid options value: must be true, falsy or an object'); + + if (!options) { // If no options, return null + return null; + } + + const copy = _.clone(defaults); + + if (options === true) { // If options is set to true, use defaults + return copy; + } + + return _.merge(copy, options, isNullOverride === true, false); +}; + +exports.reach = function (obj, chain, options) { + + if (chain === false || + chain === null || + typeof chain === 'undefined') { + + return obj; + } + + options = options || {}; + if (typeof options === 'string') { + options = { separator: options }; + } + + const path = chain.split(options.separator || '.'); + let ref = obj; + for (let i = 0; i < path.length; ++i) { + let key = path[i]; + if (key[0] === '-' && Array.isArray(ref)) { + key = key.slice(1, key.length); + key = ref.length - key; + } + + if (!ref || + !((typeof ref === 'object' || typeof ref === 'function') && key in ref) || + (typeof ref !== 'object' && options.functions === false)) { // Only object and function can have properties + + exports.assert(!options.strict || i + 1 === path.length, 'Missing segment', key, 'in reach path ', chain); + exports.assert(typeof ref === 'object' || options.functions === true || typeof ref !== 'function', 'Invalid segment', key, 'in reach path ', chain); + ref = options.default; + break; + } + + ref = ref[key]; + } + + return ref; +}; + +exports.mapToObject = function (array, key) { + + if (!array) { + return null; + } + + const obj = {}; + for (let i = 0; i < array.length; ++i) { + if (key) { + if (array[i][key]) { + obj[array[i][key]] = true; + } + } + else { + obj[array[i]] = true; + } + } + + return obj; +}; + +exports.deepEqual = function (obj, ref, options, seen) { + + options = options || { prototype: true }; + + const type = typeof obj; + + if (type !== typeof ref) { + return false; + } + + if (type !== 'object' || + obj === null || + ref === null) { + + if (obj === ref) { // Copied from Deep-eql, copyright(c) 2013 Jake Luer, jake@alogicalparadox.com, MIT Licensed, https://github.com/chaijs/deep-eql + return obj !== 0 || 1 / obj === 1 / ref; // -0 / +0 + } + + return obj !== obj && ref !== ref; // NaN + } + + seen = seen || []; + if (seen.indexOf(obj) !== -1) { + return true; // If previous comparison failed, it would have stopped execution + } + + seen.push(obj); + + if (Array.isArray(obj)) { + if (!Array.isArray(ref)) { + return false; + } + + if (!options.part && obj.length !== ref.length) { + return false; + } + + for (let i = 0; i < obj.length; ++i) { + if (options.part) { + let found = false; + for (let j = 0; j < ref.length; ++j) { + if (exports.deepEqual(obj[i], ref[j], options)) { + found = true; + break; + } + } + + return found; + } + + if (!exports.deepEqual(obj[i], ref[i], options)) { + return false; + } + } + + return true; + } + + if (Buffer.isBuffer(obj)) { + if (!Buffer.isBuffer(ref)) { + return false; + } + + if (obj.length !== ref.length) { + return false; + } + + for (let i = 0; i < obj.length; ++i) { + if (obj[i] !== ref[i]) { + return false; + } + } + + return true; + } + + if (obj instanceof Date) { + return (ref instanceof Date && obj.getTime() === ref.getTime()); + } + + if (obj instanceof RegExp) { + return (ref instanceof RegExp && obj.toString() === ref.toString()); + } + + if (options.prototype) { + if (Object.getPrototypeOf(obj) !== Object.getPrototypeOf(ref)) { + return false; + } + } + + const keys = Object.getOwnPropertyNames(obj); + + if (!options.part && keys.length !== Object.getOwnPropertyNames(ref).length) { + return false; + } + + for (let i = 0; i < keys.length; ++i) { + const key = keys[i]; + const descriptor = Object.getOwnPropertyDescriptor(obj, key); + if (descriptor.get) { + if (!exports.deepEqual(descriptor, Object.getOwnPropertyDescriptor(ref, key), options, seen)) { + return false; + } + } + else if (!exports.deepEqual(obj[key], ref[key], options, seen)) { + return false; + } + } + + return true; +}; + +exports.isInteger = function (value) { + + return (typeof value === 'number' && + parseFloat(value) === parseInt(value, 10) && + !isNaN(value)); +}; + +exports.clone = function (obj, seen) { + + if (typeof obj !== 'object' || + obj === null) { + + return obj; + } + + seen = seen || { orig: [], copy: [] }; + + const lookup = seen.orig.indexOf(obj); + if (lookup !== -1) { + return seen.copy[lookup]; + } + + let newObj; + let cloneDeep = false; + + if (!Array.isArray(obj)) { + if (Buffer.isBuffer(obj)) { + newObj = new Buffer(obj); + } + else if (obj instanceof Date) { + newObj = new Date(obj.getTime()); + } + else if (obj instanceof RegExp) { + newObj = new RegExp(obj); + } + else { + const proto = Object.getPrototypeOf(obj); + if (proto && + proto.isImmutable) { + + newObj = obj; + } + else { + newObj = Object.create(proto); + cloneDeep = true; + } + } + } + else { + newObj = []; + cloneDeep = true; + } + + seen.orig.push(obj); + seen.copy.push(newObj); + + if (cloneDeep) { + const keys = Object.getOwnPropertyNames(obj); + for (let i = 0; i < keys.length; ++i) { + const key = keys[i]; + const descriptor = Object.getOwnPropertyDescriptor(obj, key); + if (descriptor && + (descriptor.get || + descriptor.set)) { + + Object.defineProperty(newObj, key, descriptor); + } + else { + newObj[key] = exports.clone(obj[key], seen); + } + } + } + + return newObj; +}; + +exports.clone = function (obj, seen) { + + if (typeof obj !== 'object' || + obj === null) { + + return obj; + } + + seen = seen || { orig: [], copy: [] }; + + const lookup = seen.orig.indexOf(obj); + if (lookup !== -1) { + return seen.copy[lookup]; + } + + let newObj; + let cloneDeep = false; + + if (!Array.isArray(obj)) { + if (Buffer.isBuffer(obj)) { + newObj = new Buffer(obj); + } + else if (obj instanceof Date) { + newObj = new Date(obj.getTime()); + } + else if (obj instanceof RegExp) { + newObj = new RegExp(obj); + } + else { + const proto = Object.getPrototypeOf(obj); + if (proto && + proto.isImmutable) { + + newObj = obj; + } + else { + newObj = Object.create(proto); + cloneDeep = true; + } + } + } + else { + newObj = []; + cloneDeep = true; + } + + seen.orig.push(obj); + seen.copy.push(newObj); + + if (cloneDeep) { + const keys = Object.getOwnPropertyNames(obj); + for (let i = 0; i < keys.length; ++i) { + const key = keys[i]; + const descriptor = Object.getOwnPropertyDescriptor(obj, key); + if (descriptor && + (descriptor.get || + descriptor.set)) { + + Object.defineProperty(newObj, key, descriptor); + } + else { + newObj[key] = exports.clone(obj[key], seen); + } + } + } + + return newObj; +}; + +exports.stringify = function () { + + try { + return JSON.stringify.apply(null, arguments); + } + catch (err) { + return '[Cannot display object: ' + err.message + ']'; + } +}; + +exports.assert = function (condition /*, msg1, msg2, msg3 */) { + + if (condition) { + return; + } + + if (arguments.length === 2 && arguments[1] instanceof Error) { + throw arguments[1]; + } + + let msgs = []; + for (let i = 1; i < arguments.length; ++i) { + if (arguments[i] !== '') { + msgs.push(arguments[i]); // Avoids Array.slice arguments leak, allowing for V8 optimizations + } + } + + msgs = msgs.map((msg) => { + + return typeof msg === 'string' ? msg : msg instanceof Error ? msg.message : exports.stringify(msg); + }); + + throw new Error(msgs.join(' ') || 'Unknown error'); +}; \ No newline at end of file diff --git a/lib/ref.js b/lib/ref.js index f0bcd87..3f34f65 100755 --- a/lib/ref.js +++ b/lib/ref.js @@ -2,7 +2,10 @@ // Load modules -const Hoek = require('hoek'); + +const _ = require('lodash'); +const Util = require('util'); +const Patch = require('./patch'); // Declare internals @@ -12,13 +15,13 @@ const internals = {}; exports.create = function (key, options) { - Hoek.assert(typeof key === 'string', 'Invalid reference key:', key); + Patch.assert(typeof key === 'string', 'Invalid reference key:', key); - const settings = Hoek.clone(options); // options can be reused and modified + const settings = Patch.clone(options); // options can be reused and modified const ref = function (value, validationOptions) { - return Hoek.reach(ref.isContext ? validationOptions.context : value, ref.key, settings); + return Patch.reach(ref.isContext ? validationOptions.context : value, ref.key, settings); }; ref.isContext = (key[0] === ((settings && settings.contextPrefix) || '$')); diff --git a/lib/string.js b/lib/string.js index 7baa9ea..0923ab5 100755 --- a/lib/string.js +++ b/lib/string.js @@ -3,7 +3,9 @@ // Load modules const Net = require('net'); -const Hoek = require('hoek'); +const _ = require('lodash'); +const Util = require('util'); +const Patch = require('./patch'); const Isemail = require('isemail'); const Any = require('./any'); const Ref = require('./ref'); @@ -25,7 +27,7 @@ internals.String = function () { this._invalids.add(''); }; -Hoek.inherits(internals.String, Any); +Util.inherits(internals.String, Any); internals.compare = function (type, compare) { @@ -33,8 +35,8 @@ internals.compare = function (type, compare) { const isRef = Ref.isRef(limit); - Hoek.assert((Hoek.isInteger(limit) && limit >= 0) || isRef, 'limit must be a positive integer or reference'); - Hoek.assert(!encoding || Buffer.isEncoding(encoding), 'Invalid encoding:', encoding); + Patch.assert((Patch.isInteger(limit) && limit >= 0) || isRef, 'limit must be a positive integer or reference'); + Patch.assert(!encoding || Buffer.isEncoding(encoding), 'Invalid encoding:', encoding); return this._test(type, limit, (value, state, options) => { @@ -42,7 +44,7 @@ internals.compare = function (type, compare) { if (isRef) { compareTo = limit(state.parent, options); - if (!Hoek.isInteger(compareTo)) { + if (!Patch.isInteger(compareTo)) { return this.createError('string.ref', { ref: limit.key }, state, options); } } @@ -138,7 +140,7 @@ internals.String.prototype.length = internals.compare('length', (value, limit, e internals.String.prototype.regex = function (pattern, name) { - Hoek.assert(pattern instanceof RegExp, 'pattern must be a RegExp'); + Patch.assert(pattern instanceof RegExp, 'pattern must be a RegExp'); pattern = new RegExp(pattern.source, pattern.ignoreCase ? 'i' : undefined); // Future version should break this and forbid unsupported regex flags @@ -182,15 +184,15 @@ internals.String.prototype.token = function () { internals.String.prototype.email = function (isEmailOptions) { if (isEmailOptions) { - Hoek.assert(typeof isEmailOptions === 'object', 'email options must be an object'); - Hoek.assert(typeof isEmailOptions.checkDNS === 'undefined', 'checkDNS option is not supported'); - Hoek.assert(typeof isEmailOptions.tldWhitelist === 'undefined' || + Patch.assert(typeof isEmailOptions === 'object', 'email options must be an object'); + Patch.assert(typeof isEmailOptions.checkDNS === 'undefined', 'checkDNS option is not supported'); + Patch.assert(typeof isEmailOptions.tldWhitelist === 'undefined' || typeof isEmailOptions.tldWhitelist === 'object', 'tldWhitelist must be an array or object'); - Hoek.assert(typeof isEmailOptions.minDomainAtoms === 'undefined' || - Hoek.isInteger(isEmailOptions.minDomainAtoms) && isEmailOptions.minDomainAtoms > 0, + Patch.assert(typeof isEmailOptions.minDomainAtoms === 'undefined' || + _.isInteger(isEmailOptions.minDomainAtoms) && isEmailOptions.minDomainAtoms > 0, 'minDomainAtoms must be a positive integer'); - Hoek.assert(typeof isEmailOptions.errorLevel === 'undefined' || typeof isEmailOptions.errorLevel === 'boolean' || - (Hoek.isInteger(isEmailOptions.errorLevel) && isEmailOptions.errorLevel >= 0), + Patch.assert(typeof isEmailOptions.errorLevel === 'undefined' || typeof isEmailOptions.errorLevel === 'boolean' || + (_.isInteger(isEmailOptions.errorLevel) && isEmailOptions.errorLevel >= 0), 'errorLevel must be a non-negative integer or boolean'); } @@ -213,13 +215,13 @@ internals.String.prototype.ip = function (ipOptions) { let regex = internals.ipRegex; ipOptions = ipOptions || {}; - Hoek.assert(typeof ipOptions === 'object', 'options must be an object'); + Patch.assert(typeof ipOptions === 'object', 'options must be an object'); if (ipOptions.cidr) { - Hoek.assert(typeof ipOptions.cidr === 'string', 'cidr must be a string'); + Patch.assert(typeof ipOptions.cidr === 'string', 'cidr must be a string'); ipOptions.cidr = ipOptions.cidr.toLowerCase(); - Hoek.assert(ipOptions.cidr in Ip.cidrs, 'cidr must be one of ' + Object.keys(Ip.cidrs).join(', ')); + Patch.assert(ipOptions.cidr in Ip.cidrs, 'cidr must be one of ' + Object.keys(Ip.cidrs).join(', ')); // If we only received a `cidr` setting, create a regex for it. But we don't need to create one if `cidr` is "optional" since that is the default if (!ipOptions.version && ipOptions.cidr !== 'optional') { @@ -238,19 +240,19 @@ internals.String.prototype.ip = function (ipOptions) { ipOptions.version = [ipOptions.version]; } - Hoek.assert(ipOptions.version.length >= 1, 'version must have at least 1 version specified'); + Patch.assert(ipOptions.version.length >= 1, 'version must have at least 1 version specified'); versions = []; for (let i = 0; i < ipOptions.version.length; ++i) { let version = ipOptions.version[i]; - Hoek.assert(typeof version === 'string', 'version at position ' + i + ' must be a string'); + Patch.assert(typeof version === 'string', 'version at position ' + i + ' must be a string'); version = version.toLowerCase(); - Hoek.assert(Ip.versions[version], 'version at position ' + i + ' must be one of ' + Object.keys(Ip.versions).join(', ')); + Patch.assert(Ip.versions[version], 'version at position ' + i + ' must be one of ' + Object.keys(Ip.versions).join(', ')); versions.push(version); } // Make sure we have a set of versions - versions = Hoek.unique(versions); + versions = _.uniq(versions); regex = Ip.createIpRegex(versions, ipOptions.cidr); } @@ -277,21 +279,21 @@ internals.String.prototype.uri = function (uriOptions) { let regex = internals.uriRegex; if (uriOptions) { - Hoek.assert(typeof uriOptions === 'object', 'options must be an object'); + Patch.assert(typeof uriOptions === 'object', 'options must be an object'); if (uriOptions.scheme) { - Hoek.assert(uriOptions.scheme instanceof RegExp || typeof uriOptions.scheme === 'string' || Array.isArray(uriOptions.scheme), 'scheme must be a RegExp, String, or Array'); + Patch.assert(uriOptions.scheme instanceof RegExp || typeof uriOptions.scheme === 'string' || Array.isArray(uriOptions.scheme), 'scheme must be a RegExp, String, or Array'); if (!Array.isArray(uriOptions.scheme)) { uriOptions.scheme = [uriOptions.scheme]; } - Hoek.assert(uriOptions.scheme.length >= 1, 'scheme must have at least 1 scheme specified'); + Patch.assert(uriOptions.scheme.length >= 1, 'scheme must have at least 1 scheme specified'); // Flatten the array into a string to be used to match the schemes. for (let i = 0; i < uriOptions.scheme.length; ++i) { const scheme = uriOptions.scheme[i]; - Hoek.assert(scheme instanceof RegExp || typeof scheme === 'string', 'scheme at position ' + i + ' must be a RegExp or String'); + Patch.assert(scheme instanceof RegExp || typeof scheme === 'string', 'scheme at position ' + i + ' must be a RegExp or String'); // Add OR separators if a value already exists customScheme = customScheme + (customScheme ? '|' : ''); @@ -301,8 +303,8 @@ internals.String.prototype.uri = function (uriOptions) { customScheme = customScheme + scheme.source; } else { - Hoek.assert(/[a-zA-Z][a-zA-Z0-9+-\.]*/.test(scheme), 'scheme at position ' + i + ' must be a valid scheme'); - customScheme = customScheme + Hoek.escapeRegex(scheme); + Patch.assert(/[a-zA-Z][a-zA-Z0-9+-\.]*/.test(scheme), 'scheme at position ' + i + ' must be a valid scheme'); + customScheme = customScheme + _.escapeRegExp(scheme); } } } @@ -449,11 +451,11 @@ internals.String.prototype.trim = function () { internals.String.prototype.replace = function (pattern, replacement) { if (typeof pattern === 'string') { - pattern = new RegExp(Hoek.escapeRegex(pattern), 'g'); + pattern = new RegExp(_.escapeRegExp(pattern), 'g'); } - Hoek.assert(pattern instanceof RegExp, 'pattern must be a RegExp'); - Hoek.assert(typeof replacement === 'string', 'replacement must be a String'); + Patch.assert(pattern instanceof RegExp, 'pattern must be a RegExp'); + Patch.assert(typeof replacement === 'string', 'replacement must be a String'); // This can not be considere a test like trim, we can't "reject" // anything from this rule, so just clone the current object diff --git a/package.json b/package.json index 943b74d..039558c 100644 --- a/package.json +++ b/package.json @@ -1,23 +1,26 @@ { - "name": "joi", + "name": "react-native-joi", "description": "Object schema validation", - "version": "8.0.5", - "homepage": "https://github.com/hapijs/joi", - "repository": "git://github.com/hapijs/joi", + "version": "0.0.1", + "homepage": "https://github.com/thethanghn/react-native-joi.git", + "repository": "git@github.com:thethanghn/react-native-joi.git", "main": "lib/index.js", "keywords": [ "hapi", "schema", - "validation" + "validation", + "react native" ], "engines": { "node": ">=4.0.0" }, "dependencies": { - "hoek": "3.x.x", - "topo": "2.x.x", + "chai": "^3.5.0", "isemail": "2.x.x", - "moment": "2.x.x" + "lodash": "^4.11.1", + "moment": "2.x.x", + "topo": "2.x.x", + "util": "^0.10.3" }, "devDependencies": { "code": "2.x.x", diff --git a/test/helper.js b/test/helper.js index 0d3c62d..5775410 100755 --- a/test/helper.js +++ b/test/helper.js @@ -2,7 +2,7 @@ // Load modules -const Code = require('code'); +const chai = require('chai'); const Joi = require('../'); @@ -13,7 +13,7 @@ const internals = {}; // Test shortcuts -const expect = Code.expect; +const expect = chai.expect; exports.validate = function (schema, config, callback) {