pub mod capabilities;
pub mod database;
pub mod features;
pub mod transactions;
use crate::error::{ServiceError, ServiceResult};
use axum::{
extract::{Query, State},
response::Response,
};
use serde::Deserialize;
use std::sync::Arc;
#[derive(Clone)]
pub struct WfsState {
pub service_info: Arc<ServiceInfo>,
pub feature_types: Arc<dashmap::DashMap<String, FeatureTypeInfo>>,
pub transactions_enabled: bool,
}
#[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>,
}
#[derive(Debug, Clone)]
pub struct FeatureTypeInfo {
pub name: String,
pub title: String,
pub abstract_text: Option<String>,
pub default_crs: String,
pub other_crs: Vec<String>,
pub bbox: Option<(f64, f64, f64, f64)>,
pub source: FeatureSource,
}
#[derive(Debug, Clone)]
pub enum FeatureSource {
File(std::path::PathBuf),
Database(String),
DatabaseSource(database::DatabaseSource),
Memory(Vec<geojson::Feature>),
}
pub use database::{
BboxFilter, CacheStats, CountCacheConfig, CountResult, CqlFilter, DatabaseFeatureCounter,
DatabaseSource, DatabaseType,
};
#[derive(Debug, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub struct WfsRequest {
pub service: Option<String>,
pub version: Option<String>,
pub request: String,
#[serde(flatten)]
pub params: serde_json::Value,
}
impl WfsState {
pub fn new(service_info: ServiceInfo) -> Self {
Self {
service_info: Arc::new(service_info),
feature_types: Arc::new(dashmap::DashMap::new()),
transactions_enabled: false,
}
}
pub fn add_feature_type(&self, info: FeatureTypeInfo) -> ServiceResult<()> {
self.feature_types.insert(info.name.clone(), info);
Ok(())
}
pub fn get_feature_type(&self, name: &str) -> Option<FeatureTypeInfo> {
self.feature_types
.get(name)
.map(|entry| entry.value().clone())
}
pub fn enable_transactions(&mut self) {
self.transactions_enabled = true;
}
}
pub async fn handle_wfs_request(
State(state): State<WfsState>,
Query(params): Query<WfsRequest>,
) -> Result<Response, ServiceError> {
if let Some(ref service) = params.service {
if service.to_uppercase() != "WFS" {
return Err(ServiceError::InvalidParameter(
"SERVICE".to_string(),
format!("Expected 'WFS', 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
}
"DESCRIBEFEATURETYPE" => {
let version = params.version.as_deref().unwrap_or("2.0.0");
features::handle_describe_feature_type(&state, version, ¶ms.params).await
}
"GETFEATURE" => {
let version = params.version.as_deref().unwrap_or("2.0.0");
features::handle_get_feature(&state, version, ¶ms.params).await
}
"TRANSACTION" => {
if !state.transactions_enabled {
return Err(ServiceError::UnsupportedOperation(
"Transactions not enabled".to_string(),
));
}
let version = params.version.as_deref().unwrap_or("2.0.0");
transactions::handle_transaction(&state, version, ¶ms.params).await
}
_ => Err(ServiceError::UnsupportedOperation(params.request.clone())),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_wfs_state_creation() {
let info = ServiceInfo {
title: "Test WFS".to_string(),
abstract_text: Some("Test service".to_string()),
provider: "COOLJAPAN OU".to_string(),
service_url: "http://localhost/wfs".to_string(),
versions: vec!["2.0.0".to_string()],
};
let state = WfsState::new(info);
assert_eq!(state.service_info.title, "Test WFS");
assert!(!state.transactions_enabled);
}
#[test]
fn test_add_feature_type() {
let info = ServiceInfo {
title: "Test WFS".to_string(),
abstract_text: None,
provider: "COOLJAPAN OU".to_string(),
service_url: "http://localhost/wfs".to_string(),
versions: vec!["2.0.0".to_string()],
};
let state = WfsState::new(info);
let feature_type = FeatureTypeInfo {
name: "test_layer".to_string(),
title: "Test Layer".to_string(),
abstract_text: None,
default_crs: "EPSG:4326".to_string(),
other_crs: vec![],
bbox: Some((-180.0, -90.0, 180.0, 90.0)),
source: FeatureSource::Memory(vec![]),
};
assert!(
state.add_feature_type(feature_type).is_ok(),
"Failed to add feature type"
);
let retrieved = state.get_feature_type("test_layer");
assert!(retrieved.is_some());
assert_eq!(
retrieved.as_ref().map(|ft| &ft.name),
Some(&"test_layer".to_string())
);
}
}