Skip to content

Commit

Permalink
First pass at transform functions (#279)
Browse files Browse the repository at this point in the history
  • Loading branch information
mhamann committed Oct 26, 2017
1 parent b9c345b commit 856fdf8
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 5 deletions.
78 changes: 73 additions & 5 deletions README.md
Expand Up @@ -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`.
Expand All @@ -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
//
Expand Down Expand Up @@ -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');
```
Expand Down
28 changes: 28 additions & 0 deletions lib/nconf/common.js
Expand Up @@ -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;
}, {});
}
5 changes: 5 additions & 0 deletions lib/nconf/stores/argv.js
Expand Up @@ -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
Expand Down Expand Up @@ -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];
Expand Down
5 changes: 5 additions & 0 deletions lib/nconf/stores/env.js
Expand Up @@ -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') {
Expand Down Expand Up @@ -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) {
Expand Down
56 changes: 56 additions & 0 deletions test/complete-test.js
Expand Up @@ -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);

0 comments on commit 856fdf8

Please sign in to comment.