rascal
Version:
An advanced RabbitMQ / AMQP client built on amqplib
190 lines (155 loc) • 11.8 kB
JavaScript
const debug = require('debug')('rascal:config:validate');
const format = require('util').format;
const _ = require('lodash');
module.exports = _.curry((config, next) => {
try {
validateVhosts(config.vhosts);
validatePublications(config.publications);
validateSubscriptions(config.subscriptions);
validateEncryptionProfiles(config.encryption);
validateShovels(config.shovels);
} catch (err) {
return next(err, config);
}
next(null, config);
function validateVhosts(vhosts) {
if (!vhosts || Object.keys(vhosts).length === 0) throw new Error('No vhosts specified');
_.each(vhosts, validateVhost);
}
function validateVhost(vhost, vhostName) {
validateAttributes('Vhost', vhost, vhostName, ['defaults', 'namespace', 'name', 'concurrency', 'publicationChannelPools', 'connection', 'connections', 'connectionStrategy', 'exchanges', 'queues', 'bindings', 'check', 'assert']);
validateConnectionStrategy(vhost.connectionStrategy, vhostName);
validateConnectionAttributes(vhost.connection, vhostName, ['slashes', 'protocol', 'hostname', 'user', 'password', 'port', 'vhost', 'options', 'retry', 'auth', 'pathname', 'query', 'url', 'loggableUrl', 'management']);
validateManagementConnectionAttributes(_.get(vhost), 'connection.management', vhostName, ['slashes', 'protocol', 'hostname', 'user', 'password', 'port', 'vhost', 'options', 'auth', 'pathname', 'query', 'url', 'loggableUrl']);
validatePublicationChannelPools(vhost.publicationChannelPools, vhostName);
validateExchanges(vhost, vhostName, vhost.exchanges);
validateQueues(vhost, vhostName, vhost.queues);
validateBindings(vhost, vhostName, vhost.bindings);
}
function validateAttributes(type, object, objectName, valid) {
const invalid = _.chain(object).omit(valid).keys().value();
if (invalid.length) throw new Error(format('%s: %s refers to an unsupported attribute: %s', type, objectName, invalid[0]));
}
function validateConnectionStrategy(strategy, vhostName) {
if (![undefined, 'random', 'fixed'].includes(strategy)) throw new Error(format('Vhost: %s refers to an unknown connection strategy: %s', vhostName, strategy));
}
function validateConnectionAttributes(connection, vhostName, valid) {
const invalid = _.chain(connection).omit(valid).keys().value();
if (invalid.length) throw new Error(format('Vhost: %s connection refers to an unsupported attribute: %s', vhostName, invalid[0]));
}
function validateManagementConnectionAttributes(connection, vhostName, valid) {
if (!connection) return;
const invalid = _.chain(connection).omit(valid).keys().value();
if (invalid.length) throw new Error(format('Vhost: %s management connection refers to an unsupported attribute: %s', vhostName, invalid[0]));
}
function validatePublicationChannelPools(publicationChannelPools, vhostName) {
const invalid = _.chain(publicationChannelPools).omit(['regularPool', 'confirmPool']).keys().value();
if (invalid.length) throw new Error(format('Publication channel pool in vhost: %s refers to an unsupported attribute: %s', vhostName, invalid[0]));
}
function validateVhostChildAttributes(vhostName, type, child, childName, valid) {
const invalid = _.chain(child).omit(valid).keys().value();
if (invalid.length) throw new Error(format('%s: %s in vhost: %s refers to an unsupported attribute: %s', type, childName, vhostName, invalid[0]));
}
function validateExchanges(vhost, vhostName, exchanges) {
_.each(exchanges, validateExchange.bind(null, vhost, vhostName));
}
function validateExchange(vhost, vhostName, exchange, exchangeName) {
validateVhostChildAttributes(vhostName, 'Exchange', exchange, exchangeName, ['fullyQualifiedName', 'name', 'assert', 'check', 'type', 'options']);
}
function validateQueues(vhost, vhostName, queues) {
_.each(queues, validateQueue.bind(null, vhost, vhostName));
}
function validateQueue(vhost, vhostName, queue, queueName) {
validateVhostChildAttributes(vhostName, 'Queue', queue, queueName, ['fullyQualifiedName', 'name', 'assert', 'check', 'type', 'purge', 'replyTo', 'options']);
}
function validateBindings(vhost, vhostName, bindings) {
_.each(bindings, validateBinding.bind(null, vhost, vhostName));
}
function validateBinding(vhost, vhostName, binding, bindingName) {
validateVhostChildAttributes(vhostName, 'Binding', binding, bindingName, ['fullyQualifiedName', 'name', 'source', 'destination', 'destinationType', 'bindingKey', 'bindingKeys', 'qualifyBindingKeys', 'options']);
if (!binding.source) throw new Error(format('Binding: %s in vhost: %s is missing a source', bindingName, vhostName));
if (!binding.destination) throw new Error(format('Binding: %s in vhost: %s is missing a destination', bindingName, vhostName));
if (!binding.destinationType) throw new Error(format('Binding: %s in vhost: %s is missing a destination type', bindingName, vhostName));
if (['queue', 'exchange'].indexOf(binding.destinationType) < 0) throw new Error(format('Binding: %s in vhost: %s has an invalid destination type: %s', bindingName, vhostName, binding.destinationType));
if (!vhost.exchanges) throw new Error(format('Binding: %s in vhost: %s refers to an unknown exchange: %s', bindingName, vhostName, binding.source));
if (!vhost.exchanges[binding.source]) throw new Error(format('Binding: %s in vhost: %s refers to an unknown exchange: %s', bindingName, vhostName, binding.source));
if (binding.destinationType === 'queue') {
if (!vhost.queues) throw new Error(format('Binding: %s in vhost: %s refers to an unknown queue: %s', bindingName, vhostName, binding.destination));
if (!vhost.queues[binding.destination]) throw new Error(format('Binding: %s in vhost: %s refers to an unknown queue: %s', bindingName, vhostName, binding.destination));
} else if (!vhost.exchanges[binding.destination]) throw new Error(format('Binding: %s in vhost: %s refers to an unknown exchange: %s', bindingName, vhostName, binding.destination));
}
function validatePublications(publications) {
_.each(publications, validatePublication);
}
function validatePublication(publication, publicationName) {
validateAttributes('Publication', publication, publicationName, ['name', 'vhost', 'exchange', 'queue', 'routingKey', 'confirm', 'options', 'destination', 'autoCreated', 'deprecated', 'encryption', 'replyTo', 'timeout']);
if (!publication.vhost) throw new Error(format('Publication: %s is missing a vhost', publicationName));
if (!(Object.prototype.hasOwnProperty.call(publication, 'exchange') || publication.queue)) throw new Error(format('Publication: %s is missing an exchange or a queue', publicationName));
if (Object.prototype.hasOwnProperty.call(publication, 'exchange') && publication.queue) throw new Error(format('Publication: %s has an exchange and a queue', publicationName));
if (!config.vhosts) throw new Error(format('Publication: %s refers to an unknown vhost: %s', publicationName, publication.vhost));
if (!config.vhosts[publication.vhost]) throw new Error(format('Publication: %s refers to an unknown vhost: %s', publicationName, publication.vhost));
if (Object.prototype.hasOwnProperty.call(publication, 'exchange')) {
if (!config.vhosts[publication.vhost].exchanges) throw new Error(format('Publication: %s refers to an unknown exchange: %s in vhost: %s', publicationName, publication.exchange, publication.vhost));
if (!config.vhosts[publication.vhost].exchanges[publication.exchange]) throw new Error(format('Publication: %s refers to an unknown exchange: %s in vhost: %s', publicationName, publication.exchange, publication.vhost));
} else {
if (!config.vhosts[publication.vhost].queues) throw new Error(format('Publication: %s refers to an unknown queue: %s in vhost: %s', publicationName, publication.queue, publication.vhost));
if (!config.vhosts[publication.vhost].queues[publication.queue]) throw new Error(format('Publication: %s refers to an unknown queue: %s in vhost: %s', publicationName, publication.queue, publication.vhost));
}
if (publication.encryption) validateEncryptionProfile(publication.encryption);
}
function validateSubscriptions(subscriptions) {
_.each(subscriptions, validateSubscription);
}
function validateSubscription(subscription, subscriptionName) {
validateAttributes('Subscription', subscription, subscriptionName, [
'name',
'vhost',
'queue',
'contentType',
'options',
'prefetch',
'channelPrefetch',
'retry',
'source',
'recovery',
'workflow',
'handler',
'workflows',
'handlers',
'redeliveries',
'autoCreated',
'deprecated',
'closeTimeout',
'encryption',
'promisifyAckOrNack',
]);
if (!subscription.vhost) throw new Error(format('Subscription: %s is missing a vhost', subscriptionName));
if (!subscription.queue) throw new Error(format('Subscription: %s is missing a queue', subscriptionName));
if (!config.vhosts) throw new Error(format('Subscription: %s refers to an unknown vhost: %s', subscriptionName, subscription.vhost));
if (!config.vhosts[subscription.vhost]) throw new Error(format('Subscription: %s refers to an unknown vhost: %s', subscriptionName, subscription.vhost));
if (!config.vhosts[subscription.vhost].queues) throw new Error(format('Subscription: %s refers to an unknown queue: %s in vhost: %s', subscriptionName, subscription.queue, subscription.vhost));
if (!config.vhosts[subscription.vhost].queues[subscription.queue]) throw new Error(format('Subscription: %s refers to an unknown queue: %s in vhost: %s', subscriptionName, subscription.queue, subscription.vhost));
if (_.get(config, 'config.redeliveries.counters')) throw new Error(format('Subscription: %s refers to an unknown counter: %s in vhost: %s', subscriptionName, subscription.redeliveries.counter, subscription.vhost));
if (!config.redeliveries.counters[subscription.redeliveries.counter]) throw new Error(format('Subscription: %s refers to an unknown counter: %s in vhost: %s', subscriptionName, subscription.redeliveries.counter, subscription.vhost));
if (subscription.encryption) validateEncryptionProfiles(subscription.encryption);
}
function validateEncryptionProfiles(encryption) {
_.each(encryption, validateEncryptionProfile);
}
function validateEncryptionProfile(encryption, encryptionName) {
validateAttributes('Encryption', encryption, encryptionName, ['name', 'key', 'algorithm', 'ivLength']);
if (!encryption.key) throw new Error(format('Encryption profile: %s is missing a key', encryptionName));
if (!encryption.algorithm) throw new Error(format('Encryption profile: %s is missing an algorithm', encryptionName));
if (!encryption.ivLength) throw new Error(format('Encryption profile: %s is missing ivLength', encryptionName));
}
function validateShovels(shovels) {
_.each(shovels, validateShovel);
}
function validateShovel(shovel, shovelName) {
validateAttributes('Shovel', shovel, shovelName, ['name', 'subscription', 'publication']);
if (!shovel.subscription) throw new Error(format('Shovel: %s is missing a subscription', shovelName));
if (!shovel.publication) throw new Error(format('Shovel: %s is missing a publication', shovelName));
if (!config.subscriptions[shovel.subscription]) throw new Error(format('Shovel: %s refers to an unknown subscription: %s', shovelName, shovel.subscription));
if (!config.publications[shovel.publication]) throw new Error(format('Shovel: %s refers to an unknown publication: %s', shovelName, shovel.publication));
}
});