All files / server/api/utils validation.js

100% Statements 35/35
91.3% Branches 21/23
100% Functions 8/8
100% Lines 33/33
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124                1x 1x   1x 1x 1x   1x     1x 1x         1x                             1x 34x 4x 30x 1x 30x 1x     28x 28x   10x 9x     10x     28x                                                                                       1x 18x   18x 18x 18x     18x 18x 18x   18x 18x           28x    
/**
 * API validation utilities.
 *
 * You should be familiar with the [valdsl](https://github.com/AlphaHydrae/valdsl) validation library.
 *
 * @module server/api/utils/validation
 * @see https://github.com/AlphaHydrae/valdsl
 */
const _ = require('lodash');
const valdsl = require('valdsl');
 
const { ensureRequest } = require('../../utils/express');
const genericValidators = require('../validators/generic');
const geoJsonValidators = require('../validators/geojson');
 
const dsl = valdsl();
 
// Add our custom validators to the DSL.
dsl.dsl.extend(genericValidators);
dsl.dsl.extend({
  bboxString: geoJsonValidators.bboxString,
  geoJsonPoint: geoJsonValidators.point
});
 
exports.dsl = dsl;
 
/**
 * Asynchronously validates an arbitrary value with any number of validators.
 * This function returns a promise which is resolved if validation is
 * successful. It is rejected if validation fails.
 *
 * @param {*} value - The value to validate.
 * @param {number} status - The HTTP status code that the response should have if validation fails.
 * @param {function[]} callbacks - Validation callbacks.
 * @returns {Promise<ValidationContext>} A promise that will be resolved with
 *   an empty valdsl ValidationContext if validation was successful, or rejected
 *   with a valdsl ValidationErrorBundle containing the list of errors if validation
 *   failed.
 */
exports.validateValue = function(value, status, ...callbacks) {
  if (!_.isInteger(status) || status < 100 || status > 599) {
    throw new Error(`Status must be an HTTP status code between 100 and 599, got ${_.isFinite(status) ? status : typeof(status)}`);
  } else if (!callbacks.length) {
    throw new Error('At least one callback is required');
  } else if (_.find(callbacks, (c) => !_.isFunction(c))) {
    throw new Error('Additional arguments must be functions');
  }
 
  const validationPromise = dsl(function() {
    return this.validate(this.value(value), this.while(this.noError(this.atCurrentLocation())), ...callbacks);
  }).catch(function(err) {
    if (err.errors && !_.has(err, 'status')) {
      err.status = status;
    }
 
    return Promise.reject(err);
  });
 
  return toNativePromise(validationPromise);
};
 
/**
 * Asynchronously validates the body of the specified request with any number
 * of validators.  This function returns a promise which is resolved if
 * validation is successful. It is rejected if validation fails.
 *
 *     function validateAuthentication(req) {
 *       return validate.requestBody(req, function() {
 *         return this.parallel(
 *           this.validate(
 *             this.json('/email'),
 *             this.required(),
 *             this.type('string'),
 *             this.email()
 *           ),
 *           this.validate(
 *             this.json('/password'),
 *             this.required(),
 *             this.type('string'),
 *             this.notBlank()
 *           )
 *         );
 *       })
 *     }
 *
 * @param {Request} req - The Express request object to validate.
 *
 * @param {object} [options] - Validation options (may be omitted).
 *
 * @param {number} [options.status=422] - The HTTP status code that the response should have if validation fails.
 *
 * @param {string[]} [options.types=["object"]] - The list of allowed types for the request body.
 *   Only a single object is considered valid by default, but you might want to specify `["array"]` for
 *   a batch operation (or both, i.e. `["array", "object"]`).
 *
 * @param {function[]} callbacks - Validation callbacks.
 *
 * @returns {Promise<ValidationContext>} A promise that will be resolved with
 *   an empty valdsl ValidationContext if validation was successful, or rejected
 *   with a valdsl ValidationErrorBundle containing the list of errors if validation
 *   failed.
 */
exports.validateRequestBody = function(req, options, ...callbacks) {
  ensureRequest(req);
 
  Eif (_.isFunction(options)) {
    callbacks.unshift(options);
    options = {};
  }
 
  options = options || {};
  const status = options.status || 422;
  const types = options.types || [ 'object' ];
 
  return exports.validateValue(req, status, function() {
    return this.validate(this.property('body'), this.type(...types), ...callbacks);
  });
};
 
// FIXME: remove this fix when issue is resolved (https://github.com/petkaantonov/bluebird/issues/1404)
function toNativePromise(promise) {
  return new Promise((resolve, reject) => promise.then(resolve, reject));
};