Skip to content

Commit

Permalink
transform coerce method to be call only once (in normalizeSchema)
Browse files Browse the repository at this point in the history
  • Loading branch information
A-312 committed Dec 31, 2019
1 parent 6c589db commit baf738a
Showing 1 changed file with 65 additions and 43 deletions.
108 changes: 65 additions & 43 deletions lib/convict.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
Expand All @@ -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) {
Expand Down Expand Up @@ -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));

}
});
}
Expand All @@ -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);
Expand All @@ -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) {
Expand Down Expand Up @@ -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;
},

Expand Down

0 comments on commit baf738a

Please sign in to comment.