diff --git a/.changeset/two-jeans-kick.md b/.changeset/two-jeans-kick.md new file mode 100644 index 0000000..fe47800 --- /dev/null +++ b/.changeset/two-jeans-kick.md @@ -0,0 +1,5 @@ +--- +"react-loqate": major +--- + +Breaking: implement LoqateError and ReactLoqateError diff --git a/README.md b/README.md index 4ea9fd7..ed4d662 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,15 @@ import AddressSearch from 'react-loqate'; />; ``` +### Errors + +Two types of errors can be thrown, LoqateError and ReactLoqateError. +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/). + +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. + +It is on you as the implementing party to catch and handle these errors. + ### Contributing This codebases use [@changesets](https://github.com/changesets/changesets) for release and version management diff --git a/src/error.ts b/src/error.ts new file mode 100644 index 0000000..d1f2536 --- /dev/null +++ b/src/error.ts @@ -0,0 +1,41 @@ +type ReactLocateErrorCode = 'NO_ITEMS_RETRIEVED'; + +export class ReactLoqateError extends Error { + public code: ReactLocateErrorCode; + + constructor({ + message, + code, + }: { + message: string; + code: ReactLocateErrorCode; + }) { + super(message); + this.code = code; + } +} + +export class LoqateError extends Error { + public Cause: string; + public Description: string; + public Error: string; + public Resolution: string; + + constructor({ + Description, + Cause, + Error, + Resolution, + }: { + Description: string; + Cause: string; + Error: string; + Resolution: string; + }) { + super(Description); + this.Cause = Cause; + this.Description = Description; + this.Error = Error; + this.Resolution = Resolution; + } +} diff --git a/src/index.tsx b/src/index.tsx index f75437b..0d1ff6a 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -110,7 +110,7 @@ export interface Item { Highlight: string; } -export interface ErrorItem { +export interface LoqateErrorItem { Error: string; Description: string; Cause: string; @@ -172,16 +172,13 @@ function AddressSearch(props: Props): JSX.Element { Items = res.Items; } } catch (e) { + setSuggestions([]); // error needs to be thrown in the render in order to be caught by the ErrorBoundary setError(() => { throw e; }); } - if (Items.length) { - setSuggestions([]); - } - onSelect(Items[0] as unknown as Address); return; } diff --git a/src/utils/Loqate.ts b/src/utils/Loqate.ts index f747228..1de0070 100644 --- a/src/utils/Loqate.ts +++ b/src/utils/Loqate.ts @@ -1,9 +1,10 @@ -import { ErrorItem, Item } from '..'; +import { Item, LoqateErrorItem } from '..'; import { LOQATE_BASE_URL, LOQATE_FIND_URL, LOQATE_RETRIEVE_URL, } from '../constants/loqate'; +import { LoqateError, ReactLoqateError } from '../error'; interface FindQuery { text: string; @@ -13,7 +14,7 @@ interface FindQuery { containerId?: string; } -type LoqateResponse = { Items?: Item[] | ErrorItem[] }; +type LoqateResponse = { Items?: Item[] | LoqateErrorItem[] }; type LoqateNoErrorResponse = { Items?: Item[] }; class Loqate { constructor( @@ -29,7 +30,16 @@ class Loqate { const params = new URLSearchParams({ Id: id, Key: this.key }); const url = `${this.baseUrl}/${LOQATE_RETRIEVE_URL}?${params.toString()}`; const res = await fetch(url).then((r) => r.json()); - return this.handleErrors(res); + const noLoqateErrosRes = this.handleErrors(res); + + if (noLoqateErrosRes.Items && !noLoqateErrosRes.Items?.length) { + throw new ReactLoqateError({ + code: 'NO_ITEMS_RETRIEVED', + message: `Loqate retrieve API did not return any address items for the provided ID ${id}`, + }); + } + + return noLoqateErrosRes; } public async find(query: FindQuery): Promise { @@ -54,9 +64,9 @@ class Loqate { } private handleErrors = (res: LoqateResponse): LoqateNoErrorResponse => { - const firstItem: Item | ErrorItem | undefined = res?.Items?.[0]; + const firstItem: Item | LoqateErrorItem | undefined = res?.Items?.[0]; if (firstItem && Object.hasOwn(firstItem, 'Error')) { - throw new Error(`Loqate error: ${JSON.stringify(firstItem)}`); + throw new LoqateError(firstItem as LoqateErrorItem); } return res as LoqateNoErrorResponse; diff --git a/src/utils/__tests__/Loqate.test.ts b/src/utils/__tests__/Loqate.test.ts index 233f9e6..9db82b8 100644 --- a/src/utils/__tests__/Loqate.test.ts +++ b/src/utils/__tests__/Loqate.test.ts @@ -41,7 +41,7 @@ describe('Loqate', () => { expect({ Items }).toEqual(suggestions); }); - it('should throw loqate errors', async () => { + it('should throw errors', async () => { server.use(errorHandler); const loqate = Loqate.create('some-key'); @@ -54,10 +54,36 @@ describe('Loqate', () => { limit: 10, containerId: 'some-container-id', }); - }).rejects.toThrowError( - new Error( - '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."}' - ) + }).rejects.toThrowError(new Error('Unknown key')); + }); + + it('should throw loqate errors', async () => { + server.use(errorHandler); + + const loqate = Loqate.create('some-key'); + + let error; + try { + await loqate.find({ + text: 'some-text', + language: 'some-language', + countries: ['GB', 'US'], + limit: 10, + containerId: 'some-container-id', + }); + } catch (e) { + error = e; + } + + expect(error).toEqual(new Error('Unknown key')); + expect(JSON.stringify(error)).toEqual( + JSON.stringify({ + Cause: 'The key you are using to access the service was not found.', + Description: 'Unknown key', + Error: '2', + Resolution: + 'Please check that the key is correct. It should be in the form AA11-AA11-AA11-AA11.', + }) ); }); });