Skip to content

Commit 0b4bcdc

Browse files
committed
refactor: replace Object.assign error pattern with ApiError class
- Create ApiError class extending Error in src/utils/ApiError.ts - Rename ApiError type to ApiErrorType to avoid naming conflict - Update all hooks to throw new ApiError instances
1 parent bc3550d commit 0b4bcdc

File tree

7 files changed

+50
-35
lines changed

7 files changed

+50
-35
lines changed

src/hooks/useCollection.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { useMemo } from 'react';
22

3-
import type { ApiError, LoadingState } from '../types';
3+
import type { ApiErrorType, LoadingState } from '../types';
44
import type { Collection } from '../types/stac';
55
import useCollections from './useCollections';
66

77
type StacCollectionHook = {
88
collection?: Collection;
99
state: LoadingState;
10-
error?: ApiError;
10+
error?: ApiErrorType;
1111
reload: () => void;
1212
};
1313

@@ -19,7 +19,7 @@ function useCollection(collectionId: string): StacCollectionHook {
1919
}, [collectionId, collections]);
2020

2121
// Determine error: prefer requestError, else local 404 if collection not found
22-
const error: ApiError | undefined = requestError
22+
const error: ApiErrorType | undefined = requestError
2323
? requestError
2424
: !collection && collections
2525
? {

src/hooks/useCollections.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
import { useEffect, useState, useMemo } from 'react';
22
import { useQuery } from '@tanstack/react-query';
3-
import { type ApiError, type LoadingState } from '../types';
3+
import { type ApiErrorType, type LoadingState } from '../types';
44
import type { CollectionsResponse } from '../types/stac';
55
import debounce from '../utils/debounce';
6+
import { ApiError } from '../utils/ApiError';
67
import { generateCollectionsQueryKey } from '../utils/queryKeys';
78
import { useStacApiContext } from '../context/useStacApiContext';
89

910
type StacCollectionsHook = {
1011
collections?: CollectionsResponse;
1112
reload: () => void;
1213
state: LoadingState;
13-
error?: ApiError;
14+
error?: ApiErrorType;
1415
};
1516

1617
function useCollections(): StacCollectionsHook {
@@ -28,12 +29,7 @@ function useCollections(): StacCollectionsHook {
2829
detail = await response.text();
2930
}
3031

31-
const err = Object.assign(new Error(response.statusText), {
32-
status: response.status,
33-
statusText: response.statusText,
34-
detail,
35-
});
36-
throw err;
32+
throw new ApiError(response.statusText, response.status, detail);
3733
}
3834
return await response.json();
3935
};
@@ -44,7 +40,7 @@ function useCollections(): StacCollectionsHook {
4440
isLoading,
4541
isFetching,
4642
refetch,
47-
} = useQuery<CollectionsResponse, ApiError>({
43+
} = useQuery<CollectionsResponse, ApiErrorType>({
4844
queryKey: generateCollectionsQueryKey(),
4945
queryFn: fetchCollections,
5046
enabled: !!stacApi,
@@ -76,7 +72,7 @@ function useCollections(): StacCollectionsHook {
7672
collections,
7773
reload,
7874
state,
79-
error: error as ApiError,
75+
error: error as ApiErrorType,
8076
};
8177
}
8278

src/hooks/useItem.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import { useEffect, useState } from 'react';
22
import { useQuery } from '@tanstack/react-query';
33
import { Item } from '../types/stac';
4-
import { ApiError, LoadingState } from '../types';
4+
import { type ApiErrorType, type LoadingState } from '../types';
55
import { useStacApiContext } from '../context/useStacApiContext';
6+
import { ApiError } from '../utils/ApiError';
67
import { generateItemQueryKey } from '../utils/queryKeys';
78

89
type ItemHook = {
910
item?: Item;
1011
state: LoadingState;
11-
error?: ApiError;
12+
error?: ApiErrorType;
1213
reload: () => void;
1314
};
1415

@@ -26,12 +27,8 @@ function useItem(url: string): ItemHook {
2627
} catch {
2728
detail = await response.text();
2829
}
29-
const err = Object.assign(new Error(response.statusText), {
30-
status: response.status,
31-
statusText: response.statusText,
32-
detail,
33-
});
34-
throw err;
30+
31+
throw new ApiError(response.statusText, response.status, detail);
3532
}
3633
return await response.json();
3734
};
@@ -42,7 +39,7 @@ function useItem(url: string): ItemHook {
4239
isLoading,
4340
isFetching,
4441
refetch,
45-
} = useQuery<Item, ApiError>({
42+
} = useQuery<Item, ApiErrorType>({
4643
queryKey: generateItemQueryKey(url),
4744
queryFn: fetchItem,
4845
enabled: !!stacApi,
@@ -60,7 +57,7 @@ function useItem(url: string): ItemHook {
6057
return {
6158
item,
6259
state,
63-
error: error as ApiError,
60+
error: error as ApiErrorType,
6461
reload: refetch as () => void,
6562
};
6663
}

src/hooks/useStacSearch.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import { useCallback, useState, useMemo, useEffect } from 'react';
22
import { useQuery, useQueryClient } from '@tanstack/react-query';
33
import debounce from '../utils/debounce';
44
import { generateStacSearchQueryKey } from '../utils/queryKeys';
5-
import type { ApiError, LoadingState } from '../types';
5+
import { type ApiErrorType, type LoadingState } from '../types';
6+
import { ApiError } from '../utils/ApiError';
67
import type {
78
Link,
89
Bbox,
@@ -33,7 +34,7 @@ type StacSearchHook = {
3334
submit: () => void;
3435
results?: SearchResponse;
3536
state: LoadingState;
36-
error: ApiError | undefined;
37+
error?: ApiErrorType;
3738
nextPage: PaginationHandler | undefined;
3839
previousPage: PaginationHandler | undefined;
3940
};
@@ -122,12 +123,8 @@ function useStacSearch(): StacSearchHook {
122123
} catch {
123124
detail = await response.text();
124125
}
125-
const err = Object.assign(new Error(response.statusText), {
126-
status: response.status,
127-
statusText: response.statusText,
128-
detail,
129-
});
130-
throw err;
126+
127+
throw new ApiError(response.statusText, response.status, detail);
131128
}
132129
return await response.json();
133130
};
@@ -140,7 +137,7 @@ function useStacSearch(): StacSearchHook {
140137
error,
141138
isLoading,
142139
isFetching,
143-
} = useQuery<SearchResponse, ApiError>({
140+
} = useQuery<SearchResponse, ApiErrorType>({
144141
queryKey: currentRequest ? generateStacSearchQueryKey(currentRequest) : ['stacSearch', 'idle'],
145142
queryFn: () => fetchRequest(currentRequest!),
146143
enabled: currentRequest !== null,

src/stac-api/index.ts

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

44
type RequestPayload = SearchPayload;
@@ -88,7 +88,7 @@ class StacApi {
8888

8989
async handleError(response: Response) {
9090
const { status, statusText } = response;
91-
const e: ApiError = {
91+
const e: ApiErrorType = {
9292
status,
9393
statusText,
9494
};

src/types/index.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ export type GenericObject = {
22
[key: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any
33
};
44

5-
export type ApiError = {
5+
export type ApiErrorType = {
66
detail?: GenericObject | string;
77
status: number;
88
statusText: string;

src/utils/ApiError.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import type { GenericObject } from '../types';
2+
3+
/**
4+
* Custom error class for STAC API errors.
5+
* Extends the native Error class with HTTP response details.
6+
*/
7+
export class ApiError extends Error {
8+
status: number;
9+
statusText: string;
10+
detail?: GenericObject | string;
11+
12+
constructor(statusText: string, status: number, detail?: GenericObject | string) {
13+
super(statusText);
14+
this.name = 'ApiError';
15+
this.status = status;
16+
this.statusText = statusText;
17+
this.detail = detail;
18+
19+
// Maintains proper stack trace for where our error was thrown
20+
// Note: Error.captureStackTrace is a V8-only feature (Node.js, Chrome)
21+
if (Error.captureStackTrace) {
22+
Error.captureStackTrace(this, ApiError);
23+
}
24+
}
25+
}

0 commit comments

Comments
 (0)