From baf738a2a87c04d082d6f1f840c5f591ae5d9bb2 Mon Sep 17 00:00:00 2001 From: A-312 Date: Mon, 9 Dec 2019 02:21:43 +0100 Subject: [PATCH] transform coerce method to be call only once (in normalizeSchema) --- lib/convict.js | 108 +++++++++++++++++++++++++++++-------------------- 1 file changed, 65 insertions(+), 43 deletions(-) diff --git a/lib/convict.js b/lib/convict.js index cf6bfd1b..74e0e96f 100644 --- a/lib/convict.js +++ b/lib/convict.js @@ -192,7 +192,7 @@ function validate(instance, schema, strictValidation) { if (!(typeof schemaItem.default === 'undefined' && instanceItem === schemaItem.default)) { try { - schemaItem._format(instanceItem); + schemaItem._cvtFormat(instanceItem); } catch (err) { errors.invalid_type.push(err); } @@ -295,12 +295,11 @@ function normalizeSchema(name, rawSchema, props, fullName, env, argv, sensitive) return types[format]; } else if (Array.isArray(format)) { // assert that the value is a valid option - return contains.bind(null, format); + return contains.bind(null, format); // TODO : Improve & test this ? } else if (typeof format === 'function') { return format; } else if (format && typeof format !== 'function') { - throw new Error("'" + fullName + - "': `format` must be a function or a known format type."); + throw new Error("'" + fullName + "': `format` must be a function or a known format type."); } else { // !format // default format is the typeof the default value const defaultFormat = Object.prototype.toString.call(schema.default); @@ -314,7 +313,15 @@ function normalizeSchema(name, rawSchema, props, fullName, env, argv, sensitive) } })(); - schema._format = function(x) { + schema._cvtCoerce = (() => { + if (typeof format === 'string') { + return getCoerceMethod(schema.format); + } else { + return (v) => v; + } + })(); + + schema._cvtFormat = function(x) { try { newFormat(x, schema); // schema = this } catch (e) { @@ -359,7 +366,8 @@ function addDefaultValues(schema, node) { node[name] = {}; // node[name] is always undefined because addDefaultValues is the first to run. addDefaultValues(mySchema, node[name]); } else { - node[name] = coerce(mySchema, cloneDeep(mySchema.default)); + node[name] = mySchema._cvtCoerce(cloneDeep(mySchema.default)); + } }); } @@ -371,7 +379,7 @@ function overlay(from, to, schema) { const mySchema = (schema && schema.properties) ? schema.properties[name] : null; // leaf if (Array.isArray(from[name]) || !isObj(from[name]) || !schema || schema.format === 'object') { - to[name] = coerce(mySchema, from[name]); + to[name] = mySchema._cvtCoerce(from[name]); } else { if (!isObj(to[name])) to[name] = {}; overlay(from[name], to[name], mySchema); @@ -395,44 +403,57 @@ function traverseSchema(schema, path) { return o; } -function coerce(schema, v) { - const format = (schema && typeof schema.format === 'string') ? schema.format : null; +function isStr(value) { + return (typeof value === 'string'); +} - if (typeof v === 'string') { - if (converters.has(format)) { - return converters.get(format)(v); - } - switch (format) { +function getCoerceMethod(format) { + if (converters.has(format)) { + return converters.get(format); + } + switch (format) { case 'port': case 'nat': case 'integer': - case 'int': return parseInt(v, 10); - case 'port_or_windows_named_pipe': return isWindowsNamedPipe(v) ? v : parseInt(v, 10); - case 'number': return parseFloat(v); - case 'boolean': return (String(v).toLowerCase() !== 'false'); - case 'array': return v.split(','); - case 'object': return JSON.parse(v); - case 'regexp': return new RegExp(v); - case 'timestamp': return moment(v).valueOf(); - case 'duration': { - let split = v.split(' '); - if (split.length == 1) { - // It must be an integer in string form. - return parseInt(v, 10); - } else { - // Add an "s" as the unit of measurement used in Moment - if (!split[1].match(/s$/)) split[1] += 's'; - return moment.duration(parseInt(split[0], 10), split[1]).valueOf(); + case 'int': + return (v) => (isStr(v)) ? parseInt(v, 10) : v; + case 'port_or_windows_named_pipe': + return (v) => (isWindowsNamedPipe(v)) ? v : parseInt(v, 10); + case 'number': + return (v) => (isStr(v)) ? parseFloat(v) : v; + case 'boolean': + return (v) => (isStr(v)) ? (String(v).toLowerCase() !== 'false') : v; + case 'array': + return (v) => (isStr(v)) ? v.split(',') : v; + case 'object': + return (v) => (isStr(v)) ? JSON.parse(v) : v; + case 'regexp': + return (v) => (isStr(v)) ? new RegExp(v) : v; + case 'timestamp': + return (v) => (isStr(v)) ? moment(v).valueOf() : v; + case 'duration': + return (v) => { + if (isStr(v)) { + let split = v.split(' '); + if (split.length == 1) { + // It must be an integer in string form. + return parseInt(v, 10); + } else { + // Add an "s" as the unit of measurement used in Moment + if (!split[1].match(/s$/)) split[1] += 's'; + return moment.duration(parseInt(split[0], 10), split[1]).valueOf(); + } + } else { + return v; + } } - } default: // ? - } - - // TODO: Should we throw an exception here? } - return v; + // TODO: Should we throw an exception here? + + return (v) => v; } function loadFile(path) { @@ -572,13 +593,14 @@ let convict = function convict(def, opts) { * nested values, e.g. "database.port". If objects in the chain don't yet * exist, they will be initialized to empty objects */ - set: function(k, v) { - v = coerce(traverseSchema(this._schema, k), v); - let path = k.split('.'); - let childKey = path.pop(); - let parentKey = path.join('.'); - let parent = walk(this._instance, parentKey, true); - parent[childKey] = v; + set: function(fullpath, value) { + const mySchema = traverseSchema(this._schema, fullpath); + value = mySchema._cvtCoerce(value); + const path = fullpath.split('.'); + const childKey = path.pop(); + const parentKey = path.join('.'); + const parent = walk(this._instance, parentKey, true); + parent[childKey] = value; return this; },