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 service_type = ServiceType::new(service_type)?;
49
50 let mut info = Self {
51 id: Uuid::new_v4(),
52 name: name.into(),
53 service_type,
54 address: IpAddr::V4(Ipv4Addr::LOCALHOST),
55 port,
56 attributes: HashMap::new(),
57 protocol_type: ProtocolType::default(),
58 discovered_at: SystemTime::now(),
59 ttl: Duration::from_secs(60),
60 verified: false,
61 interface: None,
62 };
63
64 if let Some(attrs) = attributes {
65 for (key, value) in attrs {
66 info.attributes.insert(key.to_string(), value.to_string());
67 }
68 }
69
70 Ok(info)
71 }
72
73 pub fn protocol_type(&self) -> ProtocolType {
75 self.protocol_type
76 }
77
78 pub fn with_protocol_type(mut self, protocol_type: ProtocolType) -> Self {
80 self.protocol_type = protocol_type;
81 self
82 }
83
84 pub fn ttl(&self) -> Duration {
86 self.ttl
87 }
88
89 pub fn with_ttl(mut self, ttl: Duration) -> Self {
91 self.ttl = ttl;
92 self
93 }
94
95 pub fn is_expired(&self) -> bool {
97 match self.discovered_at.elapsed() {
98 Ok(elapsed) => elapsed > self.ttl,
99 Err(_) => false,
100 }
101 }
102
103 pub fn refresh(&mut self) {
105 self.discovered_at = SystemTime::now();
106 }
107
108 pub fn with_attributes<K, V>(mut self, attrs: HashMap<K, V>) -> Self
110 where
111 K: Into<String>,
112 V: Into<String>,
113 {
114 self.attributes = attrs
115 .into_iter()
116 .map(|(k, v)| (k.into(), v.into()))
117 .collect();
118 self
119 }
120
121 pub fn with_attribute<K: Into<String>, V: Into<String>>(mut self, key: K, value: V) -> Self {
123 self.attributes.insert(key.into(), value.into());
124 self
125 }
126
127 pub fn insert_attribute<K: Into<String>, V: Into<String>>(&mut self, key: K, value: V) {
129 self.attributes.insert(key.into(), value.into());
130 }
131
132 pub fn get_attribute(&self, key: &str) -> Option<&String> {
134 self.attributes.get(key)
135 }
136
137 pub fn port(&self) -> u16 {
139 self.port
140 }
141
142 pub fn address(&self) -> IpAddr {
144 self.address
145 }
146
147 pub fn with_address(mut self, address: IpAddr) -> Self {
149 self.address = address;
150 self
151 }
152
153 pub fn name(&self) -> &str {
155 &self.name
156 }
157
158 pub fn service_type(&self) -> &ServiceType {
160 &self.service_type
161 }
162}
163
164impl fmt::Display for ServiceInfo {
165 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166 write!(
167 f,
168 "{} ({}) at {}:{} via {}",
169 self.name, self.service_type, self.address, self.port, self.protocol_type
170 )
171 }
172}
173
174#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
176pub enum ServiceEvent {
177 New(ServiceInfo),
179 Updated(ServiceInfo),
181 Removed(ServiceInfo),
183 VerificationFailed(ServiceInfo),
185 DiscoveryStarted {
187 service_types: Vec<ServiceType>,
189 protocols: Vec<ProtocolType>,
191 },
192 DiscoveryCompleted {
194 services_found: usize,
196 duration: Duration,
198 },
199 DiscoveryFailed {
201 error: String,
203 service_types: Vec<ServiceType>,
205 },
206}
207
208impl ServiceEvent {
209 pub fn new(service: ServiceInfo) -> Self {
211 Self::New(service)
212 }
213
214 pub fn updated(service: ServiceInfo) -> Self {
216 Self::Updated(service)
217 }
218
219 pub fn removed(service: ServiceInfo) -> Self {
221 Self::Removed(service)
222 }
223
224 pub fn verification_failed(service: ServiceInfo) -> Self {
226 Self::VerificationFailed(service)
227 }
228
229 pub fn discovery_started(
231 service_types: Vec<ServiceType>,
232 protocols: Vec<ProtocolType>,
233 ) -> Self {
234 Self::DiscoveryStarted {
235 service_types,
236 protocols,
237 }
238 }
239
240 pub fn discovery_completed(services_found: usize, duration: Duration) -> Self {
242 Self::DiscoveryCompleted {
243 services_found,
244 duration,
245 }
246 }
247
248 pub fn discovery_failed<S: Into<String>>(error: S, service_types: Vec<ServiceType>) -> Self {
250 Self::DiscoveryFailed {
251 error: error.into(),
252 service_types,
253 }
254 }
255
256 pub fn service(&self) -> Option<&ServiceInfo> {
258 match self {
259 Self::New(service)
260 | Self::Updated(service)
261 | Self::Removed(service)
262 | Self::VerificationFailed(service) => Some(service),
263 _ => None,
264 }
265 }
266
267 pub fn is_positive(&self) -> bool {
269 matches!(self, Self::New(_) | Self::Updated(_) | Self::DiscoveryCompleted { .. })
270 }
271
272 pub fn is_negative(&self) -> bool {
274 matches!(
275 self,
276 Self::Removed(_) | Self::VerificationFailed(_) | Self::DiscoveryFailed { .. }
277 )
278 }
279}
280
281impl fmt::Display for ServiceEvent {
282 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
283 match self {
284 Self::New(service) => write!(f, "New service: {}", service),
285 Self::Updated(service) => write!(f, "Updated service: {}", service),
286 Self::Removed(service) => write!(f, "Removed service: {}", service),
287 Self::VerificationFailed(service) => write!(f, "Verification failed: {}", service),
288 Self::DiscoveryStarted {
289 service_types,
290 protocols,
291 } => write!(
292 f,
293 "Discovery started for {} service types using {} protocols",
294 service_types.len(),
295 protocols.len()
296 ),
297 Self::DiscoveryCompleted {
298 services_found,
299 duration,
300 } => write!(
301 f,
302 "Discovery completed: {} services found in {:?}",
303 services_found, duration
304 ),
305 Self::DiscoveryFailed { error, service_types } => write!(
306 f,
307 "Discovery failed for {} service types: {}",
308 service_types.len(),
309 error
310 ),
311 }
312 }
313}
314
315#[cfg(test)]
316mod tests {
317 use super::*;
318 use crate::types::ProtocolType;
319 use std::net::{IpAddr, Ipv4Addr};
320
321 #[test]
322 fn test_service_creation() -> Result<(), crate::error::DiscoveryError> {
323 let service = ServiceInfo::new(
324 "Test Service",
325 "_http._tcp",
326 8080,
327 Some(vec![("version", "1.0"), ("protocol", "HTTP/1.1")]),
328 )?;
329
330 assert_eq!(service.name, "Test Service");
331 assert_eq!(service.get_attribute("version"), Some(&"1.0".to_string()));
332 assert!(!service.is_expired());
333
334 Ok(())
335 }
336
337 #[test]
338 fn test_service_expiry() -> Result<(), crate::error::DiscoveryError> {
339 let mut service = ServiceInfo::new(
340 "Test Service",
341 "_http._tcp",
342 8080,
343 None,
344 )?
345 .with_ttl(Duration::from_nanos(1));
346
347 std::thread::sleep(Duration::from_millis(1));
348 assert!(service.is_expired());
349
350 service.refresh();
351 assert!(!service.is_expired());
352
353 Ok(())
354 }
355
356 #[test]
357 fn test_service_attributes() -> Result<(), crate::error::DiscoveryError> {
358 let service = ServiceInfo::new("Test Service", "_http._tcp", 8080, None)?
359 .with_attribute("version", "1.0")
360 .with_attribute("secure", "true");
361
362 assert_eq!(service.get_attribute("version"), Some(&"1.0".to_string()));
363 assert_eq!(service.get_attribute("secure"), Some(&"true".to_string()));
364 assert_eq!(service.get_attribute("nonexistent"), None);
365
366 Ok(())
367 }
368
369 #[test]
370 fn test_service_protocol() -> Result<(), crate::error::DiscoveryError> {
371 let service = ServiceInfo::new("Test Service", "_http._tcp", 8080, None)?
372 .with_protocol_type(ProtocolType::Mdns);
373
374 assert_eq!(service.protocol_type(), ProtocolType::Mdns);
375 Ok(())
376 }
377}