Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fully unit tested consul-powered storage
- Loading branch information
Showing
4 changed files
with
512 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
var Storage = require('../'); | ||
var consul = require('consul'); | ||
var path = require('path'); | ||
|
||
module.exports = ConsulBackend; | ||
|
||
function ConsulBackend(options) { | ||
options = options || {}; | ||
this._prefix = options.prefix || 'billimanjaro'; | ||
delete options.prefix; | ||
this._instance = consul(options); | ||
} | ||
|
||
ConsulBackend.prototype = Object.create(Storage.prototype); | ||
ConsulBackend.prototype.constructor = Storage; | ||
ConsulBackend.prototype._idGenerator = function() { return Math.random().toString(36).slice(2); }; | ||
|
||
function syncEntity(backend, entity, id, object, callback) { | ||
if(Object.keys(object).length === 0 || | ||
(Object.keys(object).length === 1 && | ||
Object.keys(object)[0] === 'id')) { | ||
callback(new Error('Nothing to save!')); | ||
return; | ||
} | ||
|
||
var count = 0; | ||
// TODO this is a bandaid over the result, this needs | ||
// to be a stack of things to send instead | ||
var hadError = false; | ||
|
||
function resultCallback(err, result, response) { | ||
if((err || response.statusCode === 404) && !hadError) { | ||
hadError = true; | ||
callback(err || new Error(response.statusMessage)); | ||
return; | ||
} | ||
if(--count === 0 && !hadError) { | ||
backend.get(entity, id, callback); | ||
} | ||
} | ||
|
||
function addChildren(object, prefix) { | ||
if(Array.isArray(object)) { | ||
object.forEach(function (element, index) { | ||
addChildren(element, path.join(prefix, String(index))); | ||
}); | ||
} else if(typeof object === 'object') { | ||
Object.keys(object).forEach(function(key) { | ||
addChildren(object[key], path.join(prefix, String(key))); | ||
}); | ||
} else { | ||
count++; | ||
backend._instance.kv.set(prefix, object, resultCallback); | ||
} | ||
} | ||
|
||
addChildren(object, path.join(String(backend._prefix), String(entity), String(id))); | ||
} | ||
|
||
ConsulBackend.prototype.list = function (entity, callback) { | ||
this._instance.kv.keys(path.join(this._prefix, entity) + '/', function (err, result, response) { | ||
err = err || (response.statusCode !== 200 ? new Error(response.statusMessage) : null); | ||
result = err !== null ? [] : result; | ||
callback(err, response.statusCode === 200 ? result : []); | ||
}); | ||
}; | ||
|
||
ConsulBackend.prototype.get = function (entity, id, callback) { | ||
this._instance.kv.get({ key: path.join(this._prefix, entity, id), recurse: true }, function (err, result, response) { | ||
callback(err, (!err && response.statusCode === 200) ? result : null); | ||
}); | ||
}; | ||
|
||
ConsulBackend.prototype.add = function (entity, object, callback) { | ||
if(object === null) { | ||
callback(new Error('Null cannot be saved!')); | ||
return; | ||
} else if(typeof object !== 'object') { | ||
callback(new Error('You must pass an object!')); | ||
return; | ||
} | ||
|
||
// ensure there is no id on the object before processing it | ||
delete object.id; | ||
var id = this._idGenerator(); | ||
object.id = id; | ||
syncEntity(this, entity, id, object, callback); | ||
}; | ||
|
||
ConsulBackend.prototype.update = function (entity, object, callback) { | ||
if(object === null) { | ||
callback(new Error('Null cannot be saved!')); | ||
return; | ||
} else if(typeof object !== 'object') { | ||
callback(new Error('You must pass an object!')); | ||
return; | ||
} else if(!object.hasOwnProperty('id')) { | ||
callback(new Error('Your object must already have an id!')); | ||
return; | ||
} | ||
|
||
this.get(entity, object.id, (function (err, result) { | ||
if(result === null) { | ||
return callback(new Error('Unable to sync entity ' + entity + '(id: ' + object.id + '), entity not found'), null); | ||
} | ||
|
||
syncEntity(this, entity, object.id, object, callback); | ||
}).bind(this)); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
module.exports = Storage; | ||
|
||
function Storage() { | ||
/* This space intentionally left blank */ | ||
} | ||
|
||
// Implementation notes: | ||
// - list should return an empty array if there is nothing | ||
// - add should fail when the entity already exists | ||
// - update should fail when the entity does not exist | ||
// - there is no delete method | ||
|
||
Storage.prototype.list = function () { | ||
throw new Error('Must be implemented by a subclass!'); | ||
}; | ||
|
||
Storage.prototype.get = function () { | ||
throw new Error('Must be implemented by a subclass!'); | ||
}; | ||
|
||
Storage.prototype.add = function () { | ||
throw new Error('Must be implemented by a subclass!'); | ||
}; | ||
|
||
Storage.prototype.update = function () { | ||
throw new Error('Must be implemented by a subclass!'); | ||
}; |
Oops, something went wrong.