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
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
//
// 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',
describe: 'Example description for usage generation',
demand: true,
default: 'some-value'
default: 'some-value',
parseValues: true,
transform: function(obj) {
if (obj.key === 'foo') {
obj.value = 'baz';
}
return obj;
}
}
});
```
### Env
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.
To ensure well-known strings ('false', 'true', 'null', 'undefined', '3', '5.1') and JSON values
are properly parsed, the `parseValues` boolean option is available.
By default, the env variables values are loaded into the configuration as strings.
#### 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
//
@ -247,7 +309,13 @@ are properly parsed, the `parseValues` boolean option is available.
match: /^whatever_matches_this_will_be_whitelisted/
whitelist: ['database__host', 'only', 'load', 'these', 'values', 'if', 'whatever_doesnt_match_but_is_whitelisted_gets_loaded_too'],
lowerCase: true,
parseValues: true
parseValues: true,
transform: function(obj) {
if (obj.key === 'foo') {
obj.value = 'baz';
}
return obj;
}
});
var dbHost = nconf.get('database:host');
```

View file

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

View file

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

View file

@ -184,5 +184,61 @@ vows.describe('nconf/multiple-stores').addBatch({
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 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);