BigstickCarpet/swagger-express-middleware

View on GitHub
lib/mock/query-collection.js

Summary

Maintainability
A
1 hr
Test Coverage
"use strict";

module.exports = {
  GET: queryCollection,
  HEAD: queryCollection,
  OPTIONS: queryCollection,
  DELETE: deleteCollection
};

const _ = require("lodash");
const util = require("../helpers/util");

/**
 * Returns an array of all resources in the collection.
 * If there are no resources, then an empty array is returned.  No 404 error is thrown.
 *
 * If the Swagger API includes "query" parameters, they can be used to filter the results.
 * Deep property names are allowed (e.g. "?address.city=New+York").
 * Query string params that are not defined in the Swagger API are ignored.
 *
 * @param   {Request}   req
 * @param   {Response}  res
 * @param   {function}  next
 * @param   {DataStore} dataStore
 */
function queryCollection (req, res, next, dataStore) {
  dataStore.getCollection(req.path, (err, resources) => {
    if (!err) {
      resources = filter(resources, req);

      if (resources.length === 0) {
        // There is no data, so use the current date/time as the "last-modified" header
        res.swagger.lastModified = new Date();

        // Use the default/example value, if there is one
        if (res.swagger.schema) {
          resources = res.swagger.schema.default || res.swagger.schema.example || [];
        }
      }
      else {
        // Determine the max "modifiedOn" date of the remaining items
        res.swagger.lastModified = _.maxBy(resources, "modifiedOn").modifiedOn;

        // Extract the "data" of each Resource
        resources = _.map(resources, "data");
      }

      // Set the response body (unless it's already been set by other middleware)
      res.body = res.body || resources;
    }

    next(err);
  });
}

/**
 * Deletes all resources in the collection.
 * If there are no resources, then nothing happens.  No error is thrown.
 *
 * If the Swagger API includes "query" parameters, they can be used to filter what gets deleted.
 * Deep property names are allowed (e.g. "?address.city=New+York").
 * Query string params that are not defined in the Swagger API are ignored.
 *
 * @param   {Request}   req
 * @param   {Response}  res
 * @param   {function}  next
 * @param   {DataStore} dataStore
 */
function deleteCollection (req, res, next, dataStore) {
  dataStore.getCollection(req.path, (err, resources) => {
    if (err) {
      next(err);
    }
    else {
      // Determine which resources to delete, based on query params
      let resourcesToDelete = filter(resources, req);

      if (resourcesToDelete.length === 0) {
        sendResponse(null, []);
      }
      else if (resourcesToDelete.length === resources.length) {
        // Delete the whole collection
        dataStore.deleteCollection(req.path, sendResponse);
      }
      else {
        // Only delete the matching resources
        dataStore.delete(resourcesToDelete, sendResponse);
      }
    }
  });

  // Responds with the deleted resources
  function sendResponse (err, resources) {
    // Extract the "data" of each Resource
    resources = _.map(resources, "data");

    // Use the current date/time as the "last-modified" header
    res.swagger.lastModified = new Date();

    // Set the response body (unless it's already been set by other middleware)
    if (err || res.body) {
      next(err);
    }
    else if (!res.swagger.isCollection) {
      // Response body is a single value, so only return the first item in the collection
      res.body = _.first(resources);
      next();
    }
    else {
      // Response body is the resource that was created/update/deleted
      res.body = resources;
      next();
    }
  }
}

/**
 * Filters the given {@link Resource} array, using the "query" params defined in the Swagger API.
 *
 * @param   {Resource[]}    resources
 * @param   {Request}       req
 * @returns {Resource[]}
 */
function filter (resources, req) {
  util.debug("There are %d resources in %s", resources.length, req.path);

  if (resources.length > 0) {
    // If there are query params, then filter the collection by them
    let queryParams = _.filter(req.swagger.params, { in: "query" });
    if (queryParams.length > 0) {
      // Build the filter object
      let filterCriteria = { data: {}};
      queryParams.forEach((param) => {
        if (req.query[param.name] !== undefined) {
          setDeepProperty(filterCriteria.data, param.name, req.query[param.name]);
        }
      });

      if (!_.isEmpty(filterCriteria.data)) {
        // Filter the collection
        util.debug("Filtering resources by %j", filterCriteria.data);
        resources = _.filter(resources, filterCriteria);
        util.debug("%d resources matched the filter criteria", resources.length);
      }
    }
  }

  return resources;
}

/**
 * Sets a deep property of the given object.
 *
 * @param   {object}    obj       - The object whose property is to be set.
 * @param   {string}    propName  - The deep property name (e.g. "Vet.Address.State")
 * @param   {*}         propValue - The value to set
 */
function setDeepProperty (obj, propName, propValue) {
  propName = propName.split(".");
  for (let i = 0; i < propName.length - 1; i++) {
    obj = obj[propName[i]] = obj[propName[i]] || {};
  }
  obj[propName[propName.length - 1]] = propValue;
}