mirror of https://github.com/pelias/api.git
Stephen Hess
7 years ago
9 changed files with 945 additions and 176 deletions
@ -0,0 +1,75 @@
|
||||
const _ = require('lodash'); |
||||
const Debug = require('../helper/debug'); |
||||
const debugLog = new Debug('controller:libpostal'); |
||||
const logger = require('pelias-logger').get('api'); |
||||
|
||||
// if there's a house_number in the libpostal response, return it
|
||||
// otherwise return the postcode field (which may be undefined)
|
||||
function findHouseNumberField(response) { |
||||
const house_number_field = response.find(f => f.label === 'house_number'); |
||||
|
||||
if (house_number_field) { |
||||
return house_number_field; |
||||
} |
||||
|
||||
return response.find(f => f.label === 'postcode'); |
||||
|
||||
} |
||||
|
||||
function setup(libpostalService, should_execute) { |
||||
function controller( req, res, next ){ |
||||
// bail early if req/res don't pass conditions for execution
|
||||
if (!should_execute(req, res)) { |
||||
return next(); |
||||
} |
||||
|
||||
const initialTime = debugLog.beginTimer(req); |
||||
|
||||
libpostalService(req, (err, response) => { |
||||
if (err) { |
||||
// push err.message or err onto req.errors
|
||||
req.errors.push( _.get(err, 'message', err) ); |
||||
|
||||
} else { |
||||
// figure out which field contains the probable house number, prefer house_number
|
||||
// libpostal parses some inputs, like `3370 cobbe ave`, as a postcode+street
|
||||
// so because we're treating the entire field as a street address, it's safe
|
||||
// to assume that an identified postcode is actually a house number.
|
||||
const house_number_field = findHouseNumberField(response); |
||||
|
||||
// if we're fairly certain that libpostal identified a house number
|
||||
// (from either the house_number or postcode field), place it into the
|
||||
// number field and remove the first instance of that value from address
|
||||
// and assign to street
|
||||
// eg - '1090 N Charlotte St' becomes number=1090 and street=N Charlotte St
|
||||
if (house_number_field) { |
||||
req.clean.parsed_text.number = house_number_field.value; |
||||
|
||||
// remove the first instance of the number and trim whitespace
|
||||
req.clean.parsed_text.street = _.trim(_.replace(req.clean.parsed_text.address, req.clean.parsed_text.number, '')); |
||||
|
||||
} else { |
||||
// otherwise no house number was identifiable, so treat the entire input
|
||||
// as a street
|
||||
req.clean.parsed_text.street = req.clean.parsed_text.address; |
||||
|
||||
} |
||||
|
||||
// the address field no longer means anything since it's been parsed, so remove it
|
||||
delete req.clean.parsed_text.address; |
||||
|
||||
debugLog.push(req, {parsed_text: response}); |
||||
|
||||
} |
||||
|
||||
debugLog.stopTimer(req, initialTime); |
||||
return next(); |
||||
|
||||
}); |
||||
|
||||
} |
||||
|
||||
return controller; |
||||
} |
||||
|
||||
module.exports = setup; |
@ -0,0 +1,33 @@
|
||||
'use strict'; |
||||
|
||||
const url = require('url'); |
||||
|
||||
const ServiceConfiguration = require('pelias-microservice-wrapper').ServiceConfiguration; |
||||
|
||||
class Libpostal extends ServiceConfiguration { |
||||
constructor(o, propertyExtractor) { |
||||
super('libpostal', o); |
||||
|
||||
// save off the propertyExtractor function
|
||||
// this is used to extract a single property from req. eg:
|
||||
// * _.property('clean.text')
|
||||
// * _.property('clean.parsed_text.address')
|
||||
// will return those properties from req
|
||||
this.propertyExtractor = propertyExtractor; |
||||
|
||||
} |
||||
|
||||
getParameters(req) { |
||||
return { |
||||
address: this.propertyExtractor(req) |
||||
}; |
||||
|
||||
} |
||||
|
||||
getUrl(req) { |
||||
return url.resolve(this.baseUrl, 'parse'); |
||||
} |
||||
|
||||
} |
||||
|
||||
module.exports = Libpostal; |
@ -0,0 +1,440 @@
|
||||
'use strict'; |
||||
|
||||
const proxyquire = require('proxyquire').noCallThru(); |
||||
const libpostal = require('../../../controller/structured_libpostal'); |
||||
const _ = require('lodash'); |
||||
const mock_logger = require('pelias-mock-logger'); |
||||
|
||||
module.exports.tests = {}; |
||||
|
||||
module.exports.tests.interface = (test, common) => { |
||||
test('valid interface', (t) => { |
||||
t.equal(typeof libpostal, 'function', 'libpostal is a function'); |
||||
t.equal(typeof libpostal(), 'function', 'libpostal returns a controller'); |
||||
t.end(); |
||||
}); |
||||
}; |
||||
|
||||
module.exports.tests.early_exit_conditions = (test, common) => { |
||||
test('should_execute returning false should not call service', t => { |
||||
const service = () => { |
||||
t.fail('service should not have been called'); |
||||
}; |
||||
|
||||
const should_execute = (req) => { |
||||
// req and res should be passed to should_execute
|
||||
t.deepEquals(req, { |
||||
clean: { |
||||
text: 'original query' |
||||
} |
||||
}); |
||||
|
||||
return false; |
||||
}; |
||||
|
||||
const controller = libpostal(service, should_execute); |
||||
|
||||
const req = { |
||||
clean: { |
||||
text: 'original query' |
||||
} |
||||
}; |
||||
|
||||
controller(req, undefined, () => { |
||||
t.deepEquals(req, { |
||||
clean: { |
||||
text: 'original query' |
||||
} |
||||
}, 'req should not have been modified'); |
||||
|
||||
t.end(); |
||||
}); |
||||
|
||||
}); |
||||
|
||||
}; |
||||
|
||||
module.exports.tests.error_conditions = (test, common) => { |
||||
test('service returning error should append and not modify req.clean', t => { |
||||
const service = (req, callback) => { |
||||
callback('libpostal service error', []); |
||||
}; |
||||
|
||||
const controller = libpostal(service, () => true); |
||||
|
||||
const req = { |
||||
clean: { |
||||
text: 'original query' |
||||
}, |
||||
errors: [] |
||||
}; |
||||
|
||||
controller(req, undefined, () => { |
||||
t.deepEquals(req, { |
||||
clean: { |
||||
text: 'original query' |
||||
}, |
||||
errors: ['libpostal service error'] |
||||
}, 'req should not have been modified'); |
||||
|
||||
t.end(); |
||||
|
||||
}); |
||||
|
||||
}); |
||||
|
||||
}; |
||||
|
||||
// module.exports.tests.failure_conditions = (test, common) => {
|
||||
// test('service returning 2 or more of a label should return undefined and log message', t => {
|
||||
// const logger = mock_logger();
|
||||
//
|
||||
// const service = (req, callback) => {
|
||||
// const response = [
|
||||
// {
|
||||
// label: 'road',
|
||||
// value: 'road value 1'
|
||||
// },
|
||||
// {
|
||||
// label: 'city',
|
||||
// value: 'city value'
|
||||
// },
|
||||
// {
|
||||
// label: 'road',
|
||||
// value: 'road value 2'
|
||||
// }
|
||||
// ];
|
||||
//
|
||||
// callback(null, response);
|
||||
// };
|
||||
//
|
||||
// const controller = proxyquire('../../../controller/libpostal', {
|
||||
// 'pelias-logger': logger
|
||||
// })(service, () => true);
|
||||
//
|
||||
// const req = {
|
||||
// clean: {
|
||||
// text: 'query value'
|
||||
// },
|
||||
// errors: []
|
||||
// };
|
||||
//
|
||||
// controller(req, undefined, () => {
|
||||
// t.ok(logger.isWarnMessage('discarding libpostal parse of \'query value\' due to duplicate field assignments'));
|
||||
//
|
||||
// t.deepEquals(req, {
|
||||
// clean: {
|
||||
// text: 'query value'
|
||||
// },
|
||||
// errors: []
|
||||
// }, 'req should not have been modified');
|
||||
//
|
||||
// t.end();
|
||||
//
|
||||
// });
|
||||
//
|
||||
// });
|
||||
//
|
||||
// test('service returning empty array should not set parsed_text or parser', t => {
|
||||
// const logger = mock_logger();
|
||||
//
|
||||
// const service = (req, callback) => {
|
||||
// callback(null, []);
|
||||
// };
|
||||
//
|
||||
// const controller = proxyquire('../../../controller/libpostal', {
|
||||
// 'pelias-logger': logger
|
||||
// })(service, () => true);
|
||||
//
|
||||
// const req = {
|
||||
// clean: {
|
||||
// text: 'query value'
|
||||
// },
|
||||
// errors: []
|
||||
// };
|
||||
//
|
||||
// controller(req, undefined, () => {
|
||||
// t.deepEquals(req, {
|
||||
// clean: {
|
||||
// text: 'query value'
|
||||
// },
|
||||
// errors: []
|
||||
// }, 'req should not have been modified');
|
||||
//
|
||||
// t.end();
|
||||
//
|
||||
// });
|
||||
//
|
||||
// });
|
||||
//
|
||||
// };
|
||||
//
|
||||
module.exports.tests.success_conditions = (test, common) => { |
||||
test('service returning house_number should set req.clean.parsed_text.', t => { |
||||
const service = (req, callback) => { |
||||
const response = [ |
||||
{ |
||||
label: 'house_number', |
||||
value: 'house_number value' |
||||
}, |
||||
{ |
||||
label: 'postcode', |
||||
value: 'postcode value' |
||||
} |
||||
]; |
||||
|
||||
callback(null, response); |
||||
}; |
||||
|
||||
const controller = libpostal(service, () => true); |
||||
|
||||
const req = { |
||||
clean: { |
||||
parsed_text: { |
||||
address: 'other value house_number value street value' |
||||
} |
||||
}, |
||||
errors: [] |
||||
}; |
||||
|
||||
controller(req, undefined, () => { |
||||
t.deepEquals(req, { |
||||
clean: { |
||||
parsed_text: { |
||||
number: 'house_number value', |
||||
street: 'other value street value' |
||||
} |
||||
}, |
||||
errors: [] |
||||
}, 'req should not have been modified'); |
||||
|
||||
t.end(); |
||||
|
||||
}); |
||||
|
||||
}); |
||||
|
||||
test('service returning postcode should set req.clean.parsed_text.', t => { |
||||
const service = (req, callback) => { |
||||
const response = [ |
||||
{ |
||||
label: 'postcode', |
||||
value: 'postcode value' |
||||
} |
||||
]; |
||||
|
||||
callback(null, response); |
||||
}; |
||||
|
||||
const controller = libpostal(service, () => true); |
||||
|
||||
const req = { |
||||
clean: { |
||||
parsed_text: { |
||||
address: 'other value postcode value street value' |
||||
} |
||||
}, |
||||
errors: [] |
||||
}; |
||||
|
||||
controller(req, undefined, () => { |
||||
t.deepEquals(req, { |
||||
clean: { |
||||
parsed_text: { |
||||
number: 'postcode value', |
||||
street: 'other value street value' |
||||
} |
||||
}, |
||||
errors: [] |
||||
}, 'req should not have been modified'); |
||||
|
||||
t.end(); |
||||
|
||||
}); |
||||
|
||||
}); |
||||
|
||||
test('service returning neither house_number nor postcode should not set req.clean.parsed_text.number', t => { |
||||
const service = (req, callback) => { |
||||
const response = [ |
||||
{ |
||||
label: 'city', |
||||
value: 'city value' |
||||
} |
||||
]; |
||||
|
||||
callback(null, response); |
||||
}; |
||||
|
||||
const controller = libpostal(service, () => true); |
||||
|
||||
const req = { |
||||
clean: { |
||||
parsed_text: { |
||||
address: 'street value' |
||||
} |
||||
}, |
||||
errors: [] |
||||
}; |
||||
|
||||
controller(req, undefined, () => { |
||||
t.deepEquals(req, { |
||||
clean: { |
||||
parsed_text: { |
||||
street: 'street value' |
||||
} |
||||
}, |
||||
errors: [] |
||||
}, 'req should not have been modified'); |
||||
|
||||
t.end(); |
||||
|
||||
}); |
||||
|
||||
}); |
||||
|
||||
// test('service returning valid response should convert and append', t => {
|
||||
// const service = (req, callback) => {
|
||||
// const response = [
|
||||
// {
|
||||
// label: 'island',
|
||||
// value: 'island value'
|
||||
// },
|
||||
// {
|
||||
// label: 'category',
|
||||
// value: 'category value'
|
||||
// },
|
||||
// {
|
||||
// label: 'house',
|
||||
// value: 'house value'
|
||||
// },
|
||||
// {
|
||||
// label: 'house_number',
|
||||
// value: 'house_number value'
|
||||
// },
|
||||
// {
|
||||
// label: 'road',
|
||||
// value: 'road value'
|
||||
// },
|
||||
// {
|
||||
// label: 'suburb',
|
||||
// value: 'suburb value'
|
||||
// },
|
||||
// {
|
||||
// label: 'city_district',
|
||||
// value: 'city_district value'
|
||||
// },
|
||||
// {
|
||||
// label: 'city',
|
||||
// value: 'city value'
|
||||
// },
|
||||
// {
|
||||
// label: 'state_district',
|
||||
// value: 'state_district value'
|
||||
// },
|
||||
// {
|
||||
// label: 'state',
|
||||
// value: 'state value'
|
||||
// },
|
||||
// {
|
||||
// label: 'postcode',
|
||||
// value: 'postcode value'
|
||||
// },
|
||||
// {
|
||||
// label: 'country',
|
||||
// value: 'country value'
|
||||
// }
|
||||
// ];
|
||||
//
|
||||
// callback(null, response);
|
||||
// };
|
||||
//
|
||||
// const controller = libpostal(service, () => true);
|
||||
//
|
||||
// const req = {
|
||||
// clean: {
|
||||
// text: 'original query'
|
||||
// },
|
||||
// errors: []
|
||||
// };
|
||||
//
|
||||
// controller(req, undefined, () => {
|
||||
// t.deepEquals(req, {
|
||||
// clean: {
|
||||
// text: 'original query',
|
||||
// parser: 'libpostal',
|
||||
// parsed_text: {
|
||||
// island: 'island value',
|
||||
// category: 'category value',
|
||||
// query: 'house value',
|
||||
// number: 'house_number value',
|
||||
// street: 'road value',
|
||||
// neighbourhood: 'suburb value',
|
||||
// borough: 'city_district value',
|
||||
// city: 'city value',
|
||||
// county: 'state_district value',
|
||||
// state: 'state value',
|
||||
// postalcode: 'postcode value',
|
||||
// country: 'country value'
|
||||
// }
|
||||
// },
|
||||
// errors: []
|
||||
// }, 'req should not have been modified');
|
||||
//
|
||||
// t.end();
|
||||
//
|
||||
// });
|
||||
//
|
||||
// });
|
||||
//
|
||||
// test('ISO-2 country should be converted to ISO-3', t => {
|
||||
// const service = (req, callback) => {
|
||||
// const response = [
|
||||
// {
|
||||
// label: 'country',
|
||||
// value: 'ca'
|
||||
// }
|
||||
// ];
|
||||
//
|
||||
// callback(null, response);
|
||||
// };
|
||||
//
|
||||
// const controller = libpostal(service, () => true);
|
||||
//
|
||||
// const req = {
|
||||
// clean: {
|
||||
// text: 'original query'
|
||||
// },
|
||||
// errors: []
|
||||
// };
|
||||
//
|
||||
// controller(req, undefined, () => {
|
||||
// t.deepEquals(req, {
|
||||
// clean: {
|
||||
// text: 'original query',
|
||||
// parser: 'libpostal',
|
||||
// parsed_text: {
|
||||
// country: 'CAN'
|
||||
// }
|
||||
// },
|
||||
// errors: []
|
||||
// }, 'req should not have been modified');
|
||||
//
|
||||
// t.end();
|
||||
//
|
||||
// });
|
||||
//
|
||||
// });
|
||||
//
|
||||
}; |
||||
|
||||
module.exports.all = (tape, common) => { |
||||
|
||||
function test(name, testFunction) { |
||||
return tape(`GET /libpostal ${name}`, testFunction); |
||||
} |
||||
|
||||
for( const testCase in module.exports.tests ){ |
||||
module.exports.tests[testCase](test, common); |
||||
} |
||||
}; |
@ -0,0 +1,99 @@
|
||||
const Libpostal = require('../../../../service/configurations/Libpostal'); |
||||
|
||||
module.exports.tests = {}; |
||||
|
||||
module.exports.tests.all = (test, common) => { |
||||
test('getName should return \'libpostal\'', (t) => { |
||||
const configBlob = { |
||||
url: 'http://localhost:1234', |
||||
timeout: 17, |
||||
retries: 19 |
||||
}; |
||||
|
||||
const libpostal = new Libpostal(configBlob); |
||||
|
||||
t.equals(libpostal.getName(), 'libpostal'); |
||||
t.equals(libpostal.getBaseUrl(), 'http://localhost:1234/'); |
||||
t.equals(libpostal.getTimeout(), 17); |
||||
t.equals(libpostal.getRetries(), 19); |
||||
t.end(); |
||||
|
||||
}); |
||||
|
||||
test('getUrl should return value passed to constructor', (t) => { |
||||
const configBlob = { |
||||
url: 'http://localhost:1234', |
||||
timeout: 17, |
||||
retries: 19 |
||||
}; |
||||
|
||||
const libpostal = new Libpostal(configBlob); |
||||
|
||||
t.equals(libpostal.getUrl(), 'http://localhost:1234/parse'); |
||||
t.end(); |
||||
|
||||
}); |
||||
|
||||
test('getParameters should return object with text and lang from req', (t) => { |
||||
const configBlob = { |
||||
url: 'http://localhost:1234', |
||||
timeout: 17, |
||||
retries: 19 |
||||
}; |
||||
|
||||
const propertyExtractor = (req) => { |
||||
t.deepEquals(req, { a: 1, b: 2}); |
||||
return 'property value'; |
||||
}; |
||||
|
||||
const libpostal = new Libpostal(configBlob, propertyExtractor); |
||||
|
||||
const req = { |
||||
a: 1, |
||||
b: 2 |
||||
}; |
||||
|
||||
t.deepEquals(libpostal.getParameters(req), { address: 'property value' }); |
||||
t.end(); |
||||
|
||||
}); |
||||
|
||||
test('getHeaders should return empty object', (t) => { |
||||
const configBlob = { |
||||
url: 'base url', |
||||
timeout: 17, |
||||
retries: 19 |
||||
}; |
||||
|
||||
const libpostal = new Libpostal(configBlob); |
||||
|
||||
t.deepEquals(libpostal.getHeaders(), {}); |
||||
t.end(); |
||||
|
||||
}); |
||||
|
||||
test('baseUrl ending in / should not have double /\'s return by getUrl', (t) => { |
||||
const configBlob = { |
||||
url: 'http://localhost:1234/', |
||||
timeout: 17, |
||||
retries: 19 |
||||
}; |
||||
|
||||
const libpostal = new Libpostal(configBlob); |
||||
|
||||
t.deepEquals(libpostal.getUrl(), 'http://localhost:1234/parse'); |
||||
t.end(); |
||||
|
||||
}); |
||||
|
||||
}; |
||||
|
||||
module.exports.all = (tape, common) => { |
||||
function test(name, testFunction) { |
||||
return tape(`SERVICE CONFIGURATION /Libpostal ${name}`, testFunction); |
||||
} |
||||
|
||||
for( var testCase in module.exports.tests ){ |
||||
module.exports.tests[testCase](test, common); |
||||
} |
||||
}; |
Loading…
Reference in new issue