Coverage for gws-app/gws/base/ows/server/error.py: 74%
70 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-05 13:35 +0200
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-05 13:35 +0200
1"""OWS exceptions."""
3import gws
4import gws.lib.xmlx as xmlx
5import gws.lib.image
6import gws.lib.mime
9class Error(gws.Error):
10 """OWS error.
12 The class name is used as the OWS exception code. The HTTP status is
13 derived from the code, 400 by default.
14 """
16 def __init__(self, *args):
17 """Create an error.
19 Args:
20 *args: The locator (usually the name of the offending parameter) and the error message, both optional.
21 """
22 super().__init__(*args)
23 self.code = self.__class__.__name__
24 self.locator = self.args[0] if len(self.args) > 0 else ''
25 self.message = self.args[1] if len(self.args) > 1 else ''
26 # NB assume it's the user's fault by default
27 self.status = _STATUS.get(self.code, 400)
29 def to_xml_response(self, xmlns='ows') -> gws.ContentResponse:
30 """Create an XML response for this error.
32 Args:
33 xmlns: Format to use: ``ows`` for an OWS ``ExceptionReport``,
34 ``ogc`` for an OGC ``ServiceExceptionReport``.
36 Returns:
37 The XML response.
39 Raises:
40 ``gws.Error``: If ``xmlns`` is invalid.
41 """
43 if xmlns == 'ows':
44 # OWS ExceptionReport, as per OGC 06-121r9, 8.5
45 ns = xmlx.namespace.c.OWS_11
46 xml = xmlx.tag(
47 'ExceptionReport',
48 xmlx.tag(
49 'Exception',
50 {'exceptionCode': self.code, 'locator': self.locator},
51 self.message,
52 ),
53 )
55 elif xmlns == 'ogc':
56 # OGC ServiceExceptionReport, as per OGC 06-042, H.2
57 ns = xmlx.namespace.c.OGC
58 xml = xmlx.tag(
59 'ServiceExceptionReport',
60 xmlx.tag(
61 'ServiceException',
62 {'code': self.code},
63 self.message,
64 ),
65 )
67 else:
68 raise gws.Error(f'invalid {xmlns=}')
70 return gws.ContentResponse(
71 status=self.status,
72 mimeType=gws.lib.mime.XML,
73 content=xml.to_string(
74 gws.XmlOptions(
75 defaultNamespace=ns,
76 withXmlDeclaration=True,
77 withNamespaceDeclarations=True,
78 )
79 ),
80 )
82 def to_image_response(self, mime_type='image/png') -> gws.ContentResponse:
83 """Create an image response for this error.
85 The image is a single error pixel.
87 Args:
88 mime_type: Image mime type.
90 Returns:
91 The image response.
92 """
94 return gws.ContentResponse(
95 status=self.status,
96 mimeType=mime_type,
97 content=gws.lib.image.error_pixel(mime_type),
98 )
101def from_exception(exc: Exception) -> Error:
102 """Convert an exception to an OWS error.
104 ``gws.NotFoundError``, ``gws.ForbiddenError`` and ``gws.BadRequestError`` are
105 mapped to the corresponding errors, any other exception is logged and becomes
106 ``NoApplicableCode``. OWS errors are returned unchanged.
108 Args:
109 exc: An exception.
111 Returns:
112 The OWS error. A converted error has ``exc`` as its cause.
113 """
115 if isinstance(exc, Error):
116 return exc
118 e = None
120 if isinstance(exc, gws.NotFoundError):
121 e = NotFound()
122 elif isinstance(exc, gws.ForbiddenError):
123 e = Forbidden()
124 elif isinstance(exc, gws.BadRequestError):
125 e = BadRequest()
127 if e:
128 gws.log.warning(f'OWS Exception: {e.code} cause={exc!r}')
129 else:
130 gws.log.exception()
131 e = NoApplicableCode('', 'Internal Server Error')
133 e.__cause__ = exc
134 return e
137# @formatter:off
139# out extensions
142class NotFound(Error):
143 """The requested resource was not found (HTTP 404)."""
146class Forbidden(Error):
147 """Access to the requested resource is forbidden (HTTP 403)."""
150class BadRequest(Error):
151 """The request is invalid (HTTP 400)."""
154# OGC 06-121r9
155# Table 27 — Standard exception codes and meanings
158class InvalidParameterValue(Error):
159 """A parameter has an invalid value."""
162class InvalidUpdateSequence(Error):
163 """The update sequence is greater than the current one."""
166class MissingParameterValue(Error):
167 """A required parameter is missing."""
170class NoApplicableCode(Error):
171 """No other exception code applies, e.g. an internal server error."""
174class OperationNotSupported(Error):
175 """The requested operation is not supported."""
178class OptionNotSupported(Error):
179 """The requested option is not supported."""
182class VersionNegotiationFailed(Error):
183 """None of the requested versions is supported."""
186# OGC 06-042
187# Table E.1 — Service exception codes
190class CurrentUpdateSequence(Error):
191 """The update sequence equals the current one."""
194class InvalidCRS(Error):
195 """The requested CRS is not supported."""
198class InvalidDimensionValue(Error):
199 """A dimension parameter has an invalid value."""
202class InvalidFormat(Error):
203 """The requested format is not supported."""
206class InvalidPoint(Error):
207 """The point coordinates are invalid."""
210class LayerNotDefined(Error):
211 """The requested layer does not exist."""
214class LayerNotQueryable(Error):
215 """The requested layer cannot be queried."""
218class MissingDimensionValue(Error):
219 """A required dimension value is missing."""
222class StyleNotDefined(Error):
223 """The requested style does not exist."""
226# OGC 07-057r7
227# Table 20 — Exception codes for GetCapabilities operation
228# Table 23 — Exception codes for GetTile operation
231class PointIJOutOfRange(Error):
232 """The pixel coordinates are outside the tile."""
235class TileOutOfRange(Error):
236 """The tile row or column is out of range."""
239# OGC 09-025r1
240# Table 3 — WFS exception codes
243class CannotLockAllFeatures(Error):
244 """Not all requested features could be locked."""
247class DuplicateStoredQueryIdValue(Error):
248 """A stored query with this id already exists."""
251class DuplicateStoredQueryParameterName(Error):
252 """A stored query parameter name is used more than once."""
255class FeaturesNotLocked(Error):
256 """The features to modify are not locked."""
259class InvalidLockId(Error):
260 """The lock id is invalid."""
263class InvalidValue(Error):
264 """A value is invalid."""
267class LockHasExpired(Error):
268 """The lock has expired."""
271class OperationParsingFailed(Error):
272 """The request could not be parsed."""
275class OperationProcessingFailed(Error):
276 """The request could not be processed."""
279class ResponseCacheExpired(Error):
280 """The cached response has expired."""
283# OGC 06-121r9 8.6 HTTP STATUS codes for OGC Exceptions
285_STATUS = dict(
286 OperationNotSupported=501,
287 MissingParameterValue=400,
288 InvalidParameterValue=400,
289 VersionNegotiationFailed=400,
290 InvalidUpdateSequence=400,
291 OptionNotSupported=501,
292 NoApplicableCode=500,
293 NotFound=404,
294 Forbidden=403,
295 BadRequest=400,
296)