Skip to main content

oxigdal_services/wps/
mod.rs

1//! WPS (Web Processing Service) 2.0 implementation
2//!
3//! Provides OGC-compliant Web Processing Service supporting:
4//! - GetCapabilities: Service and process metadata
5//! - DescribeProcess: Process input/output descriptions
6//! - Execute: Process execution (synchronous and asynchronous)
7//!
8//! # Standards
9//!
10//! - OGC WPS 2.0.0
11//!
12//! # Example
13//!
14//! ```no_run
15//! use oxigdal_services::wps::{ServiceInfo, WpsState};
16//!
17//! # fn example() -> Result<(), Box<dyn std::error::Error>> {
18//! let info = ServiceInfo {
19//!     title: "My WPS Service".to_string(),
20//!     abstract_text: None,
21//!     provider: "Provider".to_string(),
22//!     service_url: "http://localhost:8080/wps".to_string(),
23//!     versions: vec!["2.0.0".to_string()],
24//! };
25//! let service = WpsState::new(info);
26//! // Add processes and handle requests
27//! # Ok(())
28//! # }
29//! ```
30
31pub 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/// WPS service state
45#[derive(Clone)]
46pub struct WpsState {
47    /// Service metadata
48    pub service_info: Arc<ServiceInfo>,
49    /// Process registry
50    pub processes: Arc<dashmap::DashMap<String, Arc<dyn Process>>>,
51}
52
53/// Service metadata
54#[derive(Debug, Clone)]
55pub struct ServiceInfo {
56    /// Service title
57    pub title: String,
58    /// Service abstract/description
59    pub abstract_text: Option<String>,
60    /// Service provider
61    pub provider: String,
62    /// Service URL
63    pub service_url: String,
64    /// Supported versions
65    pub versions: Vec<String>,
66}
67
68/// Process trait
69#[async_trait]
70pub trait Process: Send + Sync {
71    /// Get process identifier
72    fn identifier(&self) -> &str;
73
74    /// Get process title
75    fn title(&self) -> &str;
76
77    /// Get process abstract
78    fn abstract_text(&self) -> Option<&str> {
79        None
80    }
81
82    /// Get input descriptions
83    fn inputs(&self) -> Vec<InputDescription>;
84
85    /// Get output descriptions
86    fn outputs(&self) -> Vec<OutputDescription>;
87
88    /// Execute process
89    async fn execute(&self, inputs: ProcessInputs) -> ServiceResult<ProcessOutputs>;
90}
91
92/// Input description
93#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct InputDescription {
95    /// Input identifier
96    pub identifier: String,
97    /// Input title
98    pub title: String,
99    /// Input abstract
100    pub abstract_text: Option<String>,
101    /// Data type
102    pub data_type: DataType,
103    /// Minimum occurrences
104    pub min_occurs: usize,
105    /// Maximum occurrences (None = unbounded)
106    pub max_occurs: Option<usize>,
107}
108
109/// Output description
110#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct OutputDescription {
112    /// Output identifier
113    pub identifier: String,
114    /// Output title
115    pub title: String,
116    /// Output abstract
117    pub abstract_text: Option<String>,
118    /// Data type
119    pub data_type: DataType,
120}
121
122/// Data type
123#[derive(Debug, Clone, Serialize, Deserialize)]
124pub enum DataType {
125    /// Literal data (string, number, etc.)
126    Literal(LiteralDataType),
127    /// Complex data (GeoJSON, GeoTIFF, etc.)
128    Complex(ComplexDataType),
129    /// Bounding box
130    BoundingBox,
131}
132
133/// Literal data type
134#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct LiteralDataType {
136    /// Data type (integer, double, string, etc.)
137    pub data_type: String,
138    /// Allowed values
139    pub allowed_values: Option<Vec<String>>,
140}
141
142/// Complex data type
143#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct ComplexDataType {
145    /// MIME type
146    pub mime_type: String,
147    /// Encoding
148    pub encoding: Option<String>,
149    /// Schema
150    pub schema: Option<String>,
151}
152
153/// Process inputs
154#[derive(Debug, Clone, Default)]
155pub struct ProcessInputs {
156    /// Input values
157    pub inputs: dashmap::DashMap<String, Vec<InputValue>>,
158}
159
160/// Input value
161#[derive(Debug, Clone)]
162pub enum InputValue {
163    /// Literal value
164    Literal(String),
165    /// Complex data
166    Complex(Vec<u8>),
167    /// Reference (URL)
168    Reference(String),
169    /// Bounding box
170    BoundingBox {
171        /// Lower corner (x, y)
172        lower: (f64, f64),
173        /// Upper corner (x, y)
174        upper: (f64, f64),
175        /// CRS
176        crs: Option<String>,
177    },
178}
179
180/// Process outputs
181#[derive(Debug, Clone, Default)]
182pub struct ProcessOutputs {
183    /// Output values
184    pub outputs: dashmap::DashMap<String, OutputValue>,
185}
186
187/// Output value
188#[derive(Debug, Clone)]
189pub enum OutputValue {
190    /// Literal value
191    Literal(String),
192    /// Complex data
193    Complex(Vec<u8>),
194    /// Reference (URL to result)
195    Reference(String),
196}
197
198/// WPS request parameters
199#[derive(Debug, Deserialize)]
200#[serde(rename_all = "UPPERCASE")]
201pub struct WpsRequest {
202    /// Service name (must be "WPS")
203    pub service: Option<String>,
204    /// WPS version
205    pub version: Option<String>,
206    /// Request operation
207    pub request: String,
208    /// Additional parameters
209    #[serde(flatten)]
210    pub params: serde_json::Value,
211}
212
213impl WpsState {
214    /// Create new WPS service state
215    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        // Register built-in processes
222        builtin::register_builtin_processes(&state);
223
224        state
225    }
226
227    /// Add a process
228    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    /// Get process by identifier
235    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
242/// Main WPS request handler
243pub async fn handle_wps_request(
244    State(state): State<WpsState>,
245    Query(params): Query<WpsRequest>,
246) -> Result<Response, ServiceError> {
247    // Validate service parameter
248    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    // Route to appropriate handler based on request type
258    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, &params.params).await
266        }
267        "EXECUTE" => {
268            let version = params.version.as_deref().unwrap_or("2.0.0");
269            processes::handle_execute(&state, version, &params.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        // Built-in processes should be registered
292        assert!(!state.processes.is_empty());
293    }
294}