Skip to content

Commit b360423

Browse files
authored
Merge pull request #36 from labd/errorHandling
chore: custom errors
2 parents 0476b44 + fcb7f10 commit b360423

6 files changed

Lines changed: 103 additions & 15 deletions

File tree

.changeset/two-jeans-kick.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"react-loqate": major
3+
---
4+
5+
Breaking: implement LoqateError and ReactLoqateError

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,15 @@ import AddressSearch from 'react-loqate';
9595
/>;
9696
```
9797

98+
### Errors
99+
100+
Two types of errors can be thrown, LoqateError and ReactLoqateError.
101+
Loqate Errors are errors from the Loqate API. Their structure, causes and resolutions can be [found in the loqate docs](https://www.loqate.com/developers/api/generic-errors/).
102+
103+
Currently only one ReactLoqateError can be thrown. This error occurs when the Retrieve API returns an empty Items array after querying it with an existing ID.
104+
105+
It is on you as the implementing party to catch and handle these errors.
106+
98107
### Contributing
99108

100109
This codebases use [@changesets](https://github.com/changesets/changesets) for release and version management

src/error.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
type ReactLocateErrorCode = 'NO_ITEMS_RETRIEVED';
2+
3+
export class ReactLoqateError extends Error {
4+
public code: ReactLocateErrorCode;
5+
6+
constructor({
7+
message,
8+
code,
9+
}: {
10+
message: string;
11+
code: ReactLocateErrorCode;
12+
}) {
13+
super(message);
14+
this.code = code;
15+
}
16+
}
17+
18+
export class LoqateError extends Error {
19+
public Cause: string;
20+
public Description: string;
21+
public Error: string;
22+
public Resolution: string;
23+
24+
constructor({
25+
Description,
26+
Cause,
27+
Error,
28+
Resolution,
29+
}: {
30+
Description: string;
31+
Cause: string;
32+
Error: string;
33+
Resolution: string;
34+
}) {
35+
super(Description);
36+
this.Cause = Cause;
37+
this.Description = Description;
38+
this.Error = Error;
39+
this.Resolution = Resolution;
40+
}
41+
}

src/index.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ export interface Item {
110110
Highlight: string;
111111
}
112112

113-
export interface ErrorItem {
113+
export interface LoqateErrorItem {
114114
Error: string;
115115
Description: string;
116116
Cause: string;
@@ -172,16 +172,13 @@ function AddressSearch(props: Props): JSX.Element {
172172
Items = res.Items;
173173
}
174174
} catch (e) {
175+
setSuggestions([]);
175176
// error needs to be thrown in the render in order to be caught by the ErrorBoundary
176177
setError(() => {
177178
throw e;
178179
});
179180
}
180181

181-
if (Items.length) {
182-
setSuggestions([]);
183-
}
184-
185182
onSelect(Items[0] as unknown as Address);
186183
return;
187184
}

src/utils/Loqate.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { ErrorItem, Item } from '..';
1+
import { Item, LoqateErrorItem } from '..';
22
import {
33
LOQATE_BASE_URL,
44
LOQATE_FIND_URL,
55
LOQATE_RETRIEVE_URL,
66
} from '../constants/loqate';
7+
import { LoqateError, ReactLoqateError } from '../error';
78

89
interface FindQuery {
910
text: string;
@@ -13,7 +14,7 @@ interface FindQuery {
1314
containerId?: string;
1415
}
1516

16-
type LoqateResponse = { Items?: Item[] | ErrorItem[] };
17+
type LoqateResponse = { Items?: Item[] | LoqateErrorItem[] };
1718
type LoqateNoErrorResponse = { Items?: Item[] };
1819
class Loqate {
1920
constructor(
@@ -29,7 +30,16 @@ class Loqate {
2930
const params = new URLSearchParams({ Id: id, Key: this.key });
3031
const url = `${this.baseUrl}/${LOQATE_RETRIEVE_URL}?${params.toString()}`;
3132
const res = await fetch(url).then<LoqateResponse>((r) => r.json());
32-
return this.handleErrors(res);
33+
const noLoqateErrosRes = this.handleErrors(res);
34+
35+
if (noLoqateErrosRes.Items && !noLoqateErrosRes.Items?.length) {
36+
throw new ReactLoqateError({
37+
code: 'NO_ITEMS_RETRIEVED',
38+
message: `Loqate retrieve API did not return any address items for the provided ID ${id}`,
39+
});
40+
}
41+
42+
return noLoqateErrosRes;
3343
}
3444

3545
public async find(query: FindQuery): Promise<LoqateNoErrorResponse> {
@@ -54,9 +64,9 @@ class Loqate {
5464
}
5565

5666
private handleErrors = (res: LoqateResponse): LoqateNoErrorResponse => {
57-
const firstItem: Item | ErrorItem | undefined = res?.Items?.[0];
67+
const firstItem: Item | LoqateErrorItem | undefined = res?.Items?.[0];
5868
if (firstItem && Object.hasOwn(firstItem, 'Error')) {
59-
throw new Error(`Loqate error: ${JSON.stringify(firstItem)}`);
69+
throw new LoqateError(firstItem as LoqateErrorItem);
6070
}
6171

6272
return res as LoqateNoErrorResponse;

src/utils/__tests__/Loqate.test.ts

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ describe('Loqate', () => {
4141
expect({ Items }).toEqual(suggestions);
4242
});
4343

44-
it('should throw loqate errors', async () => {
44+
it('should throw errors', async () => {
4545
server.use(errorHandler);
4646

4747
const loqate = Loqate.create('some-key');
@@ -54,10 +54,36 @@ describe('Loqate', () => {
5454
limit: 10,
5555
containerId: 'some-container-id',
5656
});
57-
}).rejects.toThrowError(
58-
new Error(
59-
'Loqate error: {"Error":"2","Description":"Unknown key","Cause":"The key you are using to access the service was not found.","Resolution":"Please check that the key is correct. It should be in the form AA11-AA11-AA11-AA11."}'
60-
)
57+
}).rejects.toThrowError(new Error('Unknown key'));
58+
});
59+
60+
it('should throw loqate errors', async () => {
61+
server.use(errorHandler);
62+
63+
const loqate = Loqate.create('some-key');
64+
65+
let error;
66+
try {
67+
await loqate.find({
68+
text: 'some-text',
69+
language: 'some-language',
70+
countries: ['GB', 'US'],
71+
limit: 10,
72+
containerId: 'some-container-id',
73+
});
74+
} catch (e) {
75+
error = e;
76+
}
77+
78+
expect(error).toEqual(new Error('Unknown key'));
79+
expect(JSON.stringify(error)).toEqual(
80+
JSON.stringify({
81+
Cause: 'The key you are using to access the service was not found.',
82+
Description: 'Unknown key',
83+
Error: '2',
84+
Resolution:
85+
'Please check that the key is correct. It should be in the form AA11-AA11-AA11-AA11.',
86+
})
6187
);
6288
});
6389
});

0 commit comments

Comments
 (0)