lib/processors.js

/**
 * @fileOverview processors
 * @module lib/processors
 */

const generateSchema = require('generate-schema');
const utils = require('./utils');

/**
 *
 * @param req
 * @param method
 * @param pathKey
 */
module.exports.processPath = (req, method, pathKey) => {
  if (pathKey.indexOf('{') === -1) {
    return;
  }

  const pathRegexp = pathKey.replace(/{([^/]+)}/g, '(.+)?');
  const matches = req.url.match(pathRegexp);

  if (!matches) {
    return;
  }

  let i = 1;
  method.parameters.forEach(p => {
    if (p.in === 'path') {
      p.type = utils.getType(matches[i]);
      p.example = p.type === 'string' ? matches[i] : Number(matches[i]);
      i += 1;
    }
  });
};

/**
 *
 * @param method
 * @param headerName
 */
function updateSecurity(method, headerName) {
  method.security = method.security || [];
  if (method.security.map(s => Object.keys(s)[0]).indexOf(headerName) === -1) {
    const obj = {};
    obj[headerName] = [];
    method.security.push(obj);
  }
}

/**
 *
 * @param spec
 * @param headerName
 */
function updateSecurityDefinitions(spec, headerName) {
  spec.securityDefinitions = spec.securityDefinitions || {};
  spec.securityDefinitions[headerName] = {
    name: headerName,
    in: 'header',
    type: 'apiKey',
  };
}

/**
 *
 * @param req
 * @param method
 * @param spec
 */
module.exports.processHeaders = (req, method, spec) => {
  Object
    .keys(req.headers)
    .filter(h => h.toLowerCase() === 'authorization' || h.toLowerCase().startsWith('x-'))
    .forEach(h => {
      if (h.toLowerCase() === 'authorization') {
        method.responses = method.responses || {};
        method.responses[401] = { description: 'Unauthorized' };
      }
      updateSecurity(method, h);
      updateSecurityDefinitions(spec, h);
    });
};

/**
 *
 * @param req
 * @param method
 */
module.exports.processBody = (req, method) => {
  if (!req.body || Object.keys(req.body).length === 0) {
    return;
  }

  method.parameters = method.parameters || [];
  if (method.parameters.filter(p => p.in === 'body').length !== 0) {
    return;
  }

  method.parameters.push({
    in: 'body',
    name: 'body',
    required: true,
    schema: utils.getSchema(req.body),
  });
};

/**
 *
 * @param req
 * @param method
 */
module.exports.processQuery = (req, method) => {
  const params = req.query;
  if (!params || Object.keys(params).length === 0) {
    return;
  }

  const props = generateSchema.json(params).properties;
  for (const p in props) {
    if (method.parameters.filter(param => param.name === p).length !== 0) {
      continue;
    }

    const param = props[p];
    param.name = p;
    param.in = 'query';
    param.example = params[p];
    if (param.type === 'array') {
      param.collectionFormat = 'multi';
    } else if (param.type === 'string') {
      param.type = utils.getType(params[p]);
    }
    method.parameters.push(param);
  }
};

/**
 *
 * @param res
 * @param method
 */
function updateProduces(res, method) {
  let contentType = res.get('content-type');
  if (!contentType) {
    return;
  }

  contentType = contentType.split(';')[0];
  method.produces = method.produces || [];
  if (method.produces.indexOf(contentType) === -1) {
    method.produces.push(contentType);
  }
}

/**
 *
 * @param res
 * @param method
 * @param chunks
 */
function updateResponses(res, method, chunks) {
  method.responses = method.responses || {};
  method.responses[res.statusCode] = {};
  const contentType = res.get('content-type');
  if (!contentType || contentType.indexOf('json') === -1 && contentType.indexOf('text') === -1) {
    return;
  }

  let body = '';
  let schema;
  try {
    body = Buffer.concat(chunks).toString('utf8');
    body = JSON.parse(body);
    schema = utils.getSchema(body);
  } catch (ex) {
    const type = utils.getType(body);
    schema = {
      type: type,
      example: type === 'string' ? body : Number(body)
    };
  }
  method.responses[res.statusCode].schema = schema;
}

/**
 *
 * @param res
 * @param method
 * @param chunks
 * @param chunk
 */
function appendChunkIfNeeded(res, method, chunks, chunk) {
  if (method.responses && method.responses[res.statusCode]) {
    return;
  }

  if (!chunk) {
    return;
  }

  chunks.push(new Buffer(chunk));
}

/**
 *
 * @param res
 * @returns {boolean}
 */
function isCompressed(res) {
  const headers = res.getHeaders ? res.getHeaders() : res.headers();
  return ['gzip', 'compress', 'deflate'].indexOf(headers['content-encoding']) !== -1;
}

/**
 *
 * @param res
 * @param method
 */
module.exports.processResponse = (res, method) => {
  const oldWrite = res.write;
  const oldEnd = res.end;
  const chunks = [];

  res.write = function(chunk) {
    try {
      if (!isCompressed(res)) {
        appendChunkIfNeeded(res, method, chunks, chunk);
      }
    } finally {
      oldWrite.apply(res, arguments);
    }
  };

  res.end = function(chunk) {
    try {
      if (!isCompressed(res)) {
        appendChunkIfNeeded(res, method, chunks, chunk);
        updateProduces(res, method);
        if (!method.responses || !method.responses[res.statusCode]) {
          updateResponses(res, method, chunks);
        }
      }
    } finally {
      oldEnd.apply(res, arguments);
    }
  };
};