rpc | get environment | test rpc from spec | Search

This code converts an OpenAPI specification into a format compatible with Google Discovery, facilitating integration with Google APIs.

Run example

npm run import -- "get rpc from spec"

get rpc from spec

var url = require('url')
var util = require('util')
var importer = require('../Core')
var {request} = importer.import("http request")

function getRpcFromSpec(spec, req, base) {
    if(req && req.request)
        req = req.request.bind(req)
    base = spec.baseUrl || base;
    var GoogleSpec = Object.keys(spec.resources || {}).reduce((obj, key) => {
        obj[key] = Object.keys(spec.resources[key].methods || {}).reduce((o, k) => {
            spec.resources[key].methods[k].parameters2 = spec.parameters
            o[k] = assignAndRequest.bind(spec,
                                         base,
                                         spec.resources[key].methods[k],
                                         req || request);
            return o;
        }, {})
        // combine parent parameters with child paramters
        Object.assign(obj[key], getRpcFromSpec(spec.resources[key], req, base))
        return obj;
    }, {})
    var version = ((spec.info || {}).version || '1').split('.')[0]
    // convert stupid OpenAPI to Google Discovery format
    var OpenAPI = Object.keys(spec.paths || {}).reduce((obj, key) => {
        var method = {
            path: '',
            parameters2: {}
        }
        var keys = key.replace(/^\/|\/$/ig, '').split('/')
            .reduce((keylist, k) => {
                if(k == 'json') {
                    method.parameters2['format'] = {
                        "enum": [
                            "json"
                        ],
                        "type": "string",
                        "enumDescriptions": [
                            "Responses with Content-Type of application/json"
                        ],
                        "location": "path",
                        "description": "Data format for response.",
                        "default": "json"
                    }
                    method.path += '/{format}'
                } else if (k == 'v' + version) {
                    method.parameters2['version'] = {
                        "enum": [
                            "json"
                        ],
                        "type": "string",
                        "location": "path",
                        "default": k
                    }
                    method.path += '/{version}'
                } else if (k == 'api' || k == '@' || k.length < 0) {
                    method.path += '/' + k
                } else {
                    keylist[keylist.length] = k
                    method.path += '/' + k
                }
                return keylist
            }, [])
        var currentPath = obj
        keys.forEach(k => {
            if(!currentPath[k]) currentPath[k] = {}
            currentPath = currentPath[k]
        })
        Object.keys(spec.paths[key]).forEach(k => {
            var parameters = (spec.paths[key][k].parameters || [])
                .reduce((o, p) => {
                    o[p.name] = {
                        location: p.in,
                        type: p.schema ? p.schema['$ref'] || p.schema.type || 'string' : 'string',
                        description: p.description,
                        required: p.required
                    }
                    return o
                }, {})
            var methodSpec = Object.assign({httpMethod: k.toUpperCase(), parameters: parameters}, method)
            var requestFunc = assignAndRequest.bind(spec,
                                                    spec.paths[key][k].servers[0].url,
                                                    methodSpec,
                                                    req || request);
            currentPath[k] = requestFunc
        })
        return obj
    }, GoogleSpec)
    return OpenAPI
}

function assignAndRequest(base, resource, request, input) {
    // TODO: get path parameters
    var path = getResourceParameters(resource, input, 'path')
    var address = `${base}${resource.path.replace(/\{(.*?)\}/ig, ($0, $1) => {
        if(!path[$1]) {
            throw new Error(`path parameter ${$1} not defined!`);
        }
        return path[$1];
    })}`;
    // TODO: move this to polyfills
    var location = url.parse(address)
    var params = Object.assign(
        getResourceParameters(resource, input, 'query'), 
        location.search
            ? querystring.parse((/\?(.*)/ig).exec(location.search)[1])
            : {});
    //console.log(`requesting ${address} ${JSON.stringify(params)}`);
    var data = getResourceParameters(resource, input, 'body')
    if(Object.values(data).length === 0) data = null
    var finalURL = address.replace(/\?.*$/ig, '') + '?' + querystring.stringify(params)
    console.log('Requesting: ' + finalURL)
    return request({
        method: resource.httpMethod,
        url: finalURL,
        data: data,
        body: JSON.stringify(input.resource),
        params: params
    })
    
}

function getResourceParameters(resource, input, type) {
    var paramters = {}
    Object.assign(paramters, resource.parameters2)
    Object.assign(paramters, resource.parameters)
    return Object.keys(paramters)
        .filter(k => paramters[k].location === type)
        .reduce((obj, key) => {
            if(paramters[key].required
               && (!input || typeof input[key] === 'undefined')) {
                throw new Error(`required field ${key} not defined!`);
            }
            if(typeof input[key] !== 'undefined')
                obj[key] = input[key];
            return obj;
        }, {})
}

module.exports = getRpcFromSpec;

What the code could have been:

const { url } = require('url');
const { querystring } = require('querystring');
const importer = require('../Core');
const { request } = importer.import('http request');

/**
 * Converts spec to Google Discovery format.
 * @param {Object} spec - API specification.
 * @param {Object} req - Request object.
 * @param {string} base - Base URL.
 * @returns {Object} OpenAPI in Google Discovery format.
 */
function getRpcFromSpec(spec, req, base) {
  // Check if req is a function and bind it to the request object
  if (req && req.request) {
    req = req.request.bind(req);
  }

  // Set base URL
  base = spec.baseUrl || base;

  // Reduce spec to Google Discovery format
  return Object.keys(spec.resources || {}).reduce((obj, key) => {
    obj[key] = Object.keys(spec.resources[key].methods || {}).reduce((o, k) => {
      spec.resources[key].methods[k].parameters2 = spec.parameters;
      o[k] = assignAndRequest.bind(
        spec,
        base,
        spec.resources[key].methods[k],
        req || request
      );
      return o;
    }, {});
    // Combine parent parameters with child parameters
    Object.assign(obj[key], getRpcFromSpec(spec.resources[key], req, base));
    return obj;
  }, {});
}

/**
 * Assigns request parameters and makes a request.
 * @param {Object} spec - API specification.
 * @param {Object} resource - Resource object.
 * @param {Object} request - Request function.
 * @param {Object} input - Input object.
 * @returns {Promise} Request promise.
 */
function assignAndRequest(spec, resource, request, input) {
  try {
    // Get path parameters
    const pathParams = getResourceParameters(resource, input, 'path');
    const address = `${resource.base}${resource.path.replace(
      /\{(.*?)\}/ig,
      (match, param) => {
        if (!pathParams[param]) {
          throw new Error(`Path parameter ${param} not defined!`);
        }
        return pathParams[param];
      }
    )}`;

    // Parse URL and query parameters
    const location = url.parse(address);
    const queryParams = getResourceParameters(resource, input, 'query');
    const params = Object.assign(queryParams, location.search? querystring.parse(location.search.slice(1)) : {});

    // Get body parameters
    const bodyParams = getResourceParameters(resource, input, 'body');
    const data = bodyParams.length === 0? null : bodyParams;

    // Construct final URL
    const finalURL = `${address}?${querystring.stringify(params)}`;

    // Log request
    console.log('Requesting:', finalURL);

    // Make request
    return request({
      method: resource.httpMethod,
      url: finalURL,
      data: data? JSON.stringify(input.resource) : data,
      params: params,
    });
  } catch (error) {
    // Handle errors
    console.error(error);
    throw error;
  }
}

/**
 * Gets resource parameters.
 * @param {Object} resource - Resource object.
 * @param {Object} input - Input object.
 * @param {string} type - Parameter type (path, query, or body).
 * @returns {Object} Resource parameters.
 */
function getResourceParameters(resource, input, type) {
  const parameters = {...resource.parameters2,...resource.parameters };
  return Object.keys(parameters)
   .filter((k) => parameters[k].location === type)
   .reduce((obj, key) => {
      if (parameters[key].required &&!input[key]) {
        throw new Error(`Required field ${key} not defined!`);
      }
      if (input[key]) obj[key] = input[key];
      return obj;
    }, {});
}

module.exports = getRpcFromSpec;

This code snippet focuses on transforming an OpenAPI specification into a format compatible with Google Discovery.

Here's a breakdown:

  1. Imports:

  2. getRpcFromSpec Function:

  3. OpenAPI to Google Discovery Conversion:

In essence, this code acts as a translator, converting an OpenAPI specification into a format suitable for use with Google Discovery, enabling seamless integration with Google APIs.