mod file;
#[cfg(test)]
#[path = "mod_tests.rs"]
mod tests;
pub use file::FileStateStore;
use std::io;
use thiserror::Error;
use crate::network::AdapterSnapshot;
#[derive(Debug, Clone)]
pub enum LoadResult {
Loaded(Vec<AdapterSnapshot>),
NotFound,
Corrupted {
reason: String,
},
}
impl LoadResult {
#[must_use]
pub fn into_snapshots(self) -> Vec<AdapterSnapshot> {
match self {
Self::Loaded(snapshots) => snapshots,
Self::NotFound | Self::Corrupted { .. } => Vec::new(),
}
}
#[must_use]
pub const fn is_loaded(&self) -> bool {
matches!(self, Self::Loaded(_))
}
}
#[derive(Debug, Error)]
pub enum StateError {
#[error("Failed to write state file: {0}")]
Write(#[source] io::Error),
#[error("Failed to serialize state: {0}")]
Serialize(#[source] serde_json::Error),
}
pub trait StateStore: Send + Sync {
fn load(&self) -> LoadResult;
fn save(
&self,
snapshots: &[AdapterSnapshot],
) -> impl std::future::Future<Output = Result<(), StateError>> + Send;
}
#[cfg(test)]
pub mod mock {
use super::*;
use std::sync::RwLock;
#[derive(Debug)]
pub struct MockStateStore {
load_result: LoadResult,
saved: RwLock<Option<Vec<AdapterSnapshot>>>,
}
impl MockStateStore {
#[must_use]
pub fn with_loaded(snapshots: Vec<AdapterSnapshot>) -> Self {
Self {
load_result: LoadResult::Loaded(snapshots),
saved: RwLock::new(None),
}
}
#[must_use]
pub fn not_found() -> Self {
Self {
load_result: LoadResult::NotFound,
saved: RwLock::new(None),
}
}
#[must_use]
pub fn corrupted(reason: impl Into<String>) -> Self {
Self {
load_result: LoadResult::Corrupted {
reason: reason.into(),
},
saved: RwLock::new(None),
}
}
#[must_use]
pub fn saved_snapshots(&self) -> Option<Vec<AdapterSnapshot>> {
self.saved.read().unwrap().clone()
}
}
impl StateStore for MockStateStore {
fn load(&self) -> LoadResult {
self.load_result.clone()
}
async fn save(&self, snapshots: &[AdapterSnapshot]) -> Result<(), StateError> {
*self.saved.write().unwrap() = Some(snapshots.to_vec());
Ok(())
}
}
}