heroku-client
Advanced tools
Comparing version 0.3.1 to 0.3.2
@@ -1,3 +0,3 @@ | ||
var client = require('./request'), | ||
_ = require('underscore'); | ||
var Request = require('./request'), | ||
_ = require('lodash'); | ||
@@ -10,3 +10,3 @@ module.exports = Heroku; | ||
Heroku.request = client.request; | ||
Heroku.request = Request.request; | ||
@@ -18,6 +18,6 @@ Heroku.prototype.request = function (options, callback) { | ||
} else { | ||
options = _.extend(_.clone(this.options), options); | ||
options = _.defaults(options, this.options); | ||
} | ||
return Heroku.request(options, function (err, body) { | ||
return Request.request(options, function (err, body) { | ||
if (callback) callback(err, body); | ||
@@ -24,0 +24,0 @@ }); |
var https = require('https'), | ||
memjs = require('memjs'), | ||
q = require('q'), | ||
_ = require('underscore'), | ||
_ = require('lodash'), | ||
cache; | ||
exports.request = function request(options, callback) { | ||
options || (options = {}); | ||
var deferred = q.defer(), | ||
path = options.path; | ||
module.exports = Request; | ||
getCache(path, options.cacheKeyPostfix, function(cachedResponse) { | ||
var headers = _.extend({ | ||
'Accept': 'application/vnd.heroku+json; version=3', | ||
'Content-type': 'application/json' | ||
}, options.headers || {}); | ||
if (cachedResponse) { | ||
headers['If-None-Match'] = cachedResponse.etag; | ||
} | ||
/* | ||
* Create an object capable of making API | ||
* calls. Accepts custom request options and | ||
* a callback function. | ||
*/ | ||
function Request (options, callback) { | ||
this.options = options || {}; | ||
this.callback = callback; | ||
this.deferred = q.defer(); | ||
var requestOptions = { | ||
hostname: 'api.heroku.com', | ||
port: 443, | ||
path: path, | ||
auth: ':' + options.token, | ||
method: options.method || 'GET', | ||
headers: headers | ||
}; | ||
_.bindAll(this); } | ||
var req = https.request(requestOptions, function(res) { | ||
if (res.statusCode === 304 && cachedResponse) { | ||
deferred.resolve(cachedResponse.body); | ||
callback(null, cachedResponse.body); | ||
} else { | ||
var buffer = ''; | ||
res.on('data', function(data) { | ||
buffer += data; | ||
}); | ||
/* | ||
* Instantiate a Request object and makes a | ||
* request, returning the request promise. | ||
*/ | ||
Request.request = function request (options, callback) { | ||
var req = new Request(options, function (err, body) { | ||
if (callback) callback(err, body); | ||
}); | ||
res.on('end', function() { | ||
if (expectedStatus(res, options)) { | ||
handleSuccess(res, buffer, options, deferred, callback); | ||
} else { | ||
handleFailure(res, buffer, options, deferred, callback); | ||
} | ||
}); | ||
return req.request(); | ||
}; | ||
/* | ||
* Check for a cached response, then | ||
* perform an API request. Return the | ||
* request object's promise. | ||
*/ | ||
Request.prototype.request = function request () { | ||
this.getCache(this.performRequest); | ||
return this.deferred.promise; | ||
}; | ||
/* | ||
* Perform the actual API request. | ||
*/ | ||
Request.prototype.performRequest = function performRequest (cachedResponse) { | ||
var headers, | ||
requestOptions, | ||
req; | ||
this.cachedResponse = cachedResponse; | ||
headers = _.extend({ | ||
'Accept': 'application/vnd.heroku+json; version=3', | ||
'Content-type': 'application/json' | ||
}, this.options.headers || {}); | ||
if (this.cachedResponse) { | ||
headers['If-None-Match'] = this.cachedResponse.etag; | ||
} | ||
requestOptions = { | ||
hostname: 'api.heroku.com', | ||
port: 443, | ||
path: this.options.path, | ||
auth: ':' + this.options.token, | ||
method: this.options.method || 'GET', | ||
headers: headers | ||
}; | ||
req = https.request(requestOptions, this.handleResponse); | ||
this.writeBody(req); | ||
this.setRequestTimeout(req); | ||
req.on('error', this.handleError); | ||
req.end(); | ||
}; | ||
/* | ||
* If the cache client is alive, get the | ||
* cached response from the cache. | ||
*/ | ||
Request.prototype.getCache = function getCache (callback) { | ||
if (!cache) return callback(null); | ||
var key = this.options.path + '-' + this.options.cacheKeyPostfix; | ||
cache.get(key, function (err, res) { | ||
callback(JSON.parse(res)); | ||
}); | ||
}; | ||
/* | ||
* Handle an API response, returning the | ||
* cached body if it's still valid, or the | ||
* new API response. | ||
*/ | ||
Request.prototype.handleResponse = function handleResponse (res) { | ||
var _this = this, | ||
buffer; | ||
if (res.statusCode === 304 && this.cachedResponse) { | ||
this.deferred.resolve(this.cachedResponse.body); | ||
this.callback(null, this.cachedResponse.body); | ||
} else { | ||
buffer = ''; | ||
res.on('data', function (data) { | ||
buffer += data; | ||
}); | ||
res.on('end', function () { | ||
if (_this.expectedStatus(res)) { | ||
_this.handleSuccess(res, buffer); | ||
} else { | ||
_this.handleFailure(res, buffer); | ||
} | ||
}); | ||
} | ||
}; | ||
if (options.body) { | ||
var body = JSON.stringify(options.body); | ||
req.setHeader('Content-length', body.length); | ||
req.write(body); | ||
} | ||
/* | ||
* If the request options include a body, | ||
* write the body to the request and set | ||
* an appropriate 'Content-length' header. | ||
*/ | ||
Request.prototype.writeBody = function writeBody (req) { | ||
if (!this.options.body) return; | ||
req.on('error', function(err) { | ||
deferred.reject(err); | ||
callback(err); | ||
}); | ||
var body = JSON.stringify(this.options.body); | ||
if (options.timeout && options.timeout > 0) { | ||
req.setTimeout(options.timeout, function() { | ||
req.abort(); | ||
req.setHeader('Content-length', body.length); | ||
req.write(body); | ||
} | ||
var err = new Error('Request took longer than ' + options.timeout + 'ms to complete.'); | ||
deferred.reject(err); | ||
callback(err); | ||
}); | ||
} | ||
req.end(); | ||
/* | ||
* If the request options include a timeout, | ||
* set the timeout and provide a callback | ||
* function in case the request exceeds the | ||
* timeout period. | ||
*/ | ||
Request.prototype.setRequestTimeout = function setRequestTimeout (req) { | ||
var _this = this; | ||
if (!this.options.timeout) return; | ||
req.setTimeout(this.options.timeout, function () { | ||
var err = new Error('Request took longer than ' + _this.options.timeout + 'ms to complete.'); | ||
req.abort(); | ||
_this.deferred.reject(err); | ||
_this.callback(err); | ||
}); | ||
} | ||
return deferred.promise; | ||
/* | ||
* In the event of an error in performing | ||
* the API request, reject the deferred | ||
* object and return an error to the callback. | ||
*/ | ||
Request.prototype.handleError = function handleError (err) { | ||
this.deferred.reject(err); | ||
this.callback(err); | ||
} | ||
function expectedStatus(res, options) { | ||
/* | ||
* Check that the status from the API response | ||
* matches the one(s) that are expected. | ||
*/ | ||
Request.prototype.expectedStatus = function expectedStatus (res) { | ||
var options = this.options; | ||
if (options.expectedStatus) { | ||
@@ -93,6 +199,16 @@ if (Array.isArray(options.expectedStatus)) { | ||
function handleFailure(res, buffer, options, deferred, callback) { | ||
var statusString = options.expectedStatus, | ||
message; | ||
/* | ||
* In the event of a non-successful API request, | ||
* fail with an appropriate error message and | ||
* status code. | ||
*/ | ||
Request.prototype.handleFailure = function handleFailure (res, buffer) { | ||
var options = this.options, | ||
callback = this.callback, | ||
deferred = this.deferred, | ||
statusString = options.expectedStatus, | ||
message, | ||
err; | ||
if (options.expectedStatus) { | ||
@@ -108,3 +224,3 @@ if (Array.isArray(options.expectedStatus)) { | ||
var err = new Error(message); | ||
err = new Error(message); | ||
err.statusCode = res.statusCode; | ||
@@ -117,7 +233,16 @@ err.body = JSON.parse(buffer || "{}"); | ||
function handleSuccess(res, buffer, options, deferred, callback) { | ||
var body = JSON.parse(buffer || '{}'); | ||
setCache(options.path, options.cacheKeyPostfix, res, body); | ||
/* | ||
* In the event of a successful API response, | ||
* write the response to the cache and resolve | ||
* with the response body. | ||
*/ | ||
Request.prototype.handleSuccess = function handleSuccess (res, buffer) { | ||
var options = this.options, | ||
callback = this.callback, | ||
deferred = this.deferred, | ||
body = JSON.parse(buffer || '{}'); | ||
this.setCache(options.path, options.cacheKeyPostfix, res, body); | ||
deferred.resolve(body); | ||
@@ -127,17 +252,15 @@ callback(null, body); | ||
function getCache(path, postfix, callback) { | ||
if (!cache) return callback(null); | ||
var key = path + '-' + postfix; | ||
/* | ||
* If the cache client is alive, write the | ||
* provided response and body to the cache. | ||
*/ | ||
Request.prototype.setCache = function setCache (path, cacheKeyPostfix, res, body) { | ||
var key, value; | ||
cache.get(key, function(err, res) { | ||
callback(JSON.parse(res)); | ||
}); | ||
} | ||
function setCache(path, cacheKeyPostfix, res, body) { | ||
if ((!cache) || !(res.headers.etag)) return; | ||
var key = path + '-' + cacheKeyPostfix; | ||
var value = JSON.stringify({ | ||
key = path + '-' + cacheKeyPostfix; | ||
value = JSON.stringify({ | ||
body: body, | ||
@@ -150,8 +273,17 @@ etag: res.headers.etag | ||
exports.connectCacheClient = function connectCacheClient() { | ||
/* | ||
* Connect a cache client. | ||
*/ | ||
Request.connectCacheClient = function connectCacheClient() { | ||
cache = memjs.Client.create(); | ||
}; | ||
/* | ||
* Automatically connect a cache client if | ||
* NODE_ENV is set to 'production'. | ||
*/ | ||
if (process.env.NODE_ENV === 'production') { | ||
exports.connectCacheClient(); | ||
Request.connectCacheClient(); | ||
} |
var Heroku = require('./heroku'), | ||
inflection = require('inflection'), | ||
resources = require('./resources').resources, | ||
_ = require('underscore'); | ||
_ = require('lodash'); | ||
@@ -6,0 +6,0 @@ |
{ | ||
"name": "heroku-client", | ||
"version": "0.3.1", | ||
"version": "0.3.2", | ||
"description": "A wrapper for the Heroku v3 API", | ||
@@ -28,4 +28,4 @@ "main": "./lib/heroku.js", | ||
"inflection": "~1.2.6", | ||
"underscore": "~1.5.1" | ||
"lodash": "~1.3.1" | ||
} | ||
} |
@@ -1,7 +0,8 @@ | ||
var Heroku = require('../../lib/heroku'), | ||
heroku = new Heroku({ token: '12345' }); | ||
var Heroku = require('../../lib/heroku'), | ||
Request = require('../../lib/request'), | ||
heroku = new Heroku({ token: '12345' }); | ||
describe('Heroku', function() { | ||
beforeEach(function() { | ||
spyOn(Heroku, 'request').andCallFake(function(options, callback) { | ||
spyOn(Request, 'request').andCallFake(function(options, callback) { | ||
callback(); | ||
@@ -13,3 +14,3 @@ }); | ||
heroku.apps('my-app').create({}, function() { | ||
expect(Heroku.request.mostRecentCall.args[0].method).toEqual('POST'); | ||
expect(Request.request.mostRecentCall.args[0].method).toEqual('POST'); | ||
done(); | ||
@@ -21,3 +22,3 @@ }); | ||
heroku.apps('my-app').dynos().list(function() { | ||
expect(Heroku.request.mostRecentCall.args[0].expectedStatus).toEqual([200, 206]); | ||
expect(Request.request.mostRecentCall.args[0].expectedStatus).toEqual([200, 206]); | ||
done(); | ||
@@ -30,3 +31,3 @@ }); | ||
heroku.apps().list(function() { | ||
expect(Heroku.request.mostRecentCall.args[0].path).toEqual('/apps'); | ||
expect(Request.request.mostRecentCall.args[0].path).toEqual('/apps'); | ||
done(); | ||
@@ -38,3 +39,3 @@ }); | ||
heroku.apps('my-app').info(function() { | ||
expect(Heroku.request.mostRecentCall.args[0].path).toEqual('/apps/my-app'); | ||
expect(Request.request.mostRecentCall.args[0].path).toEqual('/apps/my-app'); | ||
done(); | ||
@@ -46,3 +47,3 @@ }); | ||
heroku.apps('my-app').collaborators('jonathan@heroku.com').info(function() { | ||
expect(Heroku.request.mostRecentCall.args[0].path).toEqual('/apps/my-app/collaborators/jonathan@heroku.com'); | ||
expect(Request.request.mostRecentCall.args[0].path).toEqual('/apps/my-app/collaborators/jonathan@heroku.com'); | ||
done(); | ||
@@ -56,3 +57,3 @@ }); | ||
heroku.apps().create({ name: 'my-app' }, function() { | ||
expect(Heroku.request.mostRecentCall.args[0].path).toEqual('/apps'); | ||
expect(Request.request.mostRecentCall.args[0].path).toEqual('/apps'); | ||
done(); | ||
@@ -64,3 +65,3 @@ }); | ||
heroku.apps().create({ name: 'my-new-app' }, function() { | ||
expect(Heroku.request.mostRecentCall.args[0].body).toEqual({ name: 'my-new-app' }); | ||
expect(Request.request.mostRecentCall.args[0].body).toEqual({ name: 'my-new-app' }); | ||
done(); | ||
@@ -74,3 +75,3 @@ }); | ||
heroku.apps('my-app').addons().create({ name: 'papertrail:choklad' }, function() { | ||
expect(Heroku.request.mostRecentCall.args[0].path).toEqual('/apps/my-app/addons'); | ||
expect(Request.request.mostRecentCall.args[0].path).toEqual('/apps/my-app/addons'); | ||
done(); | ||
@@ -82,3 +83,3 @@ }); | ||
heroku.apps('my-app').addons().create({ name: 'papertrail:choklad' }, function() { | ||
expect(Heroku.request.mostRecentCall.args[0].body).toEqual({ name: 'papertrail:choklad' }); | ||
expect(Request.request.mostRecentCall.args[0].body).toEqual({ name: 'papertrail:choklad' }); | ||
done(); | ||
@@ -92,3 +93,3 @@ }); | ||
heroku.apps('my-app').addons('papertrail:choklad').update({ name: 'papertrail:fixa' }, function() { | ||
expect(Heroku.request.mostRecentCall.args[0].path).toEqual('/apps/my-app/addons/papertrail:choklad'); | ||
expect(Request.request.mostRecentCall.args[0].path).toEqual('/apps/my-app/addons/papertrail:choklad'); | ||
done(); | ||
@@ -100,3 +101,3 @@ }); | ||
heroku.apps('my-app').addons('papertrail:choklad').update({ name: 'papertrail:fixa' }, function() { | ||
expect(Heroku.request.mostRecentCall.args[0].body).toEqual({ name: 'papertrail:fixa' }); | ||
expect(Request.request.mostRecentCall.args[0].body).toEqual({ name: 'papertrail:fixa' }); | ||
done(); | ||
@@ -103,0 +104,0 @@ }); |
var https = require("https"), | ||
client = require('../../lib/request'), | ||
Request = require('../../lib/request'), | ||
memjs = require('memjs'), | ||
@@ -156,3 +156,3 @@ MockCache = require('../helpers/mockCache'), | ||
spyOn(memjs.Client, 'create').andReturn(cache); | ||
client.connectCacheClient(); | ||
Request.connectCacheClient(); | ||
}); | ||
@@ -203,9 +203,9 @@ | ||
spyOn(https, 'request').andCallFake(function(options, httpsCallback) { | ||
var req = new MockRequest(); | ||
var res = new MockResponse(testOptions.response || {}); | ||
spyOn(https, 'request').andCallFake(function (options, httpsCallback) { | ||
var req = new MockRequest(), | ||
res = new MockResponse(testOptions.response || {}); | ||
httpsCallback(res); | ||
setTimeout(function() { | ||
setTimeout(function () { | ||
res.emit('data', '{ "message": "ok" }'); | ||
@@ -219,5 +219,5 @@ if (!req.isAborted) res.emit('end'); | ||
return client.request(options, function(err, body) { | ||
return Request.request(options, function (err, body) { | ||
if (callback) callback(err, body); | ||
}); | ||
}; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
84129
2181
+ Addedlodash@~1.3.1
+ Addedlodash@1.3.1(transitive)
- Removedunderscore@~1.5.1
- Removedunderscore@1.5.2(transitive)