use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ReportingMessage {
Schema {
channel_names: Vec<String>,
channel_units: Vec<Option<String>>,
monotonic_epoch_ns: u64,
#[serde(default)]
is_session_end: bool,
},
Row {
seq: u64,
timestamp: f64,
system_time: String,
values: Vec<f64>,
},
}
impl ReportingMessage {
pub fn encode_into<'a>(&self, buf: &'a mut Vec<u8>) -> Result<&'a [u8], postcard::Error> {
let start = buf.len();
let owned = std::mem::take(buf);
*buf = postcard::to_extend(self, owned)?;
Ok(&buf[start..])
}
pub fn decode(bytes: &[u8]) -> Result<Self, postcard::Error> {
postcard::from_bytes(bytes)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn schema_round_trip() {
let msg = ReportingMessage::Schema {
channel_names: vec!["daq7_tc_K".to_string(), "daq7_rtd_ohm".to_string()],
channel_units: vec![Some("K".to_string()), Some("ohm".to_string())],
monotonic_epoch_ns: 1_713_530_000_000_000_000_u64,
is_session_end: false,
};
let mut buf = Vec::new();
msg.encode_into(&mut buf).expect("encode Schema");
let decoded = ReportingMessage::decode(&buf).expect("decode Schema");
assert_eq!(msg, decoded);
}
#[test]
fn row_round_trip() {
let msg = ReportingMessage::Row {
seq: 42,
timestamp: std::f64::consts::PI,
system_time: "2026-04-19T14:32:01.123456789Z".to_string(),
values: vec![300.15, 99.87, -1.0],
};
let mut buf = Vec::new();
msg.encode_into(&mut buf).expect("encode Row");
let decoded = ReportingMessage::decode(&buf).expect("decode Row");
assert_eq!(msg, decoded);
}
#[test]
fn schema_tag_is_zero() {
let msg = ReportingMessage::Schema {
channel_names: vec![],
channel_units: vec![],
monotonic_epoch_ns: 0,
is_session_end: false,
};
let mut buf = Vec::new();
msg.encode_into(&mut buf).expect("encode");
assert_eq!(buf[0], 0x00, "Schema variant tag must be 0");
}
#[test]
fn row_tag_is_one() {
let msg = ReportingMessage::Row {
seq: 0,
timestamp: 0.0,
system_time: String::new(),
values: vec![],
};
let mut buf = Vec::new();
msg.encode_into(&mut buf).expect("encode");
assert_eq!(buf[0], 0x01, "Row variant tag must be 1");
}
#[test]
fn session_end_schema_round_trip() {
let msg = ReportingMessage::Schema {
channel_names: vec!["ch0".to_string()],
channel_units: vec![None],
monotonic_epoch_ns: 42,
is_session_end: true,
};
let mut buf = Vec::new();
msg.encode_into(&mut buf)
.expect("encode session-end Schema");
let decoded = ReportingMessage::decode(&buf).expect("decode session-end Schema");
assert_eq!(msg, decoded);
}
#[test]
fn encode_into_appends_without_clearing() {
let mut buf = vec![0xFFu8; 4];
let msg = ReportingMessage::Row {
seq: 1,
timestamp: 0.0,
system_time: String::new(),
values: vec![],
};
let written_len = msg.encode_into(&mut buf).expect("encode").len();
assert_eq!(&buf[..4], &[0xFF; 4], "prefix must be preserved");
assert!(written_len > 0);
}
}