pub mod builtin;
pub mod capabilities;
pub mod processes;
use crate::error::{ServiceError, ServiceResult};
use async_trait::async_trait;
use axum::{
extract::{Query, State},
response::Response,
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
#[derive(Clone)]
pub struct WpsState {
pub service_info: Arc<ServiceInfo>,
pub processes: Arc<dashmap::DashMap<String, Arc<dyn Process>>>,
}
#[derive(Debug, Clone)]
pub struct ServiceInfo {
pub title: String,
pub abstract_text: Option<String>,
pub provider: String,
pub service_url: String,
pub versions: Vec<String>,
}
#[async_trait]
pub trait Process: Send + Sync {
fn identifier(&self) -> &str;
fn title(&self) -> &str;
fn abstract_text(&self) -> Option<&str> {
None
}
fn inputs(&self) -> Vec<InputDescription>;
fn outputs(&self) -> Vec<OutputDescription>;
async fn execute(&self, inputs: ProcessInputs) -> ServiceResult<ProcessOutputs>;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InputDescription {
pub identifier: String,
pub title: String,
pub abstract_text: Option<String>,
pub data_type: DataType,
pub min_occurs: usize,
pub max_occurs: Option<usize>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OutputDescription {
pub identifier: String,
pub title: String,
pub abstract_text: Option<String>,
pub data_type: DataType,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum DataType {
Literal(LiteralDataType),
Complex(ComplexDataType),
BoundingBox,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LiteralDataType {
pub data_type: String,
pub allowed_values: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComplexDataType {
pub mime_type: String,
pub encoding: Option<String>,
pub schema: Option<String>,
}
#[derive(Debug, Clone, Default)]
pub struct ProcessInputs {
pub inputs: dashmap::DashMap<String, Vec<InputValue>>,
}
#[derive(Debug, Clone)]
pub enum InputValue {
Literal(String),
Complex(Vec<u8>),
Reference(String),
BoundingBox {
lower: (f64, f64),
upper: (f64, f64),
crs: Option<String>,
},
}
#[derive(Debug, Clone, Default)]
pub struct ProcessOutputs {
pub outputs: dashmap::DashMap<String, OutputValue>,
}
#[derive(Debug, Clone)]
pub enum OutputValue {
Literal(String),
Complex(Vec<u8>),
Reference(String),
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub struct WpsRequest {
pub service: Option<String>,
pub version: Option<String>,
pub request: String,
#[serde(flatten)]
pub params: serde_json::Value,
}
impl WpsState {
pub fn new(service_info: ServiceInfo) -> Self {
let state = Self {
service_info: Arc::new(service_info),
processes: Arc::new(dashmap::DashMap::new()),
};
builtin::register_builtin_processes(&state);
state
}
pub fn add_process(&self, process: Arc<dyn Process>) -> ServiceResult<()> {
self.processes
.insert(process.identifier().to_string(), process);
Ok(())
}
pub fn get_process(&self, identifier: &str) -> Option<Arc<dyn Process>> {
self.processes
.get(identifier)
.map(|entry| Arc::clone(entry.value()))
}
}
pub async fn handle_wps_request(
State(state): State<WpsState>,
Query(params): Query<WpsRequest>,
) -> Result<Response, ServiceError> {
if let Some(ref service) = params.service {
if service.to_uppercase() != "WPS" {
return Err(ServiceError::InvalidParameter(
"SERVICE".to_string(),
format!("Expected 'WPS', got '{}'", service),
));
}
}
match params.request.to_uppercase().as_str() {
"GETCAPABILITIES" => {
let version = params.version.as_deref().unwrap_or("2.0.0");
capabilities::handle_get_capabilities(&state, version).await
}
"DESCRIBEPROCESS" => {
let version = params.version.as_deref().unwrap_or("2.0.0");
processes::handle_describe_process(&state, version, ¶ms.params).await
}
"EXECUTE" => {
let version = params.version.as_deref().unwrap_or("2.0.0");
processes::handle_execute(&state, version, ¶ms.params).await
}
_ => Err(ServiceError::UnsupportedOperation(params.request.clone())),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_wps_state_creation() {
let info = ServiceInfo {
title: "Test WPS".to_string(),
abstract_text: Some("Test service".to_string()),
provider: "COOLJAPAN OU".to_string(),
service_url: "http://localhost/wps".to_string(),
versions: vec!["2.0.0".to_string()],
};
let state = WpsState::new(info);
assert_eq!(state.service_info.title, "Test WPS");
assert!(!state.processes.is_empty());
}
}