use serde::{Deserialize, Serialize};
use serde_json::Value;
pub const SLA_STATE_SUFFIX: &str = "__sla__";
pub fn sla_state_key(base: &str) -> String {
format!("{base}::{SLA_STATE_SUFFIX}")
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(default)]
pub struct SlaState {
pub last_success_unix: Option<i64>,
pub volumes: Vec<u64>,
}
impl SlaState {
pub fn from_value(v: Value) -> Self {
match serde_json::from_value(v) {
Ok(s) => s,
Err(e) => {
tracing::warn!(error = %e, "unreadable SLA state — starting a fresh baseline");
Self::default()
}
}
}
pub fn to_value(&self) -> Value {
serde_json::to_value(self).unwrap_or(Value::Null)
}
pub fn record_success(&mut self, rows: u64, now_unix: i64, window: usize) {
self.last_success_unix = Some(now_unix);
self.volumes.push(rows);
if self.volumes.len() > window {
let excess = self.volumes.len() - window;
self.volumes.drain(..excess);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn key_is_suffixed() {
assert_eq!(sla_state_key("orders::default"), "orders::default::__sla__");
}
#[test]
fn round_trips_through_value() {
let mut s = SlaState::default();
s.record_success(100, 1_750_000_000, 20);
s.record_success(120, 1_750_003_600, 20);
let v = s.to_value();
assert_eq!(SlaState::from_value(v), s);
assert_eq!(s.volumes, vec![100, 120]);
assert_eq!(s.last_success_unix, Some(1_750_003_600));
}
#[test]
fn corrupt_value_degrades_to_default() {
assert_eq!(
SlaState::from_value(json!({"volumes": "not-an-array"})),
SlaState::default()
);
assert_eq!(SlaState::from_value(json!([1, 2, 3])), SlaState::default());
}
#[test]
fn missing_fields_default() {
let s = SlaState::from_value(json!({"last_success_unix": 5}));
assert_eq!(s.last_success_unix, Some(5));
assert!(s.volumes.is_empty());
}
#[test]
fn window_trims_oldest_first() {
let mut s = SlaState::default();
for i in 0..25u64 {
s.record_success(i, i as i64, 20);
}
assert_eq!(s.volumes.len(), 20);
assert_eq!(s.volumes[0], 5);
assert_eq!(*s.volumes.last().unwrap(), 24);
}
}