From 3472e3ae68cce856f4cbe73b45a20670a9723954 Mon Sep 17 00:00:00 2001 From: Magnun A V F Date: Tue, 8 Oct 2024 16:45:01 -0300 Subject: [PATCH 01/12] feat: improve fetch error handling in applications pkg --- .../src/cache-settings/services/index.ts | 87 ++++++++------ .../src/device-groups/services/index.ts | 87 ++++++++------ .../src/functions-instances/services/index.ts | 86 ++++++++------ .../src/main-settings/services/index.ts | 109 ++++++++++-------- .../src/origins/services/index.ts | 87 ++++++++------ .../src/rules-engine/services/index.ts | 86 ++++++++------ packages/applications/src/utils.ts | 51 ++++++++ 7 files changed, 357 insertions(+), 236 deletions(-) diff --git a/packages/applications/src/cache-settings/services/index.ts b/packages/applications/src/cache-settings/services/index.ts index 2ef6375..4c4953c 100644 --- a/packages/applications/src/cache-settings/services/index.ts +++ b/packages/applications/src/cache-settings/services/index.ts @@ -1,3 +1,4 @@ +import { fetchWithErrorHandling } from '../../utils'; import { ApiCreateCacheSettingPayload, ApiCreateCacheSettingResponse, @@ -25,12 +26,14 @@ export const getCacheSettings = async ( sort, order, }); - const response = await fetch(`${BASE_URL}/${Id}/cache_settings?${queryParams.toString()}`, { - method: 'GET', - headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, - }); - const data = await response.json(); - if (debug) console.log('Response:', data); + const data = await fetchWithErrorHandling( + `${BASE_URL}/${Id}/cache_settings?${queryParams.toString()}`, + { + method: 'GET', + headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, + }, + debug, + ); return data; } catch (error) { if (debug) console.error('Error getting cache settings:', error); @@ -45,12 +48,14 @@ export const getCacheSetting = async ( debug?: boolean, ): Promise => { try { - const response = await fetch(`${BASE_URL}/${Id}/cache_settings/${cacheSettingId}`, { - method: 'GET', - headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, - }); - const data = await response.json(); - if (debug) console.log('Response:', data); + const data = await fetchWithErrorHandling( + `${BASE_URL}/${Id}/cache_settings/${cacheSettingId}`, + { + method: 'GET', + headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, + }, + debug, + ); return data; } catch (error) { if (debug) console.error('Error getting cache setting by ID:', error); @@ -65,17 +70,19 @@ export const postCacheSetting = async ( debug?: boolean, ): Promise => { try { - const response = await fetch(`${BASE_URL}/${Id}/cache_settings`, { - method: 'POST', - headers: { - Accept: 'application/json; version=3', - 'Content-Type': 'application/json', - Authorization: `Token ${token}`, + const data = await fetchWithErrorHandling( + `${BASE_URL}/${Id}/cache_settings`, + { + method: 'POST', + headers: { + Accept: 'application/json; version=3', + 'Content-Type': 'application/json', + Authorization: `Token ${token}`, + }, + body: JSON.stringify(cacheSettingData), }, - body: JSON.stringify(cacheSettingData), - }); - const data = await response.json(); - if (debug) console.log('Response:', data); + debug, + ); return data; } catch (error) { if (debug) console.error('Error creating cache setting:', error); @@ -91,17 +98,19 @@ export const patchCacheSetting = async ( debug?: boolean, ): Promise => { try { - const response = await fetch(`${BASE_URL}/${Id}/cache_settings/${cacheSettingId}`, { - method: 'PATCH', - headers: { - Accept: 'application/json; version=3', - 'Content-Type': 'application/json', - Authorization: `Token ${token}`, + const data = await fetchWithErrorHandling( + `${BASE_URL}/${Id}/cache_settings/${cacheSettingId}`, + { + method: 'PATCH', + headers: { + Accept: 'application/json; version=3', + 'Content-Type': 'application/json', + Authorization: `Token ${token}`, + }, + body: JSON.stringify(cacheSettingData), }, - body: JSON.stringify(cacheSettingData), - }); - const data = await response.json(); - if (debug) console.log('Response:', data); + debug, + ); return data; } catch (error) { if (debug) console.error('Error updating cache setting:', error); @@ -116,12 +125,14 @@ export const deleteCacheSetting = async ( debug?: boolean, ): Promise => { try { - const response = await fetch(`${BASE_URL}/${Id}/cache_settings/${cacheSettingId}`, { - method: 'DELETE', - headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, - }); - const data = await response.json(); - if (debug) console.log('Response:', data); + const data = await fetchWithErrorHandling( + `${BASE_URL}/${Id}/cache_settings/${cacheSettingId}`, + { + method: 'DELETE', + headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, + }, + debug, + ); return data; } catch (error) { if (debug) console.error('Error deleting cache setting:', error); diff --git a/packages/applications/src/device-groups/services/index.ts b/packages/applications/src/device-groups/services/index.ts index a9e6858..d0ab3f1 100644 --- a/packages/applications/src/device-groups/services/index.ts +++ b/packages/applications/src/device-groups/services/index.ts @@ -1,3 +1,4 @@ +import { fetchWithErrorHandling } from '../../utils'; import { ApiCreateDeviceGroupPayload, ApiCreateDeviceGroupResponse, @@ -34,12 +35,14 @@ const getDeviceGroups = async ( sort, order, }); - const response = await fetch(`${BASE_URL}/${Id}/device_groups?${queryParams.toString()}`, { - method: 'GET', - headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, - }); - const data = await response.json(); - if (debug) console.log('Response:', data); + const data = await fetchWithErrorHandling( + `${BASE_URL}/${Id}/device_groups?${queryParams.toString()}`, + { + method: 'GET', + headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, + }, + debug, + ); return data; } catch (error) { if (debug) console.error('Error getting device groups:', error); @@ -63,12 +66,14 @@ const getDeviceGroupById = async ( debug?: boolean, ): Promise => { try { - const response = await fetch(`${BASE_URL}/${Id}/device_groups/${deviceGroupId}`, { - method: 'GET', - headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, - }); - const data = await response.json(); - if (debug) console.log('Response:', data); + const data = await fetchWithErrorHandling( + `${BASE_URL}/${Id}/device_groups/${deviceGroupId}`, + { + method: 'GET', + headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, + }, + debug, + ); return data; } catch (error) { if (debug) console.error('Error getting device group by ID:', error); @@ -92,17 +97,19 @@ const createDeviceGroup = async ( debug?: boolean, ): Promise => { try { - const response = await fetch(`${BASE_URL}/${Id}/device_groups`, { - method: 'POST', - headers: { - Accept: 'application/json; version=3', - 'Content-Type': 'application/json', - Authorization: `Token ${token}`, + const data = await fetchWithErrorHandling( + `${BASE_URL}/${Id}/device_groups`, + { + method: 'POST', + headers: { + Accept: 'application/json; version=3', + 'Content-Type': 'application/json', + Authorization: `Token ${token}`, + }, + body: JSON.stringify(deviceGroupData), }, - body: JSON.stringify(deviceGroupData), - }); - const data = await response.json(); - if (debug) console.log('Response:', data); + debug, + ); return data; } catch (error) { if (debug) console.error('Error creating device group:', error); @@ -128,17 +135,19 @@ const updateDeviceGroup = async ( debug?: boolean, ): Promise => { try { - const response = await fetch(`${BASE_URL}/${Id}/device_groups/${deviceGroupId}`, { - method: 'PATCH', - headers: { - Accept: 'application/json; version=3', - 'Content-Type': 'application/json', - Authorization: `Token ${token}`, + const data = await fetchWithErrorHandling( + `${BASE_URL}/${Id}/device_groups/${deviceGroupId}`, + { + method: 'PATCH', + headers: { + Accept: 'application/json; version=3', + 'Content-Type': 'application/json', + Authorization: `Token ${token}`, + }, + body: JSON.stringify(deviceGroupData), }, - body: JSON.stringify(deviceGroupData), - }); - const data = await response.json(); - if (debug) console.log('Response:', data); + debug, + ); return data; } catch (error) { if (debug) console.error('Error updating device group:', error); @@ -162,12 +171,14 @@ const deleteDeviceGroup = async ( debug?: boolean, ): Promise => { try { - const response = await fetch(`${BASE_URL}/${Id}/device_groups/${deviceGroupId}`, { - method: 'DELETE', - headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, - }); - const data = await response.json(); - if (debug) console.log('Response:', data); + const data = await fetchWithErrorHandling( + `${BASE_URL}/${Id}/device_groups/${deviceGroupId}`, + { + method: 'DELETE', + headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, + }, + debug, + ); return data; } catch (error) { if (debug) console.error('Error deleting device group:', error); diff --git a/packages/applications/src/functions-instances/services/index.ts b/packages/applications/src/functions-instances/services/index.ts index c530084..0c5a5a1 100644 --- a/packages/applications/src/functions-instances/services/index.ts +++ b/packages/applications/src/functions-instances/services/index.ts @@ -1,3 +1,4 @@ +import { fetchWithErrorHandling } from '../../utils'; import { ApiCreateFunctionInstancePayload, ApiCreateFunctionInstanceResponse, @@ -35,12 +36,14 @@ const listFunctionInstances = async ( order_by, filter, }); - const response = await fetch(`${BASE_URL}/${Id}/functions_instances?${queryParams.toString()}`, { - method: 'GET', - headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, - }); - const data = await response.json(); - if (debug) console.log('Response:', data); + const data = await fetchWithErrorHandling( + `${BASE_URL}/${Id}/functions_instances?${queryParams.toString()}`, + { + method: 'GET', + headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, + }, + debug, + ); return data; } catch (error) { if (debug) console.error('Error listing function instances:', error); @@ -64,12 +67,14 @@ const getFunctionInstanceById = async ( debug?: boolean, ): Promise => { try { - const response = await fetch(`${BASE_URL}/${Id}/functions_instances/${functionInstanceId}`, { - method: 'GET', - headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, - }); - const data = await response.json(); - if (debug) console.log('Response:', data); + const data = await fetchWithErrorHandling( + `${BASE_URL}/${Id}/functions_instances/${functionInstanceId}`, + { + method: 'GET', + headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, + }, + debug, + ); return data; } catch (error) { if (debug) console.error('Error getting function instance by ID:', error); @@ -93,16 +98,19 @@ const createFunctionInstance = async ( debug?: boolean, ): Promise => { try { - const response = await fetch(`${BASE_URL}/${edgeApplicationId}/functions_instances`, { - method: 'POST', - headers: { - Accept: 'application/json; version=3', - 'Content-Type': 'application/json', - Authorization: `Token ${token}`, + const data = await fetchWithErrorHandling( + `${BASE_URL}/${edgeApplicationId}/functions_instances`, + { + method: 'POST', + headers: { + Accept: 'application/json; version=3', + 'Content-Type': 'application/json', + Authorization: `Token ${token}`, + }, + body: JSON.stringify(functionInstanceData), }, - body: JSON.stringify(functionInstanceData), - }); - const data = await response.json(); + debug, + ); if (debug) console.log('Response:', data); return data; } catch (error) { @@ -129,17 +137,19 @@ const updateFunctionInstance = async ( debug?: boolean, ): Promise => { try { - const response = await fetch(`${BASE_URL}/${Id}/functions_instances/${functionInstanceId}`, { - method: 'PATCH', - headers: { - Accept: 'application/json; version=3', - 'Content-Type': 'application/json', - Authorization: `Token ${token}`, + const data = await fetchWithErrorHandling( + `${BASE_URL}/${Id}/functions_instances/${functionInstanceId}`, + { + method: 'PATCH', + headers: { + Accept: 'application/json; version=3', + 'Content-Type': 'application/json', + Authorization: `Token ${token}`, + }, + body: JSON.stringify(functionInstanceData), }, - body: JSON.stringify(functionInstanceData), - }); - const data = await response.json(); - if (debug) console.log('Response:', data); + debug, + ); return data; } catch (error) { if (debug) console.error('Error updating function instance:', error); @@ -163,12 +173,14 @@ const deleteFunctionInstance = async ( debug?: boolean, ): Promise => { try { - const response = await fetch(`${BASE_URL}/${edgeApplicationId}/functions_instances/${edgeFunctionInstanceId}`, { - method: 'DELETE', - headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, - }); - const data = await response.json(); - if (debug) console.log('Response:', data); + const data = await fetchWithErrorHandling( + `${BASE_URL}/${edgeApplicationId}/functions_instances/${edgeFunctionInstanceId}`, + { + method: 'DELETE', + headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, + }, + debug, + ); return data; } catch (error) { if (debug) console.error('Error deleting function instance:', error); diff --git a/packages/applications/src/main-settings/services/index.ts b/packages/applications/src/main-settings/services/index.ts index f804266..9d33663 100644 --- a/packages/applications/src/main-settings/services/index.ts +++ b/packages/applications/src/main-settings/services/index.ts @@ -1,3 +1,4 @@ +import { fetchWithErrorHandling } from '../../utils'; import { ApiCreateApplicationPayload, ApiCreateApplicationResponse, @@ -32,12 +33,14 @@ const getApplications = async ( page: String(page), page_size: String(page_size), }); - const response = await fetch(`${BASE_URL}?${queryParams.toString()}`, { - method: 'GET', - headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, - }); - const data = await response.json(); - if (debug) console.log('Response:', data); + const data = await fetchWithErrorHandling( + `${BASE_URL}?${queryParams.toString()}`, + { + method: 'GET', + headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, + }, + debug, + ); return data; } catch (error) { if (debug) console.error('Error getting all applications:', error); @@ -55,12 +58,14 @@ const getApplications = async ( */ const getApplicationById = async (token: string, Id: number, debug?: boolean): Promise => { try { - const response = await fetch(`${BASE_URL}/${Id}`, { - method: 'GET', - headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, - }); - const data = await response.json(); - if (debug) console.log('Response:', data); + const data = await fetchWithErrorHandling( + `${BASE_URL}/${Id}`, + { + method: 'GET', + headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, + }, + debug, + ); return data; } catch (error) { if (debug) console.error('Error getting application by ID:', error); @@ -82,17 +87,19 @@ const postApplication = async ( debug?: boolean, ): Promise => { try { - const response = await fetch(BASE_URL, { - method: 'POST', - headers: { - Accept: 'application/json; version=3', - 'Content-Type': 'application/json', - Authorization: `Token ${token}`, + const data = await fetchWithErrorHandling( + BASE_URL, + { + method: 'POST', + headers: { + Accept: 'application/json; version=3', + 'Content-Type': 'application/json', + Authorization: `Token ${token}`, + }, + body: JSON.stringify(applicationData), }, - body: JSON.stringify(applicationData), - }); - const data = await response.json(); - if (debug) console.log('Response:', data); + debug, + ); return data; } catch (error) { if (debug) console.error('Error creating application:', error); @@ -116,17 +123,19 @@ const putApplication = async ( debug?: boolean, ): Promise => { try { - const response = await fetch(`${BASE_URL}/${Id}`, { - method: 'PUT', - headers: { - Accept: 'application/json; version=3', - 'Content-Type': 'application/json', - Authorization: `Token ${token}`, + const data = await fetchWithErrorHandling( + `${BASE_URL}/${Id}`, + { + method: 'PUT', + headers: { + Accept: 'application/json; version=3', + 'Content-Type': 'application/json', + Authorization: `Token ${token}`, + }, + body: JSON.stringify(applicationData), }, - body: JSON.stringify(applicationData), - }); - const data = await response.json(); - if (debug) console.log('Response:', data); + debug, + ); return data; } catch (error) { if (debug) console.error('Error updating application:', error); @@ -150,17 +159,19 @@ const patchApplication = async ( debug?: boolean, ): Promise => { try { - const response = await fetch(`${BASE_URL}/${Id}`, { - method: 'PATCH', - headers: { - Accept: 'application/json; version=3', - 'Content-Type': 'application/json', - Authorization: `Token ${token}`, + const data = await fetchWithErrorHandling( + `${BASE_URL}/${Id}`, + { + method: 'PATCH', + headers: { + Accept: 'application/json; version=3', + 'Content-Type': 'application/json', + Authorization: `Token ${token}`, + }, + body: JSON.stringify(applicationData), }, - body: JSON.stringify(applicationData), - }); - const data = await response.json(); - if (debug) console.log('Response:', data); + debug, + ); return data; } catch (error) { if (debug) console.error('Error patching application:', error); @@ -178,12 +189,14 @@ const patchApplication = async ( */ const deleteApplication = async (token: string, Id: number, debug?: boolean): Promise => { try { - const response = await fetch(`${BASE_URL}/${Id}`, { - method: 'DELETE', - headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, - }); - const data = await response.json(); - if (debug) console.log('Response:', data); + const data = await fetchWithErrorHandling( + `${BASE_URL}/${Id}`, + { + method: 'DELETE', + headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, + }, + debug, + ); return data; } catch (error) { if (debug) console.error('Error deleting application:', error); diff --git a/packages/applications/src/origins/services/index.ts b/packages/applications/src/origins/services/index.ts index 6cb587a..0417f99 100644 --- a/packages/applications/src/origins/services/index.ts +++ b/packages/applications/src/origins/services/index.ts @@ -1,3 +1,4 @@ +import { fetchWithErrorHandling } from '../../utils'; import { ApiCreateOriginPayload, ApiCreateOriginResponse, @@ -35,12 +36,14 @@ const listOrigins = async ( order, filter, }); - const response = await fetch(`${BASE_URL}/${Id}/origins?${queryParams.toString()}`, { - method: 'GET', - headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, - }); - const data = await response.json(); - if (debug) console.log('Response:', data); + const data = await fetchWithErrorHandling( + `${BASE_URL}/${Id}/origins?${queryParams.toString()}`, + { + method: 'GET', + headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, + }, + debug, + ); return data; } catch (error) { if (debug) console.error('Error listing origins:', error); @@ -64,12 +67,14 @@ const getOriginByKey = async ( debug?: boolean, ): Promise => { try { - const response = await fetch(`${BASE_URL}/${Id}/origins/${originKey}`, { - method: 'GET', - headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, - }); - const data = await response.json(); - if (debug) console.log('Response:', data); + const data = await fetchWithErrorHandling( + `${BASE_URL}/${Id}/origins/${originKey}`, + { + method: 'GET', + headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, + }, + debug, + ); return data; } catch (error) { if (debug) console.error('Error getting origin by key:', error); @@ -93,17 +98,19 @@ const createOrigin = async ( debug?: boolean, ): Promise => { try { - const response = await fetch(`${BASE_URL}/${Id}/origins`, { - method: 'POST', - headers: { - Accept: 'application/json; version=3', - 'Content-Type': 'application/json', - Authorization: `Token ${token}`, + const data = await fetchWithErrorHandling( + `${BASE_URL}/${Id}/origins`, + { + method: 'POST', + headers: { + Accept: 'application/json; version=3', + 'Content-Type': 'application/json', + Authorization: `Token ${token}`, + }, + body: JSON.stringify(originData), }, - body: JSON.stringify(originData), - }); - const data = await response.json(); - if (debug) console.log('Response:', data); + debug, + ); return data; } catch (error) { if (debug) console.error('Error creating origin:', error); @@ -129,17 +136,19 @@ const updateOrigin = async ( debug?: boolean, ): Promise => { try { - const response = await fetch(`${BASE_URL}/${Id}/origins/${originKey}`, { - method: 'PATCH', - headers: { - Accept: 'application/json; version=3', - 'Content-Type': 'application/json', - Authorization: `Token ${token}`, + const data = await fetchWithErrorHandling( + `${BASE_URL}/${Id}/origins/${originKey}`, + { + method: 'PATCH', + headers: { + Accept: 'application/json; version=3', + 'Content-Type': 'application/json', + Authorization: `Token ${token}`, + }, + body: JSON.stringify(originData), }, - body: JSON.stringify(originData), - }); - const data = await response.json(); - if (debug) console.log('Response:', data); + debug, + ); return data; } catch (error) { if (debug) console.error('Error updating origin:', error); @@ -163,12 +172,14 @@ const deleteOrigin = async ( debug?: boolean, ): Promise => { try { - const response = await fetch(`${BASE_URL}/${Id}/origins/${originKey}`, { - method: 'DELETE', - headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, - }); - const data = await response.json(); - if (debug) console.log('Response:', data); + const data = await fetchWithErrorHandling( + `${BASE_URL}/${Id}/origins/${originKey}`, + { + method: 'DELETE', + headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, + }, + debug, + ); return data; } catch (error) { if (debug) console.error('Error deleting origin:', error); diff --git a/packages/applications/src/rules-engine/services/index.ts b/packages/applications/src/rules-engine/services/index.ts index b31e28d..2b4061a 100644 --- a/packages/applications/src/rules-engine/services/index.ts +++ b/packages/applications/src/rules-engine/services/index.ts @@ -1,3 +1,4 @@ +import { fetchWithErrorHandling } from '../../utils'; import { ApiCreateRulePayload, ApiListRulesParams, @@ -33,12 +34,14 @@ export const listRules = async ( sort, order, }); - const response = await fetch(`${BASE_URL}/${Id}/rules_engine/${phase}/rules?${queryParams.toString()}`, { - method: 'GET', - headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, - }); - const data = await response.json(); - if (debug) console.log('Response:', data); + const data = await fetchWithErrorHandling( + `${BASE_URL}/${Id}/rules_engine/${phase}/rules?${queryParams.toString()}`, + { + method: 'GET', + headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, + }, + debug, + ); return data; } catch (error) { if (debug) console.error('Error listing rules:', error); @@ -63,12 +66,14 @@ export const getRuleById = async ( debug?: boolean, ): Promise => { try { - const response = await fetch(`${BASE_URL}/${Id}/rules_engine/${phase}/rules/${ruleId}`, { - method: 'GET', - headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, - }); - const data = await response.json(); - if (debug) console.log('Response:', data); + const data = await fetchWithErrorHandling( + `${BASE_URL}/${Id}/rules_engine/${phase}/rules/${ruleId}`, + { + method: 'GET', + headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, + }, + debug, + ); return data; } catch (error) { if (debug) console.error('Error getting rule by ID:', error); @@ -94,17 +99,19 @@ export const createRule = async ( debug?: boolean, ): Promise => { try { - const response = await fetch(`${BASE_URL}/${Id}/rules_engine/${phase}/rules`, { - method: 'POST', - headers: { - Accept: 'application/json; version=3', - 'Content-Type': 'application/json', - Authorization: `Token ${token}`, + const data = await fetchWithErrorHandling( + `${BASE_URL}/${Id}/rules_engine/${phase}/rules`, + { + method: 'POST', + headers: { + Accept: 'application/json; version=3', + 'Content-Type': 'application/json', + Authorization: `Token ${token}`, + }, + body: JSON.stringify(ruleData), }, - body: JSON.stringify(ruleData), - }); - const data = await response.json(); - if (debug) console.log('Response:', data); + debug, + ); return data; } catch (error) { if (debug) console.error('Error creating rule:', error); @@ -131,17 +138,19 @@ export const updateRule = async ( debug?: boolean, ): Promise => { try { - const response = await fetch(`${BASE_URL}/${Id}/rules_engine/${phase}/rules/${ruleId}`, { - method: 'PATCH', - headers: { - Accept: 'application/json; version=3', - 'Content-Type': 'application/json', - Authorization: `Token ${token}`, + const data = await fetchWithErrorHandling( + `${BASE_URL}/${Id}/rules_engine/${phase}/rules/${ruleId}`, + { + method: 'PATCH', + headers: { + Accept: 'application/json; version=3', + 'Content-Type': 'application/json', + Authorization: `Token ${token}`, + }, + body: JSON.stringify(ruleData), }, - body: JSON.stringify(ruleData), - }); - const data = await response.json(); - if (debug) console.log('Response:', data); + debug, + ); return data; } catch (error) { if (debug) console.error('Error updating rule:', error); @@ -166,11 +175,14 @@ export const deleteRule = async ( debug?: boolean, ): Promise => { try { - const response = await fetch(`${BASE_URL}/${Id}/rules_engine/${phase}/rules/${ruleId}`, { - method: 'DELETE', - headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, - }); - if (debug) console.log('Response status:', response.status); + await fetchWithErrorHandling( + `${BASE_URL}/${Id}/rules_engine/${phase}/rules/${ruleId}`, + { + method: 'DELETE', + headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, + }, + debug, + ); } catch (error) { if (debug) console.error('Error deleting rule:', error); throw error; diff --git a/packages/applications/src/utils.ts b/packages/applications/src/utils.ts index 3eb7420..e36c88d 100644 --- a/packages/applications/src/utils.ts +++ b/packages/applications/src/utils.ts @@ -54,3 +54,54 @@ export const mapApiError = ( operation, }; }; + +/** + * Fetches data from the given URL and handles errors based on the HTTP status code. + * It also ensures the response is in JSON format, throwing an error if it is not. + * + * @param {string} url - The URL to send the fetch request to. + * @param {RequestInit} [options] - Optional configuration for the fetch request (headers, method, body, ...). + * @returns {Promise} - A promise that resolves to the fetched data if the request is successful. + * @throws {Error} - Throws an error if the response status is not in the 2xx range or if the response is not in JSON format. + * + * @example + * async function fetchData() { + * try { + * const data = await fetchWithErrorHandling('https://example.com/x') + * console.log('response data:', data) + * } catch (err) { + * console.log('error:', err) + * } + * } + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export async function fetchWithErrorHandling(url: string, options?: RequestInit, debug?: boolean): Promise { + try { + const response = await fetch(url, options); + + if (!response.ok) { + const msg = `HTTP error! Status: ${response.status} - ${response.statusText}`; + + if (debug) console.log(`Error in fetch: ${msg}`); + + throw new Error(msg); + } + + const contentType = response.headers.get('content-type'); + if (!contentType || !contentType.includes('application/json')) { + const textResponse = await response.text(); + const msg = `Expected JSON response, but got: ${textResponse}`; + + if (debug) console.log(`Error in fetch: ${msg}`); + + throw new Error(msg); + } + + const data = await response.json(); + return data; + } catch (err) { + if (debug) console.log(`Error in fetch: ${err}`); + + throw err; + } +} From 1497585b67a317697e5f874bbdef4b52b476002f Mon Sep 17 00:00:00 2001 From: Magnun A V F Date: Tue, 8 Oct 2024 17:44:24 -0300 Subject: [PATCH 02/12] feat: improve fetch error handling in domains pkg --- packages/domains/src/services/api/index.ts | 83 +++++++++++----------- packages/domains/src/utils/index.ts | 32 +++++++++ 2 files changed, 75 insertions(+), 40 deletions(-) diff --git a/packages/domains/src/services/api/index.ts b/packages/domains/src/services/api/index.ts index 9e05c59..0c4a96a 100644 --- a/packages/domains/src/services/api/index.ts +++ b/packages/domains/src/services/api/index.ts @@ -1,4 +1,5 @@ import { AzionClientOptions, AzionDomain } from '../../types'; +import { fetchWithErrorHandling } from '../../utils/index'; import { ApiAzionDomainResponse, ApiAzionDomainResult, @@ -56,14 +57,15 @@ const createDomain = async ( crl_list: domain.mtls.crlList, }; } - - const response = await fetch(BASE_URL, { - method: 'POST', - headers: makeHeaders(token), - body: JSON.stringify(body), - }); - const data = await response.json(); - if (debug) console.log('Response Post Domains', data); + const data = await fetchWithErrorHandling( + BASE_URL, + { + method: 'POST', + headers: makeHeaders(token), + body: JSON.stringify(body), + }, + debug, + ); if (!data.results) { return { error: { message: handleApiError(['detail', 'edge_application_id'], data), operation: 'create domain' }, @@ -84,12 +86,14 @@ const getDomains = async ( try { const { page_size = 10, page = 1 } = queryParams || {}; const queryParamsUrl = new URLSearchParams({ page_size: String(page_size), page: String(page) }); - const response = await fetch(`${BASE_URL}?${queryParamsUrl.toString()}`, { - method: 'GET', - headers: makeHeaders(token), - }); - const data = await response.json(); - if (options?.debug) console.log('Response List Domains', data); + const data = await fetchWithErrorHandling( + `${BASE_URL}?${queryParamsUrl.toString()}`, + { + method: 'GET', + headers: makeHeaders(token), + }, + options?.debug, + ); if (!data.results) { return { error: { message: handleApiError(['detail', 'edge_application_id'], data), operation: 'list domains' }, @@ -129,12 +133,14 @@ const getDomainById = async ( options?: AzionClientOptions, ): Promise => { try { - const response = await fetch(`${BASE_URL}/${domainId}`, { - method: 'GET', - headers: makeHeaders(token), - }); - const data = await response.json(); - if (options?.debug) console.log('Response Get Domain', data); + const data = await fetchWithErrorHandling( + `${BASE_URL}/${domainId}`, + { + method: 'GET', + headers: makeHeaders(token), + }, + options?.debug, + ); if (!data.results) { return { error: { message: handleApiError(['detail', 'edge_application_id'], data), operation: 'get domain' }, @@ -178,13 +184,15 @@ const updateDomain = async ( }; } - const response = await fetch(`${BASE_URL}/${domainId}`, { - method: 'PUT', - headers: makeHeaders(token), - body: JSON.stringify(body), - }); - const data = await response.json(); - if (options?.debug) console.log('Response Put Domains', data); + const data = await fetchWithErrorHandling( + `${BASE_URL}/${domainId}`, + { + method: 'PUT', + headers: makeHeaders(token), + body: JSON.stringify(body), + }, + options?.debug, + ); if (!data.results) { return { error: { message: handleApiError(['detail', 'edge_application_id'], data), operation: 'update domain' }, @@ -203,19 +211,14 @@ const deleteDomain = async ( options?: AzionClientOptions, ): Promise => { try { - const result = await fetch(`${BASE_URL}/${domainId}`, { - method: 'DELETE', - headers: makeHeaders(token), - }); - - if (result.status !== 204) { - const message = await result.json(); - return { - error: { message: handleApiError(['detail', 'edge_application_id'], message), operation: 'delete domain' }, - }; - } - - if (options?.debug) console.log('Response Delete Domain'); + await fetchWithErrorHandling( + `${BASE_URL}/${domainId}`, + { + method: 'DELETE', + headers: makeHeaders(token), + }, + options?.debug, + ); return { results: undefined }; } catch (error) { if (options?.debug) console.error('Error deleting Domain:', error); diff --git a/packages/domains/src/utils/index.ts b/packages/domains/src/utils/index.ts index e528631..803a92a 100644 --- a/packages/domains/src/utils/index.ts +++ b/packages/domains/src/utils/index.ts @@ -31,3 +31,35 @@ export const limitArraySize = (array: T[], limit: number): T[] => { } return array; }; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export async function fetchWithErrorHandling(url: string, options?: RequestInit, debug?: boolean): Promise { + try { + const response = await fetch(url, options); + + if (!response.ok) { + const msg = `HTTP error! Status: ${response.status} - ${response.statusText}`; + + if (debug) console.log(`Error in fetch: ${msg}`); + + throw new Error(msg); + } + + const contentType = response.headers.get('content-type'); + if (!contentType || !contentType.includes('application/json')) { + const textResponse = await response.text(); + const msg = `Expected JSON response, but got: ${textResponse}`; + + if (debug) console.log(`Error in fetch: ${msg}`); + + throw new Error(msg); + } + + const data = await response.json(); + return data; + } catch (err) { + if (debug) console.log(`Error in fetch: ${err}`); + + throw err; + } +} From c7e73ffc8308b2663b16b7d936e85b287986f32f Mon Sep 17 00:00:00 2001 From: Magnun A V F Date: Tue, 8 Oct 2024 17:58:33 -0300 Subject: [PATCH 03/12] feat: improve fetch error handling in purge pkg --- packages/purge/README.md | 2 +- packages/purge/src/services/api/index.ts | 55 +++++++++++++++++++----- 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/packages/purge/README.md b/packages/purge/README.md index a35e349..bae2866 100644 --- a/packages/purge/README.md +++ b/packages/purge/README.md @@ -201,7 +201,7 @@ if (purgeWildCardResponse) { **TypeScript:** ```typescript -import { createClient, } from 'azion/purge'; +import { createClient } from 'azion/purge'; import type { AzionPurgeClient, AzionPurgeResponse, AzionPurge } from 'azion/purge'; const client: AzionPurgeClient = createClient({ token: 'your-api-token', options: { debug: true } }); diff --git a/packages/purge/src/services/api/index.ts b/packages/purge/src/services/api/index.ts index fbaed52..5f0d205 100644 --- a/packages/purge/src/services/api/index.ts +++ b/packages/purge/src/services/api/index.ts @@ -20,6 +20,38 @@ const handleApiError = (fields: string[], data: any, operation: string) => { return error; }; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +async function fetchWithErrorHandling(url: string, options?: RequestInit, debug?: boolean): Promise { + try { + const response = await fetch(url, options); + + if (!response.ok) { + const msg = `HTTP error! Status: ${response.status} - ${response.statusText}`; + + if (debug) console.log(`Error in fetch: ${msg}`); + + throw new Error(msg); + } + + const contentType = response.headers.get('content-type'); + if (!contentType || !contentType.includes('application/json')) { + const textResponse = await response.text(); + const msg = `Expected JSON response, but got: ${textResponse}`; + + if (debug) console.log(`Error in fetch: ${msg}`); + + throw new Error(msg); + } + + const data = await response.json(); + return data; + } catch (err) { + if (debug) console.log(`Error in fetch: ${err}`); + + throw err; + } +} + /** * Purge URLs from the Azion Edge cache. * @@ -67,17 +99,20 @@ const postPurgeWildcard = async (token: string, urls: string[], debug?: boolean) */ const postPurge = async (url: string, token: string, urls: string[], debug?: boolean): Promise => { try { - const response = await fetch(url, { - method: 'POST', - headers: { - Authorization: `Token ${token}`, - 'Content-Type': 'application/json', - Accept: 'application/json; version=3', + const result = await fetchWithErrorHandling( + url, + { + method: 'POST', + headers: { + Authorization: `Token ${token}`, + 'Content-Type': 'application/json', + Accept: 'application/json; version=3', + }, + credentials: 'include', + body: JSON.stringify({ items: urls, layer: 'edge_cache' }), }, - credentials: 'include', - body: JSON.stringify({ items: urls, layer: 'edge_cache' }), - }); - const result = await response.json(); + debug, + ); if (!result.data) { if (debug) console.log('Response Error:', result); result.error = handleApiError(['detail', 'error', 'items'], result, 'post purge'); From 1c8c525f2426a8d63332ae58ec090000d021ee7f Mon Sep 17 00:00:00 2001 From: Magnun A V F Date: Tue, 8 Oct 2024 18:17:05 -0300 Subject: [PATCH 04/12] feat: improve fetch error handling in sql pkg --- packages/sql/src/services/api/index.ts | 86 +++++++++++++++----------- packages/sql/src/utils/fetch/index.ts | 35 +++++++++++ 2 files changed, 85 insertions(+), 36 deletions(-) create mode 100644 packages/sql/src/utils/fetch/index.ts diff --git a/packages/sql/src/services/api/index.ts b/packages/sql/src/services/api/index.ts index 19cfb05..aa4b1f1 100644 --- a/packages/sql/src/services/api/index.ts +++ b/packages/sql/src/services/api/index.ts @@ -1,5 +1,6 @@ import { AzionDatabaseCollectionOptions } from '../../types'; import { limitArraySize } from '../../utils'; +import fetchWithErrorHandling from '../../utils/fetch'; import type { ApiCreateDatabaseResponse, ApiDeleteDatabaseResponse, @@ -36,15 +37,18 @@ const handleApiError = (fields: string[], data: any, operation: string) => { */ const postEdgeDatabase = async (token: string, name: string, debug?: boolean): Promise => { try { - const response = await fetch(BASE_URL, { - method: 'POST', - headers: { - Authorization: `Token ${token}`, - 'Content-Type': 'application/json', + const result = await fetchWithErrorHandling( + BASE_URL, + { + method: 'POST', + headers: { + Authorization: `Token ${token}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ name }), }, - body: JSON.stringify({ name }), - }); - const result = await response.json(); + debug, + ); if (!result.state) { result.error = handleApiError(['detail'], result, 'post database'); return { @@ -81,14 +85,16 @@ const postEdgeDatabase = async (token: string, name: string, debug?: boolean): P */ const deleteEdgeDatabase = async (token: string, id: number, debug?: boolean): Promise => { try { - const response = await fetch(`${BASE_URL}/${id}`, { - method: 'DELETE', - headers: { - Authorization: `Token ${token}`, + const result = await fetchWithErrorHandling( + `${BASE_URL}/${id}`, + { + method: 'DELETE', + headers: { + Authorization: `Token ${token}`, + }, }, - }); - if (debug) console.log('Response Delete Database:', response); - const result = await response.json(); + debug, + ); if (!result.state) { result.error = handleApiError(['detail'], result, 'delete database'); return { @@ -122,16 +128,18 @@ const postQueryEdgeDatabase = async ( debug?: boolean, ): Promise => { try { - const response = await fetch(`${BASE_URL}/${id}/query`, { - method: 'POST', - headers: { - Authorization: `Token ${token}`, - 'Content-Type': 'application/json', + const result = await fetchWithErrorHandling( + `${BASE_URL}/${id}/query`, + { + method: 'POST', + headers: { + Authorization: `Token ${token}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ statements }), }, - body: JSON.stringify({ statements }), - }); - - const result = await response.json(); + debug, + ); if (!result.data || !Array.isArray(result.data)) { result.error = handleApiError(['detail'], result, 'post query'); return { @@ -182,13 +190,16 @@ const postQueryEdgeDatabase = async ( */ const getEdgeDatabaseById = async (token: string, id: number, debug?: boolean): Promise => { try { - const response = await fetch(`${BASE_URL}/${id}`, { - method: 'GET', - headers: { - Authorization: `Token ${token}`, + const result = await fetchWithErrorHandling( + `${BASE_URL}/${id}`, + { + method: 'GET', + headers: { + Authorization: `Token ${token}`, + }, }, - }); - const result = await response.json(); + debug, + ); if (!result.data) { result.error = handleApiError(['detail'], result, 'get databases'); return { @@ -224,13 +235,16 @@ const getEdgeDatabases = async ( } }); } - const response = await fetch(url?.toString(), { - method: 'GET', - headers: { - Authorization: `Token ${token}`, + const data = await fetchWithErrorHandling( + url?.toString(), + { + method: 'GET', + headers: { + Authorization: `Token ${token}`, + }, }, - }); - const data = await response.json(); + debug, + ); if (!data.results) { data.error = handleApiError(['detail'], data, 'get databases'); return { diff --git a/packages/sql/src/utils/fetch/index.ts b/packages/sql/src/utils/fetch/index.ts new file mode 100644 index 0000000..49e7903 --- /dev/null +++ b/packages/sql/src/utils/fetch/index.ts @@ -0,0 +1,35 @@ +export default async function fetchWithErrorHandling( + url: string, + options?: RequestInit, + debug?: boolean, + // eslint-disable-next-line @typescript-eslint/no-explicit-any +): Promise { + try { + const response = await fetch(url, options); + + if (!response.ok) { + const msg = `HTTP error! Status: ${response.status} - ${response.statusText}`; + + if (debug) console.log(`Error in fetch: ${msg}`); + + throw new Error(msg); + } + + const contentType = response.headers.get('content-type'); + if (!contentType || !contentType.includes('application/json')) { + const textResponse = await response.text(); + const msg = `Expected JSON response, but got: ${textResponse}`; + + if (debug) console.log(`Error in fetch: ${msg}`); + + throw new Error(msg); + } + + const data = await response.json(); + return data; + } catch (err) { + if (debug) console.log(`Error in fetch: ${err}`); + + throw err; + } +} From a1a6fdec4d547e032a9e2b5b442ab6444b24916b Mon Sep 17 00:00:00 2001 From: Magnun A V F Date: Wed, 9 Oct 2024 08:55:32 -0300 Subject: [PATCH 05/12] feat: improve fetch error handling in storage pkg --- packages/storage/src/services/api/index.ts | 126 ++++++++++++--------- packages/storage/src/utils/index.ts | 36 ++++++ 2 files changed, 111 insertions(+), 51 deletions(-) diff --git a/packages/storage/src/services/api/index.ts b/packages/storage/src/services/api/index.ts index d3bfda4..4368425 100644 --- a/packages/storage/src/services/api/index.ts +++ b/packages/storage/src/services/api/index.ts @@ -1,3 +1,4 @@ +import fetchWithErrorHandling from '../../utils/index'; import { ApiCreateBucketResponse, ApiCreateObjectResponse, @@ -47,11 +48,14 @@ const getBuckets = async ( try { const { page_size = 10, page = 1 } = params || {}; const queryParams = new URLSearchParams({ page_size: String(page_size), page: String(page) }); - const response = await fetch(`${BASE_URL}?${queryParams.toString()}`, { - method: 'GET', - headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, - }); - const data = await response.json(); + const data = await fetchWithErrorHandling( + `${BASE_URL}?${queryParams.toString()}`, + { + method: 'GET', + headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, + }, + debug, + ); if (!data.results) { data.error = handleApiError(['detail'], data, 'get all buckets'); return { @@ -82,13 +86,15 @@ const postBucket = async ( debug?: boolean, ): Promise => { try { - const response = await fetch(BASE_URL, { - method: 'POST', - headers: { Accept: 'application/json', 'Content-Type': 'application/json', Authorization: `Token ${token}` }, - body: JSON.stringify({ name, edge_access }), - }); - const data = await response.json(); - + const data = await fetchWithErrorHandling( + BASE_URL, + { + method: 'POST', + headers: { Accept: 'application/json', 'Content-Type': 'application/json', Authorization: `Token ${token}` }, + body: JSON.stringify({ name, edge_access }), + }, + debug, + ); if (!data?.state) { data.error = handleApiError(['name', 'edge_access', 'detail'], data, 'create bucket'); return { @@ -119,12 +125,15 @@ const patchBucket = async ( debug?: boolean, ): Promise => { try { - const response = await fetch(`${BASE_URL}/${name}`, { - method: 'PATCH', - headers: { Accept: 'application/json', 'Content-Type': 'application/json', Authorization: `Token ${token}` }, - body: JSON.stringify({ edge_access }), - }); - const data = await response.json(); + const data = await fetchWithErrorHandling( + `${BASE_URL}/${name}`, + { + method: 'PATCH', + headers: { Accept: 'application/json', 'Content-Type': 'application/json', Authorization: `Token ${token}` }, + body: JSON.stringify({ edge_access }), + }, + debug, + ); if (!data?.state) { data.error = handleApiError(['name', 'edge_access', 'detail'], data, 'update bucket'); return { @@ -149,11 +158,14 @@ const patchBucket = async ( */ const deleteBucket = async (token: string, name: string, debug?: boolean): Promise => { try { - const response = await fetch(`${BASE_URL}/${name}`, { - method: 'DELETE', - headers: { Accept: 'application/json', Authorization: `Token ${token}` }, - }); - const data = await response.json(); + const data = await fetchWithErrorHandling( + `${BASE_URL}/${name}`, + { + method: 'DELETE', + headers: { Accept: 'application/json', Authorization: `Token ${token}` }, + }, + debug, + ); if (!data?.state) { data.error = handleApiError(['detail'], data, 'delete bucket'); return { @@ -186,11 +198,14 @@ const getObjects = async ( try { const { max_object_count = 10000 } = params || {}; const queryParams = new URLSearchParams({ max_object_count: String(max_object_count) }); - const response = await fetch(`${BASE_URL}/${bucketName}/objects?${queryParams.toString()}`, { - method: 'GET', - headers: { Accept: 'application/json', Authorization: `Token ${token}` }, - }); - const data = await response.json(); + const data = await fetchWithErrorHandling( + `${BASE_URL}/${bucketName}/objects?${queryParams.toString()}`, + { + method: 'GET', + headers: { Accept: 'application/json', Authorization: `Token ${token}` }, + }, + debug, + ); if (!data.results) { data.error = handleApiError(['detail'], data, 'get all objects'); return { @@ -223,16 +238,19 @@ const postObject = async ( debug?: boolean, ): Promise => { try { - const response = await fetch(`${BASE_URL}/${bucketName}/objects/${key}`, { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/octet-stream', - Authorization: `Token ${token}`, + const data = await fetchWithErrorHandling( + `${BASE_URL}/${bucketName}/objects/${key}`, + { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/octet-stream', + Authorization: `Token ${token}`, + }, + body: file, }, - body: file, - }); - const data = await response.json(); + debug, + ); if (!data?.state) { data.error = handleApiError(['detail'], data, 'create object'); return { @@ -303,16 +321,19 @@ const putObject = async ( debug?: boolean, ): Promise => { try { - const response = await fetch(`${BASE_URL}/${bucketName}/objects/${key}`, { - method: 'PUT', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/octet-stream', - Authorization: `Token ${token}`, + const data = await fetchWithErrorHandling( + `${BASE_URL}/${bucketName}/objects/${key}`, + { + method: 'PUT', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/octet-stream', + Authorization: `Token ${token}`, + }, + body: file, }, - body: file, - }); - const data = await response.json(); + debug, + ); if (debug) console.log('Response:', data); return data; } catch (error) { @@ -337,11 +358,14 @@ const deleteObject = async ( debug?: boolean, ): Promise => { try { - const response = await fetch(`${BASE_URL}/${bucketName}/objects/${key}`, { - method: 'DELETE', - headers: { Accept: 'application/json', Authorization: `Token ${token}` }, - }); - const data = await response.json(); + const data = await fetchWithErrorHandling( + `${BASE_URL}/${bucketName}/objects/${key}`, + { + method: 'DELETE', + headers: { Accept: 'application/json', Authorization: `Token ${token}` }, + }, + debug, + ); if (!data?.state) { data.error = handleApiError(['detail'], data, 'delete object'); return { diff --git a/packages/storage/src/utils/index.ts b/packages/storage/src/utils/index.ts index 26193db..71de9ad 100644 --- a/packages/storage/src/utils/index.ts +++ b/packages/storage/src/utils/index.ts @@ -84,3 +84,39 @@ export const findBucketByName = async ( data: bucket, }; }; + +export default async function fetchWithErrorHandling( + url: string, + options?: RequestInit, + debug?: boolean, + // eslint-disable-next-line @typescript-eslint/no-explicit-any +): Promise { + try { + const response = await fetch(url, options); + + if (!response.ok) { + const msg = `HTTP error! Status: ${response.status} - ${response.statusText}`; + + if (debug) console.log(`Error in fetch: ${msg}`); + + throw new Error(msg); + } + + const contentType = response.headers.get('content-type'); + if (!contentType || !contentType.includes('application/json')) { + const textResponse = await response.text(); + const msg = `Expected JSON response, but got: ${textResponse}`; + + if (debug) console.log(`Error in fetch: ${msg}`); + + throw new Error(msg); + } + + const data = await response.json(); + return data; + } catch (err) { + if (debug) console.log(`Error in fetch: ${err}`); + + throw err; + } +} From 0fba4229250fe843f8e0156ec6f5933cd66aee9c Mon Sep 17 00:00:00 2001 From: Magnun A V F Date: Thu, 10 Oct 2024 17:16:19 -0300 Subject: [PATCH 06/12] test: create tests for custom fetch --- packages/domains/src/utils/index.test.ts | 114 ++++++++++++++++++++++- packages/domains/src/utils/index.ts | 36 +++---- 2 files changed, 128 insertions(+), 22 deletions(-) diff --git a/packages/domains/src/utils/index.test.ts b/packages/domains/src/utils/index.test.ts index fcf67a1..87206e5 100644 --- a/packages/domains/src/utils/index.test.ts +++ b/packages/domains/src/utils/index.test.ts @@ -1,4 +1,6 @@ -import { limitArraySize, resolveDebug, resolveToken } from './index'; +import { limitArraySize, resolveDebug, resolveToken, fetchWithErrorHandling } from './index'; + +global.fetch = jest.fn(); describe('Utils', () => { describe('resolveToken', () => { @@ -100,4 +102,114 @@ describe('Utils', () => { expect(result).toEqual([]); }); }); + + describe('fetchWithErrorHandling', () => { + const URL = 'https://example.com'; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should return data when the fetch is successful and the response is JSON', async () => { + const mockResponseData = { message: 'Success' }; + const mockResponse = { + ok: true, + status: 200, + statusText: 'OK', + json: jest.fn().mockResolvedValue(mockResponseData), + headers: { + get: jest.fn().mockReturnValue('application/json'), + }, + }; + + (global.fetch as jest.Mock).mockResolvedValue(mockResponse); + + const result = await fetchWithErrorHandling(URL); + + expect(result).toEqual(mockResponseData); + expect(fetch).toHaveBeenCalledWith(URL, undefined); + }); + + it('should throw an error if the fetch response is not ok', async () => { + const mockResponse = { + ok: false, + status: 404, + statusText: 'Not Found', + headers: { + get: jest.fn().mockReturnValue('application/json'), + }, + }; + + (global.fetch as jest.Mock).mockResolvedValue(mockResponse); + + await expect(fetchWithErrorHandling(URL)).rejects.toThrow('HTTP error! Status: 404 - Not Found'); + + expect(fetch).toHaveBeenCalledWith(URL, undefined); + }); + + it('should throw an error if the response is not JSON', async () => { + const mockResponse = { + ok: true, + status: 200, + statusText: 'OK', + text: jest.fn().mockResolvedValue('Not JSON'), + headers: { + get: jest.fn().mockReturnValue('text/html'), + }, + }; + + (global.fetch as jest.Mock).mockResolvedValue(mockResponse); + + await expect(fetchWithErrorHandling(URL)).rejects.toThrow('Expected JSON response, but got: Not JSON'); + + expect(fetch).toHaveBeenCalledWith(URL, undefined); + }); + + it('should log an error if debug is enabled and fetch response is not ok', async () => { + const mockConsoleLog = jest.spyOn(console, 'log').mockImplementation(); + + const mockResponse = { + ok: false, + status: 500, + statusText: 'Internal Server Error', + headers: { + get: jest.fn().mockReturnValue('application/json'), + }, + }; + + (global.fetch as jest.Mock).mockResolvedValue(mockResponse); + + await expect(fetchWithErrorHandling(URL, undefined, true)).rejects.toThrow( + 'HTTP error! Status: 500 - Internal Server Error', + ); + + expect(mockConsoleLog).toHaveBeenCalledWith('Error in fetch: HTTP error! Status: 500 - Internal Server Error'); + + mockConsoleLog.mockRestore(); + }); + + it('should log an error if debug is enabled and response is not JSON', async () => { + const mockConsoleLog = jest.spyOn(console, 'log').mockImplementation(); + + const mockResponse = { + ok: true, + status: 200, + statusText: 'OK', + text: jest.fn().mockReturnValue('Not JSON'), + headers: { + get: jest.fn().mockReturnValue('text/html'), + }, + }; + + (global.fetch as jest.Mock).mockResolvedValue(mockResponse); + + await expect(fetchWithErrorHandling(URL, undefined, true)).rejects.toThrow( + 'Expected JSON response, but got: Not JSON', + ); + + expect(mockConsoleLog).toHaveBeenCalledWith('Error in fetch: Expected JSON response, but got: Not JSON'); + + mockConsoleLog.mockRestore(); + }); + }); }); diff --git a/packages/domains/src/utils/index.ts b/packages/domains/src/utils/index.ts index 803a92a..b38181a 100644 --- a/packages/domains/src/utils/index.ts +++ b/packages/domains/src/utils/index.ts @@ -34,32 +34,26 @@ export const limitArraySize = (array: T[], limit: number): T[] => { // eslint-disable-next-line @typescript-eslint/no-explicit-any export async function fetchWithErrorHandling(url: string, options?: RequestInit, debug?: boolean): Promise { - try { - const response = await fetch(url, options); + const response = await fetch(url, options); - if (!response.ok) { - const msg = `HTTP error! Status: ${response.status} - ${response.statusText}`; + if (!response.ok) { + const msg = `HTTP error! Status: ${response.status} - ${response.statusText}`; - if (debug) console.log(`Error in fetch: ${msg}`); + if (debug) console.log(`Error in fetch: ${msg}`); - throw new Error(msg); - } - - const contentType = response.headers.get('content-type'); - if (!contentType || !contentType.includes('application/json')) { - const textResponse = await response.text(); - const msg = `Expected JSON response, but got: ${textResponse}`; - - if (debug) console.log(`Error in fetch: ${msg}`); + throw new Error(msg); + } - throw new Error(msg); - } + const contentType = response.headers.get('content-type'); + if (!contentType || !contentType.includes('application/json')) { + const textResponse = await response.text(); + const msg = `Expected JSON response, but got: ${textResponse}`; - const data = await response.json(); - return data; - } catch (err) { - if (debug) console.log(`Error in fetch: ${err}`); + if (debug) console.log(`Error in fetch: ${msg}`); - throw err; + throw new Error(msg); } + + const data = await response.json(); + return data; } From 8390ff61d0eaa8f2d3f38381b6556294165aaef9 Mon Sep 17 00:00:00 2001 From: Magnun A V F Date: Fri, 11 Oct 2024 14:46:54 -0300 Subject: [PATCH 07/12] feat: enable text return option in custom fetch --- packages/domains/src/utils/index.test.ts | 30 +++++++++++++++++++--- packages/domains/src/utils/index.ts | 32 ++++++++++++++++-------- 2 files changed, 47 insertions(+), 15 deletions(-) diff --git a/packages/domains/src/utils/index.test.ts b/packages/domains/src/utils/index.test.ts index 87206e5..9d1b87e 100644 --- a/packages/domains/src/utils/index.test.ts +++ b/packages/domains/src/utils/index.test.ts @@ -1,4 +1,4 @@ -import { limitArraySize, resolveDebug, resolveToken, fetchWithErrorHandling } from './index'; +import { fetchWithErrorHandling, limitArraySize, resolveDebug, resolveToken } from './index'; global.fetch = jest.fn(); @@ -110,7 +110,7 @@ describe('Utils', () => { jest.clearAllMocks(); }); - it('should return data when the fetch is successful and the response is JSON', async () => { + it('should return JSON data when jsonResponse is true, the fetch is successful and the response is JSON', async () => { const mockResponseData = { message: 'Success' }; const mockResponse = { ok: true, @@ -130,7 +130,7 @@ describe('Utils', () => { expect(fetch).toHaveBeenCalledWith(URL, undefined); }); - it('should throw an error if the fetch response is not ok', async () => { + it('should throw an error if the fetch response is NOT ok', async () => { const mockResponse = { ok: false, status: 404, @@ -147,7 +147,7 @@ describe('Utils', () => { expect(fetch).toHaveBeenCalledWith(URL, undefined); }); - it('should throw an error if the response is not JSON', async () => { + it('should throw an error if jsonResponse is true but response is NOT JSON', async () => { const mockResponse = { ok: true, status: 200, @@ -165,6 +165,28 @@ describe('Utils', () => { expect(fetch).toHaveBeenCalledWith(URL, undefined); }); + it('should return text data when jsonResponse is false, the fetch is successful and the response is NOT JSON', async () => { + const mockResponseData = 'Some plain text.'; + const mockResponse = { + ok: true, + status: 200, + statusText: 'OK', + text: jest.fn().mockResolvedValue(mockResponseData), + headers: { + get: jest.fn().mockReturnValue('text/plain'), + }, + }; + + (global.fetch as jest.Mock).mockResolvedValue(mockResponse); + + const result = await fetchWithErrorHandling(URL, undefined, false, false); + + expect(result).toEqual(mockResponseData); + expect(fetch).toHaveBeenCalledWith(URL, undefined); + }); + + it('should throw an error if fetch response is not ok and jsonResponse is false', async () => {}); + it('should log an error if debug is enabled and fetch response is not ok', async () => { const mockConsoleLog = jest.spyOn(console, 'log').mockImplementation(); diff --git a/packages/domains/src/utils/index.ts b/packages/domains/src/utils/index.ts index b38181a..8293b57 100644 --- a/packages/domains/src/utils/index.ts +++ b/packages/domains/src/utils/index.ts @@ -32,8 +32,13 @@ export const limitArraySize = (array: T[], limit: number): T[] => { return array; }; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export async function fetchWithErrorHandling(url: string, options?: RequestInit, debug?: boolean): Promise { +export async function fetchWithErrorHandling( + url: string, + options?: RequestInit, + debug?: boolean, + jsonResponse: boolean = true, + // eslint-disable-next-line @typescript-eslint/no-explicit-any +): Promise { const response = await fetch(url, options); if (!response.ok) { @@ -44,16 +49,21 @@ export async function fetchWithErrorHandling(url: string, options?: RequestInit, throw new Error(msg); } - const contentType = response.headers.get('content-type'); - if (!contentType || !contentType.includes('application/json')) { - const textResponse = await response.text(); - const msg = `Expected JSON response, but got: ${textResponse}`; + if (jsonResponse) { + const contentType = response.headers.get('content-type'); + if (!contentType || !contentType.includes('application/json')) { + const textResponse = await response.text(); + const msg = `Expected JSON response, but got: ${textResponse}`; - if (debug) console.log(`Error in fetch: ${msg}`); + if (debug) console.log(`Error in fetch: ${msg}`); - throw new Error(msg); - } + throw new Error(msg); + } - const data = await response.json(); - return data; + const data = await response.json(); + return data; + } else { + const data = await response.text(); + return data; + } } From 25ffcb26ca39fead592ccad626bab4ab27423bc6 Mon Sep 17 00:00:00 2001 From: Magnun A V F Date: Fri, 11 Oct 2024 14:50:37 -0300 Subject: [PATCH 08/12] refactor: standardize error returns in domains pkg --- packages/domains/src/index.test.ts | 185 ++++++++++++++++++--- packages/domains/src/services/api/index.ts | 33 ++-- 2 files changed, 181 insertions(+), 37 deletions(-) diff --git a/packages/domains/src/index.test.ts b/packages/domains/src/index.test.ts index 6488f7d..80bf710 100644 --- a/packages/domains/src/index.test.ts +++ b/packages/domains/src/index.test.ts @@ -22,10 +22,18 @@ describe('Domains Package', () => { is_active: true, }, }; - jest.spyOn(global, 'fetch').mockResolvedValue({ json: () => Promise.resolve(mockResponse) } as any); + + jest.spyOn(global, 'fetch').mockResolvedValue({ + json: () => Promise.resolve(mockResponse), + ok: true, + headers: { + get: () => 'application/json', + }, + } as any); jest.spyOn(services, 'createDomain'); const result = await createDomain({ name: 'example.com', edgeApplicationId: 123 }, { debug: mockDebug }); + expect(result).toEqual({ data: { state: 'executed', @@ -43,13 +51,17 @@ describe('Domains Package', () => { ); }); - it('should throw an error if the domain creation fails', async () => { + it('should return an error if the domain creation fails', async () => { const mockError = new Error('Error creating Domain: Request failed'); + jest.spyOn(global, 'fetch').mockRejectedValue(mockError); jest.spyOn(services, 'createDomain'); - await expect(createDomain({ name: 'example.com', edgeApplicationId: 123 }, { debug: mockDebug })).rejects.toThrow( - mockError, - ); + + const result = await createDomain({ name: 'example.com', edgeApplicationId: 123 }, { debug: mockDebug }); + + expect(result).toEqual({ + error: { message: 'Error: Error creating Domain: Request failed', operation: 'create domain' }, + }); expect(services.createDomain).toHaveBeenCalledWith( mockToken, { name: 'example.com', edgeApplicationId: 123 }, @@ -67,7 +79,13 @@ describe('Domains Package', () => { is_active: true, }, }; - jest.spyOn(global, 'fetch').mockResolvedValue({ json: () => Promise.resolve(mockResponse) } as any); + jest.spyOn(global, 'fetch').mockResolvedValue({ + json: () => Promise.resolve(mockResponse), + ok: true, + headers: { + get: () => 'application/json', + }, + } as any); jest.spyOn(services, 'createDomain'); const domain: any = { name: 'example.com', @@ -93,12 +111,16 @@ describe('Domains Package', () => { expect(services.createDomain).toHaveBeenCalledWith(mockToken, domain, { debug: mockDebug }); }); - it('should an error if the domain edgeApplicationId is not provided', async () => { + it('should return an error if the domain edgeApplicationId is not provided', async () => { jest.spyOn(global, 'fetch').mockResolvedValue({ json: () => Promise.resolve({ edge_application_id: ['This field is required.'], }), + ok: true, + headers: { + get: () => 'application/json', + }, } as any); // @eslint-disable-next-line @typescript-eslint/no-explicit-any await expect(createDomain({ name: 'example.com' } as any, { debug: mockDebug })).resolves.toEqual({ @@ -110,7 +132,13 @@ describe('Domains Package', () => { const mockResponse = { detail: 'Invalid Token', }; - jest.spyOn(global, 'fetch').mockResolvedValue({ json: () => Promise.resolve(mockResponse) } as any); + jest.spyOn(global, 'fetch').mockResolvedValue({ + json: () => Promise.resolve(mockResponse), + ok: true, + headers: { + get: () => 'application/json', + }, + } as any); jest.spyOn(services, 'createDomain'); const result = await createDomain({ name: 'example.com', edgeApplicationId: 123 }, { debug: mockDebug }); @@ -161,7 +189,13 @@ describe('Domains Package', () => { }); it('should list domains', async () => { - jest.spyOn(global, 'fetch').mockResolvedValue({ json: () => Promise.resolve(mockResponseListDomains) } as any); + jest.spyOn(global, 'fetch').mockResolvedValue({ + json: () => Promise.resolve(mockResponseListDomains), + ok: true, + headers: { + get: () => 'application/json', + }, + } as any); jest.spyOn(services, 'getDomains'); const results = await getDomains({ debug: mockDebug }); @@ -183,7 +217,13 @@ describe('Domains Package', () => { }); it('should list domains with all fields', async () => { - jest.spyOn(global, 'fetch').mockResolvedValue({ json: () => Promise.resolve(mockResponseListDomains) } as any); + jest.spyOn(global, 'fetch').mockResolvedValue({ + json: () => Promise.resolve(mockResponseListDomains), + ok: true, + headers: { + get: () => 'application/json', + }, + } as any); jest.spyOn(services, 'getDomains'); const queryParams: any = { orderBy: 'id', page: 1, pageSize: 1, sort: 'asc' }; @@ -210,7 +250,13 @@ describe('Domains Package', () => { mockResponseListDomains.results[0].mtls_verification = 'enforce'; mockResponseListDomains.results[0].mtls_trusted_ca_certificate_id = 123; mockResponseListDomains.results[0].crl_list = [111]; - jest.spyOn(global, 'fetch').mockResolvedValue({ json: () => Promise.resolve(mockResponseListDomains) } as any); + jest.spyOn(global, 'fetch').mockResolvedValue({ + json: () => Promise.resolve(mockResponseListDomains), + ok: true, + headers: { + get: () => 'application/json', + }, + } as any); jest.spyOn(services, 'getDomains'); const results = await getDomains({ debug: mockDebug }); @@ -248,7 +294,13 @@ describe('Domains Package', () => { is_active: true, }, }; - jest.spyOn(global, 'fetch').mockResolvedValue({ json: () => Promise.resolve(mockResponse) } as any); + jest.spyOn(global, 'fetch').mockResolvedValue({ + json: () => Promise.resolve(mockResponse), + ok: true, + headers: { + get: () => 'application/json', + }, + } as any); jest.spyOn(services, 'getDomainById'); const result = await getDomain(123, { debug: mockDebug }); @@ -285,7 +337,13 @@ describe('Domains Package', () => { crl_list: [111], }, }; - jest.spyOn(global, 'fetch').mockResolvedValue({ json: () => Promise.resolve(mockResponse) } as any); + jest.spyOn(global, 'fetch').mockResolvedValue({ + json: () => Promise.resolve(mockResponse), + ok: true, + headers: { + get: () => 'application/json', + }, + } as any); jest.spyOn(services, 'getDomainById'); const result = await getDomain(123, { debug: mockDebug }); @@ -333,7 +391,13 @@ describe('Domains Package', () => { edge_firewall_id: null, }, }; - jest.spyOn(global, 'fetch').mockResolvedValue({ json: () => Promise.resolve(mockResponse) } as any); + jest.spyOn(global, 'fetch').mockResolvedValue({ + json: () => Promise.resolve(mockResponse), + ok: true, + headers: { + get: () => 'application/json', + }, + } as any); jest.spyOn(services, 'updateDomain'); const result = await updateDomain( @@ -369,7 +433,13 @@ describe('Domains Package', () => { const mockResponse = { edge_application_id: ['This field is required.'], }; - jest.spyOn(global, 'fetch').mockResolvedValue({ json: () => Promise.resolve(mockResponse) } as any); + jest.spyOn(global, 'fetch').mockResolvedValue({ + json: () => Promise.resolve(mockResponse), + ok: true, + headers: { + get: () => 'application/json', + }, + } as any); const result = await updateDomain( 170, @@ -388,7 +458,13 @@ describe('Domains Package', () => { const mockResponse = { duplicated_domain_name: 'my domain', }; - jest.spyOn(global, 'fetch').mockResolvedValue({ json: () => Promise.resolve(mockResponse) } as any); + jest.spyOn(global, 'fetch').mockResolvedValue({ + json: () => Promise.resolve(mockResponse), + ok: true, + headers: { + get: () => 'application/json', + }, + } as any); const result = await updateDomain(170, { name: 'my domain', edgeApplicationId: 123 }, { debug: mockDebug }); @@ -417,7 +493,13 @@ describe('Domains Package', () => { edge_firewall_id: null, }, }; - jest.spyOn(global, 'fetch').mockResolvedValue({ json: () => Promise.resolve(mockResponse) } as any); + jest.spyOn(global, 'fetch').mockResolvedValue({ + json: () => Promise.resolve(mockResponse), + ok: true, + headers: { + get: () => 'application/json', + }, + } as any); jest.spyOn(services, 'updateDomain'); const domain: any = { @@ -460,7 +542,15 @@ describe('Domains Package', () => { describe('deleteDomain', () => { it('should delete a domain', async () => { - jest.spyOn(global, 'fetch').mockResolvedValue({ ok: true, status: 204 } as any); + jest.spyOn(global, 'fetch').mockResolvedValue({ + json: () => Promise.resolve({}), + ok: true, + headers: { + get: () => 'application/json', + }, + status: 204, + text: jest.fn().mockResolvedValue(''), + } as any); jest.spyOn(services, 'deleteDomain'); const result = await deleteDomain(123, { debug: mockDebug }); @@ -468,16 +558,22 @@ describe('Domains Package', () => { expect(services.deleteDomain).toHaveBeenCalledWith(mockToken, 123, { debug: mockDebug }); }); - it('should throw an error if the domain deletion fails with a message', async () => { + it('should return an error if the domain deletion fails with a message', async () => { jest.spyOn(global, 'fetch').mockResolvedValue({ - ok: true, + ok: false, status: 404, + statusText: 'Not Found', json: () => Promise.resolve({ detail: 'Not found.' }), } as any); jest.spyOn(services, 'deleteDomain'); const result = await deleteDomain(123, { debug: mockDebug }); - expect(result).toEqual(expect.objectContaining({ error: { message: 'Not found.', operation: 'delete domain' } })); + + expect(result).toEqual( + expect.objectContaining({ + error: { message: 'Error: HTTP error! Status: 404 - Not Found', operation: 'delete domain' }, + }), + ); expect(services.deleteDomain).toHaveBeenCalledWith(mockToken, 123, { debug: mockDebug }); }); }); @@ -493,7 +589,15 @@ describe('Domains Package', () => { is_active: true, }, }; - jest.spyOn(global, 'fetch').mockResolvedValue({ json: () => Promise.resolve(mockResponse) } as any); + jest.spyOn(global, 'fetch').mockResolvedValue({ + ok: true, + status: 200, + statusText: 'OK', + json: jest.fn().mockResolvedValue(mockResponse), + headers: { + get: jest.fn().mockReturnValue('application/json'), + }, + } as any); jest.spyOn(services, 'createDomain'); const client = createClient({ token: mockToken, options: { debug: mockDebug } }); @@ -541,7 +645,15 @@ describe('Domains Package', () => { }, ], }; - jest.spyOn(global, 'fetch').mockResolvedValue({ json: () => Promise.resolve(mockResponseListDomains) } as any); + jest.spyOn(global, 'fetch').mockResolvedValue({ + ok: true, + status: 200, + statusText: 'OK', + json: jest.fn().mockResolvedValue(mockResponseListDomains), + headers: { + get: jest.fn().mockReturnValue('application/json'), + }, + } as any); jest.spyOn(services, 'getDomains'); const client = createClient({ token: mockToken, options: { debug: mockDebug } }); @@ -574,7 +686,15 @@ describe('Domains Package', () => { is_active: true, }, }; - jest.spyOn(global, 'fetch').mockResolvedValue({ json: () => Promise.resolve(mockResponse) } as any); + jest.spyOn(global, 'fetch').mockResolvedValue({ + ok: true, + status: 200, + statusText: 'OK', + json: jest.fn().mockResolvedValue(mockResponse), + headers: { + get: jest.fn().mockReturnValue('application/json'), + }, + } as any); jest.spyOn(services, 'getDomainById'); const client = createClient({ token: mockToken, options: { debug: mockDebug } }); @@ -612,7 +732,15 @@ describe('Domains Package', () => { edge_firewall_id: null, }, }; - jest.spyOn(global, 'fetch').mockResolvedValue({ json: () => Promise.resolve(mockResponse) } as any); + jest.spyOn(global, 'fetch').mockResolvedValue({ + ok: true, + status: 200, + statusText: 'OK', + json: jest.fn().mockResolvedValue(mockResponse), + headers: { + get: jest.fn().mockReturnValue('application/json'), + }, + } as any); jest.spyOn(services, 'updateDomain'); const client = createClient({ token: mockToken, options: { debug: mockDebug } }); @@ -642,7 +770,12 @@ describe('Domains Package', () => { }); it('should create a client with deleteDomain', async () => { - jest.spyOn(global, 'fetch').mockResolvedValue({ ok: true, status: 204 } as any); + jest.spyOn(global, 'fetch').mockResolvedValue({ + ok: true, + status: 204, + statusText: 'No Content', + text: jest.fn().mockResolvedValue(''), + } as any); jest.spyOn(services, 'deleteDomain'); const client = createClient({ token: mockToken, options: { debug: mockDebug } }); diff --git a/packages/domains/src/services/api/index.ts b/packages/domains/src/services/api/index.ts index 0c4a96a..f7b1be1 100644 --- a/packages/domains/src/services/api/index.ts +++ b/packages/domains/src/services/api/index.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { AzionClientOptions, AzionDomain } from '../../types'; import { fetchWithErrorHandling } from '../../utils/index'; import { @@ -10,7 +11,6 @@ import { const BASE_URL = process.env.AZION_ENV === 'stage' ? 'https://stage-api.azion.net/domains' : 'https://api.azionapi.net/domains'; -// eslint-disable-next-line @typescript-eslint/no-explicit-any const handleApiError = (fields: string[], data: any) => { let error = undefined; fields.forEach((field: string) => { @@ -72,9 +72,11 @@ const createDomain = async ( }; } return data; - } catch (error) { + } catch (error: any) { if (debug) console.error('Error creating Domain:', error); - throw error; + return { + error: { message: error.toString(), operation: 'create domain' }, + }; } }; @@ -121,9 +123,11 @@ const getDomains = async ( : undefined, })), }; - } catch (error) { + } catch (error: any) { if (options?.debug) console.error('Error listing Domains:', error); - throw error; + return { + error: { message: error.toString(), operation: 'list domains' }, + }; } }; @@ -147,9 +151,11 @@ const getDomainById = async ( }; } return data; - } catch (error) { + } catch (error: any) { if (options?.debug) console.error('Error getting Domain:', error); - throw error; + return { + error: { message: error.toString(), operation: 'get domain' }, + }; } }; @@ -199,9 +205,11 @@ const updateDomain = async ( }; } return data; - } catch (error) { + } catch (error: any) { if (options?.debug) console.error('Error updating Domain:', error); - throw error; + return { + error: { message: error.toString(), operation: 'update domain' }, + }; } }; @@ -218,11 +226,14 @@ const deleteDomain = async ( headers: makeHeaders(token), }, options?.debug, + false, ); return { results: undefined }; - } catch (error) { + } catch (error: any) { if (options?.debug) console.error('Error deleting Domain:', error); - throw error; + return { + error: { message: error.toString(), operation: 'delete domain' }, + }; } }; From 52ffd438ac002e9364ce1b3ca78d3d832d596d45 Mon Sep 17 00:00:00 2001 From: Magnun A V F Date: Mon, 14 Oct 2024 08:49:56 -0300 Subject: [PATCH 09/12] refactor: standardize error returns in purge pkg --- packages/purge/src/services/api/index.ts | 37 ++++++++++++++---------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/packages/purge/src/services/api/index.ts b/packages/purge/src/services/api/index.ts index 5f0d205..b6c2cc7 100644 --- a/packages/purge/src/services/api/index.ts +++ b/packages/purge/src/services/api/index.ts @@ -20,19 +20,24 @@ const handleApiError = (fields: string[], data: any, operation: string) => { return error; }; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -async function fetchWithErrorHandling(url: string, options?: RequestInit, debug?: boolean): Promise { - try { - const response = await fetch(url, options); +async function fetchWithErrorHandling( + url: string, + options?: RequestInit, + debug?: boolean, + jsonResponse: boolean = true, + // eslint-disable-next-line @typescript-eslint/no-explicit-any +): Promise { + const response = await fetch(url, options); - if (!response.ok) { - const msg = `HTTP error! Status: ${response.status} - ${response.statusText}`; + if (!response.ok) { + const msg = `HTTP error! Status: ${response.status} - ${response.statusText}`; - if (debug) console.log(`Error in fetch: ${msg}`); + if (debug) console.log(`Error in fetch: ${msg}`); - throw new Error(msg); - } + throw new Error(msg); + } + if (jsonResponse) { const contentType = response.headers.get('content-type'); if (!contentType || !contentType.includes('application/json')) { const textResponse = await response.text(); @@ -45,10 +50,9 @@ async function fetchWithErrorHandling(url: string, options?: RequestInit, debug? const data = await response.json(); return data; - } catch (err) { - if (debug) console.log(`Error in fetch: ${err}`); - - throw err; + } else { + const data = await response.text(); + return data; } } @@ -122,9 +126,12 @@ const postPurge = async (url: string, token: string, urls: string[], debug?: boo } if (debug) console.log('Response:', result); return result; - } catch (error) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { if (debug) console.error('Error purging:', error); - throw error; + return { + error: { message: error.toString(), operation: 'post purge' }, + }; } }; From 964686a76cb69429721ad6909f072df22c973e77 Mon Sep 17 00:00:00 2001 From: Magnun A V F Date: Mon, 14 Oct 2024 09:28:43 -0300 Subject: [PATCH 10/12] refactor: standardize error returns in sql pkg --- packages/sql/src/services/api/index.ts | 33 +++++++++++++++++--------- packages/sql/src/utils/fetch/index.ts | 22 ++++++++--------- 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/packages/sql/src/services/api/index.ts b/packages/sql/src/services/api/index.ts index aa4b1f1..4bb2fff 100644 --- a/packages/sql/src/services/api/index.ts +++ b/packages/sql/src/services/api/index.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { AzionDatabaseCollectionOptions } from '../../types'; import { limitArraySize } from '../../utils'; import fetchWithErrorHandling from '../../utils/fetch'; @@ -70,9 +71,11 @@ const postEdgeDatabase = async (token: string, name: string, debug?: boolean): P updatedAt: result.data.updated_at, }, }; - } catch (error) { + } catch (error: any) { if (debug) console.error('Error creating EdgeDB:', error); - throw error; + return { + error: { message: error.toString(), operation: 'post database' }, + }; } }; @@ -107,9 +110,11 @@ const deleteEdgeDatabase = async (token: string, id: number, debug?: boolean): P id, }, }; - } catch (error) { + } catch (error: any) { if (debug) console.error('Error deleting EdgeDB:', error); - throw error; + return { + error: { message: error.toString(), operation: 'delete database' }, + }; } }; @@ -175,9 +180,11 @@ const postQueryEdgeDatabase = async ( state: result.state, data: result.data, }; - } catch (error) { + } catch (error: any) { if (debug) console.error('Error querying EdgeDB:', error); - throw new Error((error as Error)?.message); + return { + error: { message: error.toString(), operation: 'post query' }, + }; } }; @@ -201,16 +208,18 @@ const getEdgeDatabaseById = async (token: string, id: number, debug?: boolean): debug, ); if (!result.data) { - result.error = handleApiError(['detail'], result, 'get databases'); + result.error = handleApiError(['detail'], result, 'get database'); return { error: result.error ?? JSON.stringify(result), }; } if (debug) console.log('Response:', result); return result; - } catch (error) { + } catch (error: any) { if (debug) console.error('Error getting EdgeDB:', error); - throw error; + return { + error: { message: error.toString(), operation: 'get database' }, + }; } }; @@ -274,9 +283,11 @@ const getEdgeDatabases = async ( updatedAt: result.updated_at, })), }; - } catch (error) { + } catch (error: any) { if (debug) console.error('Error getting all EdgeDBs:', error); - throw error; + return { + error: { message: error.toString(), operation: 'get databases' }, + }; } }; diff --git a/packages/sql/src/utils/fetch/index.ts b/packages/sql/src/utils/fetch/index.ts index 49e7903..07acbab 100644 --- a/packages/sql/src/utils/fetch/index.ts +++ b/packages/sql/src/utils/fetch/index.ts @@ -2,19 +2,20 @@ export default async function fetchWithErrorHandling( url: string, options?: RequestInit, debug?: boolean, + jsonResponse: boolean = true, // eslint-disable-next-line @typescript-eslint/no-explicit-any ): Promise { - try { - const response = await fetch(url, options); + const response = await fetch(url, options); - if (!response.ok) { - const msg = `HTTP error! Status: ${response.status} - ${response.statusText}`; + if (!response.ok) { + const msg = `HTTP error! Status: ${response.status} - ${response.statusText}`; - if (debug) console.log(`Error in fetch: ${msg}`); + if (debug) console.log(`Error in fetch: ${msg}`); - throw new Error(msg); - } + throw new Error(msg); + } + if (jsonResponse) { const contentType = response.headers.get('content-type'); if (!contentType || !contentType.includes('application/json')) { const textResponse = await response.text(); @@ -27,9 +28,8 @@ export default async function fetchWithErrorHandling( const data = await response.json(); return data; - } catch (err) { - if (debug) console.log(`Error in fetch: ${err}`); - - throw err; + } else { + const data = await response.text(); + return data; } } From a310380ffe03e3e9a70999c0ae0fce5b0ed95513 Mon Sep 17 00:00:00 2001 From: Magnun A V F Date: Mon, 14 Oct 2024 09:56:31 -0300 Subject: [PATCH 11/12] refactor: standardize error returns in storage pkg --- packages/storage/src/services/api/index.ts | 59 ++++++++++++++-------- packages/storage/src/utils/index.ts | 24 ++++----- 2 files changed, 51 insertions(+), 32 deletions(-) diff --git a/packages/storage/src/services/api/index.ts b/packages/storage/src/services/api/index.ts index 4368425..6550b6b 100644 --- a/packages/storage/src/services/api/index.ts +++ b/packages/storage/src/services/api/index.ts @@ -1,4 +1,5 @@ -import fetchWithErrorHandling from '../../utils/index'; +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { fetchWithErrorHandling } from '../../utils/index'; import { ApiCreateBucketResponse, ApiCreateObjectResponse, @@ -64,9 +65,11 @@ const getBuckets = async ( } if (debug) console.log('Response:', data); return data; - } catch (error) { + } catch (error: any) { if (debug) console.error('Error getting all buckets:', error); - throw error; + return { + error: { message: error.toString(), operation: 'get all buckets' }, + }; } }; @@ -103,9 +106,11 @@ const postBucket = async ( } if (debug) console.log('Response:', data); return data; - } catch (error) { + } catch (error: any) { if (debug) console.error('Error creating bucket:', error); - throw error; + return { + error: { message: error.toString(), operation: 'create bucket' }, + }; } }; @@ -142,9 +147,11 @@ const patchBucket = async ( } if (debug) console.log('Response:', data); return data; - } catch (error) { + } catch (error: any) { if (debug) console.error('Error updating bucket:', error); - throw error; + return { + error: { message: error.toString(), operation: 'update bucket' }, + }; } }; @@ -174,9 +181,11 @@ const deleteBucket = async (token: string, name: string, debug?: boolean): Promi } if (debug) console.log('Response Delete Bucket:', data); return data; - } catch (error) { + } catch (error: any) { if (debug) console.error('Error deleting bucket:', error); - throw error; + return { + error: { message: error.toString(), operation: 'delete bucket' }, + }; } }; @@ -214,9 +223,11 @@ const getObjects = async ( } if (debug) console.log('Response:', data); return data; - } catch (error) { + } catch (error: any) { if (debug) console.error('Error getting all objects:', error); - throw error; + return { + error: { message: error.toString(), operation: 'get all objects' }, + }; } }; @@ -259,9 +270,11 @@ const postObject = async ( } if (debug) console.log('Response:', data); return data; - } catch (error) { + } catch (error: any) { if (debug) console.error('Error posting object:', error); - throw error; + return { + error: { message: error.toString(), operation: 'create object' }, + }; } }; @@ -287,7 +300,7 @@ const getObjectByKey = async ( }); if (response.headers.get('content-type') === 'application/json') { const data = await response.json(); - const error = handleApiError(['detail'], data, 'get all objects'); + const error = handleApiError(['detail'], data, 'get object by key'); return { error: error ?? JSON.stringify(data), }; @@ -297,9 +310,11 @@ const getObjectByKey = async ( return { data, }; - } catch (error) { + } catch (error: any) { if (debug) console.error('Error getting object by name:', error); - throw error; + return { + error: { message: error.toString(), operation: 'get object by key' }, + }; } }; @@ -336,9 +351,11 @@ const putObject = async ( ); if (debug) console.log('Response:', data); return data; - } catch (error) { + } catch (error: any) { if (debug) console.error('Error putting object:', error); - throw error; + return { + error: { message: error.toString(), operation: 'put object' }, + }; } }; @@ -374,9 +391,11 @@ const deleteObject = async ( } if (debug) console.log('Response:', data); return data; - } catch (error) { + } catch (error: any) { if (debug) console.error('Error deleting object:', error); - throw error; + return { + error: { message: error.toString(), operation: 'delete object' }, + }; } }; diff --git a/packages/storage/src/utils/index.ts b/packages/storage/src/utils/index.ts index 71de9ad..0e5188c 100644 --- a/packages/storage/src/utils/index.ts +++ b/packages/storage/src/utils/index.ts @@ -85,23 +85,24 @@ export const findBucketByName = async ( }; }; -export default async function fetchWithErrorHandling( +export async function fetchWithErrorHandling( url: string, options?: RequestInit, debug?: boolean, + jsonResponse: boolean = true, // eslint-disable-next-line @typescript-eslint/no-explicit-any ): Promise { - try { - const response = await fetch(url, options); + const response = await fetch(url, options); - if (!response.ok) { - const msg = `HTTP error! Status: ${response.status} - ${response.statusText}`; + if (!response.ok) { + const msg = `HTTP error! Status: ${response.status} - ${response.statusText}`; - if (debug) console.log(`Error in fetch: ${msg}`); + if (debug) console.log(`Error in fetch: ${msg}`); - throw new Error(msg); - } + throw new Error(msg); + } + if (jsonResponse) { const contentType = response.headers.get('content-type'); if (!contentType || !contentType.includes('application/json')) { const textResponse = await response.text(); @@ -114,9 +115,8 @@ export default async function fetchWithErrorHandling( const data = await response.json(); return data; - } catch (err) { - if (debug) console.log(`Error in fetch: ${err}`); - - throw err; + } else { + const data = await response.text(); + return data; } } From 44e4e8f300901c2754e5d29ae5de72637df299f8 Mon Sep 17 00:00:00 2001 From: Magnun A V F Date: Mon, 14 Oct 2024 13:36:14 -0300 Subject: [PATCH 12/12] chore: improve fetch error handling in applications pkg --- packages/applications/src/utils.ts | 34 ++++++++++++++++++------------ 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/packages/applications/src/utils.ts b/packages/applications/src/utils.ts index e36c88d..df385bc 100644 --- a/packages/applications/src/utils.ts +++ b/packages/applications/src/utils.ts @@ -61,6 +61,8 @@ export const mapApiError = ( * * @param {string} url - The URL to send the fetch request to. * @param {RequestInit} [options] - Optional configuration for the fetch request (headers, method, body, ...). + * @param {boolean} debug - Running in debug mode. + * @param {boolean} jsonResponse - The fetch must expect a json response. * @returns {Promise} - A promise that resolves to the fetched data if the request is successful. * @throws {Error} - Throws an error if the response status is not in the 2xx range or if the response is not in JSON format. * @@ -74,19 +76,24 @@ export const mapApiError = ( * } * } */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export async function fetchWithErrorHandling(url: string, options?: RequestInit, debug?: boolean): Promise { - try { - const response = await fetch(url, options); +export async function fetchWithErrorHandling( + url: string, + options?: RequestInit, + debug?: boolean, + jsonResponse: boolean = true, + // eslint-disable-next-line @typescript-eslint/no-explicit-any +): Promise { + const response = await fetch(url, options); - if (!response.ok) { - const msg = `HTTP error! Status: ${response.status} - ${response.statusText}`; + if (!response.ok) { + const msg = `HTTP error! Status: ${response.status} - ${response.statusText}`; - if (debug) console.log(`Error in fetch: ${msg}`); + if (debug) console.log(`Error in fetch: ${msg}`); - throw new Error(msg); - } + throw new Error(msg); + } + if (jsonResponse) { const contentType = response.headers.get('content-type'); if (!contentType || !contentType.includes('application/json')) { const textResponse = await response.text(); @@ -99,9 +106,8 @@ export async function fetchWithErrorHandling(url: string, options?: RequestInit, const data = await response.json(); return data; - } catch (err) { - if (debug) console.log(`Error in fetch: ${err}`); - - throw err; + } else { + const data = await response.text(); + return data; } -} +} \ No newline at end of file