datafire
Version:
[![Travis][travis-image]][travis-link] [![Downloads][downloads-image]][npm-link] [![NPM version][npm-image]][npm-link] [](https://www.npmjs.com/package/datafire) <!--[![Dependency status][deps-i
249 lines (238 loc) • 9 kB
JavaScript
let openapiUtil = require('../util/openapi');
let request = require('request');
let Action = require('./action');
let Response = require('./response');
let rssParser = require('rss-parser');
let zlib = require('zlib');
const ZLIB_OPTIONS = {
flush: zlib.Z_SYNC_FLUSH,
finishFlush: zlib.Z_SYNC_FLUSH
}
const BODY_METHODS = ['put', 'patch', 'post'];
const getActionFromOperation = module.exports = function(method, path, openapi, integration, modifyReq) {
let op = openapi.paths[path][method];
let params = op.parameters || [];
let hasRequiredParam = !!params.filter(p => p.required).length;
let inputSchema = {
type: hasRequiredParam ? 'object' : ['object', 'null'],
properties: {},
additionalProperties: false,
definitions: openapi.definitions,
};
let names = openapiUtil.getUniqueNames(params);
params.forEach((param, idx) => {
let name = names[idx];
inputSchema.properties[name] = getSchemaFromParam(param);
if (param.required) {
inputSchema.required = inputSchema.required || [];
inputSchema.required.push(name);
}
});
let response = getDefaultResponse(op);
let outputSchema = Object.assign({definitions: openapi.definitions}, response.schema);
let actionSecurity = {};
if (!op.security || op.security.length) {
actionSecurity = integration.security;
} else {
actionSecurity[integration.id] = false;
}
return new Action({
title: op.operationId || (method.toUpperCase() + ' ' + path),
description: op.description || op.summary,
inputSchema: params.length ? inputSchema : {},
outputSchema: outputSchema,
security: actionSecurity,
ajv: integration.ajv,
handler: function(input, ctx) {
input = input || {};
let account = ctx.accounts[integration.id];
let scheme = openapiUtil.getBestScheme(openapi.schemes);
if (!openapi.host && (!account || !account.host)) {
throw new Error("The 'host' field must be specified in the " + integration.id + " account");
}
let url = (account && account.host) || (scheme + '://' + openapi.host);
let reqOpts = {
method,
url,
qs: {},
qsStringifyOptions: {},
headers: {},
form: {},
formData: {},
body: null,
encoding: null,
}
if (openapi.basePath && openapi.basePath !== '/') reqOpts.url += openapi.basePath;
reqOpts.url += path;
let addParam = (loc, name, val, isFile) => {
if (val === undefined) return;
if (loc === 'query') {
reqOpts.qs[name] = val;
} else if (loc === 'header') {
reqOpts.headers[name] = val;
} else if (loc === 'path') {
reqOpts.url = reqOpts.url.replace('{' + name + '}', val);
} else if (loc === 'body') {
reqOpts.body = JSON.stringify(val);
} else if (loc === 'formData') {
if (isFile) {
if (typeof val === 'object') {
let content = val.encoding ? new Buffer(val.content, val.encoding) : val.content;
val = {
value: content,
options: {
filename: val.filename || name,
contentType: val.contentType,
knownLength: val.knownLength,
}
}
} else {
val = {
value: val,
options: {
filename: name,
}
}
}
reqOpts.formData[name] = val;
} else {
reqOpts.form[name] = val;
}
}
}
let names = openapiUtil.getUniqueNames(params);
params.forEach((param, idx) => {
let val = input[names[idx]];
if (param.in === 'query' && Array.isArray(val)) {
if (param.collectionFormat === 'multi') {
reqOpts.qsStringifyOptions.arrayFormat = 'repeat';
} else {
reqOpts.qsStringifyOptions.sep = openapiUtil.getCollectionFormatSeparator(param.collectionFormat);
}
}
addParam(param.in, param.name, val, param.type === 'file');
});
let hasRefreshToken = false;
let oauthDef = null;
if (account) {
for (let key in openapi.securityDefinitions || {}) {
let def = openapi.securityDefinitions[key];
if (def.type === 'basic' && account.username && account.password) {
let details = account.username + ':' + account.password;
addParam('header', 'Authorization', "Basic " + new Buffer(details, 'utf8').toString('base64'));
} else if (def.type === 'apiKey' && account[key]) {
addParam(def.in, def.name, account[key]);
} else if (def.type === 'oauth2' && account.access_token) {
hasRefreshToken = !!account.refresh_token;
if (!oauthDef || oauthDef.flow === 'implicit') {
oauthDef = def;
}
if (def.in) {
addParam(def.in, def.name, account.access_token);
} else {
addParam('header', 'Authorization', "Bearer " + account.access_token);
}
}
}
}
if (Object.keys(reqOpts.formData).length === 0) {
delete reqOpts.formData;
}
if (Object.keys(reqOpts.form).length === 0) {
delete reqOpts.form;
}
let refreshOAuthToken = (callback) => {
let form = {
client_id: account.client_id,
client_secret: account.client_secret,
refresh_token: account.refresh_token,
grant_type: 'refresh_token'
};
request.post({
url: account.refresh_url || oauthDef.refreshUrl || oauthDef.tokenUrl,
headers: account.refresh_headers || {},
json: true,
form,
}, (err, resp, body) => {
if (err) return callback(err);
if (resp.statusCode >= 300) return callback(new Error(resp.statusCode));
account.access_token = body.access_token;
account.refresh_token = body.refresh_token || account.refresh_token;
Action.callOAuthRefreshCallbacks(account);
addParam('header', 'Authorization', "Bearer " + body.access_token);
callback();
})
}
if (BODY_METHODS.indexOf(method) !== -1 && reqOpts.body !== null) {
let consumes = op.consumes || ['application/json'];
let cType = consumes.indexOf('application/json') === -1 ? consumes[0] : 'application/json';
addParam('header', 'Content-Type', cType);
}
addParam('header', 'User-Agent', 'DataFire');
let sendRequest = (resolve, reject, isRetry) => {
request(reqOpts, (err, resp, body) => {
if (err) {
return reject(err);
}
if (body) {
let encoding = resp.headers['content-encoding'];
if (encoding === 'gzip') {
body = zlib.gunzipSync(body, ZLIB_OPTIONS).toString('utf8');
} else if (encoding === 'deflate') {
body = zlib.inflateSync(body, ZLIB_OPTIONS).toString('utf8');
} else {
body = body.toString('utf8');
}
}
if (!isRetry && resp.statusCode === 401 && hasRefreshToken) {
refreshOAuthToken(err => {
if (err) reject(new Response({statusCode: 401}));
else sendRequest(resolve, reject, true);
})
return;
} else if (resp.statusCode >= 300) {
return reject(new Response({statusCode: resp.statusCode, body}));
}
let ctype = resp.headers['content-type'] || '';
if (body && ctype.indexOf('application/json') !== -1) {
body = JSON.parse(body);
resolve(body);
} else if (openapi.info['x-datafire'] && openapi.info['x-datafire'].type === 'rss') {
rssParser.parseString(body, (err, feed) => {
if (err) reject(err);
else resolve(feed);
})
} else {
resolve(body);
}
})
}
if (modifyReq) modifyReq(reqOpts, ctx);
return new Promise(sendRequest);
}
});
}
const getSchemaFromParam = function(param) {
if (param.in === 'body' && param.schema) return param.schema;
let schema = {
type: param.type,
};
if (param.type === 'file') {
schema.type = ['string', 'object'];
schema.properties = {
content: {type: 'string'},
encoding: {type: 'string', enum: ['ascii', 'utf8', 'utf16le', 'base64', 'binary', 'hex']},
contentType: {type: 'string'},
filename: {type: 'string'},
}
}
schema.type = param.type === 'file' ? ['string', 'object'] : param.type;
openapiUtil.PARAM_SCHEMA_FIELDS.forEach(f => {
if (param[f] !== undefined) schema[f] = param[f];
})
return schema;
}
const getDefaultResponse = function(op) {
let keys = Object.keys(op.responses).sort();
return op.responses[keys[0]];
}