Skip to content

Commit 68bfbf6

Browse files
committed
Nominatim: reject suggest() per usage policy
The Nominatim usage policy forbids auto-complete: "you must not implement such a service on the client side using the API". Implement suggest() so that it throws a dedicated SuggestUnsupportedError referencing the policy, rather than leaving the method absent. Since the control feature-detects suggest() by its presence, it now catches SuggestUnsupportedError, detaches the input listener and stops issuing suggestion requests -- so the default geocoder keeps working on submit. https://operations.osmfoundation.org/policies/nominatim/
1 parent d8f8c8a commit 68bfbf6

4 files changed

Lines changed: 73 additions & 5 deletions

File tree

spec/nominatim.spec.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11
import { afterEach, describe, expect, it, vi } from 'vite-plus/test';
22
import { mockFetchRequest } from './mockFetchRequest';
33
import { Nominatim, NominatimResponse } from '../src/geocoders/nominatim';
4+
import { SuggestUnsupportedError } from '../src/geocoders/api';
45

56
describe('L.Control.Geocoder.Nominatim', () => {
67
afterEach(() => vi.clearAllMocks());
78
const geocoder = new Nominatim();
89

10+
it('refuses to suggest, per the Nominatim usage policy', async () => {
11+
await expect(geocoder.suggest('innsbruck')).rejects.toThrow(SuggestUnsupportedError);
12+
await expect(geocoder.suggest('innsbruck')).rejects.toThrow(
13+
/must not implement such a service/
14+
);
15+
});
16+
917
it('geocodes Innsbruck', async () => {
1018
const result = await mockFetchRequest(
1119
'https://nominatim.openstreetmap.org/search?q=innsbruck&limit=5&format=json&addressdetails=1',

src/control.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import * as L from 'leaflet';
22
import { Nominatim } from './geocoders/index';
3-
import { IGeocoder, GeocodingResult, GeocodingContext } from './geocoders/api';
3+
import {
4+
IGeocoder,
5+
GeocodingResult,
6+
GeocodingContext,
7+
SuggestUnsupportedError
8+
} from './geocoders/api';
49

510
export interface GeocoderControlOptions extends L.ControlOptions {
611
/**
@@ -144,6 +149,7 @@ export class GeocoderControl extends EventedControl {
144149
private _results: any;
145150
private _selection: any;
146151
private _suggestTimeout: any;
152+
private _suggestUnsupported = false;
147153

148154
/**
149155
* Instantiates a geocoder control (to be invoked using `new`)
@@ -317,9 +323,22 @@ export class GeocoderControl extends EventedControl {
317323
this.fire(suggest ? 'startsuggest' : 'startgeocode', event);
318324

319325
const context: GeocodingContext = { map: this._map };
320-
const results = suggest
321-
? await this.options.geocoder!.suggest!(value, context)
322-
: await this.options.geocoder!.geocode(value, context);
326+
let results: GeocodingResult[];
327+
if (suggest) {
328+
try {
329+
results = await this.options.geocoder!.suggest!(value, context);
330+
} catch (e) {
331+
if (e instanceof SuggestUnsupportedError) {
332+
// the geocoder's service forbids auto-complete — stop asking
333+
this._suggestUnsupported = true;
334+
L.DomEvent.off(this._input, 'input', this._change, this);
335+
return;
336+
}
337+
throw e;
338+
}
339+
} else {
340+
results = await this.options.geocoder!.geocode(value, context);
341+
}
323342

324343
if (requestCount === this._requestCount) {
325344
const event: FinishGeocodeEvent = { input: value, results };
@@ -454,6 +473,9 @@ export class GeocoderControl extends EventedControl {
454473
}
455474

456475
private _change() {
476+
if (this._suggestUnsupported) {
477+
return;
478+
}
457479
const v = this._input.value;
458480
if (v !== this._lastGeocode) {
459481
clearTimeout(this._suggestTimeout);

src/geocoders/api.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,18 @@ export interface GeocodingResult {
4040
properties?: any;
4141
}
4242

43+
/**
44+
* Thrown by {@link IGeocoder.suggest} when the backing geocoding service does not
45+
* permit auto-complete style queries. The control catches this and stops issuing
46+
* suggestion requests for the remainder of its lifetime.
47+
*/
48+
export class SuggestUnsupportedError extends Error {
49+
constructor(message: string) {
50+
super(message);
51+
this.name = 'SuggestUnsupportedError';
52+
}
53+
}
54+
4355
/**
4456
* An interface implemented to respond to geocoding queries
4557
*/
@@ -54,6 +66,10 @@ export interface IGeocoder {
5466
* Performs a geocoding query suggestion (this happens while typing) and returns the results as promise
5567
* @param query the query
5668
* @param context the context for the query
69+
*
70+
* A geocoder whose backing service forbids auto-complete may implement this method
71+
* and throw {@link SuggestUnsupportedError}, in which case the control silently
72+
* disables suggestions instead of querying on every keystroke.
5773
*/
5874
suggest?(query: string, context?: GeocodingContext): Promise<GeocodingResult[]>;
5975
/**

src/geocoders/nominatim.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
import * as L from 'leaflet';
22
import { template, getJSON } from '../util';
3-
import { IGeocoder, GeocoderOptions, geocodingParams, GeocodingResult, reverseParams } from './api';
3+
import {
4+
IGeocoder,
5+
GeocoderOptions,
6+
geocodingParams,
7+
GeocodingResult,
8+
reverseParams,
9+
SuggestUnsupportedError
10+
} from './api';
411

512
export type NominatimResponse = NominatimResult[];
613

@@ -108,6 +115,21 @@ export class Nominatim implements IGeocoder {
108115
});
109116
}
110117

118+
/**
119+
* Auto-complete is explicitly forbidden by the Nominatim usage policy, which states:
120+
* "Auto-complete search — This is not yet supported by Nominatim and you must not
121+
* implement such a service on the client side using the API."
122+
*
123+
* @see https://operations.osmfoundation.org/policies/nominatim/
124+
* @throws {SuggestUnsupportedError} always
125+
*/
126+
async suggest(_query: string): Promise<GeocodingResult[]> {
127+
throw new SuggestUnsupportedError(
128+
'Nominatim forbids auto-complete search: "you must not implement such a service on ' +
129+
'the client side using the API". See https://operations.osmfoundation.org/policies/nominatim/'
130+
);
131+
}
132+
111133
async reverse(location: L.LatLngLiteral, scale: number) {
112134
const params = reverseParams(this.options, {
113135
lat: location.lat,

0 commit comments

Comments
 (0)