Comparing version 0.29.1 to 1.0.0-alpha.1
@@ -1,233 +0,209 @@ | ||
/** | ||
* HTTP client. | ||
*/ | ||
'use strict'; | ||
/** | ||
* Module dependencies. | ||
*/ | ||
const events = require('events'); | ||
const http = require('http'); | ||
const https = require('https'); | ||
const url = require('url'); | ||
const util = require('util'); | ||
var events = require('events'); | ||
var http = require('http'); | ||
var https = require('https'); | ||
var url = require('url'); | ||
var util = require('util'); | ||
const constants = require('./constants'); | ||
const errors = require('./errors'); | ||
const meta = require('../package.json'); | ||
const utils = require('./utils'); | ||
var constants = require('./constants'); | ||
var errors = require('./errors'); | ||
var meta = require('../package.json'); | ||
var utils = require('./utils'); | ||
/** | ||
* Client | ||
*/ | ||
class Client extends events.EventEmitter { | ||
constructor(opts) { | ||
super(); | ||
function Client(opts) { | ||
if (!(this instanceof Client)) { | ||
return new Client(opts); | ||
} | ||
opts = opts || {}; | ||
events.EventEmitter.call(this); | ||
if (typeof opts === 'string') { | ||
opts = { baseUrl: opts }; | ||
} else { | ||
opts = utils.merge(opts); | ||
} | ||
opts = opts || {}; | ||
if (!opts.baseUrl) { | ||
throw new errors.ValidationError('baseUrl required'); | ||
} | ||
if (typeof opts === 'string') { | ||
opts = { baseUrl: opts }; | ||
} else { | ||
opts = utils.merge(opts); | ||
} | ||
if (!(opts.baseUrl instanceof url.Url)) { | ||
if (typeof opts.baseUrl !== 'string') { | ||
throw new errors.ValidationError('baseUrl must be a string: ' + | ||
opts.baseUrl); | ||
} | ||
if (!opts.baseUrl) { | ||
throw errors.Validation('baseUrl required'); | ||
} | ||
if (!(opts.baseUrl instanceof url.Url)) { | ||
if (typeof opts.baseUrl !== 'string') { | ||
throw errors.Validation('baseUrl must be a string: ' + opts.baseUrl); | ||
opts.baseUrl = url.parse(opts.baseUrl); | ||
} | ||
opts.baseUrl = url.parse(opts.baseUrl); | ||
} | ||
const path = opts.baseUrl.pathname; | ||
opts.baseUrl = utils.pick(opts.baseUrl, | ||
'auth', 'hostname', 'port', 'protocol'); | ||
opts.baseUrl.path = path; | ||
var path = opts.baseUrl.pathname; | ||
opts.baseUrl = utils.pick(opts.baseUrl, | ||
'auth', 'hostname', 'port', 'protocol'); | ||
opts.baseUrl.path = path; | ||
if (opts.baseUrl.path === '/') { | ||
opts.baseUrl.path = ''; | ||
} else if (opts.baseUrl.path[opts.baseUrl.path.length - 1] === '/') { | ||
throw new errors.ValidationError( | ||
'baseUrl must not end with a forward slash'); | ||
} | ||
if (opts.baseUrl.path === '/') { | ||
opts.baseUrl.path = ''; | ||
} else if (opts.baseUrl.path[opts.baseUrl.path.length - 1] === '/') { | ||
throw errors.Validation('baseUrl must not end with a forward slash'); | ||
} | ||
opts.headers = utils.mergeHeaders(opts.headers); | ||
if (opts.tags) { | ||
if (Array.isArray(opts.tags)) { | ||
opts.tags = opts.tags.slice(0); | ||
opts.headers = utils.mergeHeaders(opts.headers); | ||
if (opts.tags) { | ||
if (Array.isArray(opts.tags)) { | ||
opts.tags = opts.tags.slice(0); | ||
} else { | ||
throw new errors.ValidationError('tags must be an array'); | ||
} | ||
} else { | ||
throw errors.Validation('tags must be an array'); | ||
opts.tags = []; | ||
} | ||
} else { | ||
opts.tags = []; | ||
} | ||
if (opts.name && !~opts.tags.indexOf(opts.name)) { | ||
opts.tags.push(opts.name); | ||
} | ||
if (opts.name && !~opts.tags.indexOf(opts.name)) { | ||
opts.tags.push(opts.name); | ||
} | ||
opts.encoders = utils.merge(constants.ENCODERS, opts.encoders); | ||
opts.decoders = utils.merge(constants.DECODERS, opts.decoders); | ||
opts.encoders = utils.merge(constants.ENCODERS, opts.encoders); | ||
opts.decoders = utils.merge(constants.DECODERS, opts.decoders); | ||
this._opts = opts; | ||
this._exts = {}; | ||
} | ||
util.inherits(Client, events.EventEmitter); | ||
/** | ||
* Add information to error | ||
*/ | ||
Client.prototype._err = function(err, opts) { | ||
if (!err) return err; | ||
if (!(err instanceof Error)) err = new Error(err); | ||
if (opts && opts.name) { | ||
err.message = util.format('%s: %s', opts.name, err.message); | ||
this._opts = opts; | ||
this._exts = {}; | ||
} | ||
if (this._opts.name) { | ||
err.message = util.format('%s: %s', this._opts.name, err.message); | ||
} | ||
/** | ||
* Add information to error | ||
*/ | ||
_err(err, opts) { | ||
if (!err) return err; | ||
return err; | ||
}; | ||
if (!(err instanceof Error)) err = new Error(err); | ||
/** | ||
* Register an extension | ||
*/ | ||
if (opts && opts.name) { | ||
err.message = util.format('%s: %s', opts.name, err.message); | ||
} | ||
Client.prototype._ext = function(eventName, callback) { | ||
if (!eventName || typeof eventName !== 'string') { | ||
throw this._err(errors.Validation('extension eventName required')); | ||
} | ||
if (this._opts.name) { | ||
err.message = util.format('%s: %s', this._opts.name, err.message); | ||
} | ||
if (typeof callback !== 'function') { | ||
throw this._err(errors.Validation('extension callback required')); | ||
return err; | ||
} | ||
if (!this._exts[eventName]) this._exts[eventName] = []; | ||
/** | ||
* Register an extension | ||
*/ | ||
_ext(eventName, callback) { | ||
if (!eventName || typeof eventName !== 'string') { | ||
throw this._err(new errors.ValidationError( | ||
'extension eventName required')); | ||
} | ||
this._exts[eventName].push(callback); | ||
}; | ||
if (typeof callback !== 'function') { | ||
throw this._err(new errors.ValidationError( | ||
'extension callback required')); | ||
} | ||
/** | ||
* Register a plugin | ||
*/ | ||
if (!this._exts[eventName]) this._exts[eventName] = []; | ||
Client.prototype._plugin = function(plugin, options) { | ||
if (!plugin) { | ||
throw this._err(errors.Validation('plugin required')); | ||
this._exts[eventName].push(callback); | ||
} | ||
if (typeof plugin.register !== 'function') { | ||
throw this._err(errors.Validation('plugin must have register function')); | ||
} | ||
/** | ||
* Register a plugin | ||
*/ | ||
_plugin(plugin, options) { | ||
if (!plugin) { | ||
throw this._err(new errors.ValidationError('plugin required')); | ||
} | ||
var attributes = plugin.register.attributes; | ||
if (typeof plugin.register !== 'function') { | ||
throw this._err(new errors.ValidationError( | ||
'plugin must have register function')); | ||
} | ||
if (!attributes) { | ||
throw this._err(errors.Validation('plugin attributes required')); | ||
} | ||
const attributes = plugin.register.attributes; | ||
if (!attributes.name) { | ||
throw this._err(errors.Validation('plugin attributes name required')); | ||
} | ||
if (!attributes) { | ||
throw this._err(new errors.ValidationError('plugin attributes required')); | ||
} | ||
if (!attributes.version) { | ||
throw this._err(errors.Validation('plugin attributes version required')); | ||
} | ||
if (!attributes.name) { | ||
throw this._err(new errors.ValidationError( | ||
'plugin attributes name required')); | ||
} | ||
return plugin.register(this, options || {}); | ||
}; | ||
if (!attributes.version) { | ||
throw this._err(new errors.ValidationError( | ||
'plugin attributes version required')); | ||
} | ||
/** | ||
* Log request events | ||
*/ | ||
Client.prototype._log = function(tags, data) { | ||
return this.emit('log', tags, data); | ||
}; | ||
/** | ||
* Encode | ||
*/ | ||
Client.prototype._encode = function(mime, value) { | ||
if (!this._opts.encoders[mime]) { | ||
throw errors.Codec('unknown encoder: ' + mime); | ||
return plugin.register(this, options || {}); | ||
} | ||
try { | ||
return this._opts.encoders[mime](value); | ||
} catch (err) { | ||
err.message = 'encode (' + mime + ') failed: ' + err.message; | ||
throw errors.Codec(err); | ||
/** | ||
* Log request events | ||
*/ | ||
_log(tags, data) { | ||
return this.emit('log', tags, data); | ||
} | ||
}; | ||
/** | ||
* Decode | ||
*/ | ||
/** | ||
* Encode | ||
*/ | ||
_encode(mime, value) { | ||
if (!this._opts.encoders[mime]) { | ||
throw new errors.CodecError('unknown encoder: ' + mime); | ||
} | ||
Client.prototype._decode = function(mime, value) { | ||
if (!this._opts.decoders[mime]) { | ||
throw errors.Codec('unknown decoder: ' + mime); | ||
try { | ||
return this._opts.encoders[mime](value); | ||
} catch (err) { | ||
err.message = 'encode (' + mime + ') failed: ' + err.message; | ||
throw new errors.CodecError(err); | ||
} | ||
} | ||
try { | ||
return this._opts.decoders[mime](value); | ||
} catch (err) { | ||
err.message = 'decode (' + mime + ') failed: ' + err.message; | ||
throw errors.Codec(err); | ||
} | ||
}; | ||
/** | ||
* Decode | ||
*/ | ||
_decode(mime, value) { | ||
if (!this._opts.decoders[mime]) { | ||
throw new errors.CodecError('unknown decoder: ' + mime); | ||
} | ||
/** | ||
* Push ext list | ||
*/ | ||
Client.prototype.__push = function(request, name) { | ||
if (this._exts[name]) { | ||
request._stack.push.apply(request._stack, this._exts[name]); | ||
try { | ||
return this._opts.decoders[mime](value); | ||
} catch (err) { | ||
err.message = 'decode (' + mime + ') failed: ' + err.message; | ||
throw new errors.CodecError(err); | ||
} | ||
} | ||
if (request.opts && request.opts.exts && request.opts.exts[name]) { | ||
if (Array.isArray(request.opts.exts[name])) { | ||
request._stack.push.apply(request._stack, request.opts.exts[name]); | ||
} else { | ||
request._stack.push(request.opts.exts[name]); | ||
/** | ||
* Push ext list | ||
*/ | ||
__push(request, name) { | ||
if (this._exts[name]) { | ||
request._stack.push.apply(request._stack, this._exts[name]); | ||
} | ||
if (request.opts && request.opts.exts && request.opts.exts[name]) { | ||
if (Array.isArray(request.opts.exts[name])) { | ||
request._stack.push.apply(request._stack, request.opts.exts[name]); | ||
} else { | ||
request._stack.push(request.opts.exts[name]); | ||
} | ||
} | ||
} | ||
}; | ||
/** | ||
* Run request pipeline | ||
*/ | ||
/** | ||
* Run client request | ||
*/ | ||
_request(opts) { | ||
if (!opts) opts = {}; | ||
Client.prototype._request = function(opts) { | ||
var self = this; | ||
var request; | ||
if (this.__request) { | ||
request = this.__request; | ||
opts = request.opts; | ||
self = request._client; | ||
} else { | ||
request = { | ||
_args: Array.prototype.slice.call(arguments), | ||
_client: this, | ||
const request = { | ||
opts: opts, | ||
@@ -237,11 +213,2 @@ state: {}, | ||
if (!opts) opts = request.opts = {}; | ||
if (request._args.length > 1) { | ||
request._callback = request._args[request._args.length - 1]; | ||
} else { | ||
return self.emit('error', self._err( | ||
errors.Validation('callback required'), opts)); | ||
} | ||
// if ctx is an event emitter we use it to abort requests when done is | ||
@@ -254,3 +221,3 @@ // emitted | ||
// combine global and request tags | ||
opts.tags = (opts.tags || []).concat(self._opts.tags); | ||
opts.tags = (opts.tags || []).concat(this._opts.tags); | ||
@@ -267,8 +234,8 @@ // inject request name into tags if not already defined | ||
// restart request | ||
request.retry = function() { | ||
request.retry = () => { | ||
if (request._retryable === false) { | ||
throw errors.Validation('request is not retryable'); | ||
throw new errors.ValidationError('request is not retryable'); | ||
} | ||
self._log(['papi', 'request', 'retry'].concat(request.opts.tags)); | ||
this._log(['papi', 'request', 'retry'].concat(request.opts.tags)); | ||
@@ -281,3 +248,3 @@ delete request.body; | ||
self._request.call({ __request: request }); | ||
this.__pipeline(request); | ||
}; | ||
@@ -287,111 +254,109 @@ | ||
self.__push(request, 'onCreate'); | ||
this.__push(request, 'onCreate'); | ||
request._stack.push(self.__create); | ||
request._stack.push(this.__create); | ||
self.__push(request, 'onRequest'); | ||
this.__push(request, 'onRequest'); | ||
request._stack.push(self.__execute); | ||
request._stack.push(this.__execute); | ||
self.__push(request, 'onResponse'); | ||
this.__push(request, 'onResponse'); | ||
request._stack.push.apply( | ||
request._stack, | ||
request._args.slice(1, request._args.length - 1) | ||
); | ||
} | ||
request._stack.push.apply(request._stack, | ||
Array.prototype.slice.call(arguments, 1)); | ||
var i = 0; | ||
function next(err) { | ||
if (err) return request._callback(self._err(err, opts)); | ||
return new Promise((resolve, reject) => { | ||
request._resolve = resolve; | ||
request._reject = reject; | ||
// middlware can call next(false, args...) to stop middleware | ||
if (err === false) { | ||
return request._callback.apply(null, | ||
Array.prototype.slice.call(arguments, 1)); | ||
} | ||
var fn = request._stack[i++]; | ||
if (fn) { | ||
fn.call(self, request, next); | ||
} else { | ||
request._callback.call(self, self._err(request.err, opts), request.res); | ||
} | ||
this.__pipeline(request); | ||
}); | ||
} | ||
next(); | ||
}; | ||
/** | ||
* Run request pipeline | ||
*/ | ||
__pipeline(request) { | ||
const self = this; | ||
/** | ||
* Create HTTP request | ||
*/ | ||
let i = 0; | ||
function next(err, value, maybeValue) { | ||
if (err) return request._reject(self._err(err, request.opts)); | ||
Client.prototype.__create = function(request, next) { | ||
var self = this; | ||
// middlware can call next(false, value) to stop middleware | ||
if (err === false) { | ||
// basic backwards compatibility for old interface | ||
if (value instanceof Error) { | ||
return request._reject(value); | ||
} else if (!value && maybeValue) { | ||
value = maybeValue; | ||
} | ||
var opts = request.opts; | ||
var path = opts.path; | ||
return request._resolve(value); | ||
} | ||
if (typeof path !== 'string') { | ||
return next(errors.Validation('path required')); | ||
const fn = request._stack[i++]; | ||
if (fn) { | ||
fn.call(self, request, next); | ||
} else if (request.err) { | ||
request._reject(self._err(request.err, request.opts)); | ||
} else { | ||
request._resolve(request.res); | ||
} | ||
} | ||
next(); | ||
} | ||
var headers = utils.mergeHeaders(self._opts.headers, opts.headers); | ||
/** | ||
* Create HTTP request | ||
*/ | ||
__create(request, next) { | ||
const opts = request.opts; | ||
let path = opts.path; | ||
// path | ||
try { | ||
path = path.replace(/\{(\w+)\}/g, function(src, dst) { | ||
if (!opts.params.hasOwnProperty(dst)) { | ||
throw errors.Validation('missing param: ' + dst); | ||
} | ||
if (typeof path !== 'string') { | ||
return next(new errors.ValidationError('path required')); | ||
} | ||
var part = opts.params[dst] || ''; | ||
const headers = utils.mergeHeaders(this._opts.headers, opts.headers); | ||
// optionally disable param encoding | ||
return part.encode === false && part.toString ? | ||
part.toString() : encodeURIComponent(part); | ||
}); | ||
} catch (err) { | ||
return next(err); | ||
} | ||
// path | ||
try { | ||
path = path.replace(/\{(\w+)\}/g, (src, dst) => { | ||
if (!opts.params.hasOwnProperty(dst)) { | ||
throw new errors.ValidationError('missing param: ' + dst); | ||
} | ||
// query | ||
if (!utils.isEmpty(opts.query)) { | ||
try { | ||
path += '?' + self._encode('application/x-www-form-urlencoded', | ||
opts.query).toString(); | ||
let part = opts.params[dst] || ''; | ||
// optionally disable param encoding | ||
return part.encode === false && part.toString ? | ||
part.toString() : encodeURIComponent(part); | ||
}); | ||
} catch (err) { | ||
return next(err); | ||
} | ||
} | ||
// body | ||
if (opts.body !== undefined) { | ||
var mime = constants.MIME_ALIAS[opts.type] || | ||
headers['content-type'] || | ||
constants.MIME_ALIAS[self._opts.type]; | ||
var isFunction = typeof opts.body === 'function'; | ||
if (isFunction) { | ||
// query | ||
if (!utils.isEmpty(opts.query)) { | ||
try { | ||
request.body = opts.body(); | ||
path += '?' + this._encode('application/x-www-form-urlencoded', | ||
opts.query).toString(); | ||
} catch (err) { | ||
return next(err); | ||
} | ||
} else { | ||
request.body = opts.body; | ||
} | ||
var isBuffer = Buffer.isBuffer(request.body); | ||
var isStream = utils.isReadableStream(request.body); | ||
// body | ||
if (opts.body !== undefined) { | ||
let mime = constants.MIME_ALIAS[opts.type] || | ||
headers['content-type'] || | ||
constants.MIME_ALIAS[this._opts.type]; | ||
if (!isBuffer && !isStream && !mime) { | ||
return next(errors.Validation('type required')); | ||
} | ||
let isFunction = typeof opts.body === 'function'; | ||
if (!isBuffer && !isStream) { | ||
if (self._opts.encoders[mime]) { | ||
if (isFunction) { | ||
try { | ||
request.body = this._encode(mime, request.body); | ||
request.body = opts.body(); | ||
} catch (err) { | ||
@@ -401,262 +366,295 @@ return next(err); | ||
} else { | ||
return next(errors.Codec('type is unknown: ' + mime)); | ||
request.body = opts.body; | ||
} | ||
} | ||
if (!headers['content-type'] && mime) { | ||
headers['content-type'] = mime + '; charset=' + constants.CHARSET; | ||
} | ||
const isBuffer = Buffer.isBuffer(request.body); | ||
const isStream = utils.isReadableStream(request.body); | ||
if (isStream) { | ||
if (!isFunction) request._retryable = false; | ||
} else { | ||
headers['content-length'] = request.body.length; | ||
} | ||
} else if (!~constants.EXCLUDE_CONTENT_LENGTH.indexOf(opts.method)) { | ||
headers['content-length'] = 0; | ||
} | ||
if (!isBuffer && !isStream && !mime) { | ||
return next(new errors.ValidationError('type required')); | ||
} | ||
// response pipe | ||
if (opts.pipe) { | ||
var isPipeFunction = typeof opts.pipe === 'function'; | ||
if (!isBuffer && !isStream) { | ||
if (this._opts.encoders[mime]) { | ||
try { | ||
request.body = this._encode(mime, request.body); | ||
} catch (err) { | ||
return next(err); | ||
} | ||
} else { | ||
return next(new errors.CodecError('type is unknown: ' + mime)); | ||
} | ||
} | ||
if (isPipeFunction) { | ||
try { | ||
request.pipe = opts.pipe(); | ||
} catch (err) { | ||
return next(err); | ||
if (!headers['content-type'] && mime) { | ||
headers['content-type'] = mime + '; charset=' + constants.CHARSET; | ||
} | ||
} else { | ||
request.pipe = opts.pipe; | ||
request._retryable = false; | ||
if (isStream) { | ||
if (!isFunction) request._retryable = false; | ||
} else { | ||
headers['content-length'] = request.body.length; | ||
} | ||
} else if (!~constants.EXCLUDE_CONTENT_LENGTH.indexOf( | ||
(opts.method || '').toUpperCase())) { | ||
headers['content-length'] = 0; | ||
} | ||
if (!utils.isWritableStream(request.pipe)) { | ||
return next(errors.Validation('pipe must be a writable stream')); | ||
} | ||
} | ||
// response pipe | ||
if (opts.pipe) { | ||
const isPipeFunction = typeof opts.pipe === 'function'; | ||
// build http.request options | ||
request.req = utils.merge( | ||
utils.pick(self._opts, constants.CLIENT_OPTIONS), | ||
utils.pick(self._opts.baseUrl, 'auth', 'hostname', 'port', 'path'), | ||
utils.pick(opts, constants.REQUEST_OPTIONS), | ||
{ headers: headers } | ||
); | ||
if (isPipeFunction) { | ||
try { | ||
request.pipe = opts.pipe(); | ||
} catch (err) { | ||
return next(err); | ||
} | ||
} else { | ||
request.pipe = opts.pipe; | ||
// append request path to baseUrl | ||
request.req.path += path; | ||
request._retryable = false; | ||
} | ||
// pick http transport | ||
if (self._opts.baseUrl.protocol === 'https:') { | ||
request.transport = https; | ||
if (!request.req.port) request.req.port = 443; | ||
} else { | ||
request.transport = http; | ||
if (!request.req.port) request.req.port = 80; | ||
} | ||
if (!utils.isWritableStream(request.pipe)) { | ||
return next(new errors.ValidationError( | ||
'pipe must be a writable stream')); | ||
} | ||
} | ||
if (request.req.auth === null) delete request.req.auth; | ||
// build http.request options | ||
request.req = utils.merge( | ||
utils.pick(this._opts, constants.CLIENT_OPTIONS), | ||
utils.pick(this._opts.baseUrl, 'auth', 'hostname', 'port', 'path'), | ||
utils.pick(opts, constants.REQUEST_OPTIONS), | ||
{ headers: headers } | ||
); | ||
next(); | ||
}; | ||
// append request path to baseUrl | ||
request.req.path += path; | ||
/** | ||
* Execute HTTP request | ||
*/ | ||
// pick http transport | ||
if (this._opts.baseUrl.protocol === 'https:') { | ||
request.transport = https; | ||
if (!request.req.port) request.req.port = 443; | ||
} else { | ||
request.transport = http; | ||
if (!request.req.port) request.req.port = 80; | ||
} | ||
Client.prototype.__execute = function(request, next) { | ||
var self = this; | ||
if (request.req.auth === null) delete request.req.auth; | ||
if (request.ctx) { | ||
if (request.ctx.canceled === true) { | ||
return next(errors.Validation('ctx already canceled')); | ||
} else if (request.ctx.finished === true) { | ||
return next(errors.Validation('ctx already finished')); | ||
} | ||
next(); | ||
} | ||
var done = false; | ||
/** | ||
* Execute HTTP request | ||
*/ | ||
__execute(request, next) { | ||
if (request.ctx) { | ||
if (request.ctx.canceled === true) { | ||
return next(new errors.ValidationError('ctx already canceled')); | ||
} else if (request.ctx.finished === true) { | ||
return next(new errors.ValidationError('ctx already finished')); | ||
} | ||
} | ||
var opts = request.opts; | ||
let done = false; | ||
var abort; | ||
var timeoutId; | ||
var timeout = opts.hasOwnProperty('timeout') ? | ||
opts.timeout : self._opts.timeout; | ||
const opts = request.opts; | ||
self._log(['papi', 'request'].concat(opts.tags), request.req); | ||
let abort; | ||
let timeoutId; | ||
let timeout = opts.hasOwnProperty('timeout') ? | ||
opts.timeout : this._opts.timeout; | ||
var req = request.transport.request(request.req); | ||
this._log(['papi', 'request'].concat(opts.tags), request.req); | ||
var userAgent = req.getHeader('user-agent'); | ||
const req = request.transport.request(request.req); | ||
if (userAgent === undefined) { | ||
req.setHeader('user-agent', 'papi/' + meta.version); | ||
} else if (userAgent === null) { | ||
req.removeHeader('user-agent'); | ||
} | ||
const userAgent = req.getHeader('user-agent'); | ||
req.on('error', function(err) { | ||
self._log(['papi', 'request', 'error'].concat(opts.tags), err); | ||
if (userAgent === undefined) { | ||
req.setHeader('user-agent', 'papi/' + meta.version); | ||
} else if (userAgent === null) { | ||
req.removeHeader('user-agent'); | ||
} | ||
if (done) return; | ||
done = true; | ||
req.on('error', err => { | ||
this._log(['papi', 'request', 'error'].concat(opts.tags), err); | ||
if (abort) request.ctx.removeListener('cancel', abort); | ||
if (timeoutId) clearTimeout(timeoutId); | ||
if (done) return; | ||
done = true; | ||
request.err = err; | ||
next(); | ||
}); | ||
if (abort) request.ctx.removeListener('cancel', abort); | ||
if (timeoutId) clearTimeout(timeoutId); | ||
if (request.ctx) { | ||
abort = function() { | ||
req.abort(); | ||
req.emit('error', errors.Abort('request aborted')); | ||
}; | ||
request.err = err; | ||
next(); | ||
}); | ||
request.ctx.once('cancel', abort); | ||
} | ||
if (request.ctx) { | ||
abort = () => { | ||
req.abort(); | ||
req.emit('error', new errors.AbortError('request aborted')); | ||
}; | ||
// set request and absolute timeout | ||
if (timeout && timeout > 0) { | ||
timeoutId = setTimeout(function() { | ||
req.emit('timeout'); | ||
req.abort(); | ||
}, timeout); | ||
request.ctx.once('cancel', abort); | ||
} | ||
req.setTimeout(timeout); | ||
} | ||
// set request and absolute timeout | ||
if (timeout && timeout > 0) { | ||
timeoutId = setTimeout(() => { | ||
req.emit('timeout'); | ||
req.abort(); | ||
}, timeout); | ||
req.on('timeout', function(err) { | ||
self._log(['papi', 'request', 'error', 'timeout'].concat(opts.tags)); | ||
if (err) { | ||
err = errors.Timeout(err); | ||
} else { | ||
err = errors.Timeout('request timed out (' + timeout + 'ms)'); | ||
req.setTimeout(timeout); | ||
} | ||
req.emit('error', err); | ||
}); | ||
req.on('response', function(res) { | ||
var chunks = []; | ||
var bodyLength = 0; | ||
self._log(['papi', 'response'].concat(opts.tags), { | ||
method: opts.method, | ||
path: req.path, | ||
statusCode: res.statusCode, | ||
headers: res.headers, | ||
remoteAddress: res.connection && res.connection.remoteAddress, | ||
remotePort: res.connection && res.connection.remotePort, | ||
req.on('timeout', err => { | ||
this._log(['papi', 'request', 'error', 'timeout'].concat(opts.tags)); | ||
if (err) { | ||
err = new errors.TimeoutError(err); | ||
} else { | ||
err = new errors.TimeoutError('request timed out (' + timeout + 'ms)'); | ||
} | ||
req.emit('error', err); | ||
}); | ||
request.res = res; | ||
req.on('response', (res) => { | ||
let chunks = []; | ||
let bodyLength = 0; | ||
if (request.pipe) { | ||
res.pipe(request.pipe); | ||
} else { | ||
res.on('data', function(chunk) { | ||
chunks.push(chunk); | ||
bodyLength += chunk.length; | ||
this._log(['papi', 'response'].concat(opts.tags), { | ||
method: opts.method, | ||
path: req.path, | ||
statusCode: res.statusCode, | ||
headers: res.headers, | ||
remoteAddress: res.socket && res.socket.remoteAddress, | ||
remotePort: res.socket && res.socket.remotePort, | ||
}); | ||
} | ||
res.on('end', function() { | ||
if (done) return; | ||
done = true; | ||
request.res = res; | ||
if (abort) request.ctx.removeListener('cancel', abort); | ||
if (timeoutId) clearTimeout(timeoutId); | ||
if (request.pipe) { | ||
res.pipe(request.pipe); | ||
} else { | ||
res.on('data', (chunk) => { | ||
chunks.push(chunk); | ||
bodyLength += chunk.length; | ||
}); | ||
} | ||
// body content mime | ||
var mime; | ||
res.on('end', () => { | ||
if (done) return; | ||
done = true; | ||
// decode body | ||
if (bodyLength) { | ||
res.body = Buffer.concat(chunks, bodyLength); | ||
if (abort) request.ctx.removeListener('cancel', abort); | ||
if (timeoutId) clearTimeout(timeoutId); | ||
// don't decode if user explicitly asks for buffer | ||
if (!opts.buffer) { | ||
mime = (res.headers['content-type'] || '').split(';')[0].trim(); | ||
// body content mime | ||
let mime; | ||
if (self._opts.decoders[mime]) { | ||
try { | ||
res.body = self._decode(mime, res.body); | ||
} catch (err) { | ||
request.err = err; | ||
return next(); | ||
// decode body | ||
if (bodyLength) { | ||
res.body = Buffer.concat(chunks, bodyLength); | ||
// don't decode if user explicitly asks for buffer | ||
if (!opts.buffer) { | ||
mime = (res.headers['content-type'] || '').split(';')[0].trim(); | ||
if (this._opts.decoders[mime]) { | ||
try { | ||
res.body = this._decode(mime, res.body); | ||
} catch (err) { | ||
request.err = err; | ||
return next(); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
// any non-200 is consider an error | ||
if (Math.floor(res.statusCode / 100) !== 2) { | ||
var err = errors.Response(); | ||
// any non-200 is consider an error | ||
if (Math.floor(res.statusCode / 100) !== 2) { | ||
var message; | ||
if (res.body && mime === 'text/plain' && res.body.length < 80) { | ||
err.message = res.body; | ||
} | ||
if (res.body && mime === 'text/plain' && res.body.length < 80) { | ||
message = res.body; | ||
} | ||
if (!err.message) { | ||
if (http.STATUS_CODES[res.statusCode]) { | ||
err.message = http.STATUS_CODES[res.statusCode].toLowerCase(); | ||
} else { | ||
err.message = 'request failed: ' + res.statusCode; | ||
if (!message) { | ||
if (http.STATUS_CODES[res.statusCode]) { | ||
message = http.STATUS_CODES[res.statusCode].toLowerCase(); | ||
} else { | ||
message = 'request failed: ' + res.statusCode; | ||
} | ||
} | ||
request.err = new errors.ResponseError(message, res); | ||
} | ||
err.statusCode = res.statusCode; | ||
request.err = err; | ||
} | ||
next(); | ||
next(); | ||
}); | ||
}); | ||
}); | ||
if (utils.isReadableStream(request.body)) { | ||
request.body.pipe(req); | ||
} else { | ||
req.end(request.body); | ||
if (utils.isReadableStream(request.body)) { | ||
request.body.pipe(req); | ||
} else { | ||
req.end(request.body); | ||
} | ||
} | ||
}; | ||
/** | ||
* Shortcuts | ||
*/ | ||
__shortcut(method, callerArgs) { | ||
let args; | ||
let opts = callerArgs[0]; | ||
constants.METHODS.forEach(function(method) { | ||
var reqMethod = method.toUpperCase(); | ||
if (typeof opts === 'string') { | ||
args = Array.prototype.slice.call(callerArgs); | ||
args[0] = opts = { path: opts }; | ||
} else if (!opts) { | ||
args = Array.prototype.slice.call(callerArgs); | ||
args[0] = opts = {}; | ||
} else { | ||
args = callerArgs; | ||
} | ||
Client.prototype['_' + method] = function(opts) { | ||
var args; | ||
opts.method = method; | ||
if (typeof opts === 'string') { | ||
opts = { path: opts, method: reqMethod }; | ||
return this._request.apply(this, args); | ||
} | ||
args = Array.prototype.slice.call(arguments); | ||
args[0] = opts; | ||
_options() { | ||
return this.__shortcut('OPTIONS', arguments); | ||
} | ||
return this._request.apply(this, args); | ||
} else if (!opts) { | ||
args = Array.prototype.slice.call(arguments); | ||
args[0] = {}; | ||
_get() { | ||
return this.__shortcut('GET', arguments); | ||
} | ||
return this._request.apply(this, args); | ||
} | ||
_head() { | ||
return this.__shortcut('HEAD', arguments); | ||
} | ||
opts.method = reqMethod; | ||
_post() { | ||
return this.__shortcut('POST', arguments); | ||
} | ||
return this._request.apply(this, arguments); | ||
}; | ||
}); | ||
_put() { | ||
return this.__shortcut('PUT', arguments); | ||
} | ||
/** | ||
* Module exports. | ||
*/ | ||
_delete() { | ||
return this.__shortcut('DELETE', arguments); | ||
} | ||
_del() { | ||
return this.__shortcut('DELETE', arguments); | ||
} | ||
_patch() { | ||
return this.__shortcut('PATCH', arguments); | ||
} | ||
} | ||
exports.Client = Client; |
@@ -1,13 +0,5 @@ | ||
/** | ||
* Encoders/Decoders | ||
*/ | ||
'use strict'; | ||
/** | ||
* Module dependencies. | ||
*/ | ||
const querystring = require('querystring'); | ||
var querystring = require('querystring'); | ||
/** | ||
@@ -17,12 +9,7 @@ * Text | ||
var text = {}; | ||
text.encode = function(data) { | ||
return Buffer.from(data, 'utf8'); | ||
const text = { | ||
encode: data => Buffer.from(data, 'utf8'), | ||
decode: data => Buffer.isBuffer(data) ? data.toString() : data, | ||
}; | ||
text.decode = function(data) { | ||
return Buffer.isBuffer(data) ? data.toString() : data; | ||
}; | ||
/** | ||
@@ -32,12 +19,7 @@ * JSON | ||
var json = {}; | ||
json.encode = function(data) { | ||
return text.encode(JSON.stringify(data)); | ||
const json = { | ||
encode: data => text.encode(JSON.stringify(data)), | ||
decode: data => JSON.parse(text.decode(data)), | ||
}; | ||
json.decode = function(data) { | ||
return JSON.parse(text.decode(data)); | ||
}; | ||
/** | ||
@@ -47,18 +29,9 @@ * Form | ||
var form = {}; | ||
form.encode = function(data) { | ||
return text.encode(querystring.stringify(data)); | ||
const form = { | ||
encode: data => text.encode(querystring.stringify(data)), | ||
decode: data => querystring.parse(text.decode(data)), | ||
}; | ||
form.decode = function(data) { | ||
return querystring.parse(text.decode(data)); | ||
}; | ||
/** | ||
* Module exports. | ||
*/ | ||
exports.json = json; | ||
exports.form = form; | ||
exports.text = text; |
'use strict'; | ||
/** | ||
* Module dependencies. | ||
*/ | ||
const codecs = require('./codecs'); | ||
var codecs = require('./codecs'); | ||
/** | ||
* Constants | ||
*/ | ||
exports.CHARSET = 'utf-8'; | ||
exports.ENCODERS = { | ||
exports.ENCODERS = Object.freeze({ | ||
'application/json': codecs.json.encode, | ||
'application/x-www-form-urlencoded': codecs.form.encode, | ||
'text/plain': codecs.text.encode, | ||
}; | ||
}); | ||
exports.DECODERS = { | ||
exports.DECODERS = Object.freeze({ | ||
'application/json': codecs.json.decode, | ||
@@ -27,15 +19,5 @@ 'application/x-www-form-urlencoded': codecs.form.decode, | ||
'text/plain': codecs.text.decode, | ||
}; | ||
}); | ||
exports.METHODS = [ | ||
'options', | ||
'get', | ||
'head', | ||
'post', | ||
'put', | ||
'delete', | ||
'patch', | ||
]; | ||
exports.MIME_ALIAS = { | ||
exports.MIME_ALIAS = Object.freeze({ | ||
form: 'application/x-www-form-urlencoded', | ||
@@ -46,11 +28,11 @@ json: 'application/json', | ||
text: 'text/plain', | ||
}; | ||
}); | ||
exports.EXCLUDE_CONTENT_LENGTH = [ | ||
exports.EXCLUDE_CONTENT_LENGTH = Object.freeze([ | ||
'GET', | ||
'HEAD', | ||
'OPTIONS', | ||
]; | ||
]); | ||
exports.CLIENT_OPTIONS = [ | ||
exports.CLIENT_OPTIONS = Object.freeze([ | ||
'agent', | ||
@@ -74,6 +56,6 @@ // tls | ||
'sessionIdContext', | ||
]; | ||
]); | ||
exports.REQUEST_OPTIONS = exports.CLIENT_OPTIONS.concat([ | ||
exports.REQUEST_OPTIONS = Object.freeze(exports.CLIENT_OPTIONS.concat([ | ||
'method', | ||
]); | ||
])); |
@@ -1,90 +0,67 @@ | ||
/** | ||
* Errors | ||
*/ | ||
'use strict'; | ||
/** | ||
* Create | ||
*/ | ||
class PapiError extends Error { | ||
constructor(message) { | ||
if (message instanceof Error) { | ||
super(message.message); | ||
this.cause = message; | ||
} else { | ||
super(message); | ||
} | ||
function create(message) { | ||
var error = message instanceof Error ? | ||
message : | ||
new Error(message ? message : undefined); | ||
error.isPapi = true; | ||
return error; | ||
this.isPapi = true; | ||
} | ||
} | ||
/** | ||
* Codec | ||
*/ | ||
class CodecError extends PapiError { | ||
constructor(message) { | ||
super(message); | ||
function codec(message) { | ||
var error = create(message); | ||
error.isCodec = true; | ||
return error; | ||
this.isCodec = true; | ||
} | ||
} | ||
/** | ||
* Response | ||
*/ | ||
class ResponseError extends PapiError { | ||
constructor(message, response) { | ||
super(message); | ||
function response(message) { | ||
var error = create(message); | ||
this.isResponse = true; | ||
error.isResponse = true; | ||
this.response = response; | ||
} | ||
return error; | ||
get statusCode() { | ||
return this.response.statusCode; | ||
} | ||
} | ||
/** | ||
* Abort | ||
*/ | ||
class AbortError extends PapiError { | ||
constructor(message) { | ||
super(message); | ||
function abort(message) { | ||
var error = create(message); | ||
error.isAbort = true; | ||
return error; | ||
this.isAbort = true; | ||
} | ||
} | ||
/** | ||
* Timeout | ||
*/ | ||
class TimeoutError extends PapiError { | ||
constructor(message) { | ||
super(message); | ||
function timeout(message) { | ||
var error = create(message); | ||
error.isTimeout = true; | ||
return error; | ||
this.isTimeout = true; | ||
} | ||
} | ||
/** | ||
* Validation | ||
*/ | ||
class ValidationError extends PapiError { | ||
constructor(message) { | ||
super(message); | ||
function validation(message) { | ||
var error = create(message); | ||
error.isValidation = true; | ||
return error; | ||
this.isValidation = true; | ||
} | ||
} | ||
/** | ||
* Module exports. | ||
*/ | ||
exports.Codec = codec; | ||
exports.Response = response; | ||
exports.Abort = abort; | ||
exports.Timeout = timeout; | ||
exports.Validation = validation; | ||
exports.create = create; | ||
exports.PapiError = PapiError; | ||
exports.CodecError = CodecError; | ||
exports.ResponseError = ResponseError; | ||
exports.AbortError = AbortError; | ||
exports.TimeoutError = TimeoutError; | ||
exports.ValidationError = ValidationError; |
'use strict'; | ||
/** | ||
* Module dependencies. | ||
*/ | ||
const Client = require('./client').Client; | ||
const codecs = require('./codecs'); | ||
const errors = require('./errors'); | ||
const shortcuts = require('./shortcuts'); | ||
const tools = require('./tools'); | ||
var Client = require('./client').Client; | ||
var codecs = require('./codecs'); | ||
var shortcuts = require('./shortcuts'); | ||
var tools = require('./tools'); | ||
exports.Client = Client; | ||
/** | ||
* Module exports. | ||
*/ | ||
exports.PapiError = errors.PapiError; | ||
exports.CodecError = errors.CodecError; | ||
exports.ResponseError = errors.ResponseError; | ||
exports.AbortError = errors.AbortError; | ||
exports.TimeoutError = errors.TimeoutError; | ||
exports.ValidationError = errors.ValidationError; | ||
exports.Client = Client; | ||
exports.request = shortcuts.request; | ||
@@ -19,0 +19,0 @@ exports.get = shortcuts.method('GET'); |
'use strict'; | ||
/** | ||
* Module dependencies. | ||
*/ | ||
const url = require('url'); | ||
var url = require('url'); | ||
const Client = require('./client').Client; | ||
const errors = require('./errors'); | ||
var Client = require('./client').Client; | ||
var errors = require('./errors'); | ||
/** | ||
@@ -23,31 +19,20 @@ * Request. | ||
try { | ||
if (!opts.url) { | ||
throw errors.Validation('url required'); | ||
} | ||
if (!opts.url) { | ||
return Promise.reject(new errors.ValidationError('url required')); | ||
} | ||
if (typeof opts.url !== 'string') { | ||
throw errors.Validation('url must be a string'); | ||
} | ||
if (typeof opts.url !== 'string') { | ||
return Promise.reject(new errors.ValidationError('url must be a string')); | ||
} | ||
var baseUrl = url.parse(opts.url); | ||
const baseUrl = url.parse(opts.url); | ||
opts.path = baseUrl.pathname.replace('%7B', '{').replace('%7D', '}'); | ||
baseUrl.pathname = ''; | ||
opts.path = baseUrl.pathname.replace('%7B', '{').replace('%7D', '}'); | ||
baseUrl.pathname = ''; | ||
var client = new Client({ baseUrl: baseUrl }); | ||
const client = new Client({ baseUrl: baseUrl }); | ||
delete opts.url; | ||
delete opts.url; | ||
client._request.apply(client, arguments); | ||
} catch (err) { | ||
var callback = arguments[arguments.length - 1]; | ||
if (typeof callback !== 'function') { | ||
err.message = 'no callback: ' + err.message; | ||
throw err; | ||
} | ||
callback(err); | ||
} | ||
return client._request.apply(client, arguments); | ||
} | ||
@@ -69,11 +54,7 @@ | ||
request.apply(null, arguments); | ||
return request.apply(null, arguments); | ||
}; | ||
} | ||
/** | ||
* Module exports. | ||
*/ | ||
exports.method = method; | ||
exports.request = request; |
@@ -1,7 +0,5 @@ | ||
/** | ||
* Random useful tools. | ||
*/ | ||
'use strict'; | ||
const util = require('util'); | ||
/** | ||
@@ -26,5 +24,7 @@ * Walk "standard" library | ||
Object.keys(obj.prototype).forEach(function(key) { | ||
var v = obj.prototype[key]; | ||
Object.getOwnPropertyNames(obj.prototype).forEach(key => { | ||
if (key === 'constructor') return; | ||
const v = obj.prototype[key]; | ||
if (!key.match(/^[a-z]+/)) return; | ||
@@ -38,9 +38,9 @@ if (!tree.methods) tree.methods = {}; | ||
var meta = obj.meta || {}; | ||
const meta = obj.meta || {}; | ||
tree.methods[key].type = meta[key] && meta[key].type || 'callback'; | ||
tree.methods[key].type = meta[key] && meta[key].type || 'promise'; | ||
}); | ||
Object.keys(obj).forEach(function(key) { | ||
var v = obj[key]; | ||
Object.keys(obj).forEach(key => { | ||
const v = obj[key]; | ||
@@ -62,54 +62,15 @@ if (!key.match(/^[A-Z]+/)) return; | ||
/** | ||
* Callback wrapper | ||
* Converts promise methods to callbacks | ||
*/ | ||
function fromCallback(fn) { | ||
return new Promise(function(resolve, reject) { | ||
try { | ||
return fn(function(err, data) { | ||
if (err) return reject(err); | ||
return resolve(data); | ||
}); | ||
} catch (err) { | ||
return reject(err); | ||
} | ||
}); | ||
} | ||
/** | ||
* Wrap callbacks with promises | ||
*/ | ||
function promisify(client, wrapper) { | ||
function callbackify(client) { | ||
if (!client) throw new Error('client required'); | ||
if (!wrapper) { | ||
if (global.Promise) { | ||
wrapper = fromCallback; | ||
} else { | ||
throw new Error('wrapper required'); | ||
} | ||
} else if (typeof wrapper !== 'function') { | ||
throw new Error('wrapper must be a function'); | ||
} | ||
var patch = function(client, tree) { | ||
Object.keys(tree.methods).forEach(function(key) { | ||
var method = tree.methods[key]; | ||
var fn = client[method.name]; | ||
const patch = (client, tree) => { | ||
Object.keys(tree.methods).forEach((key) => { | ||
const method = tree.methods[key]; | ||
const fn = client[method.name]; | ||
if (method.type === 'callback' && !client[method.name]._wrapCallback) { | ||
client[method.name] = function() { | ||
// use callback if provided | ||
if (typeof arguments[arguments.length - 1] === 'function') { | ||
return fn.apply(client, arguments); | ||
} | ||
// otherwise return promise | ||
var args = Array.prototype.slice.call(arguments); | ||
return wrapper(function(callback) { | ||
args.push(callback); | ||
return fn.apply(client, args); | ||
}); | ||
}; | ||
client[method.name]._wrapped = true; | ||
if (method.type === 'promise') { | ||
client[method.name] = util.callbackify(fn); | ||
} | ||
@@ -119,4 +80,4 @@ }); | ||
if (tree.objects) { | ||
Object.keys(tree.objects).forEach(function(key) { | ||
var clientKey = key[0].toLowerCase() + key.slice(1); | ||
Object.keys(tree.objects).forEach((key) => { | ||
const clientKey = key[0].toLowerCase() + key.slice(1); | ||
patch(client[clientKey], tree.objects[key]); | ||
@@ -130,7 +91,3 @@ }); | ||
/** | ||
* Module exports. | ||
*/ | ||
exports.promisify = promisify; | ||
exports.callbackify = callbackify; | ||
exports.walk = walk; |
@@ -1,5 +0,1 @@ | ||
/** | ||
* Helper functions | ||
*/ | ||
'use strict'; | ||
@@ -14,3 +10,3 @@ | ||
for (var p in obj) { | ||
for (let p in obj) { | ||
if (obj.hasOwnProperty(p)) return false; | ||
@@ -53,16 +49,16 @@ } | ||
function merge() { | ||
var data = {}; | ||
const data = {}; | ||
if (!arguments.length) return data; | ||
for (var i = 0; i < arguments.length; i++) { | ||
const arg = arguments[i]; | ||
var args = Array.prototype.slice.call(arguments, 0); | ||
if (arg != null) { | ||
for (const key in arg) { | ||
if (Object.prototype.hasOwnProperty.call(arg, key)) { | ||
data[key] = arg[key]; | ||
} | ||
} | ||
} | ||
} | ||
args.forEach(function(obj) { | ||
if (!obj) return; | ||
Object.keys(obj).forEach(function(key) { | ||
data[key] = obj[key]; | ||
}); | ||
}); | ||
return data; | ||
@@ -76,16 +72,16 @@ } | ||
function mergeHeaders() { | ||
var data = {}; | ||
const data = {}; | ||
if (!arguments.length) return data; | ||
for (var i = 0; i < arguments.length; i++) { | ||
const arg = arguments[i]; | ||
var args = Array.prototype.slice.call(arguments, 0); | ||
if (arg != null) { | ||
for (const key in arg) { | ||
if (Object.prototype.hasOwnProperty.call(arg, key)) { | ||
data[key.toLowerCase()] = arg[key]; | ||
} | ||
} | ||
} | ||
} | ||
args.forEach(function(obj) { | ||
if (!obj) return; | ||
Object.keys(obj).forEach(function(key) { | ||
data[key.toLowerCase()] = obj[key]; | ||
}); | ||
}); | ||
return data; | ||
@@ -99,24 +95,23 @@ } | ||
function pick(obj) { | ||
var args = Array.prototype.slice.call(arguments); | ||
args.shift(); | ||
let args = arguments; | ||
let start = 1; | ||
if (args.length === 1 && Array.isArray(args[0])) { | ||
args = args[0]; | ||
if (args.length === 2 && Array.isArray(args[1])) { | ||
args = args[1]; | ||
start = 0; | ||
} | ||
var result = {}; | ||
const data = {}; | ||
args.forEach(function(name) { | ||
if (obj.hasOwnProperty(name)) { | ||
result[name] = obj[name]; | ||
for (var i = start; i < args.length; i++) { | ||
const key = args[i]; | ||
if (obj.hasOwnProperty(key)) { | ||
data[key] = obj[key]; | ||
} | ||
}); | ||
} | ||
return result; | ||
return data; | ||
} | ||
/** | ||
* Module exports. | ||
*/ | ||
exports.isEmpty = isEmpty; | ||
@@ -123,0 +118,0 @@ exports.isReadableStream = isReadableStream; |
{ | ||
"name": "papi", | ||
"version": "0.29.1", | ||
"version": "1.0.0-alpha.1", | ||
"description": "Build HTTP API clients", | ||
"main": "lib", | ||
"devDependencies": { | ||
"async": "^2.6.1", | ||
"bluebird": "^3.5.1", | ||
"debug": "^3.1.0", | ||
"istanbul": "^0.4.5", | ||
"debug": "^4.1.1", | ||
"jscs": "^3.0.7", | ||
"jshint": "^2.9.5", | ||
"lodash": "^4.17.10", | ||
"mocha": "^5.2.0", | ||
"nock": "^9.3.2", | ||
"mocha": "^8.3.0", | ||
"nock": "^13.0.7", | ||
"nyc": "^15.1.0", | ||
"request": "^2.87.0", | ||
"should": "^13.2.1", | ||
"sinon": "^1.10.3" | ||
"sinon": "^9.2.4" | ||
}, | ||
"engines": { | ||
"node": ">=10.0.0" | ||
}, | ||
"scripts": { | ||
"cover": "istanbul cover _mocha -- --recursive && open coverage/lcov-report/index.html", | ||
"bench": "BENCHMARK=true mocha test/benchmark.js", | ||
"test": "jshint lib test && jscs lib test && istanbul cover _mocha -- --recursive --check-leaks --globals Promise && istanbul check-coverage --statements 100 --functions 100 --branches 100 --lines 100" | ||
"cover": "nyc --reporter=lcov _mocha --recursive && open coverage/lcov-report/index.html", | ||
"test": "jshint lib test && jscs lib test && nyc _mocha --recursive --check-leaks --globals Promise && nyc check-coverage --statements 100 --functions 100 --branches 100 --lines 100" | ||
}, | ||
@@ -25,0 +25,0 @@ "repository": { |
173
README.md
@@ -1,4 +0,4 @@ | ||
# Papi [![Build Status](https://travis-ci.org/silas/node-papi.png?branch=master)](https://travis-ci.org/silas/node-papi) | ||
# Papi | ||
This is a module for building HTTP API clients. | ||
This is a library for building HTTP API clients. | ||
@@ -19,4 +19,4 @@ * [Documentation](#documentation) | ||
Your client should inherit the prototype methods from this constructor and call | ||
it in your client's constructor. | ||
Your client should extend the Papi `Client` class and call `super(options)` | ||
with the appropriate options. | ||
@@ -42,18 +42,17 @@ Options | ||
const papi = require('papi'); | ||
const util = require('util'); | ||
function GitHub(opts) { | ||
opts = opts || {}; | ||
opts.baseUrl = 'https://api.github.com'; | ||
opts.header = { accept: 'application/vnd.github.v3+json' }; | ||
opts.timeout = 15 * 1000; | ||
class GitHub extends papi.Client { | ||
constructor(opts) { | ||
opts = opts || {}; | ||
opts.baseUrl = 'https://api.github.com'; | ||
opts.header = { accept: 'application/vnd.github.v3+json' }; | ||
opts.timeout = 15 * 1000; | ||
papi.Client.call(this, opts); | ||
super(opts); | ||
} | ||
} | ||
util.inherits(GitHub, papi.Client); | ||
``` | ||
<a name="client-request"></a> | ||
### client.\_request(request, [callback...], callback) | ||
### client.\_request(request, [middleware...]) | ||
@@ -68,4 +67,3 @@ Make an HTTP request. | ||
* request (Object): request options | ||
* callback... (Function<request, next>, optional): middleware functions that can mutate `request.err` or `request.res`. Call `next` without arguments to continue execution, `next(err)` to break with an error, or `next(false, arguments...)` to trigger the final callback with the given arguments | ||
* callback (Function<err, res>): request callback function | ||
* middlware (Function<request, next>, optional): middleware functions that can mutate `request.err` or `request.res`. Call `next` without arguments to continue execution, `next(err)` to break with an error, or `next(false, value)` to return immediately. | ||
@@ -85,3 +83,3 @@ Request | ||
There are also `_get`, `_head`, `_post`, `_put`, `_delete` (`_del`), `_patch`, | ||
There are also `_get`, `_head`, `_post`, `_put`, `_delete`, `_patch`, | ||
and `_options` shortcuts with the same method signature as `_request`. | ||
@@ -92,14 +90,17 @@ | ||
``` javascript | ||
GitHub.prototype.gists = function(username, callback) { | ||
const opts = { | ||
path: '/users/{username}/gists', | ||
params: { username: username }, | ||
}; | ||
class GitHub extends papi.Client { | ||
constructor() { | ||
// see example constructor above | ||
} | ||
this._get(opts, (err, res) => { | ||
if (err) return callback(err); | ||
async gists(username) { | ||
const opts = { | ||
path: '/users/{username}/gists', | ||
params: { username: username }, | ||
}; | ||
callback(null, res.body); | ||
}); | ||
}; | ||
const res = await this._get(opts); | ||
return res.body; | ||
} | ||
} | ||
``` | ||
@@ -110,4 +111,3 @@ | ||
``` | ||
statusCode 200 | ||
body [ { url: 'https://api.github.com/gists/9458207', | ||
[ { url: 'https://api.github.com/gists/9458207', | ||
... | ||
@@ -214,11 +214,7 @@ ``` | ||
``` javascript | ||
const papi = require('papi'); | ||
async function show() { | ||
const res = await papi.get('https://api.github.com/users/silas/gists'); | ||
papi.get('https://api.github.com/users/silas/gists', (err, res) => { | ||
if (err) throw err; | ||
res.body.forEach(function(gist) { | ||
console.log(gist.url); | ||
}); | ||
}); | ||
res.body.forEach(gist => console.log(gist.url)); | ||
} | ||
``` | ||
@@ -230,8 +226,3 @@ | ||
``` javascript | ||
/** | ||
* Module dependencies. | ||
*/ | ||
const papi = require('papi'); | ||
const util = require('util'); | ||
@@ -241,71 +232,61 @@ /** | ||
*/ | ||
class GitHub extends papi.Client { | ||
constructor(opts) { | ||
opts = opts || {}; | ||
function GitHub(opts) { | ||
opts = opts || {}; | ||
if (!opts.baseUrl) { | ||
opts.baseUrl = 'https://api.github.com'; | ||
} | ||
if (!opts.headers) { | ||
opts.headers = {}; | ||
} | ||
if (!opts.headers.accept) { | ||
opts.headers.accept = 'application/vnd.github.v3+json'; | ||
} | ||
if (!opts.headers['user-agent']) { | ||
opts.headers['user-agent'] = 'PapiGitHub/0.1.0'; | ||
} | ||
if (opts.tags) { | ||
opts.tags = ['github'].concat(opts.tags); | ||
} else { | ||
opts.tags = ['github']; | ||
} | ||
if (!opts.timeout) { | ||
opts.timeout = 60 * 1000; | ||
} | ||
if (!opts.baseUrl) { | ||
opts.baseUrl = 'https://api.github.com'; | ||
super(opts); | ||
if (opts.debug) { | ||
this.on('log', console.log); | ||
} | ||
} | ||
if (!opts.headers) { | ||
opts.headers = {}; | ||
} | ||
if (!opts.headers.accept) { | ||
opts.headers.accept = 'application/vnd.github.v3+json'; | ||
} | ||
if (!opts.headers['user-agent']) { | ||
opts.headers['user-agent'] = 'PapiGitHub/0.1.0'; | ||
} | ||
if (opts.tags) { | ||
opts.tags = ['github'].concat(opts.tags); | ||
} else { | ||
opts.tags = ['github']; | ||
} | ||
if (!opts.timeout) { | ||
opts.timeout = 60 * 1000; | ||
} | ||
papi.Client.call(this, opts); | ||
/** | ||
* Get user gists | ||
*/ | ||
async gists(username) { | ||
const opts = { | ||
path: '/users/{username}/gists', | ||
params: { username: username }, | ||
}; | ||
if (opts.debug) { | ||
this.on('log', console.log); | ||
const res = await this._get(opts); | ||
return res.body; | ||
} | ||
} | ||
util.inherits(GitHub, papi.Client); | ||
/** | ||
* Get user gists | ||
*/ | ||
GitHub.prototype.gists = function(username, callback) { | ||
const opts = { | ||
path: '/users/{username}/gists', | ||
params: { username: username }, | ||
}; | ||
return this._get(opts, callback); | ||
}; | ||
/** | ||
* Print gists for user `silas` | ||
*/ | ||
function main() { | ||
// Print gists for user `silas` | ||
async function main() { | ||
const github = new GitHub({ debug: true }); | ||
github.gists('silas', (err, res) => { | ||
if (err) throw err; | ||
const gists = await github.gists('silas'); | ||
console.log('----'); | ||
console.log('----'); | ||
res.body.forEach(function(gist) { | ||
if (gist.description) console.log(gist.description); | ||
}); | ||
gists.forEach(function(gist) { | ||
if (gist.description) console.log(gist.description); | ||
}); | ||
} | ||
/** | ||
* Initialize | ||
*/ | ||
if (require.main === module) { | ||
@@ -312,0 +293,0 @@ main(); |
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
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
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
122930
10
15
871
1
293