Skip to content

Commit ccc76da

Browse files
committed
Centralize stac api requests.
1 parent 9cb92fa commit ccc76da

File tree

12 files changed

+129
-227
lines changed

12 files changed

+129
-227
lines changed

src/context/StacApiProvider.test.tsx

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,11 @@ import { useStacApiContext } from './useStacApiContext';
66

77
// Mock fetch for testing - returns a successful response
88
beforeEach(() => {
9-
(global.fetch as jest.Mock) = jest.fn((url: string) =>
10-
Promise.resolve({
11-
ok: true,
12-
url, // Return the requested URL
13-
json: () =>
14-
Promise.resolve({
15-
links: [],
16-
}),
17-
})
18-
);
9+
(global.fetch as jest.Mock) = jest.fn((url: string) => {
10+
const response = new Response(JSON.stringify({ links: [] }));
11+
Object.defineProperty(response, 'url', { value: url });
12+
return Promise.resolve(response);
13+
});
1914
});
2015

2116
// Component to test that hooks work inside StacApiProvider

src/context/context.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { createContext } from 'react';
2+
import StacApi from '../stac-api';
23

34
export type StacApiContextType = {
4-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
5-
stacApi?: any;
5+
stacApi?: StacApi;
66
};
77

88
export const StacApiContext = createContext<StacApiContextType>({} as StacApiContextType);

src/hooks/useCollection.test.ts

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import fetch from 'jest-fetch-mock';
22
import { renderHook, act, waitFor } from '@testing-library/react';
33
import useCollection from './useCollection';
44
import wrapper from './wrapper';
5+
import { ApiError } from '../utils/ApiError';
56

67
describe('useCollection', () => {
78
beforeEach(() => {
@@ -31,11 +32,14 @@ describe('useCollection', () => {
3132

3233
const { result } = renderHook(() => useCollection('nonexistent'), { wrapper });
3334
await waitFor(() =>
34-
expect(result.current.error).toEqual({
35-
status: 404,
36-
statusText: 'Not Found',
37-
detail: { error: 'Collection not found' },
38-
})
35+
expect(result.current.error).toEqual(
36+
new ApiError(
37+
'Not Found',
38+
404,
39+
{ error: 'Collection not found' },
40+
'https://fake-stac-api.net/collections/nonexistent'
41+
)
42+
)
3943
);
4044
});
4145

@@ -49,11 +53,14 @@ describe('useCollection', () => {
4953

5054
const { result } = renderHook(() => useCollection('abc'), { wrapper });
5155
await waitFor(() =>
52-
expect(result.current.error).toEqual({
53-
status: 400,
54-
statusText: 'Bad Request',
55-
detail: { error: 'Wrong query' },
56-
})
56+
expect(result.current.error).toEqual(
57+
new ApiError(
58+
'Bad Request',
59+
400,
60+
{ error: 'Wrong query' },
61+
'https://fake-stac-api.net/search'
62+
)
63+
)
5764
);
5865
});
5966

@@ -64,11 +71,9 @@ describe('useCollection', () => {
6471

6572
const { result } = renderHook(() => useCollection('abc'), { wrapper });
6673
await waitFor(() =>
67-
expect(result.current.error).toEqual({
68-
status: 400,
69-
statusText: 'Bad Request',
70-
detail: 'Wrong query',
71-
})
74+
expect(result.current.error).toEqual(
75+
new ApiError('Bad Request', 400, 'Wrong query', 'https://fake-stac-api.net/search')
76+
)
7277
);
7378
});
7479

src/hooks/useCollection.ts

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { useQuery, type QueryObserverResult } from '@tanstack/react-query';
22
import type { ApiErrorType } from '../types';
33
import type { Collection } from '../types/stac';
4-
import { ApiError } from '../utils/ApiError';
54
import { generateCollectionQueryKey } from '../utils/queryKeys';
65
import { useStacApiContext } from '../context/useStacApiContext';
76

@@ -16,31 +15,6 @@ type StacCollectionHook = {
1615
function useCollection(collectionId: string): StacCollectionHook {
1716
const { stacApi } = useStacApiContext();
1817

19-
const fetchCollection = async (): Promise<Collection> => {
20-
if (!stacApi) throw new Error('No STAC API configured');
21-
const response: Response = await stacApi.getCollection(collectionId);
22-
if (!response.ok) {
23-
let detail;
24-
try {
25-
detail = await response.json();
26-
} catch {
27-
detail = await response.text();
28-
}
29-
30-
throw new ApiError(response.statusText, response.status, detail, response.url);
31-
}
32-
try {
33-
return await response.json();
34-
} catch (error) {
35-
throw new ApiError(
36-
'Invalid JSON Response',
37-
response.status,
38-
`Response is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
39-
response.url
40-
);
41-
}
42-
};
43-
4418
const {
4519
data: collection,
4620
error,
@@ -49,7 +23,7 @@ function useCollection(collectionId: string): StacCollectionHook {
4923
refetch,
5024
} = useQuery<Collection, ApiErrorType>({
5125
queryKey: generateCollectionQueryKey(collectionId),
52-
queryFn: fetchCollection,
26+
queryFn: () => stacApi!.getCollection(collectionId),
5327
enabled: !!stacApi,
5428
retry: false,
5529
});

src/hooks/useCollections.test.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import fetch from 'jest-fetch-mock';
22
import { renderHook, act, waitFor } from '@testing-library/react';
33
import useCollections from './useCollections';
44
import wrapper from './wrapper';
5+
import { ApiError } from '../utils/ApiError';
56

67
describe('useCollections', () => {
78
beforeEach(() => {
@@ -50,11 +51,14 @@ describe('useCollections', () => {
5051
const { result } = renderHook(() => useCollections(), { wrapper });
5152

5253
await waitFor(() =>
53-
expect(result.current.error).toEqual({
54-
status: 400,
55-
statusText: 'Bad Request',
56-
detail: { error: 'Wrong query' },
57-
})
54+
expect(result.current.error).toEqual(
55+
new ApiError(
56+
'Bad Request',
57+
400,
58+
{ error: 'Wrong query' },
59+
'https://fake-stac-api.net/search'
60+
)
61+
)
5862
);
5963
});
6064

@@ -65,11 +69,9 @@ describe('useCollections', () => {
6569

6670
const { result } = renderHook(() => useCollections(), { wrapper });
6771
await waitFor(() =>
68-
expect(result.current.error).toEqual({
69-
status: 400,
70-
statusText: 'Bad Request',
71-
detail: 'Wrong query',
72-
})
72+
expect(result.current.error).toEqual(
73+
new ApiError('Bad Request', 400, 'Wrong query', 'https://fake-stac-api.net/search')
74+
)
7375
);
7476
});
7577
});

src/hooks/useCollections.ts

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { useQuery, type QueryObserverResult } from '@tanstack/react-query';
22
import { type ApiErrorType } from '../types';
33
import type { CollectionsResponse } from '../types/stac';
4-
import { ApiError } from '../utils/ApiError';
54
import { generateCollectionsQueryKey } from '../utils/queryKeys';
65
import { useStacApiContext } from '../context/useStacApiContext';
76

@@ -16,31 +15,6 @@ type StacCollectionsHook = {
1615
function useCollections(): StacCollectionsHook {
1716
const { stacApi } = useStacApiContext();
1817

19-
const fetchCollections = async (): Promise<CollectionsResponse> => {
20-
if (!stacApi) throw new Error('No STAC API configured');
21-
const response: Response = await stacApi.getCollections();
22-
if (!response.ok) {
23-
let detail;
24-
try {
25-
detail = await response.json();
26-
} catch {
27-
detail = await response.text();
28-
}
29-
30-
throw new ApiError(response.statusText, response.status, detail, response.url);
31-
}
32-
try {
33-
return await response.json();
34-
} catch (error) {
35-
throw new ApiError(
36-
'Invalid JSON Response',
37-
response.status,
38-
`Response is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
39-
response.url
40-
);
41-
}
42-
};
43-
4418
const {
4519
data: collections,
4620
error,
@@ -49,7 +23,7 @@ function useCollections(): StacCollectionsHook {
4923
refetch,
5024
} = useQuery<CollectionsResponse, ApiErrorType>({
5125
queryKey: generateCollectionsQueryKey(),
52-
queryFn: fetchCollections,
26+
queryFn: () => stacApi!.getCollections(),
5327
enabled: !!stacApi,
5428
retry: false,
5529
});

src/hooks/useItem.test.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import fetch from 'jest-fetch-mock';
22
import { renderHook, act, waitFor } from '@testing-library/react';
33
import useItem from './useItem';
44
import wrapper from './wrapper';
5+
import { ApiError } from '../utils/ApiError';
56

67
describe('useItem', () => {
78
beforeEach(() => {
@@ -32,11 +33,14 @@ describe('useItem', () => {
3233
wrapper,
3334
});
3435
await waitFor(() =>
35-
expect(result.current.error).toEqual({
36-
status: 400,
37-
statusText: 'Bad Request',
38-
detail: { error: 'Wrong query' },
39-
})
36+
expect(result.current.error).toEqual(
37+
new ApiError(
38+
'Bad Request',
39+
400,
40+
{ error: 'Wrong query' },
41+
'https://fake-stac-api.net/search'
42+
)
43+
)
4044
);
4145
});
4246

@@ -49,11 +53,9 @@ describe('useItem', () => {
4953
wrapper,
5054
});
5155
await waitFor(() =>
52-
expect(result.current.error).toEqual({
53-
status: 400,
54-
statusText: 'Bad Request',
55-
detail: 'Wrong query',
56-
})
56+
expect(result.current.error).toEqual(
57+
new ApiError('Bad Request', 400, 'Wrong query', 'https://fake-stac-api.net/search')
58+
)
5759
);
5860
});
5961

src/hooks/useItem.ts

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { useQuery, type QueryObserverResult } from '@tanstack/react-query';
22
import { Item } from '../types/stac';
33
import { type ApiErrorType } from '../types';
44
import { useStacApiContext } from '../context/useStacApiContext';
5-
import { ApiError } from '../utils/ApiError';
65
import { generateItemQueryKey } from '../utils/queryKeys';
76

87
type ItemHook = {
@@ -16,31 +15,6 @@ type ItemHook = {
1615
function useItem(url: string): ItemHook {
1716
const { stacApi } = useStacApiContext();
1817

19-
const fetchItem = async (): Promise<Item> => {
20-
if (!stacApi) throw new Error('No STAC API configured');
21-
const response: Response = await stacApi.get(url);
22-
if (!response.ok) {
23-
let detail;
24-
try {
25-
detail = await response.json();
26-
} catch {
27-
detail = await response.text();
28-
}
29-
30-
throw new ApiError(response.statusText, response.status, detail, response.url);
31-
}
32-
try {
33-
return await response.json();
34-
} catch (error) {
35-
throw new ApiError(
36-
'Invalid JSON Response',
37-
response.status,
38-
`Response is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
39-
response.url
40-
);
41-
}
42-
};
43-
4418
const {
4519
data: item,
4620
error,
@@ -49,7 +23,7 @@ function useItem(url: string): ItemHook {
4923
refetch,
5024
} = useQuery<Item, ApiErrorType>({
5125
queryKey: generateItemQueryKey(url),
52-
queryFn: fetchItem,
26+
queryFn: () => stacApi!.get<Item>(url),
5327
enabled: !!stacApi,
5428
retry: false,
5529
});

src/hooks/useStacApi.ts

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,32 +14,23 @@ function useStacApi(url: string, options?: GenericObject): StacApiHook {
1414
const { data, isSuccess, isLoading, isError } = useQuery({
1515
queryKey: generateStacApiQueryKey(url, options),
1616
queryFn: async () => {
17-
let searchMode = SearchMode.GET;
1817
const response = await fetch(url, {
1918
headers: {
2019
'Content-Type': 'application/json',
2120
...options?.headers,
2221
},
2322
});
24-
const baseUrl = response.url;
25-
let json;
26-
try {
27-
json = await response.json();
28-
} catch (error) {
29-
throw new Error(
30-
`Invalid JSON response from STAC API: ${error instanceof Error ? error.message : String(error)}`
31-
);
32-
}
33-
const doesPost = json.links?.find(
23+
const stacData = await StacApi.handleResponse<{ links?: Link[] }>(response);
24+
25+
const doesPost = stacData.links?.find(
3426
({ rel, method }: Link) => rel === 'search' && method === 'POST'
3527
);
36-
if (doesPost) {
37-
searchMode = SearchMode.POST;
38-
}
39-
return new StacApi(baseUrl, searchMode, options);
28+
29+
return new StacApi(response.url, doesPost ? SearchMode.POST : SearchMode.GET, options);
4030
},
4131
staleTime: Infinity,
4232
});
33+
4334
return { stacApi: isSuccess ? data : undefined, isLoading, isError };
4435
}
4536

0 commit comments

Comments
 (0)