Merge pull request #54 from strongloop/feature/support-nested-values-in-config-overrides
Support nested values in config overrides
This commit is contained in:
commit
38c4944e2e
|
@ -10,6 +10,7 @@
|
|||
*.pid
|
||||
*.swp
|
||||
*.swo
|
||||
*.iml
|
||||
node_modules
|
||||
checkstyle.xml
|
||||
loopback-boot-*.tgz
|
||||
|
|
|
@ -112,7 +112,7 @@ function mergeConfigurations(configObjects, mergeFn) {
|
|||
|
||||
function mergeDataSourceConfig(target, config, fileName) {
|
||||
for (var ds in target) {
|
||||
var err = applyCustomConfig(target[ds], config[ds]);
|
||||
var err = mergeObjects(target[ds], config[ds]);
|
||||
if (err) {
|
||||
throw new Error('Cannot apply ' + fileName + ' to `' + ds + '`: ' + err);
|
||||
}
|
||||
|
@ -120,23 +120,73 @@ function mergeDataSourceConfig(target, config, fileName) {
|
|||
}
|
||||
|
||||
function mergeAppConfig(target, config, fileName) {
|
||||
var err = applyCustomConfig(target, config);
|
||||
var err = mergeObjects(target, config);
|
||||
if (err) {
|
||||
throw new Error('Cannot apply ' + fileName + ': ' + err);
|
||||
}
|
||||
}
|
||||
|
||||
function applyCustomConfig(target, config) {
|
||||
function mergeObjects(target, config, keyPrefix) {
|
||||
for (var key in config) {
|
||||
var value = config[key];
|
||||
if (typeof value === 'object') {
|
||||
return 'override for the option `' + key + '` is not a value type.';
|
||||
}
|
||||
target[key] = value;
|
||||
var fullKey = keyPrefix ? keyPrefix + '.' + key : key;
|
||||
var err = mergeSingleItemOrProperty(target, config, key, fullKey);
|
||||
if (err) return err;
|
||||
}
|
||||
return null; // no error
|
||||
}
|
||||
|
||||
function mergeSingleItemOrProperty(target, config, key, fullKey) {
|
||||
var origValue = target[key];
|
||||
var newValue = config[key];
|
||||
|
||||
if (!hasCompatibleType(origValue, newValue)) {
|
||||
return 'Cannot merge values of incompatible types for the option `' +
|
||||
fullKey + '`.';
|
||||
}
|
||||
|
||||
if (Array.isArray(origValue)) {
|
||||
return mergeArrays(origValue, newValue, fullKey);
|
||||
}
|
||||
|
||||
if (typeof origValue === 'object') {
|
||||
return mergeObjects(origValue, newValue, fullKey);
|
||||
}
|
||||
|
||||
target[key] = newValue;
|
||||
return null; // no error
|
||||
}
|
||||
|
||||
function mergeArrays(target, config, keyPrefix) {
|
||||
if (target.length !== config.length) {
|
||||
return 'Cannot merge array values of different length' +
|
||||
' for the option `' + keyPrefix + '`.';
|
||||
}
|
||||
|
||||
// Use for(;;) to iterate over undefined items, for(in) would skip them.
|
||||
for (var ix=0; ix < target.length; ix++) {
|
||||
var fullKey = keyPrefix + '[' + ix + ']';
|
||||
var err = mergeSingleItemOrProperty(target, config, ix, fullKey);
|
||||
if (err) return err;
|
||||
}
|
||||
|
||||
return null; // no error
|
||||
}
|
||||
|
||||
function hasCompatibleType(origValue, newValue) {
|
||||
if (origValue === null || origValue === undefined)
|
||||
return true;
|
||||
|
||||
if (Array.isArray(origValue))
|
||||
return Array.isArray(newValue);
|
||||
|
||||
if (typeof origValue === 'object')
|
||||
return typeof newValue === 'object';
|
||||
|
||||
// Note: typeof Array() is 'object' too,
|
||||
// we don't need to explicitly check array types
|
||||
return typeof newValue !== 'object';
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to read a config file with .json extension
|
||||
* @param cwd Dirname of the file
|
||||
|
|
|
@ -125,24 +125,168 @@ describe('compiler', function() {
|
|||
expect(db).to.have.property('fromJs', true);
|
||||
});
|
||||
|
||||
it('refuses to merge Object properties', function() {
|
||||
it('merges new Object values', function() {
|
||||
var objectValue = { key: 'value' };
|
||||
appdir.createConfigFilesSync();
|
||||
appdir.writeConfigFileSync('datasources.local.json', {
|
||||
db: { nested: { key: 'value' } }
|
||||
db: { nested: objectValue }
|
||||
});
|
||||
|
||||
expect(function() { boot.compile(appdir.PATH); })
|
||||
.to.throw(/`nested` is not a value type/);
|
||||
var instructions = boot.compile(appdir.PATH);
|
||||
|
||||
var db = instructions.dataSources.db;
|
||||
expect(db).to.have.property('nested');
|
||||
expect(db.nested).to.eql(objectValue);
|
||||
});
|
||||
|
||||
it('refuses to merge Array properties', function() {
|
||||
it('deeply merges Object values', function() {
|
||||
appdir.createConfigFilesSync({}, {
|
||||
email: {
|
||||
transport: {
|
||||
host: 'localhost'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
appdir.writeConfigFileSync('datasources.local.json', {
|
||||
email: {
|
||||
transport: {
|
||||
host: 'mail.example.com'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var instructions = boot.compile(appdir.PATH);
|
||||
var email = instructions.dataSources.email;
|
||||
expect(email.transport.host).to.equal('mail.example.com');
|
||||
});
|
||||
|
||||
it('deeply merges Array values of the same length', function() {
|
||||
appdir.createConfigFilesSync({}, {
|
||||
rest: {
|
||||
operations: [
|
||||
{
|
||||
template: {
|
||||
method: 'POST',
|
||||
url: 'http://localhost:12345'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
});
|
||||
appdir.writeConfigFileSync('datasources.local.json', {
|
||||
rest: {
|
||||
operations: [
|
||||
{
|
||||
template: {
|
||||
url: 'http://api.example.com'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
var instructions = boot.compile(appdir.PATH);
|
||||
|
||||
var rest = instructions.dataSources.rest;
|
||||
expect(rest.operations[0].template).to.eql({
|
||||
method: 'POST', // the value from datasources.json
|
||||
url: 'http://api.example.com' // overriden in datasources.local.json
|
||||
});
|
||||
});
|
||||
|
||||
it('merges Array properties', function() {
|
||||
var arrayValue = ['value'];
|
||||
appdir.createConfigFilesSync();
|
||||
appdir.writeConfigFileSync('datasources.local.json', {
|
||||
db: { nested: ['value'] }
|
||||
db: { nested: arrayValue }
|
||||
});
|
||||
|
||||
var instructions = boot.compile(appdir.PATH);
|
||||
|
||||
var db = instructions.dataSources.db;
|
||||
expect(db).to.have.property('nested');
|
||||
expect(db.nested).to.eql(arrayValue);
|
||||
});
|
||||
|
||||
it('refuses to merge Array properties of different length', function() {
|
||||
appdir.createConfigFilesSync({
|
||||
nest: {
|
||||
array: []
|
||||
}
|
||||
});
|
||||
|
||||
appdir.writeConfigFileSync('config.local.json', {
|
||||
nest: {
|
||||
array: [
|
||||
{
|
||||
key: 'value'
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
expect(function() { boot.compile(appdir.PATH); })
|
||||
.to.throw(/`nested` is not a value type/);
|
||||
.to.throw(/array values of different length.*nest\.array/);
|
||||
});
|
||||
|
||||
it('refuses to merge Array of different length in Array', function() {
|
||||
appdir.createConfigFilesSync({
|
||||
key: [[]]
|
||||
});
|
||||
|
||||
appdir.writeConfigFileSync('config.local.json', {
|
||||
key: [['value']]
|
||||
});
|
||||
|
||||
expect(function() { boot.compile(appdir.PATH); })
|
||||
.to.throw(/array values of different length.*key\[0\]/);
|
||||
});
|
||||
|
||||
it('returns full key of an incorrect Array value', function() {
|
||||
appdir.createConfigFilesSync({
|
||||
toplevel: [
|
||||
{
|
||||
nested: []
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
appdir.writeConfigFileSync('config.local.json', {
|
||||
toplevel: [
|
||||
{
|
||||
nested: [ 'value' ]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
expect(function() { boot.compile(appdir.PATH); })
|
||||
.to.throw(/array values of different length.*toplevel\[0\]\.nested/);
|
||||
});
|
||||
|
||||
it('refuses to merge incompatible object properties', function() {
|
||||
appdir.createConfigFilesSync({
|
||||
key: []
|
||||
});
|
||||
appdir.writeConfigFileSync('config.local.json', {
|
||||
key: {}
|
||||
});
|
||||
|
||||
expect(function() { boot.compile(appdir.PATH); })
|
||||
.to.throw(/incompatible types.*key/);
|
||||
});
|
||||
|
||||
it('refuses to merge incompatible array items', function() {
|
||||
appdir.createConfigFilesSync({
|
||||
key: [[]]
|
||||
});
|
||||
appdir.writeConfigFileSync('config.local.json', {
|
||||
key: [{}]
|
||||
});
|
||||
|
||||
expect(function() { boot.compile(appdir.PATH); })
|
||||
.to.throw(/incompatible types.*key\[0\]/);
|
||||
});
|
||||
|
||||
it('merges app configs from multiple files', function() {
|
||||
|
|
Loading…
Reference in New Issue