First pass at transform functions (#279)

This commit is contained in:
Matt Hamann 2017-10-25 22:57:58 -04:00 committed by GitHub
parent b9c345bf96
commit 856fdf8dff
5 changed files with 167 additions and 5 deletions

View file

@ -193,6 +193,32 @@ A simple in-memory storage engine that stores a nested JSON representation of th
### Argv ### Argv
Responsible for loading the values parsed from `process.argv` by `yargs` into the configuration hierarchy. See the [yargs option docs](https://github.com/bcoe/yargs#optionskey-opt) for more on the option format. Responsible for loading the values parsed from `process.argv` by `yargs` into the configuration hierarchy. See the [yargs option docs](https://github.com/bcoe/yargs#optionskey-opt) for more on the option format.
#### Options
##### `parseValues: {true|false}` (default: `false`)
Attempt to parse well-known values (e.g. 'false', 'true', 'null', 'undefined', '3', '5.1' and JSON values)
into their proper types. If a value cannot be parsed, it will remain a string.
##### `transform: function(obj)`
Pass each key/value pair to the specified function for transformation.
The input `obj` contains two properties passed in the following format:
```
{
key: '<string>',
value: '<string>'
}
```
The transformation function may alter both the key and the value.
The function may return either an object in the asme format as the input or a value that evaluates to false.
If the return value is falsey, the entry will be dropped from the store, otherwise it will replace the original key/value.
*Note: If the return value doesn't adhere to the above rules, an exception will be thrown.*
#### Examples
``` js ``` js
// //
// Can optionally also be an object literal to pass to `yargs`. // Can optionally also be an object literal to pass to `yargs`.
@ -202,16 +228,52 @@ Responsible for loading the values parsed from `process.argv` by `yargs` into th
alias: 'example', alias: 'example',
describe: 'Example description for usage generation', describe: 'Example description for usage generation',
demand: true, demand: true,
default: 'some-value' default: 'some-value',
parseValues: true,
transform: function(obj) {
if (obj.key === 'foo') {
obj.value = 'baz';
}
return obj;
}
} }
}); });
``` ```
### Env ### Env
Responsible for loading the values parsed from `process.env` into the configuration hierarchy. Responsible for loading the values parsed from `process.env` into the configuration hierarchy.
Usually the env variables values are loaded into the configuration as strings. By default, the env variables values are loaded into the configuration as strings.
To ensure well-known strings ('false', 'true', 'null', 'undefined', '3', '5.1') and JSON values
are properly parsed, the `parseValues` boolean option is available. #### Options
##### `lowerCase: {true|false}` (default: `false`)
Convert all input keys to lower case. Values are not modified.
If this option is enabled, all calls to `nconf.get()` must pass in a lowercase string (e.g. `nconf.get('port')`)
##### `parseValues: {true|false}` (default: `false`)
Attempt to parse well-known values (e.g. 'false', 'true', 'null', 'undefined', '3', '5.1' and JSON values)
into their proper types. If a value cannot be parsed, it will remain a string.
##### `transform: function(obj)`
Pass each key/value pair to the specified function for transformation.
The input `obj` contains two properties passed in the following format:
```
{
key: '<string>',
value: '<string>'
}
```
The transformation function may alter both the key and the value.
The function may return either an object in the asme format as the input or a value that evaluates to false.
If the return value is falsey, the entry will be dropped from the store, otherwise it will replace the original key/value.
*Note: If the return value doesn't adhere to the above rules, an exception will be thrown.*
#### Examples
``` js ``` js
// //
@ -247,7 +309,13 @@ are properly parsed, the `parseValues` boolean option is available.
match: /^whatever_matches_this_will_be_whitelisted/ match: /^whatever_matches_this_will_be_whitelisted/
whitelist: ['database__host', 'only', 'load', 'these', 'values', 'if', 'whatever_doesnt_match_but_is_whitelisted_gets_loaded_too'], whitelist: ['database__host', 'only', 'load', 'these', 'values', 'if', 'whatever_doesnt_match_but_is_whitelisted_gets_loaded_too'],
lowerCase: true, lowerCase: true,
parseValues: true parseValues: true,
transform: function(obj) {
if (obj.key === 'foo') {
obj.value = 'baz';
}
return obj;
}
}); });
var dbHost = nconf.get('database:host'); var dbHost = nconf.get('database:host');
``` ```

View file

@ -141,3 +141,31 @@ common.parseValues = function (value) {
return val; return val;
}; };
//
// ### function transform(map, fn)
// #### @map {object} Object of key/value pairs to apply `fn` to
// #### @fn {function} Transformation function that will be applied to every key/value pair
// transform a set of key/value pairs and return the transformed result
common.transform = function(map, fn) {
var pairs = Object.keys(map).map(function(key) {
var obj = { key: key, value: map[key]};
var result = fn.call(null, obj);
if (!result) {
return null;
} else if (result.key && typeof result.value !== 'undefined') {
return result;
}
var error = new Error('Transform function passed to store returned an invalid format: ' + JSON.stringify(result));
error.name = 'RuntimeError';
throw error;
});
return pairs.reduce(function(accumulator, pair) {
accumulator[pair.key] = pair.value;
return accumulator;
}, {});
}

View file

@ -24,6 +24,7 @@ var Argv = exports.Argv = function (options, usage) {
this.options = options; this.options = options;
this.usage = usage; this.usage = usage;
this.parseValues = options.parseValues || false; this.parseValues = options.parseValues || false;
this.transform = options.transform || false;
}; };
// Inherit from the Memory store // Inherit from the Memory store
@ -59,6 +60,10 @@ Argv.prototype.loadArgv = function () {
return; return;
} }
if (this.transform) {
argv = common.transform(argv, this.transform);
}
this.readOnly = false; this.readOnly = false;
Object.keys(argv).forEach(function (key) { Object.keys(argv).forEach(function (key) {
var val = argv[key]; var val = argv[key];

View file

@ -25,6 +25,7 @@ var Env = exports.Env = function (options) {
this.separator = options.separator || ''; this.separator = options.separator || '';
this.lowerCase = options.lowerCase || false; this.lowerCase = options.lowerCase || false;
this.parseValues = options.parseValues || false; this.parseValues = options.parseValues || false;
this.transform = options.transform || false;
if (({}).toString.call(options.match) === '[object RegExp]' if (({}).toString.call(options.match) === '[object RegExp]'
&& typeof options !== 'string') { && typeof options !== 'string') {
@ -67,6 +68,10 @@ Env.prototype.loadEnv = function () {
}); });
} }
if (this.transform) {
env = common.transform(env, this.transform);
}
this.readOnly = false; this.readOnly = false;
Object.keys(env).filter(function (key) { Object.keys(env).filter(function (key) {
if (self.match && self.whitelist.length) { if (self.match && self.whitelist.length) {

View file

@ -184,5 +184,61 @@ vows.describe('nconf/multiple-stores').addBatch({
teardown: function () { teardown: function () {
nconf.remove('env'); nconf.remove('env');
} }
},
}).addBatch({
// Threw this in it's own batch to make sure it's run separately from the
// sync check
"When using env with transform:fn": {
topic: function () {
function testTransform(obj) {
if (obj.key === 'FOO') {
obj.key = 'FOOD';
obj.value = 'BARFOO';
}
return obj;
}
var that = this;
helpers.cp(complete, completeTest, function () {
nconf.env({ transform: testTransform })
that.callback();
});
}, "env vars": {
"port key/value properly transformed": function() {
assert.equal(nconf.get('FOOD'), 'BARFOO');
}
}
},
teardown: function () {
nconf.remove('env');
}
}).addBatch({
// Threw this in it's own batch to make sure it's run separately from the
// sync check
"When using env with a bad transform:fn": {
topic: function () {
function testTransform() {
return {foo: 'bar'};
}
var that = this;
helpers.cp(complete, completeTest, function () {
try {
nconf.env({ transform: testTransform });
that.callback();
} catch (err) {
that.callback(null, err);
}
});
}, "env vars": {
"port key/value throws transformation error": function(err) {
assert.equal(err.name, 'RuntimeError');
}
}
},
teardown: function () {
nconf.remove('env');
} }
}).export(module); }).export(module);