spatio/
config.rs

1//! Configuration and database settings for Spatio
2//!
3//! This module provides configuration types and re-exports spatial types
4//! from the `spatio-types` crate for convenience.
5use bytes::Bytes;
6use serde::de::Error;
7use std::time::SystemTime;
8
9pub use spatio_types::bbox::{
10    BoundingBox2D, BoundingBox3D, TemporalBoundingBox2D, TemporalBoundingBox3D,
11};
12pub use spatio_types::point::{Point3d, TemporalPoint, TemporalPoint3D};
13pub use spatio_types::polygon::{Polygon3D, PolygonDynamic, PolygonDynamic3D};
14
15pub use spatio_types::config::{SyncMode, SyncPolicy};
16
17/// Database configuration
18#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
19#[serde(deny_unknown_fields)]
20pub struct Config {
21    #[serde(default = "Config::default_sync_policy")]
22    pub sync_policy: SyncPolicy,
23
24    #[serde(default)]
25    pub sync_mode: SyncMode,
26
27    #[serde(default = "Config::default_sync_batch_size")]
28    pub sync_batch_size: usize,
29
30    #[cfg(feature = "time-index")]
31    #[serde(default)]
32    pub history_capacity: Option<usize>,
33
34    /// Buffer capacity per object for recent history in ColdState
35    #[serde(default = "Config::default_buffer_capacity")]
36    pub buffer_capacity: usize,
37
38    /// Persistence configuration
39    #[serde(default)]
40    pub persistence: PersistenceConfig,
41}
42
43/// Configuration for data persistence and durability
44#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
45#[serde(deny_unknown_fields)]
46pub struct PersistenceConfig {
47    /// Number of writes to buffer in memory before flushing to disk
48    #[serde(default = "PersistenceConfig::default_buffer_size")]
49    pub buffer_size: usize,
50}
51
52impl PersistenceConfig {
53    const fn default_buffer_size() -> usize {
54        512
55    }
56}
57
58impl Default for PersistenceConfig {
59    fn default() -> Self {
60        Self {
61            buffer_size: Self::default_buffer_size(),
62        }
63    }
64}
65
66impl Config {
67    const fn default_sync_batch_size() -> usize {
68        1
69    }
70
71    const fn default_sync_policy() -> SyncPolicy {
72        SyncPolicy::EverySecond
73    }
74
75    pub fn with_sync_policy(mut self, policy: SyncPolicy) -> Self {
76        self.sync_policy = policy;
77        self
78    }
79
80    pub fn with_sync_mode(mut self, mode: SyncMode) -> Self {
81        self.sync_mode = mode;
82        self
83    }
84
85    pub fn with_sync_batch_size(mut self, batch_size: usize) -> Self {
86        assert!(batch_size > 0, "Sync batch size must be greater than zero");
87        self.sync_batch_size = batch_size;
88        self
89    }
90
91    #[cfg(feature = "time-index")]
92    pub fn with_history_capacity(mut self, capacity: usize) -> Self {
93        assert!(capacity > 0, "History capacity must be greater than zero");
94
95        if capacity > 100_000 {
96            log::warn!(
97                "History capacity of {} is very large and may consume significant memory. \
98                Each entry stores key + value + timestamp.",
99                capacity
100            );
101        }
102
103        self.history_capacity = Some(capacity);
104        self
105    }
106
107    const fn default_buffer_capacity() -> usize {
108        100
109    }
110
111    pub fn with_buffer_capacity(mut self, capacity: usize) -> Self {
112        assert!(capacity > 0, "Buffer capacity must be greater than zero");
113        self.buffer_capacity = capacity;
114        self
115    }
116
117    pub fn with_persistence(mut self, config: PersistenceConfig) -> Self {
118        self.persistence = config;
119        self
120    }
121
122    pub fn validate(&self) -> Result<(), String> {
123        #[cfg(feature = "time-index")]
124        if let Some(capacity) = self.history_capacity
125            && capacity == 0
126        {
127            return Err("History capacity must be greater than zero".to_string());
128        }
129
130        if self.sync_batch_size == 0 {
131            return Err("Sync batch size must be greater than zero".to_string());
132        }
133
134        Ok(())
135    }
136
137    pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
138        let config: Config = serde_json::from_str(json)?;
139        if let Err(e) = config.validate() {
140            return Err(Error::custom(e));
141        }
142        Ok(config)
143    }
144
145    pub fn to_json(&self) -> Result<String, serde_json::Error> {
146        serde_json::to_string_pretty(self)
147    }
148
149    #[cfg(feature = "toml")]
150    pub fn from_toml(toml_str: &str) -> Result<Self, toml::de::Error> {
151        let config: Config = toml::from_str(toml_str)?;
152        if let Err(e) = config.validate() {
153            return Err(toml::de::Error::custom(e));
154        }
155        Ok(config)
156    }
157
158    #[cfg(feature = "toml")]
159    pub fn to_toml(&self) -> Result<String, toml::ser::Error> {
160        toml::to_string_pretty(self)
161    }
162}
163
164impl Default for Config {
165    fn default() -> Self {
166        Self {
167            sync_policy: SyncPolicy::default(),
168            sync_mode: SyncMode::default(),
169            sync_batch_size: Self::default_sync_batch_size(),
170            #[cfg(feature = "time-index")]
171            history_capacity: None,
172            buffer_capacity: Self::default_buffer_capacity(),
173            persistence: PersistenceConfig::default(),
174        }
175    }
176}
177
178pub use spatio_types::config::SetOptions;
179
180/// Internal representation of a database item.
181#[derive(Debug, Clone)]
182pub struct DbItem {
183    /// The value bytes
184    pub value: Bytes,
185    pub created_at: SystemTime,
186}
187
188/// Operation types captured in history tracking.
189#[derive(Debug, Clone, PartialEq, Eq)]
190pub enum HistoryEventKind {
191    Set,
192    Delete,
193}
194
195/// Historical record for key mutations.
196#[derive(Debug, Clone)]
197pub struct HistoryEntry {
198    pub timestamp: SystemTime,
199    pub kind: HistoryEventKind,
200    pub value: Option<Bytes>,
201}
202
203impl DbItem {
204    /// Create a new item
205    pub fn new(value: impl Into<Bytes>) -> Self {
206        Self {
207            value: value.into(),
208            created_at: SystemTime::now(),
209        }
210    }
211
212    /// Create from SetOptions (timestamp can override created_at)
213    pub fn from_options(value: impl Into<Bytes>, options: Option<&SetOptions>) -> Self {
214        let value = value.into();
215        let created_at = options
216            .and_then(|o| o.timestamp)
217            .unwrap_or_else(SystemTime::now);
218        Self { value, created_at }
219    }
220}
221
222pub use spatio_types::stats::DbStats;
223
224#[cfg(test)]
225mod tests {
226    use super::*;
227
228    #[test]
229    fn test_config_default() {
230        let config = Config::default();
231        assert_eq!(config.sync_policy, SyncPolicy::EverySecond);
232        assert_eq!(config.sync_mode, SyncMode::All);
233        assert_eq!(config.sync_batch_size, 1);
234        #[cfg(feature = "time-index")]
235        assert!(config.history_capacity.is_none());
236    }
237
238    #[test]
239    fn test_config_serialization() {
240        let config = Config::default()
241            .with_sync_policy(SyncPolicy::Always)
242            .with_sync_mode(SyncMode::Data)
243            .with_sync_batch_size(8);
244
245        let json = config.to_json().unwrap();
246        let deserialized: Config = Config::from_json(&json).unwrap();
247
248        assert_eq!(deserialized.sync_policy, SyncPolicy::Always);
249        assert_eq!(deserialized.sync_mode, SyncMode::Data);
250        assert_eq!(deserialized.sync_batch_size, 8);
251    }
252
253    #[cfg(feature = "time-index")]
254    #[test]
255    fn test_config_history_capacity() {
256        let config = Config::default().with_history_capacity(5);
257        assert_eq!(config.history_capacity, Some(5));
258    }
259
260    #[test]
261    fn test_set_options() {
262        let opts = SetOptions::with_timestamp(SystemTime::now());
263        assert!(opts.timestamp.is_some());
264    }
265
266    #[test]
267    fn test_db_item() {
268        let item = DbItem::new("test");
269        assert!(!item.value.is_empty());
270    }
271
272    #[test]
273    fn test_db_stats() {
274        let mut stats = DbStats::new();
275        assert_eq!(stats.operations_count, 0);
276
277        stats.record_operation();
278        assert_eq!(stats.operations_count, 1);
279    }
280
281    #[test]
282    fn test_config_validation() {
283        let config = Config::default();
284        assert!(config.validate().is_ok());
285    }
286}