All files / server/api/validators geojson.js

100% Statements 30/30
100% Branches 18/18
100% Functions 14/14
100% Lines 29/29
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 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140      1x                       1x 21x     17x 17x 1x               63x 16x 1x                 15x 15x 15x 15x 15x                                 1x 22x 17x                                                                   60x       60x         60x         15x 15x 2x               40x 40x 6x               42x 42x 8x            
/**
 * @module server/api/validators/geojson
 */
const _ = require('lodash');
 
/**
 * Returns a ValDSL validator that checks whether a value is a well-formatted bounding box string.
 *
 * A bounding box string is composed of 4 comma-separated numbers:
 *
 * * The first 2 numbers are the coordinates (longitude & latitude) of the bounding box's south-west corner.
 * * The last 2 numbers are the coordinates (longitude & latitude) of the bounding box's north-east corner.
 *
 * @returns {function} A validator function.
 */
exports.bboxString = function() {
  return async function(ctx) {
 
    // Make sure the value is a string.
    const bbox = ctx.get('value');
    if (!_.isString(bbox)) {
      return ctx.addError({
        validator: 'bboxString',
        cause: 'wrongType',
        message: 'must be a string'
      })
    }
 
    // Make sure it has 4 values.
    const coordinates = bbox.split(',').map(value => parseFloat(value));
    if (coordinates.length != 4) {
      return ctx.addError({
        validator: 'bboxString',
        cause: 'wrongLength',
        actualLength: coordinates.length,
        message: `must have 4 comma-separated coordinates; got ${coordinates.length}`
      });
    }
 
    // Make sure the 4 values are valid longitudes and latitudes.
    await Promise.all([
      validateBboxStringCoordinate(ctx, coordinates, 0, coordinateCtx => validateLongitude(coordinateCtx)),
      validateBboxStringCoordinate(ctx, coordinates, 1, coordinateCtx => validateLatitude(coordinateCtx)),
      validateBboxStringCoordinate(ctx, coordinates, 2, coordinateCtx => validateLongitude(coordinateCtx)),
      validateBboxStringCoordinate(ctx, coordinates, 3, coordinateCtx => validateLatitude(coordinateCtx))
    ]);
  };
};
 
/**
 * Returns a ValDSL validator that checks whether a value is a GeoJSON object of type Point.
 *
 *     const { point: validateGeoJsonPoint } = require('../validators/geojson');
 *
 *     this.validate(
 *       this.json('geometry'),
 *       validateGeoJsonPoint()
 *     )
 *
 * @returns {function} A validator function.
 */
exports.point = function() {
  return function(ctx) {
    return ctx.series(
      ctx.type('object'),
      ctx.properties('type', 'coordinates'),
      ctx.parallel(
        ctx.validate(
          ctx.json('/type'),
          ctx.required(),
          ctx.type('string'),
          ctx.equals('Point')
        ),
        ctx.validate(
          ctx.json('/coordinates'),
          ctx.required(),
          ctx.type('array'),
          validateCoordinates,
          ctx.parallel(
            ctx.validate(
              ctx.json('/0'),
              ctx.type('number'),
              validateLongitude
            ),
            ctx.validate(
              ctx.json('/1'),
              ctx.type('number'),
              validateLatitude
            )
          )
        )
      )
    );
  };
}
 
function validateBboxStringCoordinate(ctx, coordinates, i, callback) {
  return ctx.validate(coordinateCtx => {
 
    // Make the error indicate the index of the invalid coordinate
    // within the bbox string, e.g. "bbox[1]".
    coordinateCtx.set({
      location: `${coordinateCtx.get('location')}[${i}]`,
      value: coordinates[i]
    });
 
    return callback(coordinateCtx);
  });
}
 
function validateCoordinates(ctx) {
  const coordinates = ctx.get('value');
  if (!_.isArray(coordinates) || coordinates.length != 2) {
    ctx.addError({
      validator: 'coordinates',
      message: 'must be an array of 2 numbers (longitude & latitude)'
    });
  }
}
 
function validateLongitude(ctx) {
  const longitude = ctx.get('value');
  if (!_.isFinite(longitude) || longitude < -180 || longitude > 180) {
    ctx.addError({
      validator: 'longitude',
      message: 'must be a number between -180 and 180'
    });
  }
}
 
function validateLatitude(ctx) {
  const latitude = ctx.get('value');
  if (!_.isFinite(latitude) || latitude < -90 || latitude > 90) {
    ctx.addError({
      validator: 'latitude',
      message: 'must be a number between -90 and 90'
    });
  }
}