Skip to main content

gregg_protocol/
health.rs

1//! Health and readiness response type.
2
3use serde::{Deserialize, Serialize};
4
5/// Coarse readiness state shared between the daemon and the client.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
7#[serde(rename_all = "snake_case")]
8pub enum ReadinessState {
9    /// The daemon has a valid cached snapshot and `/v1/status` will return it.
10    Ready,
11    /// The daemon is alive but the first counter delta is not yet available;
12    /// `/v1/status` returns `503`.
13    Warming,
14    /// The daemon's collector has failed; `/v1/status` returns `503`.
15    Failed,
16}
17
18/// Machine-readable category for a non-ready health response.
19///
20/// Categories are deliberately coarse so the client can render consistent
21/// diagnostics without leaking implementation details.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
23#[serde(rename_all = "snake_case")]
24pub enum HealthCategory {
25    /// Counter delta is still being collected.
26    Warming,
27    /// The native collector reported an error.
28    CollectorFailure,
29    /// The daemon is shutting down or otherwise refusing traffic.
30    NotServing,
31}
32
33/// Health and readiness response served by the daemon.
34///
35/// The `Ready` variant carries a fresh snapshot. The other variants carry a
36/// short human-readable message and a [`HealthCategory`]; they never include
37/// filesystem paths, internal error chains, or platform-private structures.
38#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
39#[serde(rename_all = "snake_case")]
40pub struct HealthResponse {
41    /// Daemon schema version, always
42    /// [`crate::SCHEMA_VERSION_V1`].
43    pub schema_version: u16,
44    /// Current readiness state.
45    pub state: ReadinessState,
46    /// Coarse category for non-ready responses. `None` when `state == Ready`.
47    #[serde(default, skip_serializing_if = "Option::is_none")]
48    pub category: Option<HealthCategory>,
49    /// Short human-readable message. Never includes filesystem paths or
50    /// internal error chains.
51    #[serde(default, skip_serializing_if = "Option::is_none")]
52    pub message: Option<String>,
53    /// Cached snapshot, present only when `state == Ready`.
54    #[serde(default, skip_serializing_if = "Option::is_none")]
55    pub snapshot: Option<crate::StatusSnapshot>,
56}
57
58impl HealthResponse {
59    /// A `Ready` response wrapping the supplied snapshot.
60    #[must_use]
61    pub fn ready(snapshot: crate::StatusSnapshot) -> Self {
62        Self {
63            schema_version: crate::SCHEMA_VERSION_V1,
64            state: ReadinessState::Ready,
65            category: None,
66            message: None,
67            snapshot: Some(snapshot),
68        }
69    }
70
71    /// A `Warming` response with a default message.
72    #[must_use]
73    pub fn warming() -> Self {
74        Self::warming_with_message("collector warming up")
75    }
76
77    /// A `Warming` response with a custom message.
78    #[must_use]
79    pub fn warming_with_message(message: impl Into<String>) -> Self {
80        Self {
81            schema_version: crate::SCHEMA_VERSION_V1,
82            state: ReadinessState::Warming,
83            category: Some(HealthCategory::Warming),
84            message: Some(message.into()),
85            snapshot: None,
86        }
87    }
88
89    /// A `Failed` response with the given category and message.
90    #[must_use]
91    pub fn failed(category: HealthCategory, message: impl Into<String>) -> Self {
92        Self {
93            schema_version: crate::SCHEMA_VERSION_V1,
94            state: ReadinessState::Failed,
95            category: Some(category),
96            message: Some(message.into()),
97            snapshot: None,
98        }
99    }
100}