From 2cdd0dede07399cf6f8d6f52aa2f5efbcf2759f3 Mon Sep 17 00:00:00 2001 From: Audrius Vaitonis Date: Wed, 10 Apr 2024 09:19:06 +0100 Subject: [PATCH] feat(DTFS2-7052): new API modules and endpoint /api/v1/geospatial/addresses/postcode?postcode=W1A1AA --- .cspell.json | 14 +- .env.sample | 6 + src/config/index.ts | 3 +- src/config/ordnance-survey.config.test.ts | 39 ++++++ src/config/ordnance-survey.config.ts | 21 +++ .../dto/get-addresses-response.dto.ts | 90 ++++++++++++ .../dto/get-search-postcode-query.dto.ts | 3 + ...-search-places-v1-postcode-no-results.json | 15 ++ ...esponse-for-search-places-v1-postcode.json | 53 ++++++++ .../ordnance-survey.exception.test.ts | 28 ++++ .../exception/ordnance-survey.exception.ts | 9 ++ .../ordnance-survey/known-errors.ts | 13 ++ .../ordnance-survey/ordnance-survey.module.ts | 26 ++++ .../ordnance-survey.service.test.ts | 128 ++++++++++++++++++ .../ordnance-survey.service.ts | 35 +++++ ...rap-ordnance-survey-http-error-callback.ts | 32 +++++ 16 files changed, 513 insertions(+), 2 deletions(-) create mode 100644 src/config/ordnance-survey.config.test.ts create mode 100644 src/config/ordnance-survey.config.ts create mode 100644 src/helper_modules/ordnance-survey/dto/get-addresses-response.dto.ts create mode 100644 src/helper_modules/ordnance-survey/dto/get-search-postcode-query.dto.ts create mode 100644 src/helper_modules/ordnance-survey/examples/example-response-for-search-places-v1-postcode-no-results.json create mode 100644 src/helper_modules/ordnance-survey/examples/example-response-for-search-places-v1-postcode.json create mode 100644 src/helper_modules/ordnance-survey/exception/ordnance-survey.exception.test.ts create mode 100644 src/helper_modules/ordnance-survey/exception/ordnance-survey.exception.ts create mode 100644 src/helper_modules/ordnance-survey/known-errors.ts create mode 100644 src/helper_modules/ordnance-survey/ordnance-survey.module.ts create mode 100644 src/helper_modules/ordnance-survey/ordnance-survey.service.test.ts create mode 100644 src/helper_modules/ordnance-survey/ordnance-survey.service.ts create mode 100644 src/helper_modules/ordnance-survey/wrap-ordnance-survey-http-error-callback.ts diff --git a/.cspell.json b/.cspell.json index c2e875a8..0a7de18b 100644 --- a/.cspell.json +++ b/.cspell.json @@ -51,7 +51,19 @@ "ukef", "venv", "VNET", - "CICD" + "CICD", + "DPA", + "UPRN", + "UDPRN", + "BLPU", + "TOID", + "EPSG", + "WOGAN", + "osgb", + "HJLNP", + "Zabd", + "hjlnp", + "BLPUs" ], "dictionaries": [ "en-gb", diff --git a/.env.sample b/.env.sample index 673bc1e0..68141bee 100644 --- a/.env.sample +++ b/.env.sample @@ -39,3 +39,9 @@ APIM_INFORMATICA_USERNAME= APIM_INFORMATICA_PASSWORD= APIM_INFORMATICA_MAX_REDIRECTS= APIM_INFORMATICA_TIMEOUT= + +# ORDANANCE SURVEY +ORDNANCE_SURVEY_URL= +ORDNANCE_SURVEY_KEY= +ORDNANCE_SURVEY_MAX_REDIRECTS= +ORDNANCE_SURVEY_TIMEOUT= diff --git a/src/config/index.ts b/src/config/index.ts index 4e89f16d..29fe1c91 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -2,5 +2,6 @@ import AppConfig from './app.config'; import DatabaseConfig from './database.config'; import DocConfig from './doc.config'; import InformaticaConfig from './informatica.config'; +import OrdnanceSurveyConfig from './ordnance-survey.config'; -export default [AppConfig, DocConfig, DatabaseConfig, InformaticaConfig]; +export default [AppConfig, DocConfig, DatabaseConfig, InformaticaConfig, OrdnanceSurveyConfig]; diff --git a/src/config/ordnance-survey.config.test.ts b/src/config/ordnance-survey.config.test.ts new file mode 100644 index 00000000..5b22020e --- /dev/null +++ b/src/config/ordnance-survey.config.test.ts @@ -0,0 +1,39 @@ +import { withEnvironmentVariableParsingUnitTests } from '@ukef-test/common-tests/environment-variable-parsing-unit-tests'; + +import ordnanceSurveyConfig, { OrdnanceSurveyConfig } from './ordnance-survey.config'; + +describe('ordnanceSurveyConfig', () => { + const configDirectlyFromEnvironmentVariables: { configPropertyName: keyof OrdnanceSurveyConfig; environmentVariableName: string }[] = [ + { + configPropertyName: 'baseUrl', + environmentVariableName: 'ORDNANCE_SURVEY_URL', + }, + { + configPropertyName: 'key', + environmentVariableName: 'ORDNANCE_SURVEY_KEY', + }, + ]; + + const configParsedAsIntFromEnvironmentVariablesWithDefault: { + configPropertyName: keyof OrdnanceSurveyConfig; + environmentVariableName: string; + defaultConfigValue: number; + }[] = [ + { + configPropertyName: 'maxRedirects', + environmentVariableName: 'ORDNANCE_SURVEY_MAX_REDIRECTS', + defaultConfigValue: 5, + }, + { + configPropertyName: 'timeout', + environmentVariableName: 'ORDNANCE_SURVEY_TIMEOUT', + defaultConfigValue: 30000, + }, + ]; + + withEnvironmentVariableParsingUnitTests({ + configDirectlyFromEnvironmentVariables, + configParsedAsIntFromEnvironmentVariablesWithDefault, + getConfig: () => ordnanceSurveyConfig(), + }); +}); diff --git a/src/config/ordnance-survey.config.ts b/src/config/ordnance-survey.config.ts new file mode 100644 index 00000000..e30acf8b --- /dev/null +++ b/src/config/ordnance-survey.config.ts @@ -0,0 +1,21 @@ +import { registerAs } from '@nestjs/config'; +import { getIntConfig } from '@ukef/helpers/get-int-config'; + +export const KEY = 'ordnanceSurvey'; + +export interface OrdnanceSurveyConfig { + baseUrl: string; + key: string; + maxRedirects: number; + timeout: number; +} + +export default registerAs( + KEY, + (): OrdnanceSurveyConfig => ({ + baseUrl: process.env.ORDNANCE_SURVEY_URL, + key: process.env.ORDNANCE_SURVEY_KEY, + maxRedirects: getIntConfig(process.env.ORDNANCE_SURVEY_MAX_REDIRECTS, 5), + timeout: getIntConfig(process.env.ORDNANCE_SURVEY_TIMEOUT, 30000), + }), +); diff --git a/src/helper_modules/ordnance-survey/dto/get-addresses-response.dto.ts b/src/helper_modules/ordnance-survey/dto/get-addresses-response.dto.ts new file mode 100644 index 00000000..5a9f6201 --- /dev/null +++ b/src/helper_modules/ordnance-survey/dto/get-addresses-response.dto.ts @@ -0,0 +1,90 @@ +export type GetAddressResponse = { + header: { + uri: string, + query: string, + offset: number, + totalresults: number, + format: string, + dataset: string, + lr: string, + maxresults: number, + epoch: string, + lastupdate: string, + output_srs: string + }, + results?: GetAddressResponseItem[], +}; + +interface GetAddressResponseItem { + DPA: { + UPRN: string, + UDPRN: string, + ADDRESS: string, + BUILDING_NAME?: string, + BUILDING_NUMBER?: string, + ORGANISATION_NAME?: string; + DEPENDENT_LOCALITY?: string; + THOROUGHFARE_NAME: string, + POST_TOWN: string, + POSTCODE: string, + RPC: string, + X_COORDINATE: number, + Y_COORDINATE: number, + STATUS: string, + LOGICAL_STATUS_CODE: string, + CLASSIFICATION_CODE: string, + CLASSIFICATION_CODE_DESCRIPTION: string, + LOCAL_CUSTODIAN_CODE: number, + LOCAL_CUSTODIAN_CODE_DESCRIPTION: string, + COUNTRY_CODE: string, + COUNTRY_CODE_DESCRIPTION: string, + POSTAL_ADDRESS_CODE: string, + POSTAL_ADDRESS_CODE_DESCRIPTION: string, + BLPU_STATE_CODE: string, + BLPU_STATE_CODE_DESCRIPTION: string, + TOPOGRAPHY_LAYER_TOID: string, + LAST_UPDATE_DATE: string, + ENTRY_DATE: string, + BLPU_STATE_DATE: string, + LANGUAGE: string, + MATCH: number, + MATCH_DESCRIPTION: string, + DELIVERY_POINT_SUFFIX: string + } +} + +// interface GetAddressResponseAddress { +// UPRN: string, +// UDPRN: string, +// ADDRESS: string, +// BUILDING_NAME?: string, +// BUILDING_NUMBER?: string, +// ORGANISATION_NAME?: string; +// DEPENDENT_LOCALITY?: string; +// THOROUGHFARE_NAME: string, +// POST_TOWN: string, +// POSTCODE: string, +// RPC: string, +// X_COORDINATE: number, +// Y_COORDINATE: number, +// STATUS: string, +// LOGICAL_STATUS_CODE: string, +// CLASSIFICATION_CODE: string, +// CLASSIFICATION_CODE_DESCRIPTION: string, +// LOCAL_CUSTODIAN_CODE: number, +// LOCAL_CUSTODIAN_CODE_DESCRIPTION: string, +// COUNTRY_CODE: string, +// COUNTRY_CODE_DESCRIPTION: string, +// POSTAL_ADDRESS_CODE: string, +// POSTAL_ADDRESS_CODE_DESCRIPTION: string, +// BLPU_STATE_CODE: string, +// BLPU_STATE_CODE_DESCRIPTION: string, +// TOPOGRAPHY_LAYER_TOID: string, +// LAST_UPDATE_DATE: string, +// ENTRY_DATE: string, +// BLPU_STATE_DATE: string, +// LANGUAGE: string, +// MATCH: number, +// MATCH_DESCRIPTION: string, +// DELIVERY_POINT_SUFFIX: string +// } diff --git a/src/helper_modules/ordnance-survey/dto/get-search-postcode-query.dto.ts b/src/helper_modules/ordnance-survey/dto/get-search-postcode-query.dto.ts new file mode 100644 index 00000000..8f8ea0f2 --- /dev/null +++ b/src/helper_modules/ordnance-survey/dto/get-search-postcode-query.dto.ts @@ -0,0 +1,3 @@ +export class GetSearchPostcodeOrdnanceSurveyQueryDto { + public postcode: string; +} diff --git a/src/helper_modules/ordnance-survey/examples/example-response-for-search-places-v1-postcode-no-results.json b/src/helper_modules/ordnance-survey/examples/example-response-for-search-places-v1-postcode-no-results.json new file mode 100644 index 00000000..2223c9e4 --- /dev/null +++ b/src/helper_modules/ordnance-survey/examples/example-response-for-search-places-v1-postcode-no-results.json @@ -0,0 +1,15 @@ +{ + "header": { + "uri": "https://api.os.uk/search/places/v1/postcode?postcode=CV1%2011M", + "query": "postcode=CV1 11M", + "offset": 0, + "totalresults": 0, + "format": "JSON", + "dataset": "DPA", + "lr": "EN,CY", + "maxresults": 100, + "epoch": "109", + "lastupdate": "2024-04-05", + "output_srs": "EPSG:27700" + } +} \ No newline at end of file diff --git a/src/helper_modules/ordnance-survey/examples/example-response-for-search-places-v1-postcode.json b/src/helper_modules/ordnance-survey/examples/example-response-for-search-places-v1-postcode.json new file mode 100644 index 00000000..335f7c1e --- /dev/null +++ b/src/helper_modules/ordnance-survey/examples/example-response-for-search-places-v1-postcode.json @@ -0,0 +1,53 @@ +{ + "header": { + "uri": "https://api.os.uk/search/places/v1/postcode?postcode=W1A%201AA", + "query": "postcode=W1A 1AA", + "offset": 0, + "totalresults": 1, + "format": "JSON", + "dataset": "DPA", + "lr": "EN,CY", + "maxresults": 100, + "epoch": "109", + "lastupdate": "2024-04-09", + "output_srs": "EPSG:27700" + }, + "results": [ + { + "DPA": { + "UPRN": "10092008000", + "UDPRN": "25733000", + "ADDRESS": "BRITISH BROADCASTING CORPORATION, WOGAN HOUSE, PORTLAND PLACE, LONDON, W1A 1AA", + "ORGANISATION_NAME": "BRITISH BROADCASTING CORPORATION", + "BUILDING_NAME": "WOGAN HOUSE", + "THOROUGHFARE_NAME": "PORTLAND PLACE", + "POST_TOWN": "LONDON", + "POSTCODE": "W1A 1AA", + "RPC": "2", + "X_COORDINATE": 528960.0, + "Y_COORDINATE": 181680.0, + "STATUS": "APPROVED", + "LOGICAL_STATUS_CODE": "1", + "CLASSIFICATION_CODE": "OR00", + "CLASSIFICATION_CODE_DESCRIPTION": "Additional Mail / Packet Addressee", + "LOCAL_CUSTODIAN_CODE": 7600, + "LOCAL_CUSTODIAN_CODE_DESCRIPTION": "ORDNANCE SURVEY", + "COUNTRY_CODE": "E", + "COUNTRY_CODE_DESCRIPTION": "This record is within England", + "POSTAL_ADDRESS_CODE": "D", + "POSTAL_ADDRESS_CODE_DESCRIPTION": "A record which is linked to PAF", + "BLPU_STATE_CODE": "2", + "BLPU_STATE_CODE_DESCRIPTION": "In use", + "TOPOGRAPHY_LAYER_TOID": "osgb1000005200000", + "PARENT_UPRN": "100023600000", + "LAST_UPDATE_DATE": "29/01/2023", + "ENTRY_DATE": "19/01/2012", + "BLPU_STATE_DATE": "01/01/2020", + "LANGUAGE": "EN", + "MATCH": 1.0, + "MATCH_DESCRIPTION": "EXACT", + "DELIVERY_POINT_SUFFIX": "1A" + } + } + ] +} diff --git a/src/helper_modules/ordnance-survey/exception/ordnance-survey.exception.test.ts b/src/helper_modules/ordnance-survey/exception/ordnance-survey.exception.test.ts new file mode 100644 index 00000000..aa7c8d96 --- /dev/null +++ b/src/helper_modules/ordnance-survey/exception/ordnance-survey.exception.test.ts @@ -0,0 +1,28 @@ +import { RandomValueGenerator } from '@ukef-test/support/generator/random-value-generator'; + +import { OrdnanceSurveyException } from './ordnance-survey.exception'; + +describe('OrdnanceSurveyException', () => { + const valueGenerator = new RandomValueGenerator(); + const message = valueGenerator.string(); + + it('exposes the message it was created with', () => { + const exception = new OrdnanceSurveyException(message); + + expect(exception.message).toBe(message); + }); + + it('exposes the name of the exception', () => { + const exception = new OrdnanceSurveyException(message); + + expect(exception.name).toBe('OrdnanceSurveyException'); + }); + + it('exposes the inner error it was created with', () => { + const innerError = new Error(); + + const exception = new OrdnanceSurveyException(message, innerError); + + expect(exception.innerError).toBe(innerError); + }); +}); diff --git a/src/helper_modules/ordnance-survey/exception/ordnance-survey.exception.ts b/src/helper_modules/ordnance-survey/exception/ordnance-survey.exception.ts new file mode 100644 index 00000000..64ff9ebe --- /dev/null +++ b/src/helper_modules/ordnance-survey/exception/ordnance-survey.exception.ts @@ -0,0 +1,9 @@ +export class OrdnanceSurveyException extends Error { + constructor( + message: string, + public readonly innerError?: Error, + ) { + super(message); + this.name = this.constructor.name; + } +} diff --git a/src/helper_modules/ordnance-survey/known-errors.ts b/src/helper_modules/ordnance-survey/known-errors.ts new file mode 100644 index 00000000..5684be7d --- /dev/null +++ b/src/helper_modules/ordnance-survey/known-errors.ts @@ -0,0 +1,13 @@ +import { NotFoundException } from '@nestjs/common'; +import { AxiosError } from 'axios'; + +export type KnownErrors = KnownError[]; + +type KnownError = { caseInsensitiveSubstringToFind: string; throwError: (error: AxiosError) => never }; + +export const getCustomersNotFoundKnownOrdnanceSurveyError = (): KnownError => ({ + caseInsensitiveSubstringToFind: 'Company registration not found', + throwError: (error) => { + throw new NotFoundException('Customer not found.', error); + }, +}); diff --git a/src/helper_modules/ordnance-survey/ordnance-survey.module.ts b/src/helper_modules/ordnance-survey/ordnance-survey.module.ts new file mode 100644 index 00000000..553f756b --- /dev/null +++ b/src/helper_modules/ordnance-survey/ordnance-survey.module.ts @@ -0,0 +1,26 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { OrdnanceSurveyConfig, KEY as ORDNANCE_SURVEY_CONFIG_KEY } from '@ukef/config/ordnance-survey.config'; +import { HttpModule } from '@ukef/modules/http/http.module'; + +import { OrdnanceSurveyService } from './ordnance-survey.service'; + +@Module({ + imports: [ + HttpModule.registerAsync({ + imports: [ConfigModule], + inject: [ConfigService], + useFactory: (configService: ConfigService) => { + const { baseUrl, maxRedirects, timeout } = configService.get(ORDNANCE_SURVEY_CONFIG_KEY); + return { + baseURL: baseUrl, + maxRedirects, + timeout, + }; + }, + }), + ], + providers: [OrdnanceSurveyService], + exports: [OrdnanceSurveyService], +}) +export class OrdnanceSurveyModule {} diff --git a/src/helper_modules/ordnance-survey/ordnance-survey.service.test.ts b/src/helper_modules/ordnance-survey/ordnance-survey.service.test.ts new file mode 100644 index 00000000..e895730a --- /dev/null +++ b/src/helper_modules/ordnance-survey/ordnance-survey.service.test.ts @@ -0,0 +1,128 @@ +import { HttpService } from '@nestjs/axios'; +import { RandomValueGenerator } from '@ukef-test/support/generator/random-value-generator'; +import { AxiosError } from 'axios'; +import { when } from 'jest-when'; +import { of, throwError } from 'rxjs'; + +import { OrdnanceSurveyException } from './exception/ordnance-survey.exception'; +import { OrdnanceSurveyService } from './ordnance-survey.service'; +import { ConfigService } from '@nestjs/config'; + +const expectedResponse = require('./examples/example-response-for-search-places-v1-postcode.json'); +const noResultsResponse = require('./examples/example-response-for-search-places-v1-postcode.json'); + +describe('OrdnanceSurveyService', () => { + const valueGenerator = new RandomValueGenerator(); + + let httpServiceGet: jest.Mock; + let configServiceGet: jest.Mock; + let service: OrdnanceSurveyService; + + const testPostcode = 'W1A 1AA'; + const testKey = valueGenerator.string({length: 10}); + const basePath = '/search/places/v1/postcode'; + + beforeEach(() => { + const httpService = new HttpService(); + const configService = new ConfigService(); + httpServiceGet = jest.fn(); + httpService.get = httpServiceGet; + + configServiceGet = jest.fn().mockReturnValue({key: testKey}); + configService.get = configServiceGet; + + service = new OrdnanceSurveyService(httpService, configService); + }); + + + + describe('getAddressesByPostcode', () => { + const expectedPath = `${basePath}?postcode=${testPostcode}&key=${testKey}`; + + const expectedHttpServiceGetArgs: [string, object] = [expectedPath, { headers: { 'Content-Type': 'application/json' } }]; + + it('sends a GET to the Ordnance Survey API /search endpoint with the specified request', async () => { + when(httpServiceGet) + .calledWith(...expectedHttpServiceGetArgs) + .mockReturnValueOnce( + of({ + data: expectedResponse, + status: 200, + statusText: 'OK', + config: undefined, + headers: undefined, + }), + ); + + await service.getAddressesByPostcode(testPostcode); + + expect(httpServiceGet).toHaveBeenCalledTimes(1); + expect(httpServiceGet).toHaveBeenCalledWith(...expectedHttpServiceGetArgs); + }); + + it.each([ + { + postcode: 'W1A 1AA', + expectedUrlQueryPart: `?postcode=W1A%201AA`, + }, + { + postcode: 'W1A1AA', + expectedUrlQueryPart: '?postcode=W1A1AA', + }, + ])('call Ordnance Survey API with correct and safe query parameters "$expectedUrlQueryPart"', async ({ postcode, expectedUrlQueryPart }) => { + // const expectedPath = `${basePath}${expectedUrlQueryPart}&key=${testKey}`; + + // const expectedHttpServiceGetArgs: [string, object] = [expectedPath, { headers: { 'Content-Type': 'application/json' } }]; + + when(httpServiceGet) + .calledWith(...expectedHttpServiceGetArgs) + .mockReturnValueOnce( + of({ + data: expectedResponse, + status: 200, + statusText: 'OK', + config: undefined, + headers: undefined, + }), + ); + + await service.getAddressesByPostcode(testPostcode); + + expect(httpServiceGet).toHaveBeenCalledTimes(1); + expect(httpServiceGet).toHaveBeenCalledWith(...expectedHttpServiceGetArgs); + }); + + it("no results - returns 200 without results", async () => { + when(httpServiceGet) + .calledWith(...expectedHttpServiceGetArgs) + .mockReturnValueOnce( + of({ + data: noResultsResponse, + status: 200, + statusText: 'OK', + config: undefined, + headers: undefined, + }), + ); + + const results = await service.getAddressesByPostcode(testPostcode); + + expect(httpServiceGet).toHaveBeenCalledTimes(1); + expect(httpServiceGet).toHaveBeenCalledWith(...expectedHttpServiceGetArgs); + expect(results).toBe(noResultsResponse); + }); + + it('throws an OrdnanceSurveyException if the request to Ordnance Survey fails', async () => { + const axiosRequestError = new AxiosError(); + when(httpServiceGet) + .calledWith(...expectedHttpServiceGetArgs) + .mockReturnValueOnce(throwError(() => axiosRequestError)); + + const getCustomersPromise = service.getAddressesByPostcode(testPostcode); + + await expect(getCustomersPromise).rejects.toBeInstanceOf(OrdnanceSurveyException); + await expect(getCustomersPromise).rejects.toThrow('Failed to get response from Ordnance Survey API.'); + await expect(getCustomersPromise).rejects.toHaveProperty('innerError', axiosRequestError); + }); + }); +}); diff --git a/src/helper_modules/ordnance-survey/ordnance-survey.service.ts b/src/helper_modules/ordnance-survey/ordnance-survey.service.ts new file mode 100644 index 00000000..7443a9a5 --- /dev/null +++ b/src/helper_modules/ordnance-survey/ordnance-survey.service.ts @@ -0,0 +1,35 @@ +import { HttpService } from '@nestjs/axios'; +import { Injectable } from '@nestjs/common'; +import { HttpClient } from '@ukef/modules/http/http.client'; + +import { GetAddressResponse } from './dto/get-addresses-response.dto'; +// import { getCustomersNotFoundKnownOrdnanceSurveyError } from './known-errors'; +import { createWrapOrdnanceSurveyHttpGetErrorCallback } from './wrap-ordnance-survey-http-error-callback'; +import { ConfigService } from '@nestjs/config'; +import { OrdnanceSurveyConfig, KEY as ORDNANCE_SURVEY_CONFIG_KEY } from '@ukef/config/ordnance-survey.config'; + +@Injectable() +export class OrdnanceSurveyService { + private readonly httpClient: HttpClient; + private readonly key: string; + + constructor(httpService: HttpService, configService: ConfigService) { + this.httpClient = new HttpClient(httpService); + const { key } = configService.get(ORDNANCE_SURVEY_CONFIG_KEY); + this.key = key; + } + + async getAddressesByPostcode(postcode): Promise { + const path = '/search/places/v1/postcode?postcode=' + postcode + '&key=' + this.key; + const { data } = await this.httpClient.get({ + path, + headers: { 'Content-Type': 'application/json' }, + onError: createWrapOrdnanceSurveyHttpGetErrorCallback({ + messageForUnknownError: `Failed to get response from Ordnance Survey API.`, + knownErrors: [], + // knownErrors: [getCustomersNotFoundKnownOrdnanceSurveyError()], // TODO: should we change 200 no results to 404? + }), + }); + return data; + } +} diff --git a/src/helper_modules/ordnance-survey/wrap-ordnance-survey-http-error-callback.ts b/src/helper_modules/ordnance-survey/wrap-ordnance-survey-http-error-callback.ts new file mode 100644 index 00000000..cf1e4c1a --- /dev/null +++ b/src/helper_modules/ordnance-survey/wrap-ordnance-survey-http-error-callback.ts @@ -0,0 +1,32 @@ +import { AxiosError } from 'axios'; +import { ObservableInput, throwError } from 'rxjs'; + +import { OrdnanceSurveyException } from './exception/ordnance-survey.exception'; +import { KnownErrors } from './known-errors'; + +type AcbsHttpErrorCallback = (error: Error) => ObservableInput; + +export const createWrapOrdnanceSurveyHttpGetErrorCallback = + ({ messageForUnknownError, knownErrors }: { messageForUnknownError: string; knownErrors: KnownErrors }): AcbsHttpErrorCallback => + (error: Error) => { + let errorString; + if (error instanceof AxiosError && error.response) { + if (typeof error.response.data === 'object') { + errorString = JSON.stringify(error.response.data); + } + if (typeof error.response.data === 'string') { + errorString = error.response.data; + } + if (errorString) { + const errorStringInLowerCase = errorString.toLowerCase(); + + knownErrors.forEach(({ caseInsensitiveSubstringToFind, throwError }) => { + if (errorStringInLowerCase.includes(caseInsensitiveSubstringToFind.toLowerCase())) { + return throwError(error); + } + }); + } + } + + return throwError(() => new OrdnanceSurveyException(messageForUnknownError, error)); + };