oxigdal_services/
error.rs1use axum::{
7 http::{StatusCode, header},
8 response::{IntoResponse, Response},
9};
10use thiserror::Error;
11
12#[derive(Debug, Error)]
14pub enum ServiceError {
15 #[error("Invalid parameter '{0}': {1}")]
17 InvalidParameter(String, String),
18
19 #[error("Missing required parameter: {0}")]
21 MissingParameter(String),
22
23 #[error("Resource not found: {0}")]
25 NotFound(String),
26
27 #[error("Invalid CRS: {0}")]
29 InvalidCrs(String),
30
31 #[error("Invalid bounding box: {0}")]
33 InvalidBbox(String),
34
35 #[error("Unsupported format: {0}")]
37 UnsupportedFormat(String),
38
39 #[error("Unsupported operation: {0}")]
41 UnsupportedOperation(String),
42
43 #[error("Invalid XML: {0}")]
45 InvalidXml(String),
46
47 #[error("Invalid GeoJSON: {0}")]
49 InvalidGeoJson(String),
50
51 #[error("Transaction error: {0}")]
53 Transaction(String),
54
55 #[error("Process execution error: {0}")]
57 ProcessExecution(String),
58
59 #[error("Coverage error: {0}")]
61 Coverage(String),
62
63 #[error("Catalog error: {0}")]
65 Catalog(String),
66
67 #[error("IO error: {0}")]
69 Io(#[from] std::io::Error),
70
71 #[error("OxiGDAL error: {0}")]
73 OxiGdal(#[from] oxigdal_core::OxiGdalError),
74
75 #[error("Serialization error: {0}")]
77 Serialization(String),
78
79 #[error("XML error: {0}")]
81 Xml(String),
82
83 #[error("Internal server error: {0}")]
85 Internal(String),
86}
87
88impl From<quick_xml::Error> for ServiceError {
89 fn from(err: quick_xml::Error) -> Self {
90 ServiceError::Xml(err.to_string())
91 }
92}
93
94impl From<serde_json::Error> for ServiceError {
95 fn from(err: serde_json::Error) -> Self {
96 ServiceError::Serialization(err.to_string())
97 }
98}
99
100impl From<geojson::Error> for ServiceError {
101 fn from(err: geojson::Error) -> Self {
102 ServiceError::InvalidGeoJson(err.to_string())
103 }
104}
105
106impl IntoResponse for ServiceError {
107 fn into_response(self) -> Response {
108 let (status, exception_code, exception_text) = match &self {
109 ServiceError::InvalidParameter(_, _) | ServiceError::MissingParameter(_) => (
110 StatusCode::BAD_REQUEST,
111 "InvalidParameterValue",
112 self.to_string(),
113 ),
114 ServiceError::NotFound(_) => (StatusCode::NOT_FOUND, "NotFound", self.to_string()),
115 ServiceError::UnsupportedOperation(_) | ServiceError::UnsupportedFormat(_) => (
116 StatusCode::BAD_REQUEST,
117 "OperationNotSupported",
118 self.to_string(),
119 ),
120 ServiceError::InvalidXml(_) | ServiceError::InvalidGeoJson(_) => {
121 (StatusCode::BAD_REQUEST, "InvalidRequest", self.to_string())
122 }
123 ServiceError::InvalidCrs(_) => {
124 (StatusCode::BAD_REQUEST, "InvalidCRS", self.to_string())
125 }
126 ServiceError::InvalidBbox(_) => (
127 StatusCode::BAD_REQUEST,
128 "InvalidBoundingBox",
129 self.to_string(),
130 ),
131 _ => (
132 StatusCode::INTERNAL_SERVER_ERROR,
133 "NoApplicableCode",
134 self.to_string(),
135 ),
136 };
137
138 let xml = format!(
140 r#"<?xml version="1.0" encoding="UTF-8"?>
141<ExceptionReport version="2.0.0"
142 xmlns="http://www.opengis.net/ows/2.0"
143 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
144 xsi:schemaLocation="http://www.opengis.net/ows/2.0 http://schemas.opengis.net/ows/2.0/owsExceptionReport.xsd">
145 <Exception exceptionCode="{}">
146 <ExceptionText>{}</ExceptionText>
147 </Exception>
148</ExceptionReport>"#,
149 exception_code, exception_text
150 );
151
152 (status, [(header::CONTENT_TYPE, "application/xml")], xml).into_response()
153 }
154}
155
156pub type ServiceResult<T> = Result<T, ServiceError>;
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162
163 #[test]
164 fn test_error_display() {
165 let err = ServiceError::MissingParameter("VERSION".to_string());
166 assert_eq!(err.to_string(), "Missing required parameter: VERSION");
167
168 let err = ServiceError::InvalidParameter("BBOX".to_string(), "malformed".to_string());
169 assert_eq!(err.to_string(), "Invalid parameter 'BBOX': malformed");
170 }
171
172 #[test]
173 fn test_error_conversion() {
174 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
175 let svc_err: ServiceError = io_err.into();
176 assert!(matches!(svc_err, ServiceError::Io(_)));
177 }
178}