UNPKG

elasticsearch

Version:

The official low-level Elasticsearch client for Node.js and the browser.

411 lines (366 loc) 9.98 kB
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(); }); }