1use crate::types::{ProtocolType, ServiceAttributes, ServiceType};
4use serde::{Deserialize, Serialize};
5use std::{
6 collections::HashMap,
7 fmt,
8 net::{IpAddr, Ipv4Addr},
9 time::{Duration, SystemTime},
10};
11use uuid::Uuid;
12
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15pub struct ServiceInfo {
16 pub id: Uuid,
18 pub name: String,
20 pub service_type: ServiceType,
22 pub address: IpAddr,
24 pub port: u16,
26 pub attributes: ServiceAttributes,
28 pub protocol_type: ProtocolType,
30 pub discovered_at: SystemTime,
32 pub ttl: Duration,
34 pub verified: bool,
36 pub interface: Option<String>,
38}
39
40impl ServiceInfo {
41 pub fn new(
43 name: impl Into<String>,
44 service_type: impl Into<String>,
45 port: u16,
46 attributes: Option<Vec<(&str, &str)>>
47 ) -> Result<Self, crate::error::DiscoveryError> {
48 let name = name.into();
49
50 if name.trim().is_empty() {
52 return Err(crate::error::DiscoveryError::InvalidServiceInfo {
53 field: "name".to_string(),
54 reason: "Service name cannot be empty".to_string(),
55 });
56 }
57
58 if port == 0 {
60 return Err(crate::error::DiscoveryError::InvalidServiceInfo {
61 field: "port".to_string(),
62 reason: "Port cannot be zero".to_string(),
63 });
64 }
65
66 let service_type = ServiceType::new(service_type)?;
67
68 let mut info = Self {
69 id: Uuid::new_v4(),
70 name: name.to_string(),
71 service_type,
72 address: IpAddr::V4(Ipv4Addr::LOCALHOST),
73 port,
74 attributes: HashMap::new(),
75 protocol_type: ProtocolType::default(),
76 discovered_at: SystemTime::now(),
77 ttl: Duration::from_secs(60),
78 verified: false,
79 interface: None,
80 };
81
82 if let Some(attrs) = attributes {
83 for (key, value) in attrs {
84 info.attributes.insert(key.to_string(), value.to_string());
85 }
86 }
87
88 Ok(info)
89 }
90
91 pub fn protocol_type(&self) -> ProtocolType {
93 self.protocol_type
94 }
95
96 pub fn with_protocol_type(mut self, protocol_type: ProtocolType) -> Self {
98 self.protocol_type = protocol_type;
99 self
100 }
101
102 pub fn ttl(&self) -> Duration {
104 self.ttl
105 }
106
107 pub fn with_ttl(mut self, ttl: Duration) -> Self {
109 self.ttl = ttl;
110 self
111 }
112
113 pub fn is_expired(&self) -> bool {
115 match self.discovered_at.elapsed() {
116 Ok(elapsed) => elapsed > self.ttl,
117 Err(_) => false,
118 }
119 }
120
121 pub fn refresh(&mut self) {
123 self.discovered_at = SystemTime::now();
124 }
125
126 pub fn with_attributes<K, V>(mut self, attrs: HashMap<K, V>) -> Self
128 where
129 K: Into<String>,
130 V: Into<String>,
131 {
132 self.attributes = attrs
133 .into_iter()
134 .map(|(k, v)| (k.into(), v.into()))
135 .collect();
136 self
137 }
138
139 pub fn with_attribute<K: Into<String>, V: Into<String>>(mut self, key: K, value: V) -> Self {
141 self.attributes.insert(key.into(), value.into());
142 self
143 }
144
145 pub fn insert_attribute<K: Into<String>, V: Into<String>>(&mut self, key: K, value: V) {
147 self.attributes.insert(key.into(), value.into());
148 }
149
150 pub fn get_attribute(&self, key: &str) -> Option<&String> {
152 self.attributes.get(key)
153 }
154
155 pub fn port(&self) -> u16 {
157 self.port
158 }
159
160 pub fn address(&self) -> IpAddr {
162 self.address
163 }
164
165 pub fn with_address(mut self, address: IpAddr) -> Self {
167 self.address = address;
168 self
169 }
170
171 pub fn name(&self) -> &str {
173 &self.name
174 }
175
176 pub fn service_type(&self) -> &ServiceType {
178 &self.service_type
179 }
180}
181
182impl fmt::Display for ServiceInfo {
183 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184 write!(
185 f,
186 "{} ({}) at {}:{} via {}",
187 self.name, self.service_type, self.address, self.port, self.protocol_type
188 )
189 }
190}
191
192#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
194pub enum ServiceEvent {
195 New(ServiceInfo),
197 Updated(ServiceInfo),
199 Removed(ServiceInfo),
201 VerificationFailed(ServiceInfo),
203 DiscoveryStarted {
205 service_types: Vec<ServiceType>,
207 protocols: Vec<ProtocolType>,
209 },
210 DiscoveryCompleted {
212 services_found: usize,
214 duration: Duration,
216 },
217 DiscoveryFailed {
219 error: String,
221 service_types: Vec<ServiceType>,
223 },
224}
225
226impl ServiceEvent {
227 pub fn new(service: ServiceInfo) -> Self {
229 Self::New(service)
230 }
231
232 pub fn updated(service: ServiceInfo) -> Self {
234 Self::Updated(service)
235 }
236
237 pub fn removed(service: ServiceInfo) -> Self {
239 Self::Removed(service)
240 }
241
242 pub fn verification_failed(service: ServiceInfo) -> Self {
244 Self::VerificationFailed(service)
245 }
246
247 pub fn discovery_started(
249 service_types: Vec<ServiceType>,
250 protocols: Vec<ProtocolType>,
251 ) -> Self {
252 Self::DiscoveryStarted {
253 service_types,
254 protocols,
255 }
256 }
257
258 pub fn discovery_completed(services_found: usize, duration: Duration) -> Self {
260 Self::DiscoveryCompleted {
261 services_found,
262 duration,
263 }
264 }
265
266 pub fn discovery_failed<S: Into<String>>(error: S, service_types: Vec<ServiceType>) -> Self {
268 Self::DiscoveryFailed {
269 error: error.into(),
270 service_types,
271 }
272 }
273
274 pub fn service(&self) -> Option<&ServiceInfo> {
276 match self {
277 Self::New(service)
278 | Self::Updated(service)
279 | Self::Removed(service)
280 | Self::VerificationFailed(service) => Some(service),
281 _ => None,
282 }
283 }
284
285 pub fn is_positive(&self) -> bool {
287 matches!(self, Self::New(_) | Self::Updated(_) | Self::DiscoveryCompleted { .. })
288 }
289
290 pub fn is_negative(&self) -> bool {
292 matches!(
293 self,
294 Self::Removed(_) | Self::VerificationFailed(_) | Self::DiscoveryFailed { .. }
295 )
296 }
297}
298
299impl fmt::Display for ServiceEvent {
300 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
301 match self {
302 Self::New(service) => write!(f, "New service: {service}"),
303 Self::Updated(service) => write!(f, "Updated service: {service}"),
304 Self::Removed(service) => write!(f, "Removed service: {service}"),
305 Self::VerificationFailed(service) => write!(f, "Verification failed: {service}"),
306 Self::DiscoveryStarted {
307 service_types,
308 protocols,
309 } => write!(
310 f,
311 "Discovery started for {} service types using {} protocols",
312 service_types.len(),
313 protocols.len()
314 ),
315 Self::DiscoveryCompleted {
316 services_found,
317 duration,
318 } => write!(
319 f,
320 "Discovery completed: {services_found} services found in {duration:?}"
321 ),
322 Self::DiscoveryFailed { error, service_types } => write!(
323 f,
324 "Discovery failed for {} service types: {}",
325 service_types.len(),
326 error
327 ),
328 }
329 }
330}
331
332#[cfg(test)]
333mod tests {
334 use super::*;
335 use crate::types::ProtocolType;
336
337 #[test]
338 fn test_service_creation() -> Result<(), crate::error::DiscoveryError> {
339 let service = ServiceInfo::new(
340 "Test Service",
341 "_http._tcp",
342 8080,
343 Some(vec![("version", "1.0"), ("protocol", "HTTP/1.1")]),
344 )?;
345
346 assert_eq!(service.name, "Test Service");
347 assert_eq!(service.get_attribute("version"), Some(&"1.0".to_string()));
348 assert!(!service.is_expired());
349
350 Ok(())
351 }
352
353 #[test]
354 fn test_service_expiry() -> Result<(), crate::error::DiscoveryError> {
355 let mut service = ServiceInfo::new(
356 "Test Service",
357 "_http._tcp",
358 8080,
359 None,
360 )?
361 .with_ttl(Duration::from_millis(1));
362
363 std::thread::sleep(Duration::from_millis(10));
364 assert!(service.is_expired());
365
366 service.refresh();
367 assert!(!service.is_expired());
368
369 Ok(())
370 }
371
372 #[test]
373 fn test_service_attributes() -> Result<(), crate::error::DiscoveryError> {
374 let service = ServiceInfo::new("Test Service", "_http._tcp", 8080, None)?
375 .with_attribute("version", "1.0")
376 .with_attribute("secure", "true");
377
378 assert_eq!(service.get_attribute("version"), Some(&"1.0".to_string()));
379 assert_eq!(service.get_attribute("secure"), Some(&"true".to_string()));
380 assert_eq!(service.get_attribute("nonexistent"), None);
381
382 Ok(())
383 }
384
385 #[test]
386 fn test_service_protocol() -> Result<(), crate::error::DiscoveryError> {
387 let service = ServiceInfo::new("Test Service", "_http._tcp", 8080, None)?
388 .with_protocol_type(ProtocolType::Mdns);
389
390 assert_eq!(service.protocol_type(), ProtocolType::Mdns);
391 Ok(())
392 }
393}