1use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::net::SocketAddr;
6use std::time::Duration;
7use uuid::Uuid;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
11pub enum ServiceHealth {
12 Healthy,
14
15 Unhealthy,
17
18 Unknown,
20
21 Starting,
23
24 Maintenance,
26
27 Deregistering,
29}
30
31impl Default for ServiceHealth {
32 fn default() -> Self {
33 ServiceHealth::Unknown
34 }
35}
36
37impl std::fmt::Display for ServiceHealth {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 match self {
40 ServiceHealth::Healthy => write!(f, "healthy"),
41 ServiceHealth::Unhealthy => write!(f, "unhealthy"),
42 ServiceHealth::Unknown => write!(f, "unknown"),
43 ServiceHealth::Starting => write!(f, "starting"),
44 ServiceHealth::Maintenance => write!(f, "maintenance"),
45 ServiceHealth::Deregistering => write!(f, "deregistering"),
46 }
47 }
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize, Default)]
52pub struct Service {
53 pub name: String,
55
56 pub instances: Vec<ServiceInstance>,
58
59 pub metadata: HashMap<String, String>,
61
62 pub created_at: DateTime<Utc>,
64
65 pub updated_at: DateTime<Utc>,
67}
68
69impl Service {
70 pub fn new(name: &str) -> Self {
72 let now = Utc::now();
73 Self {
74 name: name.to_string(),
75 instances: Vec::new(),
76 metadata: HashMap::new(),
77 created_at: now,
78 updated_at: now,
79 }
80 }
81
82 pub fn add_instance(&mut self, instance: ServiceInstance) {
84 self.instances.push(instance);
85 self.updated_at = Utc::now();
86 }
87
88 pub fn remove_instance(&mut self, instance_id: &str) -> Option<ServiceInstance> {
90 let position = self.instances.iter().position(|i| i.id == instance_id)?;
91 let instance = self.instances.remove(position);
92 self.updated_at = Utc::now();
93 Some(instance)
94 }
95
96 pub fn get_instance(&self, instance_id: &str) -> Option<&ServiceInstance> {
98 self.instances.iter().find(|i| i.id == instance_id)
99 }
100
101 pub fn get_instance_mut(&mut self, instance_id: &str) -> Option<&mut ServiceInstance> {
103 self.instances.iter_mut().find(|i| i.id == instance_id)
104 }
105
106 pub fn healthy_instance_count(&self) -> usize {
108 self.instances.iter().filter(|i| i.health == ServiceHealth::Healthy).count()
109 }
110
111 pub fn healthy_instances(&self) -> Vec<&ServiceInstance> {
113 self.instances.iter().filter(|i| i.health == ServiceHealth::Healthy).collect()
114 }
115
116 pub fn is_available(&self) -> bool {
118 self.instances.iter().any(|i| i.health == ServiceHealth::Healthy)
119 }
120
121 pub fn update_metadata(&mut self, key: &str, value: &str) {
123 self.metadata.insert(key.to_string(), value.to_string());
124 self.updated_at = Utc::now();
125 }
126
127 pub fn set_metadata(&mut self, metadata: HashMap<String, String>) {
129 self.metadata = metadata;
130 self.updated_at = Utc::now();
131 }
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize)]
136pub struct ServiceInstance {
137 pub id: String,
139
140 pub service_name: String,
142
143 pub host: String,
145
146 pub port: u16,
148
149 pub protocol: String,
151
152 pub health_check_path: Option<String>,
154
155 pub health: ServiceHealth,
157
158 pub tags: Vec<String>,
160
161 pub metadata: HashMap<String, String>,
163
164 pub ttl: Option<u64>,
166
167 pub last_heartbeat: Option<DateTime<Utc>>,
169
170 pub created_at: DateTime<Utc>,
172
173 pub updated_at: DateTime<Utc>,
175
176 pub node_id: String,
178
179 pub weight: u32,
181}
182
183impl ServiceInstance {
184 pub fn new(
186 service_name: &str,
187 host: &str,
188 port: u16,
189 protocol: &str,
190 node_id: &str,
191 ) -> Self {
192 let now = Utc::now();
193 Self {
194 id: Uuid::new_v4().to_string(),
195 service_name: service_name.to_string(),
196 host: host.to_string(),
197 port,
198 protocol: protocol.to_string(),
199 health_check_path: None,
200 health: ServiceHealth::Unknown,
201 tags: Vec::new(),
202 metadata: HashMap::new(),
203 ttl: None,
204 last_heartbeat: Some(now),
205 created_at: now,
206 updated_at: now,
207 node_id: node_id.to_string(),
208 weight: 100,
209 }
210 }
211
212 pub fn address(&self) -> String {
214 format!("{}://{}:{}", self.protocol, self.host, self.port)
215 }
216
217 pub fn socket_addr(&self) -> Option<SocketAddr> {
219 format!("{}:{}", self.host, self.port).parse().ok()
220 }
221
222 pub fn update_health(&mut self, health: ServiceHealth) {
224 self.health = health;
225 self.updated_at = Utc::now();
226 }
227
228 pub fn heartbeat(&mut self) {
230 self.last_heartbeat = Some(Utc::now());
231 }
232
233 pub fn is_expired(&self) -> bool {
235 match (self.ttl, self.last_heartbeat) {
236 (Some(ttl), Some(last_heartbeat)) => {
237 let duration = Utc::now() - last_heartbeat;
238 let ttl_duration = chrono::Duration::from_std(Duration::from_secs(ttl)).unwrap();
239 duration > ttl_duration
240 }
241 _ => false,
242 }
243 }
244
245 pub fn set_ttl(&mut self, ttl_secs: u64) {
247 self.ttl = Some(ttl_secs);
248 self.updated_at = Utc::now();
249 }
250
251 pub fn add_tag(&mut self, tag: &str) {
253 if !self.tags.contains(&tag.to_string()) {
254 self.tags.push(tag.to_string());
255 self.updated_at = Utc::now();
256 }
257 }
258
259 pub fn remove_tag(&mut self, tag: &str) -> bool {
261 let len = self.tags.len();
262 self.tags.retain(|t| t != tag);
263 let removed = self.tags.len() < len;
264 if removed {
265 self.updated_at = Utc::now();
266 }
267 removed
268 }
269
270 pub fn set_health_check_path(&mut self, path: &str) {
272 self.health_check_path = Some(path.to_string());
273 self.updated_at = Utc::now();
274 }
275
276 pub fn set_weight(&mut self, weight: u32) {
278 self.weight = weight;
279 self.updated_at = Utc::now();
280 }
281
282 pub fn update_metadata(&mut self, key: &str, value: &str) {
284 self.metadata.insert(key.to_string(), value.to_string());
285 self.updated_at = Utc::now();
286 }
287
288 pub fn set_metadata(&mut self, metadata: HashMap<String, String>) {
290 self.metadata = metadata;
291 self.updated_at = Utc::now();
292 }
293}