teableio/teable

View on GitHub
packages/openapi/src/openapi-snippet/index.js

Summary

Maintainability
B
5 hrs
Test Coverage
/* eslint-disable */
/**
 * openapi-snippet
 *
 * Generates code snippets from Open API (previously Swagger) documents.
 *
 * Author: Erik Wittern
 * License: MIT
 */
'use strict';

const OpenAPIToHar = require('./openapi-to-har.js');
const HTTPSnippet = require('httpsnippet-lite');

/**
 * Return snippets for endpoint identified using path and method in the given
 * OpenAPI document.
 *
 * @param {object} openApi  OpenAPI document
 * @param {string} path     Path identifying endpoint, e.g., '/users'
 * @param {string} method   HTTP method identifying endpoint, e.g., 'get'
 * @param {array} targets   List of languages to create snippets in, e.g,
 *                          ['cURL', 'Node']
 * @param {object} [values] Optional: Values for the query parameters if present
 */
const getEndpointSnippets = function (openApi, path, method, targets, values) {
  // if optional parameter is not provided, set it to empty object
  if (typeof values === 'undefined') {
    values = {};
  }

  const hars = OpenAPIToHar.getEndpoint(openApi, path, method, values);

  const snippets = [];
  for (const har of hars) {
    const snippet = new HTTPSnippet.HTTPSnippet(har);
    snippets.push(
      ...getSnippetsForTargets(
        targets,
        snippet,
        har.comment ? har.comment : undefined
      )
    );
  }

  // use first element since method, url, and description
  // are the same for all elements
  return {
    method: hars[0].method,
    url: hars[0].url,
    description: hars[0].description,
    resource: getResourceName(hars[0].url),
    snippets: snippets,
  };
};

/**
 * Return snippets for all endpoints in the given OpenAPI document.
 *
 * @param {object} openApi  OpenAPI document
 * @param {array} targets   List of languages to create snippets in, e.g,
 *                          ['cURL', 'Node']
 */
const getSnippets = function (openApi, targets) {
  const endpointHarInfoList = OpenAPIToHar.getAll(openApi);

  const results = [];
  for (let i in endpointHarInfoList) {
    // create HTTPSnippet object:
    const harInfo = endpointHarInfoList[i];
    const snippets = [];
    for (const har of harInfo.hars) {
      const snippet = new HTTPSnippet.HTTPSnippet(har);
      snippets.push(...getSnippetsForTargets(targets, snippet, har.comment));
    }

    results.push({
      method: harInfo.method,
      url: harInfo.url,
      description: harInfo.description,
      resource: getResourceName(harInfo.url),
      snippets,
    });
  }

  // sort results:
  results.sort((a, b) => {
    if (a.resource < b.resource) {
      return -1;
    } else if (a.resource > b.resource) {
      return 1;
    } else {
      return getMethodOrder(a.method.toLowerCase(), b.method.toLowerCase());
    }
  });

  return results;
};

/**
 * Determine the order of HTTP methods.
 *
 * @param  {string} a One HTTP verb in lower case
 * @param  {string} b Another HTTP verb in lower case
 * @return {number}   The order instruction for the given HTTP verbs
 */
const getMethodOrder = function (a, b) {
  const order = ['get', 'post', 'put', 'delete', 'patch'];
  if (order.indexOf(a) === -1) {
    return 1;
  } else if (order.indexOf(b) === -1) {
    return -1;
  } else if (order.indexOf(a) < order.indexOf(b)) {
    return -1;
  } else if (order.indexOf(a) > order.indexOf(b)) {
    return 1;
  } else {
    return 0;
  }
};

/**
 * Determines the name of the resource exposed by the method.
 * E.g., ../users/{userId} --> users
 *
 * @param  {string} urlStr The OpenAPI path definition
 * @return {string}        The determined resource name
 */
const getResourceName = function (urlStr) {
  const pathComponents = urlStr.split('/');
  for (let i = pathComponents.length - 1; i >= 0; i--) {
    const cand = pathComponents[i];
    if (cand !== '' && !/^{/.test(cand)) {
      return cand;
    }
  }
};

/**
 * Format the given target by splitting up language and library and making sure
 * that HTTP Snippet supports them.
 *
 * @param  {string} targetStr String defining a target, e.g., node_request
 * @return {object}           Object with formatted target, or null
 */
const formatTarget = function (targetStr) {
  const language = targetStr.split('_')[0];
  const title = capitalizeFirstLetter(language);
  let library = targetStr.split('_')[1];
  if (!library) {
    return {
      title,
      language,
    }
  }

  const validTargets = HTTPSnippet.availableTargets();
  let validLanguage = false;
  let validLibrary = false;
  for (let i in validTargets) {
    const target = validTargets[i];
    if (language === target.key) {
      validLanguage = true;
      if (typeof library === 'undefined') {
        library = target.default;
        validLibrary = true;
      } else {
        for (let j in target.clients) {
          const client = target.clients[j];
          if (library === client.key) {
            validLibrary = true;
            break;
          }
        }
      }
    }
  }

  if (!validLanguage || !validLibrary) {
    return null;
  }
  return {
    title:
      typeof library !== 'undefined'
        ? title + ' + ' + capitalizeFirstLetter(library)
        : title,
    language,
    library,
  };
};

/**
 * Generate code snippets for each of the supplied targets
 *
 * @param targets {array}               List of language targets to generate code for
 * @param snippet {Object}              Snippet object from httpsnippet to convert into the target objects
 * @param mimeType {string | undefined} Additional information to add uniqueness to the produced snippets
 */
const getSnippetsForTargets = function (targets, snippet, mimeType) {
  const snippets = [];
  for (let j in targets) {
    const target = formatTarget(targets[j]);
    if (!target) throw new Error('Invalid target: ' + targets[j]);
    snippets.push({
      id: targets[j],
      ...(mimeType !== undefined && { mimeType: mimeType }),
      title: target.title,
      content: snippet.convert(
        target.language,
        typeof target.library !== 'undefined' ? target.library : null
      ),
    });
  }
  return snippets;
};

const capitalizeFirstLetter = function (string) {
  return string.charAt(0).toUpperCase() + string.slice(1);
};

module.exports = {
  getSnippets,
  getEndpointSnippets,
};

// The if is only for when this is run from the browser
if (typeof window !== 'undefined') {
  // grab existing namespace object, or create a blank object
  // if it doesn't exist
  let OpenAPISnippets = window.OpenAPISnippets || {};

  // define that object
  OpenAPISnippets = {
    getSnippets,
    getEndpointSnippets,
  };

  // replace/create the global namespace
  window.OpenAPISnippets = OpenAPISnippets;
}