UNPKG

apiary-preprocessor

Version:

This tool automatically updates responses for Apiary markdown files.

227 lines (220 loc) 8.54 kB
'use strict'; 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() })); } ]); };