1use crate::ObservabilityError;
2use crate::validation::CustomerAppId;
3use std::collections::BTreeSet;
4use std::fmt;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
7pub enum LogSeverity {
8 Error,
9 Warn,
10 Info,
11 Debug,
12 Trace,
13}
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
16pub enum ErrorCategory {
17 Validation,
18 AuthorizationDenied,
19 StateConflict,
20 DependencyFailure,
21 Timeout,
22 Capacity,
23 InvariantViolation,
24 ExtensionTrap,
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
28pub enum HealthProbeKind {
29 Liveness,
30 Readiness,
31 Synthetic,
32}
33
34impl fmt::Display for HealthProbeKind {
35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36 match self {
37 Self::Liveness => f.write_str("liveness"),
38 Self::Readiness => f.write_str("readiness"),
39 Self::Synthetic => f.write_str("synthetic"),
40 }
41 }
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
45pub enum DependencyKind {
46 Database,
47 DistributedCache,
48 Queue,
49 ExtensionRegistry,
50 ObjectStore,
51 Secrets,
52 Tls,
53}
54
55impl fmt::Display for DependencyKind {
56 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57 match self {
58 Self::Database => f.write_str("database"),
59 Self::DistributedCache => f.write_str("distributed_cache"),
60 Self::Queue => f.write_str("queue"),
61 Self::ExtensionRegistry => f.write_str("extension_registry"),
62 Self::ObjectStore => f.write_str("object_store"),
63 Self::Secrets => f.write_str("secrets"),
64 Self::Tls => f.write_str("tls"),
65 }
66 }
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub enum DependencyStatus {
71 Healthy,
72 Degraded,
73 Unhealthy,
74 Unknown,
75}
76
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub struct ProbeDependency {
79 pub kind: DependencyKind,
80 pub required: bool,
81 pub status: DependencyStatus,
82}
83
84#[derive(Debug, Clone, PartialEq, Eq)]
85pub struct HealthReport {
86 pub kind: HealthProbeKind,
87 pub dependencies: Vec<ProbeDependency>,
88}
89
90impl HealthReport {
91 pub fn new(kind: HealthProbeKind) -> Self {
92 Self {
93 kind,
94 dependencies: Vec::new(),
95 }
96 }
97
98 pub fn with_dependency(
99 mut self,
100 kind: DependencyKind,
101 required: bool,
102 status: DependencyStatus,
103 ) -> Result<Self, ObservabilityError> {
104 if self
105 .dependencies
106 .iter()
107 .any(|dependency| dependency.kind == kind)
108 {
109 return Err(ObservabilityError::DuplicateDependency {
110 probe: self.kind,
111 dependency: kind,
112 });
113 }
114
115 self.dependencies.push(ProbeDependency {
116 kind,
117 required,
118 status,
119 });
120 Ok(self)
121 }
122
123 pub fn overall_status(&self) -> DependencyStatus {
124 if self.dependencies.iter().any(|dependency| {
125 dependency.required && dependency.status == DependencyStatus::Unhealthy
126 }) {
127 return DependencyStatus::Unhealthy;
128 }
129
130 if self.dependencies.iter().any(|dependency| {
131 dependency.required
132 && matches!(
133 dependency.status,
134 DependencyStatus::Degraded | DependencyStatus::Unknown
135 )
136 }) {
137 return DependencyStatus::Degraded;
138 }
139
140 DependencyStatus::Healthy
141 }
142
143 pub fn dependency(&self, kind: DependencyKind) -> Option<ProbeDependency> {
144 self.dependencies
145 .iter()
146 .find(|dependency| dependency.kind == kind)
147 .copied()
148 }
149
150 pub fn set_dependency_status(&mut self, kind: DependencyKind, status: DependencyStatus) -> bool {
151 let Some(dependency) = self
152 .dependencies
153 .iter_mut()
154 .find(|dependency| dependency.kind == kind)
155 else {
156 return false;
157 };
158 dependency.status = status;
159 true
160 }
161}
162
163#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
164pub enum BackgroundWorkClass {
165 QueueDrain,
166 TlsRenewal,
167 StorageSync,
168 WebhookRetry,
169 SearchMaintenance,
170}
171
172#[derive(Debug, Clone, PartialEq, Eq)]
173pub enum MaintenanceAudience {
174 Deployment,
175 CustomerApp(CustomerAppId),
176}
177
178#[derive(Debug, Clone, Copy, PartialEq, Eq)]
179pub enum MaintenanceImpact {
180 AllTraffic,
181 MutatingTrafficOnly,
182}
183
184#[derive(Debug, Clone, PartialEq, Eq)]
185pub struct MaintenanceMode {
186 pub enabled: bool,
187 pub audience: MaintenanceAudience,
188 pub impact: MaintenanceImpact,
189 pub bypass_token: Option<String>,
190 pub allowed_background_work: BTreeSet<BackgroundWorkClass>,
191}
192
193impl MaintenanceMode {
194 pub fn disabled() -> Self {
195 Self {
196 enabled: false,
197 audience: MaintenanceAudience::Deployment,
198 impact: MaintenanceImpact::AllTraffic,
199 bypass_token: None,
200 allowed_background_work: BTreeSet::new(),
201 }
202 }
203
204 pub fn blocks_request(
205 &self,
206 customer_app: Option<&CustomerAppId>,
207 method_is_mutating: bool,
208 bypass_token: Option<&str>,
209 ) -> bool {
210 if !self.enabled {
211 return false;
212 }
213
214 if self
215 .bypass_token
216 .as_deref()
217 .is_some_and(|expected| Some(expected) == bypass_token)
218 {
219 return false;
220 }
221
222 let applies_to_app = match (&self.audience, customer_app) {
223 (MaintenanceAudience::Deployment, _) => true,
224 (MaintenanceAudience::CustomerApp(expected), Some(actual)) => expected == actual,
225 (MaintenanceAudience::CustomerApp(_), None) => false,
226 };
227
228 if !applies_to_app {
229 return false;
230 }
231
232 match self.impact {
233 MaintenanceImpact::AllTraffic => true,
234 MaintenanceImpact::MutatingTrafficOnly => method_is_mutating,
235 }
236 }
237}