Skip to content

Commit f328944

Browse files
committed
feat: Add GET search
1 parent 975c55a commit f328944

File tree

5 files changed

+132
-8
lines changed

5 files changed

+132
-8
lines changed

example/src/pages/Main/index.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ function Main() {
1111
Authorization: "Basic " + btoa(process.env.REACT_APP_STAC_API_TOKEN + ":")
1212
}), []);
1313

14-
const stacApi = new StacApi(process.env.REACT_APP_STAC_API, { headers });
14+
const stacApi = useMemo(() => new StacApi(process.env.REACT_APP_STAC_API, { headers }), [headers]);
15+
1516
const {
1617
setBbox,
1718
collections,

src/hooks/useCollections.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import useCollections from './useCollections';
44
import StacApi from '../stac-api';
55

66
describe('useCollections', () => {
7+
fetch.mockResponseOnce(JSON.stringify({ links: [] }));
78
const stacApi = new StacApi('https://fake-stac-api.net');
89
beforeEach(() => fetch.resetMocks());
910

src/hooks/useStacSearch.test.ts

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ function parseRequestPayload(mockApiCall?: RequestInit) {
1111
return JSON.parse(mockApiCall.body as string);
1212
}
1313

14-
describe('useStacSearch', () => {
14+
describe('useStacSearch — API supports POST', () => {
15+
fetch.mockResponseOnce(JSON.stringify({ links: [{ rel: 'search', method: 'POST' }] }));
1516
const stacApi = new StacApi('https://fake-stac-api.net');
1617
beforeEach(() => fetch.resetMocks());
1718

@@ -410,9 +411,92 @@ describe('useStacSearch', () => {
410411
expect(result.current.results).toEqual({ data: '12345' });
411412
expect(result.current.bbox).toEqual(bbox);
412413

414+
fetch.mockResponseOnce(JSON.stringify({ links: [{ rel: 'search', method: 'POST' }] }));
413415
const newStac = new StacApi('https://otherstack.com');
414416
rerender({ stacApi: newStac });
415417
expect(result.current.results).toBeUndefined();
416418
expect(result.current.bbox).toBeUndefined();
417419
});
418420
});
421+
422+
describe('useStacSearch — API supports GET', () => {
423+
fetch.mockResponseOnce(JSON.stringify({ links: [{ rel: 'search', method: 'GET' }] }));
424+
const stacApi = new StacApi('https://fake-stac-api.net');
425+
beforeEach(() => fetch.resetMocks());
426+
427+
it('includes Bbox in search', async () => {
428+
fetch.mockResponseOnce(JSON.stringify({ data: '12345' }));
429+
430+
const { result, waitForNextUpdate } = renderHook(
431+
() => useStacSearch(stacApi)
432+
);
433+
434+
act(() => result.current.setBbox([-0.59, 51.24, 0.30, 51.74]));
435+
act(() => result.current.submit());
436+
await waitForNextUpdate();
437+
438+
expect(fetch.mock.calls[0][0]).toEqual('https://fake-stac-api.net/search?bbox=-0.59%2C51.24%2C0.3%2C51.74&limit=25');
439+
expect(result.current.results).toEqual({ data: '12345' });
440+
});
441+
442+
it('includes Collections in search', async () => {
443+
fetch.mockResponseOnce(JSON.stringify({ data: '12345' }));
444+
445+
const { result, waitForNextUpdate } = renderHook(
446+
() => useStacSearch(stacApi)
447+
);
448+
449+
act(() => result.current.setCollections(['wildfire', 'surface_temp']));
450+
act(() => result.current.submit());
451+
await waitForNextUpdate();
452+
453+
expect(fetch.mock.calls[0][0]).toEqual('https://fake-stac-api.net/search?collections=wildfire%2Csurface_temp&limit=25');
454+
expect(result.current.results).toEqual({ data: '12345' });
455+
});
456+
457+
it('includes date range in search', async () => {
458+
fetch.mockResponseOnce(JSON.stringify({ data: '12345' }));
459+
460+
const { result, waitForNextUpdate } = renderHook(
461+
() => useStacSearch(stacApi)
462+
);
463+
464+
act(() => result.current.setDateRangeFrom('2022-01-17'));
465+
act(() => result.current.setDateRangeTo('2022-05-17'));
466+
act(() => result.current.submit());
467+
await waitForNextUpdate();
468+
469+
expect(fetch.mock.calls[0][0]).toEqual('https://fake-stac-api.net/search?datetime=2022-01-17%2F2022-05-17&limit=25');
470+
expect(result.current.results).toEqual({ data: '12345' });
471+
});
472+
473+
it('includes open date range in search (no to-date)', async () => {
474+
fetch.mockResponseOnce(JSON.stringify({ data: '12345' }));
475+
476+
const { result, waitForNextUpdate } = renderHook(
477+
() => useStacSearch(stacApi)
478+
);
479+
480+
act(() => result.current.setDateRangeFrom('2022-01-17'));
481+
act(() => result.current.submit());
482+
await waitForNextUpdate();
483+
484+
expect(fetch.mock.calls[0][0]).toEqual('https://fake-stac-api.net/search?datetime=2022-01-17%2F..&limit=25');
485+
expect(result.current.results).toEqual({ data: '12345' });
486+
});
487+
488+
it('includes open date range in search (no from-date)', async () => {
489+
fetch.mockResponseOnce(JSON.stringify({ data: '12345' }));
490+
491+
const { result, waitForNextUpdate } = renderHook(
492+
() => useStacSearch(stacApi)
493+
);
494+
495+
act(() => result.current.setDateRangeTo('2022-05-17'));
496+
act(() => result.current.submit());
497+
await waitForNextUpdate();
498+
499+
expect(fetch.mock.calls[0][0]).toEqual('https://fake-stac-api.net/search?datetime=..%2F2022-05-17&limit=25');
500+
expect(result.current.results).toEqual({ data: '12345' });
501+
});
502+
});

src/hooks/useStacSearch.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,9 @@ function useStacSearch(stacApi: StacApi): StacSearchHook {
9191
.then(response => response.json())
9292
.then(data => {
9393
setResults(data);
94-
setPaginationConfig(data.links);
94+
if (data.links) {
95+
setPaginationConfig(data.links);
96+
}
9597
})
9698
.catch((err) => setError(err))
9799
.finally(() => setState('IDLE'));

src/stac-api/index.ts

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { ApiError, GenericObject } from '../types';
2-
import type { Bbox, SearchPayload, DateRange, CollectionIdList } from '../types/stac';
2+
import type { Bbox, SearchPayload, DateRange, CollectionIdList, Link } from '../types/stac';
33

44
type RequestPayload = SearchPayload;
55
type FetchOptions = {
@@ -11,10 +11,23 @@ type FetchOptions = {
1111
class StacApi {
1212
baseUrl: string;
1313
options?: GenericObject;
14+
searchMode = 'GET';
1415

1516
constructor(baseUrl: string, options?: GenericObject) {
1617
this.baseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
1718
this.options = options;
19+
this.fetchApiMeta();
20+
}
21+
22+
fetchApiMeta(): void {
23+
this.fetch(this.baseUrl)
24+
.then(r => r.json())
25+
.then(r => {
26+
const doesPost = r.links.find(({ rel, method }: Link) => rel === 'search' && method === 'POST');
27+
if (doesPost) {
28+
this.searchMode = 'POST';
29+
}
30+
});
1831
}
1932

2033
fixBboxCoordinateOrder(bbox?: Bbox): Bbox | undefined {
@@ -56,6 +69,20 @@ class StacApi {
5669
}
5770
}
5871

72+
payloadToQuery(payload: SearchPayload): string {
73+
const queryObj = {};
74+
for (const [key, value] of Object.entries(payload)) {
75+
if (!value) continue;
76+
77+
if (Array.isArray(value)) {
78+
queryObj[key] = value.join(',');
79+
} else {
80+
queryObj[key] = value;
81+
}
82+
}
83+
return new URLSearchParams(queryObj).toString();
84+
}
85+
5986
async handleError(response: Response) {
6087
const { status, statusText } = response;
6188
const e: ApiError = {
@@ -103,10 +130,19 @@ class StacApi {
103130
datetime: this.makeDatetimePayload(dateRange),
104131
limit: 25
105132
};
106-
return this.fetch(
107-
`${this.baseUrl}/search`,
108-
{ method: 'POST', payload: requestPayload, headers }
109-
);
133+
134+
if (this.searchMode === 'POST') {
135+
return this.fetch(
136+
`${this.baseUrl}/search`,
137+
{ method: 'POST', payload: requestPayload, headers }
138+
);
139+
} else {
140+
const query = this.payloadToQuery(requestPayload);
141+
return this.fetch(
142+
`${this.baseUrl}/search?${query}`,
143+
{ method: 'GET', headers }
144+
);
145+
}
110146
}
111147

112148
getCollections(): Promise<Response> {

0 commit comments

Comments
 (0)