use crate::core::errors::WbtError;
use serde_json::Value;
use std::path::Path;
pub const FORMAT: &str = "wbt.backtest_result";
pub const FORMAT_VERSION: u64 = 1;
pub fn decode_wire(bytes: &[u8]) -> Result<Value, WbtError> {
let envelope: Value = rmp_serde::from_slice(bytes)
.map_err(|e| WbtError::InvalidInput(format!("failed to decode msgpack: {e}")))?;
let format = envelope.get("format").and_then(Value::as_str);
if format != Some(FORMAT) {
return Err(WbtError::InvalidInput(format!(
"unexpected format {format:?}, expected {FORMAT:?}"
)));
}
let version = envelope.get("format_version").and_then(Value::as_u64);
if version != Some(FORMAT_VERSION) {
return Err(WbtError::InvalidInput(format!(
"unsupported format_version {version:?}, expected {FORMAT_VERSION}"
)));
}
match envelope.get("payload") {
Some(Value::Object(_)) => Ok(envelope["payload"].clone()),
_ => Err(WbtError::InvalidInput(
"invalid msgpack envelope: missing or malformed payload".into(),
)),
}
}
pub fn load_wire(path: impl AsRef<Path>) -> Result<Value, WbtError> {
let bytes = std::fs::read(path.as_ref())
.map_err(|e| WbtError::Io(format!("read {}: {e}", path.as_ref().display())))?;
decode_wire(&bytes)
}
#[cfg(test)]
mod tests {
use super::*;
fn pack(value: &Value) -> Vec<u8> {
rmp_serde::to_vec_named(value).unwrap()
}
fn valid_envelope() -> Value {
serde_json::json!({
"format": FORMAT,
"format_version": FORMAT_VERSION,
"payload": {
"symbol_count": 2,
"dates": ["2024-01-01T00:00:00", "2024-01-02T00:00:00"],
"curves": {"多空": {"daily": [0.0, 0.1]}},
},
})
}
#[test]
fn decode_valid_returns_payload() {
let payload = decode_wire(&pack(&valid_envelope())).unwrap();
assert_eq!(payload["symbol_count"].as_u64(), Some(2));
assert_eq!(payload["dates"].as_array().unwrap().len(), 2);
}
#[test]
fn decode_rejects_wrong_format() {
let mut env = valid_envelope();
env["format"] = Value::String("something.else".into());
let err = decode_wire(&pack(&env)).unwrap_err();
assert!(err.to_string().contains("unexpected format"));
}
#[test]
fn decode_rejects_unknown_version() {
let mut env = valid_envelope();
env["format_version"] = Value::from(999u64);
let err = decode_wire(&pack(&env)).unwrap_err();
assert!(err.to_string().contains("unsupported format_version"));
}
#[test]
fn decode_rejects_garbage() {
assert!(decode_wire(&[0xc1, 0x00, 0xff]).is_err());
}
#[test]
fn reads_python_fixture() {
let bytes = include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/backtest_result.msgpack"
));
let payload = decode_wire(bytes).unwrap();
let symbol_count = payload["symbol_count"].as_u64().unwrap();
assert!(symbol_count >= 1, "symbol_count should be positive");
let dates = payload["dates"].as_array().unwrap();
assert!(!dates.is_empty(), "dates should not be empty");
let curves = payload["curves"].as_object().unwrap();
for key in ["多空", "多头", "空头", "基准", "超额"] {
assert!(curves.contains_key(key), "curves missing key {key}");
}
}
}