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    ///
61    /// Callers must validate `snapshot` before constructing a ready response.
62    #[must_use]
63    pub fn ready(snapshot: crate::StatusSnapshot) -> Self {
64        Self {
65            schema_version: crate::SCHEMA_VERSION_V1,
66            state: ReadinessState::Ready,
67            category: None,
68            message: None,
69            snapshot: Some(snapshot),
70        }
71    }
72
73    /// A `Warming` response with a default message.
74    #[must_use]
75    pub fn warming() -> Self {
76        Self::warming_with_message("collector warming up")
77    }
78
79    /// A `Warming` response with a custom message.
80    #[must_use]
81    pub fn warming_with_message(message: impl Into<String>) -> Self {
82        Self {
83            schema_version: crate::SCHEMA_VERSION_V1,
84            state: ReadinessState::Warming,
85            category: Some(HealthCategory::Warming),
86            message: Some(message.into()),
87            snapshot: None,
88        }
89    }
90
91    /// A `Failed` response with the given category and message.
92    #[must_use]
93    pub fn failed(category: HealthCategory, message: impl Into<String>) -> Self {
94        Self {
95            schema_version: crate::SCHEMA_VERSION_V1,
96            state: ReadinessState::Failed,
97            category: Some(category),
98            message: Some(message.into()),
99            snapshot: None,
100        }
101    }
102}