Skip to content

Commit

Permalink
Merge pull request #6554 from w33ble/implement/pluginConfigPrefix
Browse files Browse the repository at this point in the history
Implement plugin config prefix
  • Loading branch information
w33ble committed Mar 23, 2016
2 parents 7bbfa22 + 2ebd94d commit 4408069
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 23 deletions.
35 changes: 33 additions & 2 deletions src/server/config/__tests__/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,36 @@ describe('lib/config/config', function () {

describe('constructor', function () {

it('should not allow any config if the schema is not passed', function (done) {
it('should not allow any config if the schema is not passed', function () {
var config = new Config();
var run = function () {
config.set('something.enable', true);
};
expect(run).to.throwException();
done();
});

it('should allow keys in the schema', function () {
var config = new Config(schema);
var run = function () {
config.set('test.client.host', 'http://0.0.0.0');
};
expect(run).to.not.throwException();
});

it('should not allow keys not in the schema', function () {
var config = new Config(schema);
var run = function () {
config.set('paramNotDefinedInTheSchema', true);
};
expect(run).to.throwException();
});

it('should not allow child keys not in the schema', function () {
var config = new Config(schema);
var run = function () {
config.set('test.client.paramNotDefinedInTheSchema', true);
};
expect(run).to.throwException();
});

it('should set defaults', function () {
Expand Down Expand Up @@ -198,6 +221,14 @@ describe('lib/config/config', function () {
expect(config.get('myTest.test')).to.be(true);
});

it('should allow you to extend the schema with a prefix', function () {
var newSchema = Joi.object({ test: Joi.boolean().default(true) }).default();
config.extendSchema('prefix.myTest', newSchema);
expect(config.get('prefix')).to.eql({ myTest: { test: true }});
expect(config.get('prefix.myTest')).to.eql({ test: true });
expect(config.get('prefix.myTest.test')).to.be(true);
});

it('should NOT allow you to extend the schema if somethign else is there', function () {
var newSchema = Joi.object({ test: Joi.boolean().default(true) }).default();
var run = function () {
Expand Down
83 changes: 83 additions & 0 deletions src/server/config/__tests__/unset.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import unset from '../unset';
import expect from 'expect.js';

describe('unset(obj, key)', function () {
describe('invalid input', function () {
it('should do nothing if not given an object', function () {
const obj = 'hello';
unset(obj, 'e');
expect(obj).to.equal('hello');
});

it('should do nothing if not given a key', function () {
const obj = { one: 1 };
unset(obj);
expect(obj).to.eql({ one: 1 });
});

it('should do nothing if given an empty string as a key', function () {
const obj = { one: 1 };
unset(obj, '');
expect(obj).to.eql({ one: 1 });
});
});

describe('shallow removal', function () {
let obj;

beforeEach(function () {
obj = { one: 1, two: 2, deep: { three: 3, four: 4 } };
});

it('should remove the param using a string key', function () {
unset(obj, 'two');
expect(obj).to.eql({ one: 1, deep: { three: 3, four: 4 } });
});

it('should remove the param using an array key', function () {
unset(obj, ['two']);
expect(obj).to.eql({ one: 1, deep: { three: 3, four: 4 } });
});
});

describe('deep removal', function () {
let obj;

beforeEach(function () {
obj = { one: 1, two: 2, deep: { three: 3, four: 4 } };
});

it('should remove the param using a string key', function () {
unset(obj, 'deep.three');
expect(obj).to.eql({ one: 1, two: 2, deep: { four: 4 } });
});

it('should remove the param using an array key', function () {
unset(obj, ['deep', 'three']);
expect(obj).to.eql({ one: 1, two: 2, deep: { four: 4 } });
});
});

describe('recursive removal', function () {
it('should clear object if only value is removed', function () {
const obj = { one: { two: { three: 3 } } };
unset(obj, 'one.two.three');
expect(obj).to.eql({});
});

it('should clear object if no props are left', function () {
const obj = { one: { two: { three: 3 } } };
unset(obj, 'one.two');
expect(obj).to.eql({});
});

it('should remove deep property, then clear the object', function () {
const obj = { one: { two: { three: 3, four: 4 } } };
unset(obj, 'one.two.three');
expect(obj).to.eql({ one: { two: { four: 4 } } });

unset(obj, 'one.two.four');
expect(obj).to.eql({});
});
});
});
45 changes: 29 additions & 16 deletions src/server/config/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import Promise from 'bluebird';
import Joi from 'joi';
import _ from 'lodash';
import override from './override';
import unset from './unset';
import createDefaultSchema from './schema';
import pkg from '../../utils/package_json';
import clone from './deep_clone_with_buffers';
import { zipObject } from 'lodash';

const schema = Symbol('Joi Schema');
const schemaKeys = Symbol('Schema Extensions');
const schemaExts = Symbol('Schema Extensions');
const vals = Symbol('config values');
const pendingSets = Symbol('Pending Settings');

Expand All @@ -18,16 +18,15 @@ module.exports = class Config {
}

constructor(initialSchema, initialSettings) {
this[schemaKeys] = new Map();

this[schemaExts] = Object.create(null);
this[vals] = Object.create(null);
this[pendingSets] = new Map(_.pairs(clone(initialSettings || {})));
this[pendingSets] = _.merge(Object.create(null), initialSettings || {});

if (initialSchema) this.extendSchema(initialSchema);
}

getPendingSets() {
return this[pendingSets];
return new Map(_.pairs(this[pendingSets]));
}

extendSchema(key, extension) {
Expand All @@ -41,27 +40,27 @@ module.exports = class Config {
throw new Error(`Config schema already has key: ${key}`);
}

this[schemaKeys].set(key, extension);
_.set(this[schemaExts], key, extension);
this[schema] = null;

let initialVals = this[pendingSets].get(key);
let initialVals = _.get(this[pendingSets], key);
if (initialVals) {
this.set(key, initialVals);
this[pendingSets].delete(key);
unset(this[pendingSets], key);
} else {
this._commit(this[vals]);
}
}

removeSchema(key) {
if (!this[schemaKeys].has(key)) {
if (!_.has(this[schemaExts], key)) {
throw new TypeError(`Unknown schema key: ${key}`);
}

this[schema] = null;
this[schemaKeys].delete(key);
this[pendingSets].delete(key);
delete this[vals][key];
unset(this[schemaExts], key);
unset(this[pendingSets], key);
unset(this[vals], key);
}

resetTo(obj) {
Expand Down Expand Up @@ -138,7 +137,7 @@ module.exports = class Config {
// Catch the partial paths
if (path.join('.') === key) return true;
// Only go deep on inner objects with children
if (schema._inner.children.length) {
if (_.size(schema._inner.children)) {
for (let i = 0; i < schema._inner.children.length; i++) {
let child = schema._inner.children[i];
// If the child is an object recurse through it's children and return
Expand All @@ -163,8 +162,22 @@ module.exports = class Config {

getSchema() {
if (!this[schema]) {
let objKeys = zipObject([...this[schemaKeys]]);
this[schema] = Joi.object().keys(objKeys).default();
this[schema] = (function convertToSchema(children) {
let schema = Joi.object().keys({}).default();

for (const key of Object.keys(children)) {
const child = children[key];
const childSchema = _.isPlainObject(child) ? convertToSchema(child) : child;

if (!childSchema || !childSchema.isJoi) {
throw new TypeError('Unable to convert configuration definition value to Joi schema: ' + childSchema);
}

schema = schema.keys({ [key]: childSchema });
}

return schema;
}(this[schemaExts]));
}

return this[schema];
Expand Down
26 changes: 26 additions & 0 deletions src/server/config/unset.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import _ from 'lodash';
import toPath from 'lodash/internal/toPath';

module.exports = function unset(object, rawPath) {
if (!object) return;
const path = toPath(rawPath);

switch (path.length) {
case 0:
return;

case 1:
delete object[rawPath];
break;

default:
const leaf = path.pop();
const parentPath = path.slice();
const parent = _.get(object, parentPath);
unset(parent, leaf);
if (!_.size(parent)) {
unset(object, parentPath);
}
break;
}
};
14 changes: 9 additions & 5 deletions src/server/plugins/plugin.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import _ from 'lodash';
import toPath from 'lodash/internal/toPath';
import Joi from 'joi';
import Bluebird, { attempt, fromNode } from 'bluebird';
import { basename, resolve } from 'path';
Expand Down Expand Up @@ -37,6 +38,8 @@ const defaultConfigSchema = Joi.object({
* @param {String} [opts.version=pkg.version] - the version of this plugin
* @param {Function} [opts.init] - A function that will be called to initialize
* this plugin at the appropriate time.
* @param {Function} [opts.configPrefix=this.id] - The prefix to use for configuration
* values in the main configuration service
* @param {Function} [opts.config] - A function that produces a configuration
* schema using Joi, which is passed as its
* first argument.
Expand All @@ -57,6 +60,7 @@ module.exports = class Plugin {
this.requiredIds = opts.require || [];
this.version = opts.version || pkg.version;
this.externalInit = opts.init || _.noop;
this.configPrefix = opts.configPrefix || this.id;
this.getConfigSchema = opts.config || _.noop;
this.init = _.once(this.init);
this[extendInitFns] = [];
Expand Down Expand Up @@ -86,18 +90,18 @@ module.exports = class Plugin {
async readConfig() {
let schema = await this.getConfigSchema(Joi);
let { config } = this.kbnServer;
config.extendSchema(this.id, schema || defaultConfigSchema);
config.extendSchema(this.configPrefix, schema || defaultConfigSchema);

if (config.get([this.id, 'enabled'])) {
if (config.get([...toPath(this.configPrefix), 'enabled'])) {
return true;
} else {
config.removeSchema(this.id);
config.removeSchema(this.configPrefix);
return false;
}
}

async init() {
let { id, version, kbnServer } = this;
let { id, version, kbnServer, configPrefix } = this;
let { config } = kbnServer;

// setup the hapi register function and get on with it
Expand Down Expand Up @@ -132,7 +136,7 @@ module.exports = class Plugin {
await fromNode(cb => {
kbnServer.server.register({
register: register,
options: config.has(id) ? config.get(id) : null
options: config.has(configPrefix) ? config.get(configPrefix) : null
}, cb);
});

Expand Down

0 comments on commit 4408069

Please sign in to comment.