use crate::error::{Result, SparkplugError};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct StatePayload {
pub online: bool,
pub timestamp: i64,
}
impl StatePayload {
#[must_use]
pub const fn new(online: bool, timestamp: i64) -> Self {
Self { online, timestamp }
}
#[must_use]
pub fn to_json(&self) -> String {
format!(
"{{\"online\":{},\"timestamp\":{}}}",
self.online, self.timestamp
)
}
pub fn parse(json: &str) -> Result<Self> {
let trimmed = json.trim();
let inner = trimmed
.strip_prefix('{')
.and_then(|s| s.strip_suffix('}'))
.ok_or_else(|| SparkplugError::InvalidState("expected a JSON object".to_owned()))?;
let mut online: Option<bool> = None;
let mut timestamp: Option<i64> = None;
for field in inner.split(',') {
if field.trim().is_empty() {
continue;
}
let (key, value) = field.split_once(':').ok_or_else(|| {
SparkplugError::InvalidState(format!("expected key:value, got {field:?}"))
})?;
let key = key.trim().trim_matches('"');
let value = value.trim();
match key {
"online" => {
online = Some(match value {
"true" => true,
"false" => false,
other => {
return Err(SparkplugError::InvalidState(format!(
"online must be true/false, got {other:?}"
)));
}
});
}
"timestamp" => {
timestamp = Some(value.parse::<i64>().map_err(|e| {
SparkplugError::InvalidState(format!("timestamp not an integer: {e}"))
})?);
}
_ => {}
}
}
match (online, timestamp) {
(Some(online), Some(timestamp)) => Ok(Self { online, timestamp }),
_ => Err(SparkplugError::InvalidState(
"missing 'online' or 'timestamp'".to_owned(),
)),
}
}
}