Comparing version 0.3.0 to 0.4.0
@@ -0,1 +1,7 @@ | ||
## v0.4.0 - Sep 21 2018 | ||
+ Adds `validator` option to topic configuration. | ||
## v0.3.0 - Sep 11 2018 | ||
+ Adds topic configuration | ||
## v0.2.1 - Apr 27 2018 | ||
@@ -2,0 +8,0 @@ + Adds documentation |
@@ -100,3 +100,6 @@ 'use strict'; | ||
doPrime: true, | ||
allowRepeats: false | ||
allowRepeats: false, | ||
validator: function validator() { | ||
return { valid: true, messages: [] }; | ||
} | ||
}; | ||
@@ -122,2 +125,14 @@ | ||
function buildValidationErrorMessage(topicName, validationMessages, payload) { | ||
var messages = ''; | ||
if (Array.isArray(validationMessages) && validationMessages.length > 0) { | ||
var s = validationMessages.length === 1 ? '' : 's'; | ||
messages = 'Message' + s + ':\n ' + validationMessages.join('\n '); | ||
} | ||
var payloadString = 'Received Payload:\n ' + JSON.stringify(payload, null, 2); | ||
return 'Validation failed for topic \'' + topicName + '\'.\n ' + messages + '\n ' + payloadString; | ||
} | ||
/** | ||
@@ -152,2 +167,14 @@ * @summary Configure a new topic. | ||
* </li> | ||
* <li> | ||
* `validator` - Validation function to assert that published values are valid before sent to subscribers. | ||
* Function will be called with each published payload. | ||
* If a payload fails validation, an error will be thrown during the `publish` call. | ||
* The function should return something like:<br/> | ||
* <code> | ||
* { | ||
* valid: false, | ||
* messages: ['Message 1', 'Message 2'] | ||
* } | ||
* </code> | ||
* </li> | ||
* </ul> | ||
@@ -167,2 +194,9 @@ * </p> | ||
if (isDefined(topic.default)) { | ||
var defaultValidationResult = topic.validator(topic.default); | ||
if (!defaultValidationResult.valid) { | ||
warn('\'' + topic.name + '\' has been configured with a default value that does not pass validation.\nComplete message:\n' + buildValidationErrorMessage(topic.name, defaultValidationResult.messages, topic.default)); | ||
} | ||
} | ||
topics[topic.name] = topic; | ||
@@ -288,2 +322,13 @@ } | ||
var topicConfig = getTopicConfig(topic); | ||
if (!topicConfig.eventOnly) { | ||
var validationResult = topicConfig.validator(payload); | ||
if (!validationResult || !validationResult.valid) { | ||
var messages = validationResult ? validationResult.messages : []; | ||
throw new Error(buildValidationErrorMessage(topic, messages, payload)); | ||
} | ||
} | ||
store[topic] = payload; | ||
@@ -290,0 +335,0 @@ var subs = allSubsFor(topic); |
{ | ||
"name": "pubst", | ||
"version": "0.3.0", | ||
"version": "0.4.0", | ||
"description": "A slightly opinionated pub/sub event emitter for JavaScript.", | ||
@@ -41,3 +41,3 @@ "main": "lib/pubst.js", | ||
"clear-require": "2.0.0", | ||
"eslint": "5.5.0", | ||
"eslint": "5.6.0", | ||
"jsdoc": "3.5.5", | ||
@@ -47,5 +47,5 @@ "mocha": "5.2.0", | ||
"shx": "0.3.2", | ||
"sinon": "6.2.0", | ||
"sinon": "6.3.4", | ||
"sinon-chai": "3.2.0" | ||
} | ||
} |
@@ -114,2 +114,3 @@ # Pubst | ||
+ This can be overridden by subscribers. | ||
+ `validator` - A validation function to assert that published values are valid before they are sent to subscribers. | ||
@@ -123,3 +124,18 @@ #### Examples | ||
default: false, | ||
doPrime: true | ||
doPrime: true, | ||
validator: payload => { | ||
const valid = typeof payload === 'boolean'; | ||
if (!valid) { | ||
return { | ||
valid, | ||
messages: [ | ||
'Value must be a boolean' | ||
] | ||
}; | ||
} | ||
return { | ||
valid | ||
}; | ||
} | ||
}, | ||
@@ -130,3 +146,6 @@ { | ||
allowRepeats: true, | ||
doPrime: false | ||
doPrime: false, | ||
validator: payload => ({ | ||
valid: typeof payload === 'number' | ||
}) | ||
}, | ||
@@ -133,0 +152,0 @@ { |
@@ -88,3 +88,4 @@ /** | ||
doPrime: true, | ||
allowRepeats: false | ||
allowRepeats: false, | ||
validator: () => ({valid: true, messages: []}) | ||
}; | ||
@@ -116,2 +117,14 @@ | ||
function buildValidationErrorMessage(topicName, validationMessages, payload) { | ||
let messages = ''; | ||
if (Array.isArray(validationMessages) && validationMessages.length > 0) { | ||
const s = validationMessages.length === 1 ? '' : 's'; | ||
messages = `Message${s}:\n ${validationMessages.join('\n ')}`; | ||
} | ||
const payloadString = 'Received Payload:\n ' + JSON.stringify(payload, null, 2); | ||
return `Validation failed for topic '${topicName}'.\n ${messages}\n ${payloadString}`; | ||
} | ||
/** | ||
@@ -146,2 +159,14 @@ * @summary Configure a new topic. | ||
* </li> | ||
* <li> | ||
* `validator` - Validation function to assert that published values are valid before sent to subscribers. | ||
* Function will be called with each published payload. | ||
* If a payload fails validation, an error will be thrown during the `publish` call. | ||
* The function should return something like:<br/> | ||
* <code> | ||
* { | ||
* valid: false, | ||
* messages: ['Message 1', 'Message 2'] | ||
* } | ||
* </code> | ||
* </li> | ||
* </ul> | ||
@@ -161,2 +186,11 @@ * </p> | ||
if (isDefined(topic.default)) { | ||
const defaultValidationResult = topic.validator(topic.default); | ||
if (!defaultValidationResult.valid) { | ||
warn(`'${topic.name}' has been configured with a default value that does not pass validation. | ||
Complete message: | ||
${buildValidationErrorMessage(topic.name, defaultValidationResult.messages, topic.default)}`); | ||
} | ||
} | ||
topics[topic.name] = topic; | ||
@@ -275,2 +309,13 @@ | ||
const topicConfig = getTopicConfig(topic); | ||
if (!topicConfig.eventOnly) { | ||
const validationResult = topicConfig.validator(payload); | ||
if (!validationResult || !validationResult.valid) { | ||
const messages = validationResult ? validationResult.messages : []; | ||
throw new Error(buildValidationErrorMessage(topic, messages, payload)); | ||
} | ||
} | ||
store[topic] = payload; | ||
@@ -277,0 +322,0 @@ const subs = allSubsFor(topic); |
@@ -532,5 +532,97 @@ const clearRequire = require('clear-require'); | ||
}); | ||
describe('validator', () => { | ||
it('allows all values by default', () => { | ||
pubst.addTopics([ | ||
{ | ||
name: TEST_TOPIC_1, | ||
doPrime: false | ||
} | ||
]); | ||
const handler1 = sinon.spy(); | ||
pubst.subscribe(TEST_TOPIC_1, handler1); | ||
pubst.publish(TEST_TOPIC_1, 'Hello'); | ||
pubst.publish(TEST_TOPIC_1, 12); | ||
clock.tick(1); | ||
expect(handler1).to.have.been.calledWith('Hello', TEST_TOPIC_1); | ||
expect(handler1).to.have.been.calledWith(12, TEST_TOPIC_1); | ||
}); | ||
it('fails when valid is false', () => { | ||
pubst.addTopic({ | ||
name: TEST_TOPIC_1, | ||
doPrime: false, | ||
validator: () => ({valid: false, messages: ['message 1', 'message 2']}) | ||
}); | ||
const handler1 = sinon.spy(); | ||
pubst.subscribe(TEST_TOPIC_1, handler1); | ||
try { | ||
pubst.publish(TEST_TOPIC_1, 'Hello'); | ||
throw new Error('This publish should have thrown'); | ||
} catch (e) { | ||
const {message} = e; | ||
expect(message).to.contain('message 1'); | ||
expect(message).to.contain('message 2'); | ||
expect(message).to.contain(TEST_TOPIC_1); | ||
expect(message).to.contain('Hello'); | ||
} | ||
clock.tick(1); | ||
expect(handler1).not.to.have.been.called; | ||
}); | ||
it('fails when result is falsey', () => { | ||
pubst.addTopic({ | ||
name: TEST_TOPIC_1, | ||
doPrime: false, | ||
validator: () => false | ||
}); | ||
const handler1 = sinon.spy(); | ||
pubst.subscribe(TEST_TOPIC_1, handler1); | ||
try { | ||
pubst.publish(TEST_TOPIC_1, 'Hello'); | ||
throw new Error('This publish should have thrown'); | ||
} catch (e) { | ||
const {message} = e; | ||
expect(message).to.contain(TEST_TOPIC_1); | ||
expect(message).to.contain('Hello'); | ||
} | ||
clock.tick(1); | ||
expect(handler1).not.to.have.been.called; | ||
}); | ||
it('allows payloads when valid is true', () => { | ||
pubst.addTopic({ | ||
name: TEST_TOPIC_1, | ||
doPrime: false, | ||
validator: () => ({valid: true}) | ||
}); | ||
const handler1 = sinon.spy(); | ||
pubst.subscribe(TEST_TOPIC_1, handler1); | ||
pubst.publish(TEST_TOPIC_1, 'Hello'); | ||
clock.tick(1); | ||
expect(handler1).to.have.been.calledOnceWith('Hello', TEST_TOPIC_1); | ||
}); | ||
}); | ||
}); | ||
describe('publish & subscribe', () => { | ||
@@ -537,0 +629,0 @@ it('calls the subscriber if the topic already has a set value', () => { |
85157
1682
287