Coverage for gws-app/gws/base/ows/server/error.py: 78%
72 statements
« prev ^ index » next coverage.py v7.17.0, created at 2026-10-23 20:06 +0200
« prev ^ index » next coverage.py v7.17.0, created at 2026-10-23 20:06 +0200
1"""OWS exceptions."""
3from typing import Optional
5import gws
6import gws.lib.xmlx as xmlx
7import gws.lib.image
8import gws.lib.mime
11class Error(gws.Error):
12 """OWS error.
14 The class name is used as the OWS exception code. The HTTP status is
15 derived from the code, 400 by default.
16 """
18 def __init__(self, *args):
19 """Create an error.
21 Args:
22 *args: The locator (usually the name of the offending parameter) and the error message, both optional.
23 """
24 super().__init__(*args)
25 self.code = self.__class__.__name__
26 self.locator = self.args[0] if len(self.args) > 0 else ''
27 self.message = self.args[1] if len(self.args) > 1 else ''
28 # NB assume it's the user's fault by default
29 self.status = _STATUS.get(self.code, 400)
31 def to_xml_response(self, xmlns='ows') -> gws.ContentResponse:
32 """Create an XML response for this error.
34 Args:
35 xmlns: Format to use: ``ows`` for an OWS ``ExceptionReport``,
36 ``ogc`` for an OGC ``ServiceExceptionReport``.
38 Returns:
39 The XML response.
41 Raises:
42 ``gws.Error``: If ``xmlns`` is invalid.
43 """
45 if xmlns == 'ows':
46 # OWS ExceptionReport, as per OGC 06-121r9, 8.5
47 ns = xmlx.namespace.c.OWS_11
48 xml = xmlx.tag(
49 'ExceptionReport',
50 xmlx.tag(
51 'Exception',
52 {'exceptionCode': self.code, 'locator': self.locator},
53 self.message,
54 ),
55 )
57 elif xmlns == 'ogc':
58 # OGC ServiceExceptionReport, as per OGC 06-042, H.2
59 ns = xmlx.namespace.c.OGC
60 xml = xmlx.tag(
61 'ServiceExceptionReport',
62 xmlx.tag(
63 'ServiceException',
64 {'code': self.code},
65 self.message,
66 ),
67 )
69 else:
70 raise gws.Error(f'invalid {xmlns=}')
72 return gws.ContentResponse(
73 status=self.status,
74 mimeType=gws.lib.mime.XML,
75 content=xml.to_string(
76 gws.XmlOptions(
77 defaultNamespace=ns,
78 withXmlDeclaration=True,
79 withNamespaceDeclarations=True,
80 )
81 ),
82 )
84 def to_image_response(self, ifo: Optional[gws.ImageFormat] = None) -> gws.ContentResponse:
85 """Create an image response for this error.
87 The image is a single error pixel.
89 Args:
90 ifo: Image format, PNG24 by default.
92 Returns:
93 The image response.
94 """
96 ifo = ifo or gws.lib.image.format.PNG24
97 return gws.ContentResponse(
98 status=self.status,
99 mimeType=ifo.mimeTypes[0],
100 content=gws.lib.image.util.error_pixel(ifo),
101 )
104def from_exception(exc: Exception) -> Error:
105 """Convert an exception to an OWS error.
107 ``gws.NotFoundError``, ``gws.ForbiddenError`` and ``gws.BadRequestError`` are
108 mapped to the corresponding errors, any other exception is logged and becomes
109 ``NoApplicableCode``. OWS errors are returned unchanged.
111 Args:
112 exc: An exception.
114 Returns:
115 The OWS error. A converted error has ``exc`` as its cause.
116 """
118 if isinstance(exc, Error):
119 return exc
121 e = None
123 if isinstance(exc, gws.NotFoundError):
124 e = NotFound()
125 elif isinstance(exc, gws.ForbiddenError):
126 e = Forbidden()
127 elif isinstance(exc, gws.BadRequestError):
128 e = BadRequest()
130 if e:
131 gws.log.warning(f'OWS Exception: {e.code} cause={exc!r}')
132 else:
133 gws.log.exception()
134 e = NoApplicableCode('', 'Internal Server Error')
136 e.__cause__ = exc
137 return e
140# @formatter:off
142# out extensions
145class NotFound(Error):
146 """The requested resource was not found (HTTP 404)."""
149class Forbidden(Error):
150 """Access to the requested resource is forbidden (HTTP 403)."""
153class BadRequest(Error):
154 """The request is invalid (HTTP 400)."""
157# OGC 06-121r9
158# Table 27 — Standard exception codes and meanings
161class InvalidParameterValue(Error):
162 """A parameter has an invalid value."""
165class InvalidUpdateSequence(Error):
166 """The update sequence is greater than the current one."""
169class MissingParameterValue(Error):
170 """A required parameter is missing."""
173class NoApplicableCode(Error):
174 """No other exception code applies, e.g. an internal server error."""
177class OperationNotSupported(Error):
178 """The requested operation is not supported."""
181class OptionNotSupported(Error):
182 """The requested option is not supported."""
185class VersionNegotiationFailed(Error):
186 """None of the requested versions is supported."""
189# OGC 06-042
190# Table E.1 — Service exception codes
193class CurrentUpdateSequence(Error):
194 """The update sequence equals the current one."""
197class InvalidCRS(Error):
198 """The requested CRS is not supported."""
201class InvalidDimensionValue(Error):
202 """A dimension parameter has an invalid value."""
205class InvalidFormat(Error):
206 """The requested format is not supported."""
209class InvalidPoint(Error):
210 """The point coordinates are invalid."""
213class LayerNotDefined(Error):
214 """The requested layer does not exist."""
217class LayerNotQueryable(Error):
218 """The requested layer cannot be queried."""
221class MissingDimensionValue(Error):
222 """A required dimension value is missing."""
225class StyleNotDefined(Error):
226 """The requested style does not exist."""
229# OGC 07-057r7
230# Table 20 — Exception codes for GetCapabilities operation
231# Table 23 — Exception codes for GetTile operation
234class PointIJOutOfRange(Error):
235 """The pixel coordinates are outside the tile."""
238class TileOutOfRange(Error):
239 """The tile row or column is out of range."""
242# OGC 09-025r1
243# Table 3 — WFS exception codes
246class CannotLockAllFeatures(Error):
247 """Not all requested features could be locked."""
250class DuplicateStoredQueryIdValue(Error):
251 """A stored query with this id already exists."""
254class DuplicateStoredQueryParameterName(Error):
255 """A stored query parameter name is used more than once."""
258class FeaturesNotLocked(Error):
259 """The features to modify are not locked."""
262class InvalidLockId(Error):
263 """The lock id is invalid."""
266class InvalidValue(Error):
267 """A value is invalid."""
270class LockHasExpired(Error):
271 """The lock has expired."""
274class OperationParsingFailed(Error):
275 """The request could not be parsed."""
278class OperationProcessingFailed(Error):
279 """The request could not be processed."""
282class ResponseCacheExpired(Error):
283 """The cached response has expired."""
286# OGC 06-121r9 8.6 HTTP STATUS codes for OGC Exceptions
288_STATUS = dict(
289 OperationNotSupported=501,
290 MissingParameterValue=400,
291 InvalidParameterValue=400,
292 VersionNegotiationFailed=400,
293 InvalidUpdateSequence=400,
294 OptionNotSupported=501,
295 NoApplicableCode=500,
296 NotFound=404,
297 Forbidden=403,
298 BadRequest=400,
299)