From 7b261eafdb803d64339bea7d16b4be1b16658205 Mon Sep 17 00:00:00 2001 From: Joeri de Gooijer Date: Mon, 23 Sep 2024 16:36:53 -0700 Subject: [PATCH] fix: Reject API calls that return a HTTP status that is not 2xx This should allow consumers to properly handle any 400 / 500 http errors --- src/features/autosuggest/autosuggest.ts | 4 ++-- src/features/recs-pathways/widgets.ts | 19 +++++++----------- .../search/bestseller/bestseller.spec.ts | 20 ------------------- src/features/search/bestseller/bestseller.ts | 4 ++-- .../search/category-search/category-search.ts | 4 ++-- .../search/content-search/content-search.ts | 4 ++-- .../search/product-search/product-search.ts | 4 ++-- src/shared/typed-fetch.ts | 9 +++++++++ 8 files changed, 26 insertions(+), 42 deletions(-) create mode 100644 src/shared/typed-fetch.ts diff --git a/src/features/autosuggest/autosuggest.ts b/src/features/autosuggest/autosuggest.ts index f2f9e2e..b936660 100644 --- a/src/features/autosuggest/autosuggest.ts +++ b/src/features/autosuggest/autosuggest.ts @@ -1,6 +1,7 @@ import type { Configuration } from '../../shared/configuration.type'; import { SUGGEST_ENDPOINT_PROD } from '../../shared/constants'; import { logAPICall } from '../../shared/logger'; +import { typedFetch } from '../../shared/typed-fetch'; import { buildApiUrl } from '../../shared/url-builders'; import type { AutosuggestOptions } from './autosuggest-options.type'; import type { SuggestFixedOptions } from './suggest-fixed-options.type'; @@ -34,6 +35,5 @@ export async function autoSuggest( logAPICall('autoSuggest', configuration, options, FIXED_OPTIONS, defaults, queryParams, url); - const data = await fetch(url); - return data.json() as Promise; + return typedFetch(url); } diff --git a/src/features/recs-pathways/widgets.ts b/src/features/recs-pathways/widgets.ts index 3591b67..86a419e 100644 --- a/src/features/recs-pathways/widgets.ts +++ b/src/features/recs-pathways/widgets.ts @@ -1,6 +1,7 @@ import type { Configuration } from '../../shared/configuration.type'; import { WIDGET_ENDPOINT_PROD } from '../../shared/constants'; import { logAPICall } from '../../shared/logger'; +import { typedFetch } from '../../shared/typed-fetch'; import { buildApiUrl } from '../../shared/url-builders'; import type { CategoryWidgetOptions, @@ -27,8 +28,7 @@ export async function getGlobalWidget( logAPICall('getGlobalWidget', configuration, options, {}, {}, queryParams, url); - const data = await fetch(url); - return data.json() as Promise; + return typedFetch(url); } /** @@ -46,8 +46,7 @@ export async function getCategoryWidget( logAPICall(getCategoryWidget.name, configuration, options, {}, {}, queryParams, url); - const data = await fetch(url); - return data.json() as Promise; + return typedFetch(url); } /** @@ -65,8 +64,7 @@ export async function getKeywordWidget( logAPICall('getKeywordWidget', configuration, options, {}, {}, queryParams, url); - const data = await fetch(url); - return data.json() as Promise; + return typedFetch(url); } /** @@ -84,8 +82,7 @@ export async function getItemWidget( logAPICall('getItemWidget', configuration, options, {}, {}, queryParams, url); - const data = await fetch(url); - return data.json() as Promise; + return typedFetch(url); } /** @@ -106,8 +103,7 @@ export async function getPersonalizedWidget( logAPICall('getPersonalizedWidget', configuration, options, {}, {}, queryParams, url); - const data = await fetch(url); - return data.json() as Promise; + return typedFetch(url); } /** @@ -128,6 +124,5 @@ export async function getRecentlyViewedWidget( logAPICall('getRecentlyViewedWidget', configuration, options, {}, {}, queryParams, url); - const data = await fetch(url); - return data.json() as Promise; + return typedFetch(url); } diff --git a/src/features/search/bestseller/bestseller.spec.ts b/src/features/search/bestseller/bestseller.spec.ts index 9f6035e..245a7b7 100644 --- a/src/features/search/bestseller/bestseller.spec.ts +++ b/src/features/search/bestseller/bestseller.spec.ts @@ -13,26 +13,6 @@ describe('Bestseller API', () => { q: 'testQuery', }); - test('fetch call with correct URL', async () => { - const expectedUrl = new URL(config.searchEndpoint); - expectedUrl.searchParams.set('q', 'testQuery'); - expectedUrl.searchParams.set('request_type', 'search'); - expectedUrl.searchParams.set('search_type', 'bestseller'); - expectedUrl.searchParams.set('fl', 'pid'); - expectedUrl.searchParams.set('start', '0'); - - await mockRequest( - bestseller, - [config, searchOptions], - [ - http.get(config.searchEndpoint, ({ request }) => { - expect(request.url).toBe(expectedUrl.toString()); - return HttpResponse.json(createSearchResponseMock()); - }), - ], - ); - }); - test('checks that config and searchOptions are added to the searchParams in the request URL', async () => { const { account_id, domain_key, _br_uid_2, url, q, fl, start, rows } = { ...config, diff --git a/src/features/search/bestseller/bestseller.ts b/src/features/search/bestseller/bestseller.ts index 60f430a..45ad530 100644 --- a/src/features/search/bestseller/bestseller.ts +++ b/src/features/search/bestseller/bestseller.ts @@ -1,6 +1,7 @@ import type { Configuration } from '../../../shared/configuration.type'; import { SEARCH_ENDPOINT_PROD } from '../../../shared/constants'; import { logAPICall } from '../../../shared/logger'; +import { typedFetch } from '../../../shared/typed-fetch'; import { buildApiUrl } from '../../../shared/url-builders'; import type { SearchRequestParameters } from '../search-request.type'; import type { SearchResponse } from '../search-response.type'; @@ -40,6 +41,5 @@ export async function bestseller( logAPICall('bestseller', configuration, options, FIXED_OPTIONS, defaults, queryParams, url); - const data = await fetch(url); - return data.json() as Promise; + return typedFetch(url); } diff --git a/src/features/search/category-search/category-search.ts b/src/features/search/category-search/category-search.ts index 94ad670..4f2be81 100644 --- a/src/features/search/category-search/category-search.ts +++ b/src/features/search/category-search/category-search.ts @@ -1,6 +1,7 @@ import type { Configuration } from '../../../shared/configuration.type'; import { SEARCH_ENDPOINT_PROD } from '../../../shared/constants'; import { logAPICall } from '../../../shared/logger'; +import { typedFetch } from '../../../shared/typed-fetch'; import { buildApiUrl } from '../../../shared/url-builders'; import type { SearchRequestParameters } from '../search-request.type'; import type { SearchResponse } from '../search-response.type'; @@ -42,6 +43,5 @@ export async function categorySearch( logAPICall('categorySearch', configuration, options, FIXED_OPTIONS, defaults, queryParams, url); - const data = await fetch(url); - return data.json() as Promise; + return typedFetch(url); } diff --git a/src/features/search/content-search/content-search.ts b/src/features/search/content-search/content-search.ts index 72a46a5..7179ae7 100644 --- a/src/features/search/content-search/content-search.ts +++ b/src/features/search/content-search/content-search.ts @@ -1,6 +1,7 @@ import type { Configuration } from '../../../shared/configuration.type'; import { SEARCH_ENDPOINT_PROD } from '../../../shared/constants'; import { logAPICall } from '../../../shared/logger'; +import { typedFetch } from '../../../shared/typed-fetch'; import { buildApiUrl } from '../../../shared/url-builders'; import type { ContentSearchRequestParameters } from '../search-request.type'; import type { SearchResponse } from '../search-response.type'; @@ -48,6 +49,5 @@ export async function contentSearch( logAPICall('contentSearch', configuration, options, FIXED_OPTIONS, defaults, queryParams, url); - const data = await fetch(url); - return data.json() as Promise; + return typedFetch(url); } diff --git a/src/features/search/product-search/product-search.ts b/src/features/search/product-search/product-search.ts index 308dd1b..3dfd63d 100644 --- a/src/features/search/product-search/product-search.ts +++ b/src/features/search/product-search/product-search.ts @@ -1,6 +1,7 @@ import type { Configuration } from '../../../shared/configuration.type'; import { SEARCH_ENDPOINT_PROD } from '../../../shared/constants'; import { logAPICall } from '../../../shared/logger'; +import { typedFetch } from '../../../shared/typed-fetch'; import { buildApiUrl } from '../../../shared/url-builders'; import { SearchRequestParameters } from '../search-request.type'; import type { SearchResponse } from '../search-response.type'; @@ -42,6 +43,5 @@ export async function productSearch( logAPICall('productSearch', configuration, options, FIXED_OPTIONS, defaults, queryParams, url); - const data = await fetch(url); - return data.json() as Promise; + return typedFetch(url); } diff --git a/src/shared/typed-fetch.ts b/src/shared/typed-fetch.ts new file mode 100644 index 0000000..e277205 --- /dev/null +++ b/src/shared/typed-fetch.ts @@ -0,0 +1,9 @@ +export async function typedFetch(url: URL): Promise { + const data = await fetch(url); + + if (!data.ok) { + throw new Error(`HTTP error! status: ${data.status}`); + } + + return data.json() as T; +}