auto_discovery/
service.rs

1//! Service information and event types
2
3use 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/// ServiceInfo holds information about a discovered or registered service
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15pub struct ServiceInfo {
16    /// Unique identifier for this service instance
17    pub id: Uuid,
18    /// Human-readable name of the service
19    pub name: String,
20    /// Service type (e.g., "_http._tcp")
21    pub service_type: ServiceType,
22    /// IP address of the service
23    pub address: IpAddr,
24    /// Port number of the service
25    pub port: u16,
26    /// Additional service attributes
27    pub attributes: ServiceAttributes,
28    /// Protocol used to discover this service
29    pub protocol_type: ProtocolType,
30    /// Time when the service was discovered
31    pub discovered_at: SystemTime,
32    /// Time-to-live for the service record
33    pub ttl: Duration,
34    /// Whether the service has been verified
35    pub verified: bool,
36    /// Network interface name where the service was discovered
37    pub interface: Option<String>,
38}
39
40impl ServiceInfo {
41    /// Create a new service info
42    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    /// Get protocol type used for this service
74    pub fn protocol_type(&self) -> ProtocolType {
75        self.protocol_type
76    }
77
78    /// Set protocol type
79    pub fn with_protocol_type(mut self, protocol_type: ProtocolType) -> Self {
80        self.protocol_type = protocol_type;
81        self
82    }
83
84    /// Get service TTL
85    pub fn ttl(&self) -> Duration {
86        self.ttl
87    }
88
89    /// Set service TTL
90    pub fn with_ttl(mut self, ttl: Duration) -> Self {
91        self.ttl = ttl;
92        self
93    }
94
95    /// Check if service has expired
96    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    /// Refresh service discovery time
104    pub fn refresh(&mut self) {
105        self.discovered_at = SystemTime::now();
106    }
107
108    /// Set service attributes
109    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    /// Set a single attribute
122    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    /// Insert or update an attribute
128    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    /// Get an attribute value
133    pub fn get_attribute(&self, key: &str) -> Option<&String> {
134        self.attributes.get(key)
135    }
136
137    /// Get service port
138    pub fn port(&self) -> u16 {
139        self.port
140    }
141
142    /// Get service address
143    pub fn address(&self) -> IpAddr {
144        self.address
145    }
146
147    /// Set service address
148    pub fn with_address(mut self, address: IpAddr) -> Self {
149        self.address = address;
150        self
151    }
152
153    /// Get service name
154    pub fn name(&self) -> &str {
155        &self.name
156    }
157
158    /// Get service type
159    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/// Events that can occur during service discovery
175#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
176pub enum ServiceEvent {
177    /// A new service was discovered
178    New(ServiceInfo),
179    /// An existing service was updated
180    Updated(ServiceInfo),
181    /// A service was removed or expired
182    Removed(ServiceInfo),
183    /// A service failed verification
184    VerificationFailed(ServiceInfo),
185    /// Discovery process started
186    DiscoveryStarted {
187        /// Service types being searched for
188        service_types: Vec<ServiceType>,
189        /// Protocols being used
190        protocols: Vec<ProtocolType>,
191    },
192    /// Discovery process completed
193    DiscoveryCompleted {
194        /// Number of services found
195        services_found: usize,
196        /// Time taken for discovery
197        duration: Duration,
198    },
199    /// Discovery process failed
200    DiscoveryFailed {
201        /// Error message
202        error: String,
203        /// Service types that failed
204        service_types: Vec<ServiceType>,
205    },
206}
207
208impl ServiceEvent {
209    /// Create a new service event
210    pub fn new(service: ServiceInfo) -> Self {
211        Self::New(service)
212    }
213
214    /// Create an updated service event
215    pub fn updated(service: ServiceInfo) -> Self {
216        Self::Updated(service)
217    }
218
219    /// Create a removed service event
220    pub fn removed(service: ServiceInfo) -> Self {
221        Self::Removed(service)
222    }
223
224    /// Create a verification failed event
225    pub fn verification_failed(service: ServiceInfo) -> Self {
226        Self::VerificationFailed(service)
227    }
228
229    /// Create a discovery started event
230    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    /// Create a discovery completed event
241    pub fn discovery_completed(services_found: usize, duration: Duration) -> Self {
242        Self::DiscoveryCompleted {
243            services_found,
244            duration,
245        }
246    }
247
248    /// Create a discovery failed event
249    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    /// Get the service info if this event contains one
257    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    /// Check if this is a positive event (new or updated service)
268    pub fn is_positive(&self) -> bool {
269        matches!(self, Self::New(_) | Self::Updated(_) | Self::DiscoveryCompleted { .. })
270    }
271
272    /// Check if this is a negative event (removed service or failure)
273    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}