apiary-preprocessor
Version:
This tool automatically updates responses for Apiary markdown files.
227 lines (220 loc) • 8.54 kB
JavaScript
var _ = require('lodash'),
async = require('async'),
Document = require('md-doc'),
ApiaryExtension = require('./extensions/apiary'),
CallInformation = require('./call-info'),
Caller = require('nodecaller'),
chalk = require('chalk');
function ifNoError(errorCallback, callback) {
return function (err, value) {
if (err) {
return errorCallback(err);
}
callback(value);
};
}
var defaultOptions = {
rootApiPath: '',
output: 'text',
authorize: function (callback) {
callback();
},
before: function (callback) {
callback();
},
beforeEachOnce: function (callInformation, callback) {
callback();
},
beforeEach: function (callInformation, callback) {
callback();
},
after: function (callback) {
callback();
},
afterEach: function (callInformation, callback) {
callback();
},
onRetry: function (callInformation, callback) {
callback(null, false, false);
},
replace: {},
ignoredResponseCodes: [],
updateSourceFile: false
};
/**
*
* @param source
* @param destination
* @param {object} options
* @param {function(callback)?} options.authorize
* @param {function(callback)?} options.before
* @param {function(callback)?} options.after
* @param {function(callInformation:CallInformation, callback)?} options.beforeEachOnce
* @param {function(callInformation:CallInformation, callback)?} options.beforeEach
* @param {function(callInformation:CallInformation, callback)?} options.onRetry
* @param {function(callInformation:CallInformation, callback)?} options.afterEach
* @param {object?} options.replace
* @param {string?} options.rootApiPath
* @param {string?} options.output
* @param {boolean} options.updateSourceFile
* @param {array} options.ignoredResponseCodes
* @param callback
*/
exports.process = function (source, destination, options, callback) {
options = _.defaults(options, defaultOptions);
var document = new Document();
var caller = new Caller(options.rootApiPath);
caller.options = options;
caller.super = _.clone(defaultOptions);
document.extension(ApiaryExtension);
async.waterfall([
(next) => {
caller
.continueOnError()
.run(ifNoError(callback, next));
},
(next) => {
document.readFile(source, ifNoError(callback, document=> {
async.map(document.find(ApiaryExtension.RESPONSE), (responseContent, next)=> {
process.nextTick(()=> {
try {
var callInfo = new CallInformation(responseContent);
next(null, callInfo);
} catch (e) {
next(e);
}
});
}, ifNoError(callback, callInformation=> {
next(null, document, callInformation);
}));
}));
},
(document, callInformation, next)=> {
if (!options.updateSourceFile) {
return next(null, document, callInformation);
}
document.save(source, 'text', ifNoError(callback, ()=> {
next(null, document, callInformation);
}));
},
(document, callInformation, next) => {
caller.document = document;
options.before.call(caller, ifNoError(callback, () => {
async.forEachSeries(callInformation, function (callInformation, next) {
var originalBody, originalHeaders, originalPath;
if (options.ignoredResponseCodes.indexOf(callInformation.code) >= 0) {
return next();
}
async.series([
//Before call once
function (next) {
options.beforeEachOnce.call(caller, callInformation, ifNoError(callback, function () {
originalBody = callInformation.body;
originalHeaders = callInformation.headers;
originalPath = callInformation.path;
next();
}));
},
//Execute beforeEach, replace variables in body & headers, call the route and retry if needed
function (next) {
var retry;
async.doWhilst(function (next) {
retry = false;
callInformation.body = originalBody;
callInformation.headers = originalHeaders;
callInformation.path = originalPath;
options.beforeEach.call(caller, callInformation, ifNoError(callback, function () {
//Refresh headers and body
var body = callInformation.body,
headers = callInformation.headers,
path = callInformation.path;
if (typeof body !== 'string') {
body = JSON.stringify(body);
}
var jsonHeaders = false;
if (typeof headers !== 'string') {
headers = JSON.stringify(headers);
jsonHeaders = true;
}
for (var key in options.replace) {
if (options.replace.hasOwnProperty(key)) {
var value = options.replace[key];
var regex = new RegExp('\\{\\{' + key + '}}', 'g');
body = body.replace(regex, value);
headers = headers.replace(regex, value);
path = path.replace(new RegExp('\\{' + key + '}|:' + key, 'g'), value);
}
}
callInformation.path = path.replace(/\{.*}/g, '');
callInformation.body = body;
callInformation.headers = jsonHeaders ? JSON.parse(headers) : headers;
//Make the actual call and go back in case of problems if allowed
caller
.path(callInformation.path)
.headers(callInformation.headers)
.req(callInformation.method, callInformation.body, function (value, done) {
caller.lastResponse = this.response;
caller.error = this.error;
if (this.error) {
if (options.onRetry) {
options.onRetry.call(caller, callInformation, function (err, ret, skip) {
retry = !!ret;
caller.error = null;
if (skip) {
return done();
}
if (ret === true) {
return done();
} else {
return done(err);
}
});
} else {
return done(this.error);
}
} else {
return done(null, this.response);
}
}, callInformation.code)
.run(function (err, response) {
if (err) {
return next(err);
}
if (response) {
callInformation.setResponseBody(response.body);
var receivedHeaders = {};
for (var i = 0; i + 1 < response.rawHeaders.length; i += 2) {
receivedHeaders[response.rawHeaders[i]] = response.rawHeaders[i + 1];
}
callInformation.setResponseHeaders(receivedHeaders);
}
next();
});
}));
}, function () {
return retry;
}, next);
},
//After each is called once
function (next) {
options.afterEach.call(caller, callInformation, ifNoError(callback, function () {
delete caller.lastResponse;
next();
}));
}
], next);
}, ifNoError(callback, () => {
options.after.call(caller, ifNoError(callback, () => {
next(null, document);
}));
}));
}));
},
(document) => {
document.save(destination, options.output, ifNoError(callback, ()=> {
callback()
}));
}
]);
};
;