UNPKG

apiary-preprocessor

Version:

This tool automatically updates responses for Apiary markdown files.

339 lines (325 loc) 11.1 kB
'use strict'; var Document = require('md-doc'), DocumentExtension = Document.Extension, ContentType = Document.ContentType, Content = Document.Content, _ = require('lodash'), regexUtils = require('./regex.utils'), jsonLint = require("jsonlint"), jsonStringifyPrettyCompact = require("json-stringify-pretty-compact"); var HEADERS = prefix('Headers'), HEADERS_SHORT = pfx('head'), BODY = prefix('Body'), BODY_SHORT = pfx('body'), SECTION = prefix('Section'), SECTION_SHORT = pfx('sect'), REQUEST = prefix('Request'), REQUEST_SHORT = pfx('req'), RESPONSE = prefix('Response'), RESPONSE_SHORT = pfx('res'); function sortObject(object) { if (typeof object !== 'object') return object; var temp = {}; var keys = []; if (Array.isArray(object)) { return object.map(element=>sortObject(element)); } for (var key in object) { if (object.hasOwnProperty(key)) { keys.push(key); } } keys.sort(); for (var index in keys) { if (keys.hasOwnProperty(index)) { temp[keys[index]] = sortObject(object[keys[index]]); } } return temp; } function jsonStringify(object, options) { return jsonStringifyPrettyCompact(sortObject(object), options); } function pfx(name) { return 'api-' + name; } function prefix(name) { return 'Apiary ' + name; } function processHeaders(headers) { var _headers = {}; if (typeof headers === 'string') { headers = headers.split('\n'); for (var i = 0; i < headers.length; i++) { var header = headers[i].split(':'); if (header && header.length === 2) { _headers[header[0].trim()] = header[1].trim(); } } } else { _headers = headers; } return _headers; } function getBodyContent() { var bodyContent = this.find('|' + BODY_SHORT)[0]; if (!bodyContent) { bodyContent = Content.create(getContentType(BODY), '\n + Body\n\n', this); this.contents.push(bodyContent); } return bodyContent; } function getHeadersContent() { var headersContent = this.find('|' + HEADERS_SHORT)[0]; if (!headersContent) { headersContent = Content.create(getContentType(HEADERS), '\n + Headers\n\n', this); this.contents.push(headersContent); } return headersContent; } var endOfSubsection = /(?:\n {0,4}[^ ])/; function section(indent) { // Blah [SOMETHING url] var header = /(.*)\[([^ \n]*) ?(.*)]/; return new ContentType({ match: regexUtils.replace( /\n#{_indent_}[^#]_header_((?:\n|.*)*?)(?=(?:\n#{_indent_}[^#].*)|$)/ )(/_indent_/g, String(indent))(/_header_/g, header)(), name: SECTION, short: SECTION_SHORT, isBlock: true, validAt: ['Document', 'Section', SECTION], on: function (match) { var section = new Content(this, '\n' + match[4].trim()); //Prepare level and header section.level = indent; section.name = match[1]; var req = {}; if (match[2].match(/^((POST)|(GET)|(DELETE)|(PUT)|(HEADER))$/)) { req.method = match[2]; if (match[3].length > 0) { req.address = match[3]; } } else { req.address = match[2]; } section.request = req; return section; }, getText: function () { var level = '', req; for (var i = 0; i < this.level; i++) { level += '#'; } if (this.request.method) { req = this.request.method + (this.request.address ? (' ' + this.request.address) : ''); } else if (this.request.address) { req = this.request.address; } var name = this.name.trim(); if (name.length > 0) { name += ' '; } return '\n' + level + ' ' + name + '[' + req + ']' + '\n\n' + this.getContentText(); }, getHtml: function () { var req = (this.request.method ? this.request.method + ' ' : '') + this.request.address; return '<h' + this.level + '>' + this.name + ' <b>' + req + '</b>' + '</h' + this.level + '><div>' + this.getContentHtml() + '</div>'; } }); } var contentTypes = [ section(1), section(2), section(3), section(4), section(5), section(6), ContentType.ListTypedSection(/\+/, /\n\n(?=[^ ])(?!\+ *Response)/, REQUEST, 'Request', REQUEST_SHORT, function (headerParams) { var params = headerParams.match(/.*\((.*)\)|(.*)/); this.contentType = params[1]; if (typeof this.contents === 'string') { this.contents = [ Content.create(getContentType(BODY), '\n + Body\n\n' + this.contents, this) ]; } else { var hasBody = _.find(this.contents, element=>element.type.name === BODY); if (!hasBody) { var body = this.contents[0]; if (body.type.name === 'Text Block') { this.contents[0] = Content.create(getContentType(BODY), '\n + Body\n\n' + body.contents, this); } else { this.contents.splice(0, 0, Content.create(getContentType(BODY), '\n + Body\n\n', this)); } } } this.getBodyContent = getBodyContent; this.getHeadersContent = getHeadersContent; }).setGetText(function () { var description = this.contents.filter(element=> { var t = element.type.short; return t !== HEADERS_SHORT && t !== BODY_SHORT && t !== RESPONSE_SHORT }), headers = this.getHeadersContent(), body = this.getBodyContent(), responses = this.find('|' + RESPONSE_SHORT); var text = '\n\n+ Request' + (this.contentType ? (' (' + this.contentType + ')') : '') + '\n\n'; description.forEach(description=> { text += description.getText() }); text += '\n'; if (headers) { var headersText = headers.getText(); if (headersText) { text += '\n' + headersText + '\n'; } } if (body) { var bodyText = body.getText(); if (bodyText) { text += '\n' + body.getText() + '\n'; } } responses.forEach(response=> { text += response.getText() }); return text; }).setValidChildren([RESPONSE, HEADERS, BODY]), ContentType.ListTypedSection(/\+/, true, RESPONSE, 'Response', RESPONSE_SHORT, function (headerParams) { var params = headerParams.match(/(?:(.*)\((.*)\))|(.*)/); //Response code and type if (params[3]) { this.code = parseInt(params[3].trim()); } else { this.code = parseInt(params[1].trim()); this.contentType = params[2]; } if (typeof this.contents === 'string') { this.contents = [ Content.create(getContentType(BODY), '\n + Body\n\n' + this.contents, this) ]; } this.getBodyContent = getBodyContent; this.getHeadersContent = getHeadersContent; }) .setGetText(function () { var description = this.contents.filter(element=> { var t = element.type.short; return t !== HEADERS_SHORT && t !== BODY_SHORT && t !== RESPONSE_SHORT }), headers = this.getHeadersContent(), body = this.getBodyContent(); var text = '\n+ Response'; if (this.code) { text += ' ' + this.code; } else { text += ' 200'; } if (this.contentType) { text += ' (' + this.contentType + ')'; } text += '\n'; description.forEach(description=> { text += description.getText() }); text += '\n'; if (headers) { text += '\n' + headers.getText() + '\n'; } if (body) { text += '\n' + body.getText() + '\n'; } return text; }) .setValidChildren([HEADERS, BODY]), ContentType.ListTypedSection(/\+/, endOfSubsection, HEADERS, 'Headers', HEADERS_SHORT, function () { this.getHeaders = function () { return this._headers; }; this.setHeaders = function (headers) { this._headers = processHeaders(headers); }; this.setHeaders(this.contents); this.contents = ''; }).setParseContent(false) .setValidAt([REQUEST, RESPONSE]) .setGetText(function () { var text = '', whitespace = ' ', headers = this.getHeaders(); for (var name in headers) { if (headers.hasOwnProperty(name)) { text += whitespace + name + ': ' + headers[name] + '\n'; } } if (text === '') { return ''; } return '\n + Headers\n\n' + text + '\n\n'; }), ContentType.ListTypedSection(/\+/, endOfSubsection, BODY, 'Body', BODY_SHORT, function () { this.rawBody = ('\n' + this.contents).replace(/\n {12}/g, '\n').replace(/\n {8}/g, '\n').substring(1); this.contents = ''; this.getBody = function () { var body; switch (this.parent.contentType) { case 'application/json': try { body = jsonLint.parse(this.rawBody); } catch (e) { var parent = this.parents(SECTION_SHORT)[0], what = parent ? parent.name : 'Unknown'; var err = new Error('Malformed ' + this.parent.type.name + ' JSON Body at ' + what + '\n\n' + e.message); delete err.stack; throw err; } break; default: body = this.rawBody; break; } return body; }; this.setBody = function (body) { if (typeof body !== 'string') { switch (this.parent.contentType) { case 'application/json': this.rawBody = jsonStringify(body, { indent: 4 }); break; default: this.rawBody = String(body); } } else { this.rawBody = body; } }; }) .setParseContent(false) .setGetText(function () { var contentType = this.parent.contentType, body = this.getBody(); switch (contentType) { case 'application/json': body = jsonStringify(body, { indent: 4 }); break; } if (!body) { return ''; } return '\n + Body\n\n' + (body.replace(/^|\n/g, '\n ')) + '\n'; }).setValidAt([REQUEST, RESPONSE]) ]; function getContentType(name) { return _.find(contentTypes, element=>element.name === name); } exports = module.exports = new DocumentExtension('Apiary', { onDocumentHeaderRead: function (document, callback) { var format = document.headers['FORMAT']; if (!format) { return callback(new Error('FORMAT is missing.')); } if (format !== '1A') { return callback(new Error('Only "1A" format is supported. Got "' + format + '"')); } callback(); } }, contentTypes, 0); exports.HEADERS = HEADERS_SHORT; exports.BODY = BODY_SHORT; exports.SECTION = SECTION_SHORT; exports.REQUEST = REQUEST_SHORT; exports.RESPONSE = RESPONSE_SHORT; exports.processHeaders = processHeaders;