Browse Source

Merge pull request #242 from pelias/reverse

Update /reverse endpoint params and query as per API spec
pull/246/head
Peter Johnson a.k.a. insertcoffee 9 years ago
parent
commit
d7aeeb604d
  1. 10
      query/reverse.js
  2. 35
      sanitiser/_geo_common.js
  3. 11
      sanitiser/_geo_reverse.js
  4. 4
      sanitiser/_geo_search.js
  5. 1
      sanitiser/reverse.js
  6. 43
      test/unit/fixture/reverse_boundary_circle.js
  7. 26
      test/unit/query/reverse.js
  8. 1
      test/unit/run.js
  9. 54
      test/unit/sanitiser/_geo_common.js
  10. 48
      test/unit/sanitiser/reverse.js
  11. 10
      test/unit/sanitiser/search.js

10
query/reverse.js

@ -21,8 +21,7 @@ function generateQuery( clean ){
// set defaults
vs.set({
'size': 1,
'boundary:circle:radius': '500km'
'size': 1
});
// set size
@ -30,6 +29,12 @@ function generateQuery( clean ){
vs.var( 'size', clean.size );
}
// set radius, default to 500km if not specified in request
var radius = 500;
if (clean.hasOwnProperty('boundary_circle_radius')) {
radius = clean.boundary_circle_radius;
}
// focus point centroid
if( clean.lat && clean.lon ){
vs.set({
@ -40,6 +45,7 @@ function generateQuery( clean ){
// bounding circle
'boundary:circle:lat': clean.lat,
'boundary:circle:lon': clean.lon,
'boundary:circle:radius': radius + 'km'
});
}

35
sanitiser/_geo_common.js

@ -53,7 +53,40 @@ function sanitize_coord( coord, clean, param, latlon_is_required ) {
}
}
/**
* Validate circle geometry values
*
* @param {object} clean
* @param {object} params
* @param {bool} is_required
* @param {bool} all_required
*/
function sanitize_boundary_circle( clean, params, is_required, all_required ) {
var props = {
lat: 'boundary_circle_lat',
lon: 'boundary_circle_lon',
rad: 'boundary_circle_radius'
};
// get values for each property
sanitize_coord(props.lat, clean, params['boundary.circle.lat'], all_required && is_required);
sanitize_coord(props.lon, clean, params['boundary.circle.lon'], all_required && is_required);
sanitize_coord(props.rad, clean, params['boundary.circle.radius'], all_required && is_required);
// if all are required, check if some are set but not all, throw an error if missing
if (all_required &&
(clean.hasOwnProperty(props.lat) || clean.hasOwnProperty(props.lon) || clean.hasOwnProperty(props.rad)) &&
!(clean.hasOwnProperty(props.lat) && clean.hasOwnProperty(props.lon) && clean.hasOwnProperty(props.rad))) {
throw new Error('missing part of circle: needs lat,lon,radius');
}
if (is_required && !(clean.hasOwnProperty(props.lat) || clean.hasOwnProperty(props.lon) || clean.hasOwnProperty(props.rad))) {
throw new Error('missing param boundary.circle: should be a trio of lat,lon,radius');
}
}
module.exports = {
sanitize_bbox: sanitize_bbox,
sanitize_coord: sanitize_coord
sanitize_coord: sanitize_coord,
sanitize_boundary_circle: sanitize_boundary_circle
};

11
sanitiser/_geo_reverse.js

@ -6,6 +6,8 @@ module.exports = function sanitize( req ){
var clean = req.clean || {};
var params = req.query;
var latlon_is_required = true;
var circle_is_required = false;
var circle_must_be_complete = false;
// ensure the input params are a valid object
if( !isObject( params ) ){
@ -17,9 +19,12 @@ module.exports = function sanitize( req ){
}
try {
geo_common.sanitize_coord( 'lat', clean, params.point.lat, latlon_is_required );
geo_common.sanitize_coord( 'lon', clean, params.point.lon, latlon_is_required );
geo_common.sanitize_bbox(clean, params.bbox);
geo_common.sanitize_coord( 'lat', clean, params['point.lat'], latlon_is_required );
geo_common.sanitize_coord( 'lon', clean, params['point.lon'], latlon_is_required );
// boundary.circle.* is not mandatory, and only specifying radius is fine,
// as point.lat/lon will be used to fill those values by default
geo_common.sanitize_boundary_circle( clean, params, circle_is_required, circle_must_be_complete);
}
catch (err) {
return {

4
sanitiser/_geo_search.js

@ -21,8 +21,8 @@ module.exports = function sanitize( req ){
}
try {
geo_common.sanitize_coord( 'lat', clean, params.focus.point.lat, latlon_is_required );
geo_common.sanitize_coord( 'lon', clean, params.focus.point.lon, latlon_is_required );
geo_common.sanitize_coord( 'lat', clean, params['focus.point.lat'], latlon_is_required );
geo_common.sanitize_coord( 'lon', clean, params['focus.point.lon'], latlon_is_required );
geo_common.sanitize_bbox(clean, params.bbox);
}
catch (err) {

1
sanitiser/reverse.js

@ -2,6 +2,7 @@ var _sanitize = require('../sanitiser/_sanitize'),
sanitiser = {
latlonzoom: require('../sanitiser/_geo_reverse'),
layers: require('../sanitiser/_layers'),
suorce: require('../sanitiser/_source'),
details: require('../sanitiser/_details'),
size: require('../sanitiser/_size'),
categories: function ( req ) {

43
test/unit/fixture/reverse_boundary_circle.js

@ -0,0 +1,43 @@
module.exports = {
'query': {
'filtered': {
'query': {
'bool': {}
},
'filter': {
'bool': {
'must': [
{
'geo_distance': {
'distance': '500km',
'distance_type': 'plane',
'optimize_bbox': 'indexed',
'_cache': true,
'center_point': {
'lat': 29.49136,
'lon': -82.50622
}
}
}
]
}
}
}
},
'sort': [
'_score',
{
'_geo_distance': {
'center_point': {
'lat': 29.49136,
'lon': -82.50622
},
'order': 'asc',
'distance_type': 'plane'
}
}
],
'size': 1,
'track_scores': true
};

26
test/unit/query/reverse.js

@ -21,6 +21,32 @@ module.exports.tests.query = function(test, common) {
t.deepEqual(compiled, expected, 'valid reverse query');
t.end();
});
test('valid query with radius', function(t) {
var query = generate({
lat: 29.49136, lon: -82.50622, boundary_circle_radius: 123
});
var compiled = JSON.parse( JSON.stringify( query )).query.filtered.filter.bool.must[0].geo_distance.distance;
var expected = '123km';
t.deepEqual(compiled, expected, 'distance set to boundary circle radius');
t.end();
});
test('valid query with boundary.circle lat/lon/radius', function(t) {
var clean = {
lat: 29.49136,
lon: -82.50622,
boundary_circle_lat: 111,
boundary_circle_long: 333
};
var query = generate(clean);
var compiled = JSON.parse( JSON.stringify( query )).query.filtered.filter.bool.must[0].geo_distance.center_point;
var expected = { lat: clean.lat, lon: clean.lon };
t.deepEqual(compiled, expected, 'point.lat/lon overrides boundary.circle.lat/lon');
t.end();
});
test('size fuzz test', function(t) {
// test different sizes
var sizes = [1,2,10,undefined,null];

1
test/unit/run.js

@ -21,6 +21,7 @@ var tests = [
require('./helper/geojsonify'),
require('./helper/outputSchema'),
require('./helper/types'),
require('./sanitiser/_geo_common'),
];
tests.map(function(t) {

54
test/unit/sanitiser/_geo_common.js

@ -0,0 +1,54 @@
var sanitize = require('../../../sanitiser/_geo_common');
module.exports.tests = {};
module.exports.tests.interface = function(test, common) {
test('valid interface', function(t) {
t.equal(typeof sanitize.sanitize_bbox, 'function', 'sanitize_bbox is a valid function');
t.equal(typeof sanitize.sanitize_coord, 'function', 'sanitize_coord is a valid function');
t.equal(typeof sanitize.sanitize_boundary_circle, 'function', 'sanitize_boundary_circle is a valid function');
t.end();
});
};
module.exports.tests.sanitize = function(test, common) {
test('valid circle trio', function (t) {
var clean = {};
var params = {
'boundary.circle.lat': 11,
'boundary.circle.lon': 22,
'boundary.circle.radius': 33
};
var is_required = true;
var all_required = true;
sanitize.sanitize_boundary_circle(clean, params, is_required, all_required);
t.equal(clean.boundary_circle_lat, params['boundary.circle.lat'], 'lat approved');
t.equal(clean.boundary_circle_lon, params['boundary.circle.lon'], 'lon approved');
t.equal(clean.boundary_circle_radius, params['boundary.circle.radius'], 'radius approved');
t.end();
});
test('valid circle, radius only, all not required', function (t) {
var clean = {};
var params = {
'boundary.circle.radius': 33
};
var is_required = true;
var all_required = false;
sanitize.sanitize_boundary_circle(clean, params, is_required, all_required);
t.equal(clean.boundary_circle_radius, params['boundary.circle.radius'], 'radius approved');
t.end();
});
};
module.exports.all = function (tape, common) {
function test(name, testFunction) {
return tape('SANITIZER _geo_common ' + name, testFunction);
}
for( var testCase in module.exports.tests ){
module.exports.tests[testCase](test, common);
}
};

48
test/unit/sanitiser/reverse.js

@ -37,7 +37,7 @@ module.exports.tests.sanitize_lat = function(test, common) {
};
test('invalid lat', function(t) {
lats.invalid.forEach( function( lat ){
sanitize({ point: { lat: lat, lon: 0 } }, function( err, clean ){
sanitize({ 'point.lat': lat, 'point.lon': 0 }, function( err, clean ){
t.equal(err, 'invalid param \'lat\': must be >-90 and <90', lat + ' is an invalid latitude');
t.equal(clean, undefined, 'clean not set');
});
@ -46,7 +46,7 @@ module.exports.tests.sanitize_lat = function(test, common) {
});
test('valid lat', function(t) {
lats.valid.forEach( function( lat ){
sanitize({ point: { lat: lat, lon: 0 } }, function( err, clean ){
sanitize({ 'point.lat': lat, 'point.lon': 0 }, function( err, clean ){
var expected = JSON.parse(JSON.stringify( defaultClean ));
expected.lat = parseFloat( lat );
t.equal(err, undefined, 'no error');
@ -57,7 +57,7 @@ module.exports.tests.sanitize_lat = function(test, common) {
});
test('missing lat', function(t) {
lats.missing.forEach( function( lat ){
sanitize({ point: { lat: lat, lon: 0 } }, function( err, clean ){
sanitize({ 'point.lat': lat, 'point.lon': 0 }, function( err, clean ){
t.equal(err, 'missing param \'lat\'', 'latitude is a required field');
t.equal(clean, undefined, 'clean not set');
});
@ -73,7 +73,7 @@ module.exports.tests.sanitize_lon = function(test, common) {
};
test('valid lon', function(t) {
lons.valid.forEach( function( lon ){
sanitize({ point: { lat: 0, lon: lon } }, function( err, clean ){
sanitize({ 'point.lat': 0, 'point.lon': lon }, function( err, clean ){
var expected = JSON.parse(JSON.stringify( defaultClean ));
expected.lon = parseFloat( lon );
t.equal(err, undefined, 'no error');
@ -84,7 +84,7 @@ module.exports.tests.sanitize_lon = function(test, common) {
});
test('missing lon', function(t) {
lons.missing.forEach( function( lon ){
sanitize({ point: { lat: 0, lon: lon } }, function( err, clean ){
sanitize({ 'point.lat': 0, 'point.lon': lon }, function( err, clean ){
t.equal(err, 'missing param \'lon\'', 'longitude is a required field');
t.equal(clean, undefined, 'clean not set');
});
@ -96,19 +96,19 @@ module.exports.tests.sanitize_lon = function(test, common) {
module.exports.tests.sanitize_size = function(test, common) {
test('invalid size value', function(t) {
sanitize({ size: 'a', point: { lat: 0, lon: 0 } }, function( err, clean ){
sanitize({ size: 'a', 'point.lat': 0, 'point.lon': 0 }, function( err, clean ){
t.equal(clean.size, 10, 'default size set');
t.end();
});
});
test('below min size value', function(t) {
sanitize({ size: -100, point: { lat: 0, lon: 0 } }, function( err, clean ){
sanitize({ size: -100, 'point.lat': 0, 'point.lon': 0 }, function( err, clean ){
t.equal(clean.size, 1, 'min size set');
t.end();
});
});
test('above max size value', function(t) {
sanitize({ size: 9999, point: { lat: 0, lon: 0 } }, function( err, clean ){
sanitize({ size: 9999, 'point.lat': 0, 'point.lon': 0 }, function( err, clean ){
t.equal(clean.size, 40, 'max size set');
t.end();
});
@ -119,7 +119,7 @@ module.exports.tests.sanitize_details = function(test, common) {
var invalid_values = [null, -1, 123, NaN, 'abc'];
invalid_values.forEach(function(details) {
test('invalid details param ' + details, function(t) {
sanitize({ point: { lat: 0, lon: 0 }, details: details }, function( err, clean ){
sanitize({ 'point.lat': 0, 'point.lon': 0, details: details }, function( err, clean ){
t.equal(clean.details, false, 'details set to false');
t.end();
});
@ -129,7 +129,7 @@ module.exports.tests.sanitize_details = function(test, common) {
var valid_values = [true, 'true', 1, '1', 'yes', 'y'];
valid_values.forEach(function(details) {
test('valid details param ' + details, function(t) {
sanitize({ point: { lat: 0, lon: 0 }, details: details }, function( err, clean ){
sanitize({ 'point.lat': 0, 'point.lon': 0, details: details }, function( err, clean ){
t.equal(clean.details, true, 'details set to true');
t.end();
});
@ -137,7 +137,7 @@ module.exports.tests.sanitize_details = function(test, common) {
});
test('test default behavior', function(t) {
sanitize({ point: { lat: 0, lon: 0 } }, function( err, clean ){
sanitize({ 'point.lat': 0, 'point.lon': 0 }, function( err, clean ){
t.equal(clean.details, true, 'details set to true');
t.end();
});
@ -146,7 +146,7 @@ module.exports.tests.sanitize_details = function(test, common) {
var valid_false_values = ['false', false, 0, '0', 'no', 'n'];
valid_false_values.forEach(function(details) {
test('test setting false explicitly ' + details, function(t) {
sanitize({ point: { lat: 0, lon: 0 }, details: details }, function( err, clean ){
sanitize({ 'point.lat': 0, 'point.lon': 0, details: details }, function( err, clean ){
t.equal(clean.details, false, 'details set to false');
t.end();
});
@ -156,13 +156,13 @@ module.exports.tests.sanitize_details = function(test, common) {
module.exports.tests.sanitize_layers = function(test, common) {
test('unspecified', function(t) {
sanitize({ layers: undefined, point: { lat: 0, lon: 0 } }, function( err, clean ){
sanitize({ layers: undefined, 'point.lat': 0, 'point.lon': 0 }, function( err, clean ){
t.deepEqual(clean.types.from_layers, defaultClean.types.from_layers, 'default layers set');
t.end();
});
});
test('invalid layer', function(t) {
sanitize({ layers: 'test_layer', point: { lat: 0, lon: 0 } }, function( err, clean ){
sanitize({ layers: 'test_layer', 'point.lat': 0, 'point.lon': 0 }, function( err, clean ){
var msg = 'invalid param \'layers\': must be one or more of ';
t.true(err.match(msg), 'invalid layer requested');
t.true(err.length > msg.length, 'invalid error message');
@ -171,21 +171,21 @@ module.exports.tests.sanitize_layers = function(test, common) {
});
test('poi (alias) layer', function(t) {
var poi_layers = ['geoname','osmnode','osmway'];
sanitize({ layers: 'poi', point: { lat: 0, lon: 0 } }, function( err, clean ){
sanitize({ layers: 'poi', 'point.lat': 0, 'point.lon': 0 }, function( err, clean ){
t.deepEqual(clean.types.from_layers, poi_layers, 'poi layers set');
t.end();
});
});
test('admin (alias) layer', function(t) {
var admin_layers = ['admin0','admin1','admin2','neighborhood','locality','local_admin'];
sanitize({ layers: 'admin', point: { lat: 0, lon: 0 } }, function( err, clean ){
sanitize({ layers: 'admin', 'point.lat': 0, 'point.lon': 0 }, function( err, clean ){
t.deepEqual(clean.types.from_layers, admin_layers, 'admin layers set');
t.end();
});
});
test('address (alias) layer', function(t) {
var address_layers = ['osmaddress','openaddresses'];
sanitize({ layers: 'address', point: { lat: 0, lon: 0 } }, function( err, clean ){
sanitize({ layers: 'address', 'point.lat': 0, 'point.lon': 0 }, function( err, clean ){
t.deepEqual(clean.types.from_layers, address_layers, 'address layers set');
t.end();
});
@ -193,7 +193,7 @@ module.exports.tests.sanitize_layers = function(test, common) {
test('poi alias layer plus regular layers', function(t) {
var poi_layers = ['geoname','osmnode','osmway'];
var reg_layers = ['admin0', 'admin1'];
sanitize({ layers: 'poi,admin0,admin1', point: { lat: 0, lon: 0 } }, function( err, clean ){
sanitize({ layers: 'poi,admin0,admin1', 'point.lat': 0, 'point.lon': 0 }, function( err, clean ){
t.deepEqual(clean.types.from_layers, reg_layers.concat(poi_layers), 'poi + regular layers');
t.end();
});
@ -201,7 +201,7 @@ module.exports.tests.sanitize_layers = function(test, common) {
test('admin alias layer plus regular layers', function(t) {
var admin_layers = ['admin0','admin1','admin2','neighborhood','locality','local_admin'];
var reg_layers = ['geoname', 'osmway'];
sanitize({ layers: 'admin,geoname,osmway', point: { lat: 0, lon: 0 } }, function( err, clean ){
sanitize({ layers: 'admin,geoname,osmway', 'point.lat': 0, 'point.lon': 0 }, function( err, clean ){
t.deepEqual(clean.types.from_layers, reg_layers.concat(admin_layers), 'admin + regular layers set');
t.end();
});
@ -209,21 +209,21 @@ module.exports.tests.sanitize_layers = function(test, common) {
test('address alias layer plus regular layers', function(t) {
var address_layers = ['osmaddress','openaddresses'];
var reg_layers = ['geoname', 'osmway'];
sanitize({ layers: 'address,geoname,osmway', point: { lat: 0, lon: 0 } }, function( err, clean ){
sanitize({ layers: 'address,geoname,osmway', 'point.lat': 0, 'point.lon': 0 }, function( err, clean ){
t.deepEqual(clean.types.from_layers, reg_layers.concat(address_layers), 'address + regular layers set');
t.end();
});
});
test('alias layer plus regular layers (no duplicates)', function(t) {
var poi_layers = ['geoname','osmnode','osmway'];
sanitize({ layers: 'poi,geoname,osmnode', point: { lat: 0, lon: 0 } }, function( err, clean ){
sanitize({ layers: 'poi,geoname,osmnode', 'point.lat': 0, 'point.lon': 0 }, function( err, clean ){
t.deepEqual(clean.types.from_layers, poi_layers, 'poi layers found (no duplicates)');
t.end();
});
});
test('multiple alias layers (no duplicates)', function(t) {
var alias_layers = ['geoname','osmnode','osmway','admin0','admin1','admin2','neighborhood','locality','local_admin'];
sanitize({ layers: 'poi,admin', point: { lat: 0, lon: 0 } }, function( err, clean ){
sanitize({ layers: 'poi,admin', 'point.lat': 0, 'point.lon': 0 }, function( err, clean ){
t.deepEqual(clean.types.from_layers, alias_layers, 'all layers found (no duplicates)');
t.end();
});
@ -231,7 +231,7 @@ module.exports.tests.sanitize_layers = function(test, common) {
};
module.exports.tests.sanitize_categories = function(test, common) {
var queryParams = { point: { lat: 0, lon: 0 } };
var queryParams = { 'point.lat': 0, 'point.lon': 0 };
test('unspecified', function(t) {
queryParams.categories = undefined;
sanitize(queryParams, function( err, clean ){
@ -284,7 +284,7 @@ module.exports.tests.middleware_failure = function(test, common) {
module.exports.tests.middleware_success = function(test, common) {
test('middleware success', function(t) {
var req = { query: { point: { lat: 0, lon: 0 } }};
var req = { query: { 'point.lat': 0, 'point.lon': 0 }};
var next = function( message ){
t.equal(message, undefined, 'no error message set');
t.deepEqual(req.clean, defaultClean);

10
test/unit/sanitiser/search.js

@ -93,7 +93,7 @@ module.exports.tests.sanitize_lat = function(test, common) {
};
test('invalid lat', function(t) {
lats.invalid.forEach( function( lat ){
sanitize({ text: 'test', focus: { point: { lat: lat, lon: 0 } } }, function( err, clean ){
sanitize({ text: 'test', 'focus.point.lat': lat, 'focus.point.lon': 0 }, function( err, clean ){
t.equal(err, 'invalid param \'lat\': must be >-90 and <90', lat + ' is an invalid latitude');
t.equal(clean, undefined, 'clean not set');
});
@ -102,7 +102,7 @@ module.exports.tests.sanitize_lat = function(test, common) {
});
test('valid lat', function(t) {
lats.valid.forEach( function( lat ){
sanitize({ text: 'test', focus: { point: { lat: lat, lon: 0 } } }, function( err, clean ){
sanitize({ text: 'test', 'focus.point.lat': lat, 'focus.point.lon': 0 }, function( err, clean ){
var expected_lat = parseFloat( lat );
t.equal(err, undefined, 'no error');
t.deepEqual(clean.lat, expected_lat, 'clean lat set correctly (' + lat + ')');
@ -118,7 +118,7 @@ module.exports.tests.sanitize_lon = function(test, common) {
};
test('valid lon', function(t) {
lons.valid.forEach( function( lon ){
sanitize({ text: 'test', focus: { point: { lat: 0, lon: lon } } }, function( err, clean ){
sanitize({ text: 'test', 'focus.point.lat': 0, 'focus.point.lon': lon }, function( err, clean ){
var expected = JSON.parse(JSON.stringify( defaultClean ));
expected.lon = parseFloat( lon );
t.equal(err, undefined, 'no error');
@ -139,7 +139,7 @@ module.exports.tests.sanitize_optional_geo = function(test, common) {
t.end();
});
test('no lat', function(t) {
sanitize({ text: 'test', focus: { point: { lon: 0 } } }, function( err, clean ){
sanitize({ text: 'test', 'focus.point.lon': 0 }, function( err, clean ){
var expected_lon = 0;
t.equal(err, undefined, 'no error');
t.deepEqual(clean.lon, expected_lon, 'clean set correctly (without any lat)');
@ -147,7 +147,7 @@ module.exports.tests.sanitize_optional_geo = function(test, common) {
t.end();
});
test('no lon', function(t) {
sanitize({ text: 'test', focus: { point: { lat: 0 } } }, function( err, clean ){
sanitize({ text: 'test', 'focus.point.lat': 0 }, function( err, clean ){
var expected_lat = 0;
t.equal(err, undefined, 'no error');
t.deepEqual(clean.lat, expected_lat, 'clean set correctly (without any lon)');

Loading…
Cancel
Save