diff --git a/src/shared/errors.spec.ts b/src/shared/errors.spec.ts index 0f813c0..8efbedc 100644 --- a/src/shared/errors.spec.ts +++ b/src/shared/errors.spec.ts @@ -12,167 +12,273 @@ import wmsException110 from '../../fixtures/wms/service-exception-report-1-1-0.x import wmsException111 from '../../fixtures/wms/service-exception-report-1-1-1.xml'; // @ts-expect-error ts-migrate(7016) import wmsException130 from '../../fixtures/wms/service-exception-report-1-3-0.xml'; -import { check, parse, ServiceExceptionError } from './errors.js'; +import { + check, + decodeError, + encodeError, + EndpointError, + parse, + ServiceExceptionError, +} from './errors.js'; import { findChildElement, getRootElement, parseXmlString, } from './xml-utils.js'; -describe('ServiceExceptionError', () => { - describe('it can parse a ServiceException element', () => { - it('can parse a WFS 1.0.0 ServiceException element', () => { - const url = - 'http://my.test.service/ogc/wfs?service=WFS&version=1.0.0&request=FooBar'; - const doc = parseXmlString(wfsException100); - const exception = findChildElement( - getRootElement(doc), - 'ServiceException' - ); - expect(exception).not.toBeNull(); - const error = parse(exception, url); - expect(error).toBeInstanceOf(ServiceExceptionError); - expect(error.code).toBe('InvalidParameterValue'); - expect(error.locator).toBe('request'); - expect(error.message).toBe( - 'msWFSDispatch(): WFS server error. Invalid WFS request: FooBar' - ); - expect(error.requestUrl).toBe(url); - expect(error.response).toBe(doc); - }); - it('can parse a WFS 1.1.0 ServiceException element', () => { - const url = - 'http://my.test.service/ogc/wfs?service=WFS&version=1.1.0&request=FooBar'; - const doc = parseXmlString(wfsException110); - const exception = findChildElement(getRootElement(doc), 'Exception'); - expect(exception).not.toBeNull(); - const error = parse(exception, url); - expect(error).toBeInstanceOf(ServiceExceptionError); - expect(error.code).toBe('InvalidParameterValue'); - expect(error.locator).toBe('request'); - expect(error.message).toBe( - 'msWFSDispatch(): WFS server error. Invalid WFS request: FooBar' - ); - expect(error.requestUrl).toBe(url); - expect(error.response).toBe(doc); +describe('errors utils', () => { + describe('ServiceExceptionError', () => { + describe('it can parse a ServiceException element', () => { + it('can parse a WFS 1.0.0 ServiceException element', () => { + const url = + 'http://my.test.service/ogc/wfs?service=WFS&version=1.0.0&request=FooBar'; + const doc = parseXmlString(wfsException100); + const exception = findChildElement( + getRootElement(doc), + 'ServiceException' + ); + expect(exception).not.toBeNull(); + const error = parse(exception, url); + expect(error).toBeInstanceOf(ServiceExceptionError); + expect(error.code).toBe('InvalidParameterValue'); + expect(error.locator).toBe('request'); + expect(error.message).toBe( + 'msWFSDispatch(): WFS server error. Invalid WFS request: FooBar' + ); + expect(error.requestUrl).toBe(url); + expect(error.response).toBe(doc); + }); + it('can parse a WFS 1.1.0 ServiceException element', () => { + const url = + 'http://my.test.service/ogc/wfs?service=WFS&version=1.1.0&request=FooBar'; + const doc = parseXmlString(wfsException110); + const exception = findChildElement(getRootElement(doc), 'Exception'); + expect(exception).not.toBeNull(); + const error = parse(exception, url); + expect(error).toBeInstanceOf(ServiceExceptionError); + expect(error.code).toBe('InvalidParameterValue'); + expect(error.locator).toBe('request'); + expect(error.message).toBe( + 'msWFSDispatch(): WFS server error. Invalid WFS request: FooBar' + ); + expect(error.requestUrl).toBe(url); + expect(error.response).toBe(doc); + }); + it('can parse a WFS 2.0.0 Exception element', () => { + const url = + 'http://my.test.service/ogc/wfs?service=WFS&version=2.0.0&request=FooBar'; + const doc = parseXmlString(wfsException200); + const exception = findChildElement(getRootElement(doc), 'Exception'); + expect(exception).not.toBeNull(); + const error = parse(exception, url); + expect(error).toBeInstanceOf(ServiceExceptionError); + expect(error.code).toBe('InvalidParameterValue'); + expect(error.locator).toBe('request'); + expect(error.message).toBe( + 'msWFSDispatch(): WFS server error. Invalid WFS request: FooBar' + ); + expect(error.requestUrl).toBe(url); + expect(error.response).toBe(doc); + }); + it('can parse a WMS 1.1.0 Exception element', () => { + const url = + 'http://my.test.service/ogc/wms?service=WMS&version=1.1.0&request=FooBar'; + const doc = parseXmlString(wmsException110); + const exception = findChildElement( + getRootElement(doc), + 'ServiceException' + ); + expect(exception).not.toBeNull(); + const error = parse(exception, url); + expect(error).toBeInstanceOf(ServiceExceptionError); + expect(error.code).toBe(''); + expect(error.locator).toBe(''); + expect(error.message).toBe( + 'msWMSDispatch(): WMS server error. Incomplete or unsupported WMS request' + ); + expect(error.requestUrl).toBe(url); + expect(error.response).toBe(doc); + }); + it('can parse a WMS 1.1.1 Exception element', () => { + const url = + 'http://my.test.service/ogc/wms?service=WMS&version=1.1.1&request=FooBar'; + const doc = parseXmlString(wmsException111); + const exception = findChildElement( + getRootElement(doc), + 'ServiceException' + ); + expect(exception).not.toBeNull(); + const error = parse(exception, url); + expect(error).toBeInstanceOf(ServiceExceptionError); + expect(error.code).toBe(''); + expect(error.locator).toBe(''); + expect(error.message).toBe( + 'msWMSDispatch(): WMS server error. Incomplete or unsupported WMS request' + ); + expect(error.requestUrl).toBe(url); + expect(error.response).toBe(doc); + }); + it('can parse a WMS 1.3.0 Exception element', () => { + const url = + 'http://my.test.service/ogc/wms?service=WMS&version=1.3.0&request=FooBar'; + const doc = parseXmlString(wmsException130); + const exception = findChildElement( + getRootElement(doc), + 'ServiceException' + ); + expect(exception).not.toBeNull(); + const error = parse(exception, url); + expect(error).toBeInstanceOf(ServiceExceptionError); + expect(error.code).toBe(''); + expect(error.locator).toBe(''); + expect(error.message).toBe( + 'msWMSDispatch(): WMS server error. Incomplete or unsupported WMS request' + ); + expect(error.requestUrl).toBe(url); + expect(error.response).toBe(doc); + }); }); - it('can parse a WFS 2.0.0 Exception element', () => { - const url = - 'http://my.test.service/ogc/wfs?service=WFS&version=2.0.0&request=FooBar'; - const doc = parseXmlString(wfsException200); - const exception = findChildElement(getRootElement(doc), 'Exception'); - expect(exception).not.toBeNull(); - const error = parse(exception, url); - expect(error).toBeInstanceOf(ServiceExceptionError); - expect(error.code).toBe('InvalidParameterValue'); - expect(error.locator).toBe('request'); - expect(error.message).toBe( - 'msWFSDispatch(): WFS server error. Invalid WFS request: FooBar' - ); - expect(error.requestUrl).toBe(url); - expect(error.response).toBe(doc); + + describe('it can check a response document and throw a ServiceExceptionError if necessary', () => { + it('can recognise a WFS 1.0.0 ServiceExceptionReport document', () => { + const url = + 'http://my.test.service/ogc/wfs?service=WFS&version=1.0.0&request=FooBar'; + const doc = parseXmlString(wfsException100); + expect(() => check(doc, url)).toThrow(ServiceExceptionError); + }); + it('can recognise a WFS 1.1.0 ExceptionReport document', () => { + const url = + 'http://my.test.service/ogc/wfs?service=WFS&version=1.1.0&request=FooBar'; + const doc = parseXmlString(wfsException110); + expect(() => check(doc, url)).toThrow(ServiceExceptionError); + }); + it('can recognise a WFS 2.0.0 ExceptionReport document', () => { + const url = + 'http://my.test.service/ogc/wfs?service=WFS&version=2.0.0&request=FooBar'; + const doc = parseXmlString(wfsException200); + expect(() => check(doc, url)).toThrow(ServiceExceptionError); + }); + it('can recognise a WMS 1.1.0 ServiceExceptionReport document', () => { + const url = + 'http://my.test.service/ogc/wms?service=WMS&version=1.1.0&request=FooBar'; + const doc = parseXmlString(wmsException110); + expect(() => check(doc, url)).toThrow(ServiceExceptionError); + }); + it('can recognise a WMS 1.1.1 ServiceExceptionReport document', () => { + const url = + 'http://my.test.service/ogc/wms?service=WMS&version=1.1.1&request=FooBar'; + const doc = parseXmlString(wmsException111); + expect(() => check(doc, url)).toThrow(ServiceExceptionError); + }); + it('can recognise a WMS 1.3.0 ServiceExceptionReport document', () => { + const url = + 'http://my.test.service/ogc/wms?service=WMS&version=1.3.0&request=FooBar'; + const doc = parseXmlString(wmsException130); + expect(() => check(doc, url)).toThrow(ServiceExceptionError); + }); + it('passes the document on if there is no exception reported', () => { + const url = + 'http://my.test.service/ogc/wfs?service=WFS&request=GetCapabilities'; + const doc = parseXmlString(wfsCapabilities200); + expect(() => check(doc, url)).not.toThrow(ServiceExceptionError); + }); }); - it('can parse a WMS 1.1.0 Exception element', () => { - const url = - 'http://my.test.service/ogc/wms?service=WMS&version=1.1.0&request=FooBar'; - const doc = parseXmlString(wmsException110); - const exception = findChildElement( - getRootElement(doc), - 'ServiceException' - ); - expect(exception).not.toBeNull(); - const error = parse(exception, url); - expect(error).toBeInstanceOf(ServiceExceptionError); - expect(error.code).toBe(''); - expect(error.locator).toBe(''); - expect(error.message).toBe( - 'msWMSDispatch(): WMS server error. Incomplete or unsupported WMS request' + }); + + describe('encodeError', () => { + it('can encode a ServiceExceptionError', () => { + const e = new ServiceExceptionError( + 'msWFSDispatch(): WFS server error. Invalid WFS request: FooBar', + 'http://my.test.service/ogc/wfs?service=WFS&version=1.0.0&request=FooBar', + 'InvalidParameterValue', + 'request', + parseXmlString(wfsException100) ); - expect(error.requestUrl).toBe(url); - expect(error.response).toBe(doc); + expect(encodeError(e)).toEqual({ + message: + 'msWFSDispatch(): WFS server error. Invalid WFS request: FooBar', + stack: expect.any(String), + name: 'ServiceExceptionError', + code: 'InvalidParameterValue', + locator: 'request', + requestUrl: + 'http://my.test.service/ogc/wfs?service=WFS&version=1.0.0&request=FooBar', + response: parseXmlString(wfsException100), + }); }); - it('can parse a WMS 1.1.1 Exception element', () => { - const url = - 'http://my.test.service/ogc/wms?service=WMS&version=1.1.1&request=FooBar'; - const doc = parseXmlString(wmsException111); - const exception = findChildElement( - getRootElement(doc), - 'ServiceException' - ); - expect(exception).not.toBeNull(); - const error = parse(exception, url); - expect(error).toBeInstanceOf(ServiceExceptionError); - expect(error.code).toBe(''); - expect(error.locator).toBe(''); - expect(error.message).toBe( - 'msWMSDispatch(): WMS server error. Incomplete or unsupported WMS request' - ); - expect(error.requestUrl).toBe(url); - expect(error.response).toBe(doc); + it('can encode an EndpointError', () => { + const e = new EndpointError('Network error', 0, false); + expect(encodeError(e)).toEqual({ + message: 'Network error', + stack: expect.any(String), + name: 'EndpointError', + httpStatus: 0, + isCrossOriginRelated: false, + }); }); - it('can parse a WMS 1.3.0 Exception element', () => { - const url = - 'http://my.test.service/ogc/wms?service=WMS&version=1.3.0&request=FooBar'; - const doc = parseXmlString(wmsException130); - const exception = findChildElement( - getRootElement(doc), - 'ServiceException' - ); - expect(exception).not.toBeNull(); - const error = parse(exception, url); - expect(error).toBeInstanceOf(ServiceExceptionError); - expect(error.code).toBe(''); - expect(error.locator).toBe(''); - expect(error.message).toBe( - 'msWMSDispatch(): WMS server error. Incomplete or unsupported WMS request' - ); - expect(error.requestUrl).toBe(url); - expect(error.response).toBe(doc); + it('can encode a generic Error', () => { + const e = new Error('Generic error'); + expect(encodeError(e)).toEqual({ + message: 'Generic error', + stack: expect.any(String), + name: 'Error', + }); }); }); - describe('it can check a response document and throw a ServiceExceptionError if necessary', () => { - it('can recognise a WFS 1.0.0 ServiceExceptionReport document', () => { - const url = - 'http://my.test.service/ogc/wfs?service=WFS&version=1.0.0&request=FooBar'; - const doc = parseXmlString(wfsException100); - expect(() => check(doc, url)).toThrow(ServiceExceptionError); - }); - it('can recognise a WFS 1.1.0 ExceptionReport document', () => { - const url = - 'http://my.test.service/ogc/wfs?service=WFS&version=1.1.0&request=FooBar'; - const doc = parseXmlString(wfsException110); - expect(() => check(doc, url)).toThrow(ServiceExceptionError); - }); - it('can recognise a WFS 2.0.0 ExceptionReport document', () => { - const url = - 'http://my.test.service/ogc/wfs?service=WFS&version=2.0.0&request=FooBar'; - const doc = parseXmlString(wfsException200); - expect(() => check(doc, url)).toThrow(ServiceExceptionError); - }); - it('can recognise a WMS 1.1.0 ServiceExceptionReport document', () => { - const url = - 'http://my.test.service/ogc/wms?service=WMS&version=1.1.0&request=FooBar'; - const doc = parseXmlString(wmsException110); - expect(() => check(doc, url)).toThrow(ServiceExceptionError); - }); - it('can recognise a WMS 1.1.1 ServiceExceptionReport document', () => { - const url = - 'http://my.test.service/ogc/wms?service=WMS&version=1.1.1&request=FooBar'; - const doc = parseXmlString(wmsException111); - expect(() => check(doc, url)).toThrow(ServiceExceptionError); + describe('decodeError', () => { + it('can decode a ServiceExceptionError', () => { + const e = { + message: + 'msWFSDispatch(): WFS server error. Invalid WFS request: FooBar', + stack: + 'ServiceExceptionError: msWFSDispatch(): WFS server error. Invalid WFS request: FooBar\n at Object. (http://localhost:8080/ogc-client.js:1:1)', + name: 'ServiceExceptionError', + code: 'InvalidParameterValue', + locator: 'request', + requestUrl: + 'http://my.test.service/ogc/wfs?service=WFS&version=1.0.0&request=FooBar', + response: parseXmlString(wfsException100), + }; + const error = decodeError(e) as ServiceExceptionError; + expect(error).toBeInstanceOf(ServiceExceptionError); + expect(error.message).toBe(e.message); + expect(error.stack).toBe(e.stack); + expect(error.name).toBe(e.name); + expect(error.code).toBe(e.code); + expect(error.locator).toBe(e.locator); + expect(error.requestUrl).toBe(e.requestUrl); + expect(error.response).toBe(e.response); }); - it('can recognise a WMS 1.3.0 ServiceExceptionReport document', () => { - const url = - 'http://my.test.service/ogc/wms?service=WMS&version=1.3.0&request=FooBar'; - const doc = parseXmlString(wmsException130); - expect(() => check(doc, url)).toThrow(ServiceExceptionError); + it('can decode an EndpointError', () => { + const e = { + message: 'Network error', + stack: + 'Error: Network error\n at Object. (http://localhost:8080/ogc-client.js:1:1)', + name: 'EndpointError', + httpStatus: 0, + isCrossOriginRelated: false, + }; + const error = decodeError(e) as EndpointError; + expect(error).toBeInstanceOf(EndpointError); + expect(error.message).toBe(e.message); + expect(error.stack).toBe(e.stack); + expect(error.name).toBe(e.name); + expect(error.httpStatus).toBe(e.httpStatus); + expect(error.isCrossOriginRelated).toBe(e.isCrossOriginRelated); }); - it('passes the document on if there is no exception reported', () => { - const url = - 'http://my.test.service/ogc/wfs?service=WFS&request=GetCapabilities'; - const doc = parseXmlString(wfsCapabilities200); - expect(() => check(doc, url)).not.toThrow(ServiceExceptionError); + it('can decode a generic Error', () => { + const e = { + message: 'Generic error', + stack: + 'Error: Generic error\n at Object. (http://localhost:8080/ogc-client.js:1:1)', + name: 'Error', + }; + const error = decodeError(e); + expect(error).toBeInstanceOf(Error); + expect(error.message).toBe(e.message); + expect(error.stack).toBe(e.stack); + expect(error.name).toBe(e.name); }); }); }); diff --git a/src/shared/errors.ts b/src/shared/errors.ts index 367359d..a14eded 100644 --- a/src/shared/errors.ts +++ b/src/shared/errors.ts @@ -15,6 +15,7 @@ export class EndpointError extends Error { public readonly isCrossOriginRelated?: boolean ) { super(message); + this.name = 'EndpointError'; } } @@ -41,6 +42,7 @@ export class ServiceExceptionError extends Error { public readonly response?: XmlDocument ) { super(message); + this.name = 'ServiceExceptionError'; } } @@ -94,3 +96,61 @@ export function check(response: XmlDocument, url?: string): XmlDocument { // there was nothing to convert to an Error so just pass the document on return response; } + +/** + * This transforms an error object into a JSON-serializable object to be + * transferred from a worker + */ +export function encodeError(error: Error): Record { + const base = { + message: error.message, + stack: error.stack, + name: error.name, + }; + if (error instanceof ServiceExceptionError) { + return { + ...base, + code: error.code, + locator: error.locator, + response: error.response, + requestUrl: error.requestUrl, + }; + } + if (error instanceof EndpointError) { + return { + ...base, + httpStatus: error.httpStatus, + isCrossOriginRelated: error.isCrossOriginRelated, + }; + } + return base; +} + +/** + * Recreates an error object + */ +export function decodeError(error: Record): Error { + if (error.name === 'ServiceExceptionError') { + const e = new ServiceExceptionError( + error.message as string, + error.requestUrl as string, + error.code as string, + error.locator as string, + error.response as XmlDocument + ); + e.stack = error.stack as string; + return e; + } + if (error.name === 'EndpointError') { + const e = new EndpointError( + error.message as string, + error.httpStatus as number, + error.isCrossOriginRelated as boolean + ); + e.stack = error.stack as string; + return e; + } + const e = new Error(error.message as string); + e.stack = error.stack as string; + return e; +} diff --git a/src/wfs/endpoint.spec.ts b/src/wfs/endpoint.spec.ts index 73e20d3..7eca572 100644 --- a/src/wfs/endpoint.spec.ts +++ b/src/wfs/endpoint.spec.ts @@ -88,7 +88,7 @@ describe('WfsEndpoint', () => { const error = (await endpoint .isReady() .catch((e) => e)) as EndpointError; - expect(error.constructor.name).toBe('EndpointError'); + expect(error).toBeInstanceOf(EndpointError); expect(error.message).toBe( 'The document could not be fetched due to CORS limitations' ); @@ -108,7 +108,7 @@ describe('WfsEndpoint', () => { const error = (await endpoint .isReady() .catch((e) => e)) as EndpointError; - expect(error.constructor.name).toBe('EndpointError'); + expect(error).toBeInstanceOf(EndpointError); expect(error.message).toBe( 'Fetching the document failed either due to network errors or unreachable host, error is: other kind of problem' ); @@ -131,7 +131,7 @@ describe('WfsEndpoint', () => { const error = (await endpoint .isReady() .catch((e) => e)) as EndpointError; - expect(error.constructor.name).toBe('EndpointError'); + expect(error).toBeInstanceOf(EndpointError); expect(error.message).toBe( 'Received an error with code 500: something broke in the server' ); @@ -149,7 +149,7 @@ describe('WfsEndpoint', () => { const error = (await endpoint .isReady() .catch((e) => e)) as ServiceExceptionError; - expect(error.constructor.name).toBe('ServiceExceptionError'); + expect(error).toBeInstanceOf(ServiceExceptionError); expect(error.message).toBe( 'msWFSDispatch(): WFS server error. WFS request not enabled. Check wfs/ows_enable_request settings.' ); diff --git a/src/wms/endpoint.spec.ts b/src/wms/endpoint.spec.ts index 2adcb93..b37851b 100644 --- a/src/wms/endpoint.spec.ts +++ b/src/wms/endpoint.spec.ts @@ -69,7 +69,7 @@ describe('WmsEndpoint', () => { const error = (await endpoint .isReady() .catch((e) => e)) as EndpointError; - expect(error.constructor.name).toBe('EndpointError'); + expect(error).toBeInstanceOf(EndpointError); expect(error.message).toBe( 'The document could not be fetched due to CORS limitations' ); @@ -89,7 +89,7 @@ describe('WmsEndpoint', () => { const error = (await endpoint .isReady() .catch((e) => e)) as EndpointError; - expect(error.constructor.name).toBe('EndpointError'); + expect(error).toBeInstanceOf(EndpointError); expect(error.message).toBe( 'Fetching the document failed either due to network errors or unreachable host, error is: other kind of problem' ); @@ -112,7 +112,7 @@ describe('WmsEndpoint', () => { const error = (await endpoint .isReady() .catch((e) => e)) as EndpointError; - expect(error.constructor.name).toBe('EndpointError'); + expect(error).toBeInstanceOf(EndpointError); expect(error.message).toBe( 'Received an error with code 500: something broke in the server' ); @@ -130,7 +130,7 @@ describe('WmsEndpoint', () => { const error = (await endpoint .isReady() .catch((e) => e)) as ServiceExceptionError; - expect(error.constructor.name).toBe('ServiceExceptionError'); + expect(error).toBeInstanceOf(ServiceExceptionError); expect(error.message).toBe( 'msWMSGetCapabilities(): WMS server error. WMS request not enabled. Check wms/ows_enable_request settings.' ); diff --git a/src/worker/utils.ts b/src/worker/utils.ts index e073629..e8ae3e9 100644 --- a/src/worker/utils.ts +++ b/src/worker/utils.ts @@ -1,4 +1,5 @@ import { getUniqueId } from '../shared/id.js'; +import { decodeError, encodeError } from '../shared/errors.js'; type TaskParams = Record; type TaskResponse = Record; @@ -45,7 +46,7 @@ export function sendTaskRequest( workerInstance.removeEventListener('message', workerHandler); } if ('error' in response) { - reject(response.error); + reject(decodeError(response.error)); } else { resolve(response.response as T); } @@ -80,7 +81,7 @@ export function addTaskHandler( try { response = await handler(request.params); } catch (e) { - error = e; + error = encodeError(e); } const message = /** @type {WorkerResponse} */ { taskName,