use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use stateset_primitives::TopologySnapshotId;
use strum::{Display, EnumString};
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default, Display, EnumString,
)]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case", ascii_case_insensitive)]
#[non_exhaustive]
pub enum HealthGrade {
#[default]
Unknown,
Healthy,
Degraded,
Critical,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TopologySnapshot {
pub id: TopologySnapshotId,
pub channels_total: u64,
pub channels_active: u64,
pub warehouses_total: u64,
pub products_total: u64,
pub open_orders: u64,
pub health: HealthGrade,
pub signals: serde_json::Value,
pub captured_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct CaptureTopologySnapshot {
pub channels_total: u64,
pub channels_active: u64,
pub warehouses_total: u64,
pub products_total: u64,
pub open_orders: u64,
#[serde(default)]
pub signals: serde_json::Value,
}
impl CaptureTopologySnapshot {
#[must_use]
pub const fn derive_health(&self) -> HealthGrade {
let nothing_configured =
self.channels_total == 0 && self.warehouses_total == 0 && self.products_total == 0;
if nothing_configured {
return HealthGrade::Unknown;
}
if self.channels_active == 0 {
return HealthGrade::Critical;
}
if self.warehouses_total == 0 || self.products_total == 0 {
return HealthGrade::Degraded;
}
HealthGrade::Healthy
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct TopologySnapshotFilter {
pub health: Option<HealthGrade>,
pub limit: Option<u32>,
pub offset: Option<u32>,
}
#[cfg(test)]
mod tests {
use super::*;
fn metrics(
channels: u64,
active: u64,
warehouses: u64,
products: u64,
) -> CaptureTopologySnapshot {
CaptureTopologySnapshot {
channels_total: channels,
channels_active: active,
warehouses_total: warehouses,
products_total: products,
open_orders: 0,
signals: serde_json::Value::Null,
}
}
#[test]
fn health_unknown_when_empty() {
assert_eq!(metrics(0, 0, 0, 0).derive_health(), HealthGrade::Unknown);
}
#[test]
fn health_critical_without_active_channel() {
assert_eq!(metrics(2, 0, 1, 10).derive_health(), HealthGrade::Critical);
}
#[test]
fn health_degraded_without_warehouse_or_products() {
assert_eq!(metrics(1, 1, 0, 10).derive_health(), HealthGrade::Degraded);
assert_eq!(metrics(1, 1, 2, 0).derive_health(), HealthGrade::Degraded);
}
#[test]
fn health_healthy_when_complete() {
assert_eq!(metrics(2, 1, 3, 100).derive_health(), HealthGrade::Healthy);
}
#[test]
fn grade_roundtrip() {
for g in [
HealthGrade::Unknown,
HealthGrade::Healthy,
HealthGrade::Degraded,
HealthGrade::Critical,
] {
assert_eq!(g.to_string().parse::<HealthGrade>().unwrap(), g);
}
}
}