133 lines
4 KiB
Rust
133 lines
4 KiB
Rust
|
|
use std::path::Path;
|
||
|
|
|
||
|
|
use serde::{Deserialize, Serialize};
|
||
|
|
|
||
|
|
use crate::RuntimeState;
|
||
|
|
|
||
|
|
pub const STATE_DUMP_FORMAT_VERSION: u32 = 1;
|
||
|
|
|
||
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||
|
|
pub struct RuntimeStateDumpSource {
|
||
|
|
#[serde(default)]
|
||
|
|
pub description: Option<String>,
|
||
|
|
#[serde(default)]
|
||
|
|
pub source_binary: Option<String>,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||
|
|
pub struct RuntimeStateDumpDocument {
|
||
|
|
pub format_version: u32,
|
||
|
|
pub dump_id: String,
|
||
|
|
#[serde(default)]
|
||
|
|
pub source: RuntimeStateDumpSource,
|
||
|
|
pub state: RuntimeState,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||
|
|
pub struct RuntimeStateImport {
|
||
|
|
pub import_id: String,
|
||
|
|
pub description: Option<String>,
|
||
|
|
pub state: RuntimeState,
|
||
|
|
}
|
||
|
|
|
||
|
|
pub fn validate_runtime_state_dump_document(
|
||
|
|
document: &RuntimeStateDumpDocument,
|
||
|
|
) -> Result<(), String> {
|
||
|
|
if document.format_version != STATE_DUMP_FORMAT_VERSION {
|
||
|
|
return Err(format!(
|
||
|
|
"unsupported state dump format_version {} (expected {})",
|
||
|
|
document.format_version, STATE_DUMP_FORMAT_VERSION
|
||
|
|
));
|
||
|
|
}
|
||
|
|
if document.dump_id.trim().is_empty() {
|
||
|
|
return Err("dump_id must not be empty".to_string());
|
||
|
|
}
|
||
|
|
document.state.validate()
|
||
|
|
}
|
||
|
|
|
||
|
|
pub fn load_runtime_state_import(
|
||
|
|
path: &Path,
|
||
|
|
) -> Result<RuntimeStateImport, Box<dyn std::error::Error>> {
|
||
|
|
let text = std::fs::read_to_string(path)?;
|
||
|
|
load_runtime_state_import_from_str(
|
||
|
|
&text,
|
||
|
|
path.file_stem()
|
||
|
|
.and_then(|stem| stem.to_str())
|
||
|
|
.unwrap_or("runtime-state"),
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
pub fn load_runtime_state_import_from_str(
|
||
|
|
text: &str,
|
||
|
|
fallback_id: &str,
|
||
|
|
) -> Result<RuntimeStateImport, Box<dyn std::error::Error>> {
|
||
|
|
if let Ok(document) = serde_json::from_str::<RuntimeStateDumpDocument>(text) {
|
||
|
|
validate_runtime_state_dump_document(&document)
|
||
|
|
.map_err(|err| format!("invalid runtime state dump document: {err}"))?;
|
||
|
|
return Ok(RuntimeStateImport {
|
||
|
|
import_id: document.dump_id,
|
||
|
|
description: document.source.description,
|
||
|
|
state: document.state,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
let state: RuntimeState = serde_json::from_str(text)?;
|
||
|
|
state
|
||
|
|
.validate()
|
||
|
|
.map_err(|err| format!("invalid runtime state: {err}"))?;
|
||
|
|
Ok(RuntimeStateImport {
|
||
|
|
import_id: fallback_id.to_string(),
|
||
|
|
description: None,
|
||
|
|
state,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
#[cfg(test)]
|
||
|
|
mod tests {
|
||
|
|
use super::*;
|
||
|
|
use crate::{CalendarPoint, RuntimeServiceState};
|
||
|
|
use std::collections::BTreeMap;
|
||
|
|
|
||
|
|
fn state() -> RuntimeState {
|
||
|
|
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 loads_dump_document() {
|
||
|
|
let text = serde_json::to_string(&RuntimeStateDumpDocument {
|
||
|
|
format_version: STATE_DUMP_FORMAT_VERSION,
|
||
|
|
dump_id: "dump-smoke".to_string(),
|
||
|
|
source: RuntimeStateDumpSource {
|
||
|
|
description: Some("test dump".to_string()),
|
||
|
|
source_binary: None,
|
||
|
|
},
|
||
|
|
state: state(),
|
||
|
|
})
|
||
|
|
.expect("dump should serialize");
|
||
|
|
|
||
|
|
let import =
|
||
|
|
load_runtime_state_import_from_str(&text, "fallback").expect("dump should load");
|
||
|
|
assert_eq!(import.import_id, "dump-smoke");
|
||
|
|
assert_eq!(import.description.as_deref(), Some("test dump"));
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn loads_bare_runtime_state() {
|
||
|
|
let text = serde_json::to_string(&state()).expect("state should serialize");
|
||
|
|
let import =
|
||
|
|
load_runtime_state_import_from_str(&text, "fallback").expect("state should load");
|
||
|
|
assert_eq!(import.import_id, "fallback");
|
||
|
|
assert!(import.description.is_none());
|
||
|
|
}
|
||
|
|
}
|