use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;
use std::sync::RwLock;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
pub enum SyncMode {
#[default]
FullHistory,
LatestOnly,
WindowedHistory {
window_seconds: u64,
},
}
impl SyncMode {
#[inline]
pub fn is_full_history(&self) -> bool {
matches!(self, Self::FullHistory)
}
#[inline]
pub fn is_latest_only(&self) -> bool {
matches!(self, Self::LatestOnly)
}
#[inline]
pub fn is_windowed(&self) -> bool {
matches!(self, Self::WindowedHistory { .. })
}
pub fn window_seconds(&self) -> Option<u64> {
match self {
Self::WindowedHistory { window_seconds } => Some(*window_seconds),
_ => None,
}
}
pub fn default_for_collection(collection: &str) -> Self {
match collection {
"beacons" | "platforms" | "tracks" | "nodes" | "cells" => Self::LatestOnly,
"node_states" | "squad_summaries" | "platoon_summaries" | "company_summaries" => {
Self::LatestOnly
}
"contact_reports" | "commands" | "audit_logs" | "alerts" => Self::FullHistory,
"track_history" | "capability_history" => Self::WindowedHistory {
window_seconds: 300,
},
_ => Self::FullHistory,
}
}
}
impl fmt::Display for SyncMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::FullHistory => write!(f, "FullHistory"),
Self::LatestOnly => write!(f, "LatestOnly"),
Self::WindowedHistory { window_seconds } => {
write!(f, "WindowedHistory({}s)", window_seconds)
}
}
}
}
#[derive(Debug, Default)]
pub struct SyncModeRegistry {
overrides: RwLock<HashMap<String, SyncMode>>,
}
impl SyncModeRegistry {
pub fn new() -> Self {
Self {
overrides: RwLock::new(HashMap::new()),
}
}
pub fn with_defaults() -> Self {
let registry = Self::new();
let defaults = [
("beacons", SyncMode::LatestOnly),
("platforms", SyncMode::LatestOnly),
("tracks", SyncMode::LatestOnly),
("nodes", SyncMode::LatestOnly),
("cells", SyncMode::LatestOnly),
("node_states", SyncMode::LatestOnly),
("squad_summaries", SyncMode::LatestOnly),
("platoon_summaries", SyncMode::LatestOnly),
("company_summaries", SyncMode::LatestOnly),
("contact_reports", SyncMode::FullHistory),
("commands", SyncMode::FullHistory),
("audit_logs", SyncMode::FullHistory),
("alerts", SyncMode::FullHistory),
(
"track_history",
SyncMode::WindowedHistory {
window_seconds: 300,
},
),
(
"capability_history",
SyncMode::WindowedHistory {
window_seconds: 300,
},
),
];
{
let mut overrides = registry
.overrides
.write()
.unwrap_or_else(|e| e.into_inner());
for (collection, mode) in defaults {
overrides.insert(collection.to_string(), mode);
}
}
registry
}
pub fn get(&self, collection: &str) -> SyncMode {
self.overrides
.read()
.unwrap_or_else(|e| e.into_inner())
.get(collection)
.copied()
.unwrap_or_else(|| SyncMode::default_for_collection(collection))
}
pub fn set(&self, collection: &str, mode: SyncMode) {
self.overrides
.write()
.unwrap_or_else(|e| e.into_inner())
.insert(collection.to_string(), mode);
}
pub fn remove(&self, collection: &str) -> Option<SyncMode> {
self.overrides
.write()
.unwrap_or_else(|e| e.into_inner())
.remove(collection)
}
pub fn all_overrides(&self) -> HashMap<String, SyncMode> {
self.overrides
.read()
.unwrap_or_else(|e| e.into_inner())
.clone()
}
#[inline]
pub fn is_latest_only(&self, collection: &str) -> bool {
self.get(collection).is_latest_only()
}
#[inline]
pub fn is_full_history(&self, collection: &str) -> bool {
self.get(collection).is_full_history()
}
}
impl Clone for SyncModeRegistry {
fn clone(&self) -> Self {
Self {
overrides: RwLock::new(
self.overrides
.read()
.unwrap_or_else(|e| e.into_inner())
.clone(),
),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sync_mode_defaults() {
assert_eq!(
SyncMode::default_for_collection("beacons"),
SyncMode::LatestOnly
);
assert_eq!(
SyncMode::default_for_collection("platforms"),
SyncMode::LatestOnly
);
assert_eq!(
SyncMode::default_for_collection("tracks"),
SyncMode::LatestOnly
);
assert_eq!(
SyncMode::default_for_collection("node_states"),
SyncMode::LatestOnly
);
assert_eq!(
SyncMode::default_for_collection("squad_summaries"),
SyncMode::LatestOnly
);
assert_eq!(
SyncMode::default_for_collection("platoon_summaries"),
SyncMode::LatestOnly
);
assert_eq!(
SyncMode::default_for_collection("company_summaries"),
SyncMode::LatestOnly
);
assert_eq!(
SyncMode::default_for_collection("commands"),
SyncMode::FullHistory
);
assert_eq!(
SyncMode::default_for_collection("contact_reports"),
SyncMode::FullHistory
);
assert_eq!(
SyncMode::default_for_collection("track_history"),
SyncMode::WindowedHistory {
window_seconds: 300
}
);
assert_eq!(
SyncMode::default_for_collection("unknown"),
SyncMode::FullHistory
);
}
#[test]
fn test_sync_mode_predicates() {
assert!(SyncMode::FullHistory.is_full_history());
assert!(!SyncMode::FullHistory.is_latest_only());
assert!(!SyncMode::FullHistory.is_windowed());
assert!(SyncMode::LatestOnly.is_latest_only());
assert!(!SyncMode::LatestOnly.is_full_history());
assert!(!SyncMode::LatestOnly.is_windowed());
let windowed = SyncMode::WindowedHistory { window_seconds: 60 };
assert!(windowed.is_windowed());
assert!(!windowed.is_full_history());
assert!(!windowed.is_latest_only());
assert_eq!(windowed.window_seconds(), Some(60));
}
#[test]
fn test_sync_mode_display() {
assert_eq!(SyncMode::FullHistory.to_string(), "FullHistory");
assert_eq!(SyncMode::LatestOnly.to_string(), "LatestOnly");
assert_eq!(
SyncMode::WindowedHistory {
window_seconds: 300
}
.to_string(),
"WindowedHistory(300s)"
);
}
#[test]
fn test_sync_mode_registry() {
let registry = SyncModeRegistry::with_defaults();
assert_eq!(registry.get("beacons"), SyncMode::LatestOnly);
assert_eq!(registry.get("commands"), SyncMode::FullHistory);
registry.set("beacons", SyncMode::FullHistory);
assert_eq!(registry.get("beacons"), SyncMode::FullHistory);
registry.remove("beacons");
assert_eq!(registry.get("beacons"), SyncMode::LatestOnly);
}
#[test]
fn test_sync_mode_registry_convenience_methods() {
let registry = SyncModeRegistry::with_defaults();
assert!(registry.is_latest_only("beacons"));
assert!(!registry.is_latest_only("commands"));
assert!(registry.is_full_history("commands"));
assert!(!registry.is_full_history("beacons"));
}
#[test]
fn test_sync_mode_serialization() {
let mode = SyncMode::LatestOnly;
let json = serde_json::to_string(&mode).unwrap();
assert_eq!(json, "\"LatestOnly\"");
let deserialized: SyncMode = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, SyncMode::LatestOnly);
let windowed = SyncMode::WindowedHistory {
window_seconds: 300,
};
let json = serde_json::to_string(&windowed).unwrap();
let deserialized: SyncMode = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, windowed);
}
#[test]
fn test_sync_mode_default() {
assert_eq!(SyncMode::default(), SyncMode::FullHistory);
}
}