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

1"""OWS exceptions.""" 

2 

3import gws 

4import gws.lib.xmlx as xmlx 

5import gws.lib.image 

6import gws.lib.mime 

7 

8 

9class Error(gws.Error): 

10 """OWS error. 

11 

12 The class name is used as the OWS exception code. The HTTP status is 

13 derived from the code, 400 by default. 

14 """ 

15 

16 def __init__(self, *args): 

17 """Create an error. 

18 

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) 

28 

29 def to_xml_response(self, xmlns='ows') -> gws.ContentResponse: 

30 """Create an XML response for this error. 

31 

32 Args: 

33 xmlns: Format to use: ``ows`` for an OWS ``ExceptionReport``, 

34 ``ogc`` for an OGC ``ServiceExceptionReport``. 

35 

36 Returns: 

37 The XML response. 

38 

39 Raises: 

40 ``gws.Error``: If ``xmlns`` is invalid. 

41 """ 

42 

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 ) 

54 

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 ) 

66 

67 else: 

68 raise gws.Error(f'invalid {xmlns=}') 

69 

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 ) 

81 

82 def to_image_response(self, mime_type='image/png') -> gws.ContentResponse: 

83 """Create an image response for this error. 

84 

85 The image is a single error pixel. 

86 

87 Args: 

88 mime_type: Image mime type. 

89 

90 Returns: 

91 The image response. 

92 """ 

93 

94 return gws.ContentResponse( 

95 status=self.status, 

96 mimeType=mime_type, 

97 content=gws.lib.image.error_pixel(mime_type), 

98 ) 

99 

100 

101def from_exception(exc: Exception) -> Error: 

102 """Convert an exception to an OWS error. 

103 

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. 

107 

108 Args: 

109 exc: An exception. 

110 

111 Returns: 

112 The OWS error. A converted error has ``exc`` as its cause. 

113 """ 

114 

115 if isinstance(exc, Error): 

116 return exc 

117 

118 e = None 

119 

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() 

126 

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') 

132 

133 e.__cause__ = exc 

134 return e 

135 

136 

137# @formatter:off 

138 

139# out extensions 

140 

141 

142class NotFound(Error): 

143 """The requested resource was not found (HTTP 404).""" 

144 

145 

146class Forbidden(Error): 

147 """Access to the requested resource is forbidden (HTTP 403).""" 

148 

149 

150class BadRequest(Error): 

151 """The request is invalid (HTTP 400).""" 

152 

153 

154# OGC 06-121r9 

155# Table 27 — Standard exception codes and meanings 

156 

157 

158class InvalidParameterValue(Error): 

159 """A parameter has an invalid value.""" 

160 

161 

162class InvalidUpdateSequence(Error): 

163 """The update sequence is greater than the current one.""" 

164 

165 

166class MissingParameterValue(Error): 

167 """A required parameter is missing.""" 

168 

169 

170class NoApplicableCode(Error): 

171 """No other exception code applies, e.g. an internal server error.""" 

172 

173 

174class OperationNotSupported(Error): 

175 """The requested operation is not supported.""" 

176 

177 

178class OptionNotSupported(Error): 

179 """The requested option is not supported.""" 

180 

181 

182class VersionNegotiationFailed(Error): 

183 """None of the requested versions is supported.""" 

184 

185 

186# OGC 06-042 

187# Table E.1 — Service exception codes 

188 

189 

190class CurrentUpdateSequence(Error): 

191 """The update sequence equals the current one.""" 

192 

193 

194class InvalidCRS(Error): 

195 """The requested CRS is not supported.""" 

196 

197 

198class InvalidDimensionValue(Error): 

199 """A dimension parameter has an invalid value.""" 

200 

201 

202class InvalidFormat(Error): 

203 """The requested format is not supported.""" 

204 

205 

206class InvalidPoint(Error): 

207 """The point coordinates are invalid.""" 

208 

209 

210class LayerNotDefined(Error): 

211 """The requested layer does not exist.""" 

212 

213 

214class LayerNotQueryable(Error): 

215 """The requested layer cannot be queried.""" 

216 

217 

218class MissingDimensionValue(Error): 

219 """A required dimension value is missing.""" 

220 

221 

222class StyleNotDefined(Error): 

223 """The requested style does not exist.""" 

224 

225 

226# OGC 07-057r7 

227# Table 20 — Exception codes for GetCapabilities operation 

228# Table 23 — Exception codes for GetTile operation 

229 

230 

231class PointIJOutOfRange(Error): 

232 """The pixel coordinates are outside the tile.""" 

233 

234 

235class TileOutOfRange(Error): 

236 """The tile row or column is out of range.""" 

237 

238 

239# OGC 09-025r1 

240# Table 3 — WFS exception codes 

241 

242 

243class CannotLockAllFeatures(Error): 

244 """Not all requested features could be locked.""" 

245 

246 

247class DuplicateStoredQueryIdValue(Error): 

248 """A stored query with this id already exists.""" 

249 

250 

251class DuplicateStoredQueryParameterName(Error): 

252 """A stored query parameter name is used more than once.""" 

253 

254 

255class FeaturesNotLocked(Error): 

256 """The features to modify are not locked.""" 

257 

258 

259class InvalidLockId(Error): 

260 """The lock id is invalid.""" 

261 

262 

263class InvalidValue(Error): 

264 """A value is invalid.""" 

265 

266 

267class LockHasExpired(Error): 

268 """The lock has expired.""" 

269 

270 

271class OperationParsingFailed(Error): 

272 """The request could not be parsed.""" 

273 

274 

275class OperationProcessingFailed(Error): 

276 """The request could not be processed.""" 

277 

278 

279class ResponseCacheExpired(Error): 

280 """The cached response has expired.""" 

281 

282 

283# OGC 06-121r9 8.6 HTTP STATUS codes for OGC Exceptions 

284 

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)