1use bytes::Bytes;
6use serde::de::Error;
7use serde::{Deserialize, Serialize};
8use std::time::{Duration, SystemTime};
9
10pub use spatio_types::bbox::{
11 BoundingBox2D, BoundingBox3D, TemporalBoundingBox2D, TemporalBoundingBox3D,
12};
13pub use spatio_types::point::{Point3d, TemporalPoint, TemporalPoint3D};
14pub use spatio_types::polygon::{Polygon3D, PolygonDynamic, PolygonDynamic3D};
15pub use spatio_types::trajectory::{Trajectory, Trajectory3D};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
19#[serde(rename_all = "snake_case")]
20pub enum SyncPolicy {
21 Never,
22 #[default]
23 EverySecond,
24 Always,
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
29#[serde(rename_all = "snake_case")]
30pub enum SyncMode {
31 #[default]
32 All,
33 Data,
34}
35
36#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
38#[serde(deny_unknown_fields)]
39pub struct Config {
40 #[serde(default = "Config::default_sync_policy")]
41 pub sync_policy: SyncPolicy,
42
43 #[serde(default)]
44 pub default_ttl_seconds: Option<f64>,
45
46 #[serde(default)]
47 pub sync_mode: SyncMode,
48
49 #[serde(default = "Config::default_sync_batch_size")]
50 pub sync_batch_size: usize,
51
52 #[cfg(feature = "time-index")]
53 #[serde(default)]
54 pub history_capacity: Option<usize>,
55
56 #[cfg(feature = "snapshot")]
57 #[serde(default)]
58 pub snapshot_auto_ops: Option<usize>,
59}
60
61impl Config {
62 const fn default_sync_batch_size() -> usize {
63 1
64 }
65
66 const fn default_sync_policy() -> SyncPolicy {
67 SyncPolicy::EverySecond
68 }
69
70 #[cfg(feature = "snapshot")]
71 pub fn with_snapshot_auto_ops(mut self, ops: usize) -> Self {
72 self.snapshot_auto_ops = Some(ops);
73 self
74 }
75
76 pub fn with_default_ttl(mut self, ttl: Duration) -> Self {
77 let ttl_secs = ttl.as_secs();
78
79 if ttl_secs > 365 * 24 * 3600 {
80 log::warn!(
81 "TTL of {} days is very large. This may indicate a misconfiguration.",
82 ttl_secs / (24 * 3600)
83 );
84 } else if ttl_secs < 60 {
85 log::warn!(
86 "TTL of {} seconds is very short. Consider if this is intentional.",
87 ttl_secs
88 );
89 }
90
91 self.default_ttl_seconds = Some(ttl.as_secs_f64());
92 self
93 }
94
95 pub fn with_sync_policy(mut self, policy: SyncPolicy) -> Self {
96 self.sync_policy = policy;
97 self
98 }
99
100 pub fn with_sync_mode(mut self, mode: SyncMode) -> Self {
101 self.sync_mode = mode;
102 self
103 }
104
105 pub fn with_sync_batch_size(mut self, batch_size: usize) -> Self {
106 assert!(batch_size > 0, "Sync batch size must be greater than zero");
107 self.sync_batch_size = batch_size;
108 self
109 }
110
111 #[cfg(feature = "time-index")]
112 pub fn with_history_capacity(mut self, capacity: usize) -> Self {
113 assert!(capacity > 0, "History capacity must be greater than zero");
114
115 if capacity > 100_000 {
116 log::warn!(
117 "History capacity of {} is very large and may consume significant memory. \
118 Each entry stores key + value + timestamp.",
119 capacity
120 );
121 }
122
123 self.history_capacity = Some(capacity);
124 self
125 }
126
127 pub fn default_ttl(&self) -> Option<Duration> {
128 self.default_ttl_seconds.and_then(|ttl| {
129 if ttl.is_finite() && ttl > 0.0 && ttl <= u64::MAX as f64 {
130 Some(Duration::from_secs_f64(ttl))
131 } else {
132 None
133 }
134 })
135 }
136
137 pub fn validate(&self) -> Result<(), String> {
138 if let Some(ttl) = self.default_ttl_seconds {
139 if !ttl.is_finite() {
140 return Err("Default TTL must be finite (not NaN or infinity)".to_string());
141 }
142 if ttl <= 0.0 {
143 return Err("Default TTL must be positive".to_string());
144 }
145 if ttl > u64::MAX as f64 {
146 return Err("Default TTL is too large".to_string());
147 }
148 }
149
150 #[cfg(feature = "time-index")]
151 if let Some(capacity) = self.history_capacity
152 && capacity == 0
153 {
154 return Err("History capacity must be greater than zero".to_string());
155 }
156
157 if self.sync_batch_size == 0 {
158 return Err("Sync batch size must be greater than zero".to_string());
159 }
160
161 Ok(())
162 }
163
164 pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
165 let config: Config = serde_json::from_str(json)?;
166 if let Err(e) = config.validate() {
167 return Err(Error::custom(e));
168 }
169 Ok(config)
170 }
171
172 pub fn to_json(&self) -> Result<String, serde_json::Error> {
173 serde_json::to_string_pretty(self)
174 }
175
176 #[cfg(feature = "toml")]
177 pub fn from_toml(toml_str: &str) -> Result<Self, toml::de::Error> {
178 let config: Config = toml::from_str(toml_str)?;
179 if let Err(e) = config.validate() {
180 return Err(toml::de::Error::custom(e));
181 }
182 Ok(config)
183 }
184
185 #[cfg(feature = "toml")]
186 pub fn to_toml(&self) -> Result<String, toml::ser::Error> {
187 toml::to_string_pretty(self)
188 }
189}
190
191impl Default for Config {
192 fn default() -> Self {
193 Self {
194 sync_policy: SyncPolicy::default(),
195 default_ttl_seconds: None,
196 sync_mode: SyncMode::default(),
197 sync_batch_size: Self::default_sync_batch_size(),
198 #[cfg(feature = "time-index")]
199 history_capacity: None,
200 #[cfg(feature = "snapshot")]
201 snapshot_auto_ops: None,
202 }
203 }
204}
205
206#[derive(Debug, Clone, Default)]
212pub struct SetOptions {
213 pub ttl: Option<Duration>,
215 pub expires_at: Option<SystemTime>,
217}
218
219impl SetOptions {
220 pub fn with_ttl(ttl: Duration) -> Self {
234 Self {
235 ttl: Some(ttl),
236 expires_at: None,
237 }
238 }
239
240 pub fn with_expiration(expires_at: SystemTime) -> Self {
247 Self {
248 ttl: None,
249 expires_at: Some(expires_at),
250 }
251 }
252
253 pub fn effective_expires_at(&self) -> Option<SystemTime> {
255 self.expires_at
256 .or_else(|| self.ttl.map(|ttl| SystemTime::now() + ttl))
257 }
258}
259
260#[derive(Debug, Clone)]
265pub struct DbItem {
266 pub value: Bytes,
268 pub created_at: SystemTime,
269 pub expires_at: Option<SystemTime>,
271}
272
273#[derive(Debug, Clone, PartialEq, Eq)]
275pub enum HistoryEventKind {
276 Set,
277 Delete,
278}
279
280#[derive(Debug, Clone)]
282pub struct HistoryEntry {
283 pub timestamp: SystemTime,
284 pub kind: HistoryEventKind,
285 pub value: Option<Bytes>,
286 pub expires_at: Option<SystemTime>,
287}
288
289impl DbItem {
290 pub fn new(value: impl Into<Bytes>) -> Self {
292 Self {
293 value: value.into(),
294 created_at: SystemTime::now(),
295 expires_at: None,
296 }
297 }
298
299 pub fn with_expiration(value: impl Into<Bytes>, expires_at: SystemTime) -> Self {
301 Self {
302 value: value.into(),
303 created_at: SystemTime::now(),
304 expires_at: Some(expires_at),
305 }
306 }
307
308 pub fn with_ttl(value: impl Into<Bytes>, ttl: Duration) -> Self {
310 let expires_at = SystemTime::now() + ttl;
311 Self::with_expiration(value, expires_at)
312 }
313
314 pub fn from_options(value: impl Into<Bytes>, options: Option<&SetOptions>) -> Self {
316 let value = value.into();
317
318 match options {
319 Some(opts) => {
320 let expires_at = opts.effective_expires_at();
321 Self {
322 value,
323 created_at: SystemTime::now(),
324 expires_at,
325 }
326 }
327 None => Self::new(value),
328 }
329 }
330
331 pub fn is_expired(&self) -> bool {
332 self.is_expired_at(SystemTime::now())
333 }
334
335 pub fn is_expired_at(&self, now: SystemTime) -> bool {
337 match self.expires_at {
338 Some(expires_at) => now >= expires_at,
339 None => false,
340 }
341 }
342
343 pub fn remaining_ttl(&self) -> Option<Duration> {
345 self.remaining_ttl_at(SystemTime::now())
346 }
347
348 pub fn remaining_ttl_at(&self, now: SystemTime) -> Option<Duration> {
350 match self.expires_at {
351 Some(expires_at) => {
352 if now < expires_at {
353 expires_at.duration_since(now).ok()
354 } else {
355 Some(Duration::ZERO)
356 }
357 }
358 None => None,
359 }
360 }
361}
362
363#[derive(Debug, Clone, Default, Serialize, Deserialize)]
365pub struct DbStats {
366 pub key_count: usize,
368 pub expired_count: u64,
370 pub operations_count: u64,
372 pub size_bytes: usize,
374}
375
376impl DbStats {
377 pub fn new() -> Self {
379 Self::default()
380 }
381
382 pub fn record_operation(&mut self) {
384 self.operations_count += 1;
385 }
386
387 pub fn record_expired(&mut self, count: u64) {
389 self.expired_count += count;
390 }
391
392 pub fn set_key_count(&mut self, count: usize) {
394 self.key_count = count;
395 }
396
397 pub fn set_size_bytes(&mut self, bytes: usize) {
399 self.size_bytes = bytes;
400 }
401}
402
403#[cfg(test)]
404mod tests {
405 use super::*;
406 use std::time::Duration;
407
408 #[test]
409 fn test_config_default() {
410 let config = Config::default();
411 assert_eq!(config.sync_policy, SyncPolicy::EverySecond);
412 assert_eq!(config.sync_mode, SyncMode::All);
413 assert_eq!(config.sync_batch_size, 1);
414 assert!(config.default_ttl_seconds.is_none());
415 #[cfg(feature = "time-index")]
416 assert!(config.history_capacity.is_none());
417 }
418
419 #[test]
420 fn test_config_serialization() {
421 let config = Config::default()
422 .with_default_ttl(Duration::from_secs(3600))
423 .with_sync_policy(SyncPolicy::Always)
424 .with_sync_mode(SyncMode::Data)
425 .with_sync_batch_size(8);
426
427 let json = config.to_json().unwrap();
428 let deserialized: Config = Config::from_json(&json).unwrap();
429
430 assert_eq!(deserialized.sync_policy, SyncPolicy::Always);
431 assert_eq!(deserialized.sync_mode, SyncMode::Data);
432 assert_eq!(deserialized.sync_batch_size, 8);
433 assert_eq!(
434 deserialized.default_ttl().unwrap(),
435 Duration::from_secs(3600)
436 );
437 }
438
439 #[cfg(feature = "time-index")]
440 #[test]
441 fn test_config_history_capacity() {
442 let config = Config::default().with_history_capacity(5);
443 assert_eq!(config.history_capacity, Some(5));
444 }
445
446 #[test]
447 fn test_set_options() {
448 let ttl_opts = SetOptions::with_ttl(Duration::from_secs(60));
449 assert!(ttl_opts.ttl.is_some());
450 assert!(ttl_opts.expires_at.is_none());
451
452 let exp_opts = SetOptions::with_expiration(SystemTime::now());
453 assert!(exp_opts.ttl.is_none());
454 assert!(exp_opts.expires_at.is_some());
455 }
456
457 #[test]
458 fn test_db_item_expiration() {
459 let item = DbItem::new("test");
460 assert!(!item.is_expired());
461
462 let past = SystemTime::now() - Duration::from_secs(60);
463 let expired_item = DbItem::with_expiration("test", past);
464 assert!(expired_item.is_expired());
465
466 let future = SystemTime::now() + Duration::from_secs(60);
467 let future_item = DbItem::with_expiration("test", future);
468 assert!(!future_item.is_expired());
469 }
470
471 #[test]
472 fn test_db_item_ttl() {
473 let item = DbItem::with_ttl("test", Duration::from_secs(60));
474 let remaining = item.remaining_ttl().unwrap();
475
476 assert!(remaining.as_secs() >= 59 && remaining.as_secs() <= 60);
478 }
479
480 #[test]
481 fn test_db_item_from_options() {
482 let opts = SetOptions::with_ttl(Duration::from_secs(300));
483 let item = DbItem::from_options("test", Some(&opts));
484
485 assert!(item.expires_at.is_some());
486 assert!(!item.is_expired());
487 }
488
489 #[test]
490 fn test_db_stats() {
491 let mut stats = DbStats::new();
492 assert_eq!(stats.operations_count, 0);
493
494 stats.record_operation();
495 assert_eq!(stats.operations_count, 1);
496
497 stats.record_expired(5);
498 assert_eq!(stats.expired_count, 5);
499
500 stats.set_key_count(100);
501 assert_eq!(stats.key_count, 100);
502 }
503
504 #[test]
505 fn test_config_validation() {
506 let config = Config::default();
507 assert!(config.validate().is_ok());
508
509 let config = Config {
510 default_ttl_seconds: Some(-1.0),
511 ..Default::default()
512 };
513 assert!(config.validate().is_err());
514 }
515
516 #[test]
517 fn test_config_ttl_validation() {
518 let mut config = Config::default();
519 assert!(config.validate().is_ok());
520
521 config = Config {
523 default_ttl_seconds: Some(60.0),
524 ..Default::default()
525 };
526 assert!(config.validate().is_ok());
527
528 config.default_ttl_seconds = Some(-1.0);
530 assert!(config.validate().is_err());
531
532 config.default_ttl_seconds = Some(0.0);
534 assert!(config.validate().is_err());
535
536 config.default_ttl_seconds = Some(f64::NAN);
538 assert!(config.validate().is_err());
539
540 config.default_ttl_seconds = Some(f64::INFINITY);
542 assert!(config.validate().is_err());
543
544 config.default_ttl_seconds = Some(f64::NEG_INFINITY);
546 assert!(config.validate().is_err());
547
548 config.default_ttl_seconds = Some(1e20);
550 assert!(config.validate().is_err());
551 }
552
553 #[test]
554 fn test_config_default_ttl_safe_conversion() {
555 let mut config = Config {
556 default_ttl_seconds: Some(60.0),
557 ..Default::default()
558 };
559
560 assert!(config.default_ttl().is_some());
562
563 config.default_ttl_seconds = Some(f64::NAN);
565 assert!(config.default_ttl().is_none());
566
567 config.default_ttl_seconds = Some(f64::INFINITY);
569 assert!(config.default_ttl().is_none());
570
571 config.default_ttl_seconds = Some(-1.0);
573 assert!(config.default_ttl().is_none());
574
575 config.default_ttl_seconds = Some(1e20);
577 assert!(config.default_ttl().is_none());
578
579 config.default_ttl_seconds = Some(0.0);
581 assert!(config.default_ttl().is_none());
582 }
583}