Skip to content

Commit

Permalink
Add recognition of service exception reports
Browse files Browse the repository at this point in the history
If the WMS or WFS service responds to a GetCapabilities request with a
ServiceException report then this will be recognised and the promise
rejected with a ServiceExceptionError.
  • Loading branch information
ejn committed Oct 1, 2024
1 parent c75dcba commit 891e3d5
Show file tree
Hide file tree
Showing 14 changed files with 370 additions and 15 deletions.
6 changes: 6 additions & 0 deletions fixtures/wfs/exception-report-1-1-0.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<ows:ExceptionReport xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ows="http://www.opengis.net/ows" version="1.1.0" language="en-US" xsi:schemaLocation="http://www.opengis.net/ows http://schemas.opengis.net/ows/1.0.0/owsExceptionReport.xsd">
<ows:Exception exceptionCode="InvalidParameterValue" locator="request">
<ows:ExceptionText>msWFSDispatch(): WFS server error. Invalid WFS request: FooBar</ows:ExceptionText>
</ows:Exception>
</ows:ExceptionReport>
6 changes: 6 additions & 0 deletions fixtures/wfs/exception-report-2-0-0.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<ows:ExceptionReport xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ows="http://www.opengis.net/ows/1.1" version="2.0.0" xml:lang="en-US" xsi:schemaLocation="http://www.opengis.net/ows/1.1 http://schemas.opengis.net/ows/1.1.0/owsExceptionReport.xsd">
<ows:Exception exceptionCode="InvalidParameterValue" locator="request">
<ows:ExceptionText>msWFSDispatch(): WFS server error. Invalid WFS request: FooBar</ows:ExceptionText>
</ows:Exception>
</ows:ExceptionReport>
6 changes: 6 additions & 0 deletions fixtures/wfs/exception-report-wms.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<ows:ExceptionReport xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ows="http://www.opengis.net/ows/1.1" version="2.0.0" xml:lang="en-US" xsi:schemaLocation="http://www.opengis.net/ows/1.1 http://schemas.opengis.net/ows/1.1.0/owsExceptionReport.xsd">
<ows:Exception exceptionCode="InvalidParameterValue" locator="request">
<ows:ExceptionText>msWFSDispatch(): WFS server error. WFS request not enabled. Check wfs/ows_enable_request settings.</ows:ExceptionText>
</ows:Exception>
</ows:ExceptionReport>
6 changes: 6 additions & 0 deletions fixtures/wfs/service-exception-report-1-0-0.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version='1.0' encoding="UTF-8" ?>
<ServiceExceptionReport version="1.2.0" xmlns="http://www.opengis.net/ogc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/ogc http://schemas.opengis.net/wfs/1.0.0/OGC-exception.xsd">
<ServiceException code="InvalidParameterValue" locator="request">
msWFSDispatch(): WFS server error. Invalid WFS request: FooBar
</ServiceException>
</ServiceExceptionReport>
7 changes: 7 additions & 0 deletions fixtures/wms/service-exception-report-1-1-0.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version='1.0' encoding="UTF-8" standalone="no" ?>
<!DOCTYPE ServiceExceptionReport SYSTEM "http://schemas.opengis.net/wms/1.1.0/exception_1_1_0.dtd">
<ServiceExceptionReport version="1.1.0">
<ServiceException>
msWMSDispatch(): WMS server error. Incomplete or unsupported WMS request
</ServiceException>
</ServiceExceptionReport>
7 changes: 7 additions & 0 deletions fixtures/wms/service-exception-report-1-1-1.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version='1.0' encoding="UTF-8" standalone="no" ?>
<!DOCTYPE ServiceExceptionReport SYSTEM "http://schemas.opengis.net/wms/1.1.1/exception_1_1_1.dtd">
<ServiceExceptionReport version="1.1.1">
<ServiceException>
msWMSDispatch(): WMS server error. Incomplete or unsupported WMS request
</ServiceException>
</ServiceExceptionReport>
6 changes: 6 additions & 0 deletions fixtures/wms/service-exception-report-1-3-0.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version='1.0' encoding="UTF-8" standalone="no" ?>
<ServiceExceptionReport version="1.3.0" xmlns="http://www.opengis.net/ogc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/ogc http://schemas.opengis.net/wms/1.3.0/exceptions_1_3_0.xsd">
<ServiceException>
msWMSDispatch(): WMS server error. Incomplete or unsupported WMS request
</ServiceException>
</ServiceExceptionReport>
6 changes: 6 additions & 0 deletions fixtures/wms/service-exception-report-wfs.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version='1.0' encoding="UTF-8" standalone="no" ?>
<ServiceExceptionReport version="1.3.0" xmlns="http://www.opengis.net/ogc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/ogc http://schemas.opengis.net/wms/1.3.0/exceptions_1_3_0.xsd">
<ServiceException>
msWMSGetCapabilities(): WMS server error. WMS request not enabled. Check wms/ows_enable_request settings.
</ServiceException>
</ServiceExceptionReport>
4 changes: 4 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ export {
setFetchOptions,
resetFetchOptions,
} from './shared/http-utils.js';
export {
check,
default as ServiceExceptionError,
} from './shared/service-exception-error.js';

export { enableFallbackWithoutWorker } from './worker/index.js';
import './worker-fallback/index.js';
181 changes: 181 additions & 0 deletions src/shared/service-exception-error.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// @ts-expect-error ts-migrate(7016)
import wfsCapabilities200 from '../../fixtures/wfs/capabilities-pigma-2-0-0.xml';
// @ts-expect-error ts-migrate(7016)
import wfsException100 from '../../fixtures/wfs/service-exception-report-1-0-0.xml';
// @ts-expect-error ts-migrate(7016)
import wfsException110 from '../../fixtures/wfs/exception-report-1-1-0.xml';
// @ts-expect-error ts-migrate(7016)
import wfsException200 from '../../fixtures/wfs/exception-report-2-0-0.xml';
// @ts-expect-error ts-migrate(7016)
import wmsException110 from '../../fixtures/wms/service-exception-report-1-1-0.xml';
// @ts-expect-error ts-migrate(7016)
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 ServiceExceptionError, {
check,
parse,
} from './service-exception-error.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);
});
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);
});
});

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);
});
});
});
86 changes: 86 additions & 0 deletions src/shared/service-exception-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import type { XmlDocument, XmlElement } from '@rgrove/parse-xml';
import {
findChildElement,
getElementAttribute,
getElementName,
getElementText,
getRootElement,
stripNamespace,
} from '../shared/xml-utils.js';

/**
* Representation of an Exception reported by an OWS service
*
* This is usually contained in a ServiceExceptionReport or ExceptionReport
* document and represented as a ServiceException or Exception element
*/
export default class ServiceExceptionError extends Error {
/**
* Constructor
* @param message Error message
* @param requestUrl URL which resulted in the ServiceException
* @param code Optional ServiceException code
* @param locator Optional ServiceException locator
* @param response Optional response content received
*/
public constructor(
message: string,
public readonly requestUrl?: string,
public readonly code?: string,
public readonly locator?: string,
public readonly response?: XmlDocument
) {
super(message);
}
}

/**
* Parse a ServiceException element to a ServiceExceptionError
* @param serviceException ServiceException element
* @param url URL from which the ServiceException was generated
*/
export function parse(
serviceException: XmlElement,
url?: string
): ServiceExceptionError {
const errorCode =
getElementAttribute(serviceException, 'code') ||
getElementAttribute(serviceException, 'exceptionCode');
const errorLocator = getElementAttribute(serviceException, 'locator');
const textElement =
findChildElement(serviceException, 'ExceptionText') || serviceException;
const errorMessage = getElementText(textElement).trim();
return new ServiceExceptionError(
errorMessage,
url,
errorCode,
errorLocator,
serviceException.document
);
}

/**
* Check the response for a ServiceExceptionReport and if present throw one
* @param response Response to check
* @param url URL from which response was generated
*/
export function check(response: XmlDocument, url?: string): XmlDocument {
const rootEl = getRootElement(response);
const rootElName = stripNamespace(getElementName(rootEl));
if (rootElName === 'ServiceExceptionReport') {
// document contains a ServiceExceptionReport, so generate an Error from
// the first ServiceException contained in it
const error = findChildElement(rootEl, 'ServiceException');
if (error) {
throw parse(error, url);
}
}
if (rootElName === 'ExceptionReport') {
const error = findChildElement(rootEl, 'Exception');
if (error) {
throw parse(error, url);
}
}
// there was nothing to convert to an Error so just pass the document on
return response;
}
14 changes: 14 additions & 0 deletions src/wfs/endpoint.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import getfeature200full from '../../fixtures/wfs/getfeature-props-pigma-2-0-0.x
import describefeaturetype200 from '../../fixtures/wfs/describefeaturetype-pigma-2-0-0-xsd.xml';
// @ts-expect-error ts-migrate(7016)
import capabilitiesStates from '../../fixtures/wfs/capabilities-states-2-0-0.xml';
// @ts-expect-error ts-migrate(7016)
import exceptionReportWms from '../../fixtures/wfs/exception-report-wms.xml';
import WfsEndpoint from './endpoint.js';
import { useCache } from '../shared/cache.js';

Expand Down Expand Up @@ -71,6 +73,18 @@ describe('WfsEndpoint', () => {
it('resolves with the endpoint object', async () => {
await expect(endpoint.isReady()).resolves.toEqual(endpoint);
});

describe('service exception handling', () => {
beforeEach(() => {
global.fetchResponseFactory = () => exceptionReportWms;
endpoint = new WfsEndpoint('https://my.test.service/ogc/wms');
});
it('rejects when the endpoint returns an exception report', async () => {
await expect(endpoint.isReady()).rejects.toThrow(
'msWFSDispatch(): WFS server error. WFS request not enabled. Check wfs/ows_enable_request settings.'
);
});
});
});

describe('#getVersion', () => {
Expand Down
13 changes: 13 additions & 0 deletions src/wms/endpoint.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import capabilities130 from '../../fixtures/wms/capabilities-brgm-1-3-0.xml';
// @ts-expect-error ts-migrate(7016)
import capabilitiesStates from '../../fixtures/wms/capabilities-states-1-3-0.xml';
// @ts-expect-error ts-migrate(7016)
import exceptionReportWfs from '../../fixtures/wms/service-exception-report-wfs.xml';
import WmsEndpoint from './endpoint.js';
import { useCache } from '../shared/cache.js';

Expand Down Expand Up @@ -52,6 +54,17 @@ describe('WmsEndpoint', () => {
it('resolves with the endpoint object', async () => {
await expect(endpoint.isReady()).resolves.toEqual(endpoint);
});
describe('service exception handling', () => {
beforeEach(() => {
global.fetchResponseFactory = () => exceptionReportWfs;
endpoint = new WmsEndpoint('https://my.test.service/ogc/wfs');
});
it('rejects when the endpoint returns an exception report', async () => {
await expect(endpoint.isReady()).rejects.toThrow(
'msWMSGetCapabilities(): WMS server error. WMS request not enabled. Check wms/ows_enable_request settings.'
);
});
});
});

describe('#getVersion', () => {
Expand Down
Loading

0 comments on commit 891e3d5

Please sign in to comment.