1pub mod builtin;
32pub mod capabilities;
33pub mod processes;
34
35use crate::error::{ServiceError, ServiceResult};
36use async_trait::async_trait;
37use axum::{
38 extract::{Query, State},
39 response::Response,
40};
41use serde::{Deserialize, Serialize};
42use std::sync::Arc;
43
44#[derive(Clone)]
46pub struct WpsState {
47 pub service_info: Arc<ServiceInfo>,
49 pub processes: Arc<dashmap::DashMap<String, Arc<dyn Process>>>,
51}
52
53#[derive(Debug, Clone)]
55pub struct ServiceInfo {
56 pub title: String,
58 pub abstract_text: Option<String>,
60 pub provider: String,
62 pub service_url: String,
64 pub versions: Vec<String>,
66}
67
68#[async_trait]
70pub trait Process: Send + Sync {
71 fn identifier(&self) -> &str;
73
74 fn title(&self) -> &str;
76
77 fn abstract_text(&self) -> Option<&str> {
79 None
80 }
81
82 fn inputs(&self) -> Vec<InputDescription>;
84
85 fn outputs(&self) -> Vec<OutputDescription>;
87
88 async fn execute(&self, inputs: ProcessInputs) -> ServiceResult<ProcessOutputs>;
90}
91
92#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct InputDescription {
95 pub identifier: String,
97 pub title: String,
99 pub abstract_text: Option<String>,
101 pub data_type: DataType,
103 pub min_occurs: usize,
105 pub max_occurs: Option<usize>,
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct OutputDescription {
112 pub identifier: String,
114 pub title: String,
116 pub abstract_text: Option<String>,
118 pub data_type: DataType,
120}
121
122#[derive(Debug, Clone, Serialize, Deserialize)]
124pub enum DataType {
125 Literal(LiteralDataType),
127 Complex(ComplexDataType),
129 BoundingBox,
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct LiteralDataType {
136 pub data_type: String,
138 pub allowed_values: Option<Vec<String>>,
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct ComplexDataType {
145 pub mime_type: String,
147 pub encoding: Option<String>,
149 pub schema: Option<String>,
151}
152
153#[derive(Debug, Clone, Default)]
155pub struct ProcessInputs {
156 pub inputs: dashmap::DashMap<String, Vec<InputValue>>,
158}
159
160#[derive(Debug, Clone)]
162pub enum InputValue {
163 Literal(String),
165 Complex(Vec<u8>),
167 Reference(String),
169 BoundingBox {
171 lower: (f64, f64),
173 upper: (f64, f64),
175 crs: Option<String>,
177 },
178}
179
180#[derive(Debug, Clone, Default)]
182pub struct ProcessOutputs {
183 pub outputs: dashmap::DashMap<String, OutputValue>,
185}
186
187#[derive(Debug, Clone)]
189pub enum OutputValue {
190 Literal(String),
192 Complex(Vec<u8>),
194 Reference(String),
196}
197
198#[derive(Debug, Deserialize)]
200#[serde(rename_all = "UPPERCASE")]
201pub struct WpsRequest {
202 pub service: Option<String>,
204 pub version: Option<String>,
206 pub request: String,
208 #[serde(flatten)]
210 pub params: serde_json::Value,
211}
212
213impl WpsState {
214 pub fn new(service_info: ServiceInfo) -> Self {
216 let state = Self {
217 service_info: Arc::new(service_info),
218 processes: Arc::new(dashmap::DashMap::new()),
219 };
220
221 builtin::register_builtin_processes(&state);
223
224 state
225 }
226
227 pub fn add_process(&self, process: Arc<dyn Process>) -> ServiceResult<()> {
229 self.processes
230 .insert(process.identifier().to_string(), process);
231 Ok(())
232 }
233
234 pub fn get_process(&self, identifier: &str) -> Option<Arc<dyn Process>> {
236 self.processes
237 .get(identifier)
238 .map(|entry| Arc::clone(entry.value()))
239 }
240}
241
242pub async fn handle_wps_request(
244 State(state): State<WpsState>,
245 Query(params): Query<WpsRequest>,
246) -> Result<Response, ServiceError> {
247 if let Some(ref service) = params.service {
249 if service.to_uppercase() != "WPS" {
250 return Err(ServiceError::InvalidParameter(
251 "SERVICE".to_string(),
252 format!("Expected 'WPS', got '{}'", service),
253 ));
254 }
255 }
256
257 match params.request.to_uppercase().as_str() {
259 "GETCAPABILITIES" => {
260 let version = params.version.as_deref().unwrap_or("2.0.0");
261 capabilities::handle_get_capabilities(&state, version).await
262 }
263 "DESCRIBEPROCESS" => {
264 let version = params.version.as_deref().unwrap_or("2.0.0");
265 processes::handle_describe_process(&state, version, ¶ms.params).await
266 }
267 "EXECUTE" => {
268 let version = params.version.as_deref().unwrap_or("2.0.0");
269 processes::handle_execute(&state, version, ¶ms.params).await
270 }
271 _ => Err(ServiceError::UnsupportedOperation(params.request.clone())),
272 }
273}
274
275#[cfg(test)]
276mod tests {
277 use super::*;
278
279 #[test]
280 fn test_wps_state_creation() {
281 let info = ServiceInfo {
282 title: "Test WPS".to_string(),
283 abstract_text: Some("Test service".to_string()),
284 provider: "COOLJAPAN OU".to_string(),
285 service_url: "http://localhost/wps".to_string(),
286 versions: vec!["2.0.0".to_string()],
287 };
288
289 let state = WpsState::new(info);
290 assert_eq!(state.service_info.title, "Test WPS");
291 assert!(!state.processes.is_empty());
293 }
294}