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}