elasticsearch
Version:
The official low-level Elasticsearch client for Node.js and the browser.
411 lines (366 loc) • 9.98 kB
JavaScript
var _ = require('lodash');
var utils = require('./utils');
/**
* Constructs a client action factory that uses specific defaults
* @type {Function}
*/
exports.makeFactoryWithModifier = makeFactoryWithModifier;
/**
* Constructs a function that can be called to make a request to ES
* @type {Function}
*/
exports.factory = makeFactoryWithModifier();
/**
* Constructs a proxy to another api method
* @type {Function}
*/
exports.proxyFactory = exports.factory.proxy;
// export so that we can test this
exports._resolveUrl = resolveUrl;
exports.ApiNamespace = function() {};
exports.namespaceFactory = function() {
function ClientNamespace(transport, client) {
this.transport = transport;
this.client = client;
}
ClientNamespace.prototype = new exports.ApiNamespace();
return ClientNamespace;
};
function makeFactoryWithModifier(modifier) {
modifier = modifier || _.identity;
var factory = function(spec) {
spec = modifier(spec);
if (!_.isPlainObject(spec.params)) {
spec.params = {};
}
if (!spec.method) {
spec.method = 'GET';
}
function action(params, cb) {
if (typeof params === 'function') {
cb = params;
params = {};
} else {
params = params || {};
cb = typeof cb === 'function' ? cb : null;
}
try {
return exec(this.transport, spec, _.clone(params), cb);
} catch (e) {
if (typeof cb === 'function') {
utils.nextTick(cb, e);
} else {
var def = this.transport.defer();
def.reject(e);
return def.promise;
}
}
}
action.spec = spec;
return action;
};
factory.proxy = function(fn, spec) {
return function(params, cb) {
if (typeof params === 'function') {
cb = params;
params = {};
} else {
params = params || {};
cb = typeof cb === 'function' ? cb : null;
}
if (spec.transform) {
spec.transform(params);
}
return fn.call(this, params, cb);
};
};
return factory;
}
var castType = {
enum: function validSelection(param, val, name) {
if (_.isString(val) && val.indexOf(',') > -1) {
val = commaSepList(val);
}
if (_.isArray(val)) {
return val
.map(function(v) {
return validSelection(param, v, name);
})
.join(',');
}
for (var i = 0; i < param.options.length; i++) {
if (param.options[i] === String(val)) {
return param.options[i];
}
}
throw new TypeError(
'Invalid ' +
name +
': expected ' +
(param.options.length > 1
? 'one of ' + param.options.join(',')
: param.options[0])
);
},
duration: function(param, val, name) {
if (utils.isNumeric(val) || utils.isInterval(val)) {
return val;
} else {
throw new TypeError(
'Invalid ' +
name +
': expected a number or interval ' +
'(an integer followed by one of M, w, d, h, m, s, y or ms).'
);
}
},
list: function(param, val, name) {
switch (typeof val) {
case 'number':
case 'boolean':
return '' + val;
case 'string':
val = commaSepList(val);
/* falls through */
case 'object':
if (_.isArray(val)) {
return val.join(',');
}
/* falls through */
default:
throw new TypeError(
'Invalid ' +
name +
': expected be a comma separated list, array, number or string.'
);
}
},
boolean: function(param, val) {
val = _.isString(val) ? val.toLowerCase() : val;
return val === 'no' || val === 'off' ? false : !!val;
},
number: function(param, val, name) {
if (utils.isNumeric(val)) {
return val * 1;
} else {
throw new TypeError('Invalid ' + name + ': expected a number.');
}
},
string: function(param, val, name) {
switch (typeof val) {
case 'number':
case 'string':
return '' + val;
default:
throw new TypeError('Invalid ' + name + ': expected a string.');
}
},
time: function(param, val, name) {
if (typeof val === 'string') {
return val;
} else if (utils.isNumeric(val)) {
return '' + val;
} else if (val instanceof Date) {
return '' + val.getTime();
} else {
throw new TypeError('Invalid ' + name + ': expected some sort of time.');
}
},
};
function resolveUrl(url, params) {
var vars = {};
var i;
var key;
if (url.req) {
// url has required params
if (!url.reqParamKeys) {
// create cached key list on demand
url.reqParamKeys = _.keys(url.req);
}
for (i = 0; i < url.reqParamKeys.length; i++) {
key = url.reqParamKeys[i];
if (!params.hasOwnProperty(key) || params[key] == null) {
// missing a required param
return false;
} else {
// cast of copy required param
if (castType[url.req[key].type]) {
vars[key] = castType[url.req[key].type](
url.req[key],
params[key],
key
);
} else {
vars[key] = params[key];
}
}
}
}
if (url.opt) {
// url has optional params
if (!url.optParamKeys) {
url.optParamKeys = _.keys(url.opt);
}
for (i = 0; i < url.optParamKeys.length; i++) {
key = url.optParamKeys[i];
if (params[key]) {
if (castType[url.opt[key].type] || params[key] == null) {
vars[key] = castType[url.opt[key].type](
url.opt[key],
params[key],
key
);
} else {
vars[key] = params[key];
}
} else {
vars[key] = url.opt[key]['default'];
}
}
}
if (!url.template) {
// compile the template on demand
url.template = _.template(url.fmt);
}
return url.template(
_.transform(
vars,
function(note, val, name) {
// encode each value
note[name] = encodeURIComponent(val);
// remove it from the params so that it isn't sent to the final request
delete params[name];
},
{}
)
);
}
function exec(transport, spec, params, cb) {
var request = {
method: spec.method,
};
var query = {};
var i;
// pass the timeout from the spec
if (spec.requestTimeout) {
request.requestTimeout = spec.requestTimeout;
}
if (!params.body && spec.paramAsBody) {
if (typeof spec.paramAsBody === 'object') {
params.body = {};
if (spec.paramAsBody.castToArray) {
params.body[spec.paramAsBody.body] = [].concat(
params[spec.paramAsBody.param]
);
} else {
params.body[spec.paramAsBody.body] = params[spec.paramAsBody.param];
}
delete params[spec.paramAsBody.param];
} else {
params.body = params[spec.paramAsBody];
delete params[spec.paramAsBody];
}
}
// verify that we have the body if needed
if (spec.needsBody && !params.body) {
throw new TypeError('A request body is required.');
}
// control params
if (spec.bulkBody) {
request.bulkBody = true;
}
if (spec.method === 'HEAD') {
request.castExists = true;
}
// pick the url
if (spec.url) {
// only one url option
request.path = resolveUrl(spec.url, params);
} else {
for (i = 0; i < spec.urls.length; i++) {
request.path = resolveUrl(spec.urls[i], params);
if (request.path) {
break;
}
}
}
if (!request.path) {
// there must have been some mimimun requirements that were not met
var minUrl = spec.url || spec.urls[spec.urls.length - 1];
throw new TypeError(
'Unable to build a path with those params. Supply at least ' +
_.keys(minUrl.req).join(', ')
);
}
// build the query string
if (!spec.paramKeys) {
// build a key list on demand
spec.paramKeys = _.keys(spec.params);
spec.requireParamKeys = _.transform(
spec.params,
function(req, param, key) {
if (param.required) {
req.push(key);
}
},
[]
);
}
for (var key in params) {
if (params.hasOwnProperty(key) && params[key] != null) {
switch (key) {
case 'body':
case 'headers':
case 'requestTimeout':
case 'maxRetries':
request[key] = params[key];
break;
case 'ignore':
request.ignore = _.isArray(params[key]) ? params[key] : [params[key]];
break;
case 'method':
request.method = utils.toUpperString(params[key]);
break;
default:
var paramSpec = spec.params[key];
if (paramSpec) {
// param keys don't always match the param name, in those cases it's stored in the param def as "name"
paramSpec.name = paramSpec.name || key;
if (params[key] != null) {
if (castType[paramSpec.type]) {
query[paramSpec.name] = castType[paramSpec.type](
paramSpec,
params[key],
key
);
} else {
query[paramSpec.name] = params[key];
}
if (
paramSpec['default'] &&
query[paramSpec.name] === paramSpec['default']
) {
delete query[paramSpec.name];
}
}
} else {
query[key] = params[key];
}
}
}
}
for (i = 0; i < spec.requireParamKeys.length; i++) {
if (!query.hasOwnProperty(spec.requireParamKeys[i])) {
throw new TypeError(
'Missing required parameter ' + spec.requireParamKeys[i]
);
}
}
request.query = query;
return transport.request(request, cb);
}
function commaSepList(str) {
return str.split(',').map(function(i) {
return i.trim();
});
}