111 lines
3.4 KiB
Rust
111 lines
3.4 KiB
Rust
|
|
use std::path::Path;
|
||
|
|
|
||
|
|
use serde::{Deserialize, Serialize};
|
||
|
|
|
||
|
|
use crate::{RuntimeState, RuntimeSummary};
|
||
|
|
|
||
|
|
pub const SNAPSHOT_FORMAT_VERSION: u32 = 1;
|
||
|
|
|
||
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||
|
|
pub struct RuntimeSnapshotSource {
|
||
|
|
#[serde(default)]
|
||
|
|
pub source_fixture_id: Option<String>,
|
||
|
|
#[serde(default)]
|
||
|
|
pub description: Option<String>,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||
|
|
pub struct RuntimeSnapshotDocument {
|
||
|
|
pub format_version: u32,
|
||
|
|
pub snapshot_id: String,
|
||
|
|
#[serde(default)]
|
||
|
|
pub source: RuntimeSnapshotSource,
|
||
|
|
pub state: RuntimeState,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl RuntimeSnapshotDocument {
|
||
|
|
pub fn summary(&self) -> RuntimeSummary {
|
||
|
|
RuntimeSummary::from_state(&self.state)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
pub fn validate_runtime_snapshot_document(
|
||
|
|
document: &RuntimeSnapshotDocument,
|
||
|
|
) -> Result<(), String> {
|
||
|
|
if document.format_version != SNAPSHOT_FORMAT_VERSION {
|
||
|
|
return Err(format!(
|
||
|
|
"unsupported snapshot format_version {} (expected {})",
|
||
|
|
document.format_version, SNAPSHOT_FORMAT_VERSION
|
||
|
|
));
|
||
|
|
}
|
||
|
|
if document.snapshot_id.trim().is_empty() {
|
||
|
|
return Err("snapshot_id must not be empty".to_string());
|
||
|
|
}
|
||
|
|
document.state.validate()
|
||
|
|
}
|
||
|
|
|
||
|
|
pub fn load_runtime_snapshot_document(
|
||
|
|
path: &Path,
|
||
|
|
) -> Result<RuntimeSnapshotDocument, Box<dyn std::error::Error>> {
|
||
|
|
let text = std::fs::read_to_string(path)?;
|
||
|
|
Ok(serde_json::from_str(&text)?)
|
||
|
|
}
|
||
|
|
|
||
|
|
pub fn save_runtime_snapshot_document(
|
||
|
|
path: &Path,
|
||
|
|
document: &RuntimeSnapshotDocument,
|
||
|
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||
|
|
validate_runtime_snapshot_document(document)
|
||
|
|
.map_err(|err| format!("invalid runtime snapshot document: {err}"))?;
|
||
|
|
let bytes = serde_json::to_vec_pretty(document)?;
|
||
|
|
if let Some(parent) = path.parent() {
|
||
|
|
std::fs::create_dir_all(parent)?;
|
||
|
|
}
|
||
|
|
std::fs::write(path, bytes)?;
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
|
||
|
|
#[cfg(test)]
|
||
|
|
mod tests {
|
||
|
|
use super::*;
|
||
|
|
use crate::{CalendarPoint, RuntimeServiceState};
|
||
|
|
use std::collections::BTreeMap;
|
||
|
|
|
||
|
|
fn snapshot() -> RuntimeSnapshotDocument {
|
||
|
|
RuntimeSnapshotDocument {
|
||
|
|
format_version: SNAPSHOT_FORMAT_VERSION,
|
||
|
|
snapshot_id: "snapshot-smoke".to_string(),
|
||
|
|
source: RuntimeSnapshotSource {
|
||
|
|
source_fixture_id: Some("fixture-smoke".to_string()),
|
||
|
|
description: Some("test snapshot".to_string()),
|
||
|
|
},
|
||
|
|
state: RuntimeState {
|
||
|
|
calendar: CalendarPoint {
|
||
|
|
year: 1830,
|
||
|
|
month_slot: 0,
|
||
|
|
phase_slot: 0,
|
||
|
|
tick_slot: 0,
|
||
|
|
},
|
||
|
|
world_flags: BTreeMap::new(),
|
||
|
|
companies: Vec::new(),
|
||
|
|
event_runtime_records: Vec::new(),
|
||
|
|
service_state: RuntimeServiceState::default(),
|
||
|
|
},
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn validates_snapshot_document() {
|
||
|
|
let document = snapshot();
|
||
|
|
assert!(validate_runtime_snapshot_document(&document).is_ok());
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn roundtrips_snapshot_json() {
|
||
|
|
let document = snapshot();
|
||
|
|
let value = serde_json::to_string_pretty(&document).expect("snapshot should serialize");
|
||
|
|
let reparsed: RuntimeSnapshotDocument =
|
||
|
|
serde_json::from_str(&value).expect("snapshot should deserialize");
|
||
|
|
assert_eq!(document, reparsed);
|
||
|
|
}
|
||
|
|
}
|