1pub mod capabilities;
34pub mod database;
35pub mod features;
36pub mod transactions;
37
38use crate::error::{ServiceError, ServiceResult};
39use axum::{
40 extract::{Query, State},
41 response::Response,
42};
43use serde::Deserialize;
44use std::sync::Arc;
45
46#[derive(Clone)]
48pub struct WfsState {
49 pub service_info: Arc<ServiceInfo>,
51 pub feature_types: Arc<dashmap::DashMap<String, FeatureTypeInfo>>,
53 pub transactions_enabled: bool,
55}
56
57#[derive(Debug, Clone)]
59pub struct ServiceInfo {
60 pub title: String,
62 pub abstract_text: Option<String>,
64 pub provider: String,
66 pub service_url: String,
68 pub versions: Vec<String>,
70}
71
72#[derive(Debug, Clone)]
74pub struct FeatureTypeInfo {
75 pub name: String,
77 pub title: String,
79 pub abstract_text: Option<String>,
81 pub default_crs: String,
83 pub other_crs: Vec<String>,
85 pub bbox: Option<(f64, f64, f64, f64)>,
87 pub source: FeatureSource,
89}
90
91#[derive(Debug, Clone)]
93pub enum FeatureSource {
94 File(std::path::PathBuf),
96 Database(String),
98 DatabaseSource(database::DatabaseSource),
100 Memory(Vec<geojson::Feature>),
102}
103
104pub use database::{
106 BboxFilter, CacheStats, CountCacheConfig, CountResult, CqlFilter, DatabaseFeatureCounter,
107 DatabaseSource, DatabaseType,
108};
109
110#[derive(Debug, Deserialize)]
112#[serde(rename_all = "UPPERCASE")]
113pub struct WfsRequest {
114 pub service: Option<String>,
116 pub version: Option<String>,
118 pub request: String,
120 #[serde(flatten)]
122 pub params: serde_json::Value,
123}
124
125impl WfsState {
126 pub fn new(service_info: ServiceInfo) -> Self {
128 Self {
129 service_info: Arc::new(service_info),
130 feature_types: Arc::new(dashmap::DashMap::new()),
131 transactions_enabled: false,
132 }
133 }
134
135 pub fn add_feature_type(&self, info: FeatureTypeInfo) -> ServiceResult<()> {
137 self.feature_types.insert(info.name.clone(), info);
138 Ok(())
139 }
140
141 pub fn get_feature_type(&self, name: &str) -> Option<FeatureTypeInfo> {
143 self.feature_types
144 .get(name)
145 .map(|entry| entry.value().clone())
146 }
147
148 pub fn enable_transactions(&mut self) {
150 self.transactions_enabled = true;
151 }
152}
153
154pub async fn handle_wfs_request(
156 State(state): State<WfsState>,
157 Query(params): Query<WfsRequest>,
158) -> Result<Response, ServiceError> {
159 if let Some(ref service) = params.service {
161 if service.to_uppercase() != "WFS" {
162 return Err(ServiceError::InvalidParameter(
163 "SERVICE".to_string(),
164 format!("Expected 'WFS', got '{}'", service),
165 ));
166 }
167 }
168
169 match params.request.to_uppercase().as_str() {
171 "GETCAPABILITIES" => {
172 let version = params.version.as_deref().unwrap_or("2.0.0");
173 capabilities::handle_get_capabilities(&state, version).await
174 }
175 "DESCRIBEFEATURETYPE" => {
176 let version = params.version.as_deref().unwrap_or("2.0.0");
177 features::handle_describe_feature_type(&state, version, ¶ms.params).await
178 }
179 "GETFEATURE" => {
180 let version = params.version.as_deref().unwrap_or("2.0.0");
181 features::handle_get_feature(&state, version, ¶ms.params).await
182 }
183 "TRANSACTION" => {
184 if !state.transactions_enabled {
185 return Err(ServiceError::UnsupportedOperation(
186 "Transactions not enabled".to_string(),
187 ));
188 }
189 let version = params.version.as_deref().unwrap_or("2.0.0");
190 transactions::handle_transaction(&state, version, ¶ms.params).await
191 }
192 _ => Err(ServiceError::UnsupportedOperation(params.request.clone())),
193 }
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199
200 #[test]
201 fn test_wfs_state_creation() {
202 let info = ServiceInfo {
203 title: "Test WFS".to_string(),
204 abstract_text: Some("Test service".to_string()),
205 provider: "COOLJAPAN OU".to_string(),
206 service_url: "http://localhost/wfs".to_string(),
207 versions: vec!["2.0.0".to_string()],
208 };
209
210 let state = WfsState::new(info);
211 assert_eq!(state.service_info.title, "Test WFS");
212 assert!(!state.transactions_enabled);
213 }
214
215 #[test]
216 fn test_add_feature_type() {
217 let info = ServiceInfo {
218 title: "Test WFS".to_string(),
219 abstract_text: None,
220 provider: "COOLJAPAN OU".to_string(),
221 service_url: "http://localhost/wfs".to_string(),
222 versions: vec!["2.0.0".to_string()],
223 };
224
225 let state = WfsState::new(info);
226
227 let feature_type = FeatureTypeInfo {
228 name: "test_layer".to_string(),
229 title: "Test Layer".to_string(),
230 abstract_text: None,
231 default_crs: "EPSG:4326".to_string(),
232 other_crs: vec![],
233 bbox: Some((-180.0, -90.0, 180.0, 90.0)),
234 source: FeatureSource::Memory(vec![]),
235 };
236
237 assert!(
238 state.add_feature_type(feature_type).is_ok(),
239 "Failed to add feature type"
240 );
241
242 let retrieved = state.get_feature_type("test_layer");
243 assert!(retrieved.is_some());
244 assert_eq!(
245 retrieved.as_ref().map(|ft| &ft.name),
246 Some(&"test_layer".to_string())
247 );
248 }
249}