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 name = name.into();
49        
50        // Validate name is not empty
51        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        // Validate port is not 0
59        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    /// Get protocol type used for this service
92    pub fn protocol_type(&self) -> ProtocolType {
93        self.protocol_type
94    }
95
96    /// Set protocol type
97    pub fn with_protocol_type(mut self, protocol_type: ProtocolType) -> Self {
98        self.protocol_type = protocol_type;
99        self
100    }
101
102    /// Get service TTL
103    pub fn ttl(&self) -> Duration {
104        self.ttl
105    }
106
107    /// Set service TTL
108    pub fn with_ttl(mut self, ttl: Duration) -> Self {
109        self.ttl = ttl;
110        self
111    }
112
113    /// Check if service has expired
114    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    /// Refresh service discovery time
122    pub fn refresh(&mut self) {
123        self.discovered_at = SystemTime::now();
124    }
125
126    /// Set service attributes
127    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    /// Set a single attribute
140    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    /// Insert or update an attribute
146    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    /// Get an attribute value
151    pub fn get_attribute(&self, key: &str) -> Option<&String> {
152        self.attributes.get(key)
153    }
154
155    /// Get service port
156    pub fn port(&self) -> u16 {
157        self.port
158    }
159
160    /// Get service address
161    pub fn address(&self) -> IpAddr {
162        self.address
163    }
164
165    /// Set service address
166    pub fn with_address(mut self, address: IpAddr) -> Self {
167        self.address = address;
168        self
169    }
170
171    /// Get service name
172    pub fn name(&self) -> &str {
173        &self.name
174    }
175
176    /// Get service type
177    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/// Events that can occur during service discovery
193#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
194pub enum ServiceEvent {
195    /// A new service was discovered
196    New(ServiceInfo),
197    /// An existing service was updated
198    Updated(ServiceInfo),
199    /// A service was removed or expired
200    Removed(ServiceInfo),
201    /// A service failed verification
202    VerificationFailed(ServiceInfo),
203    /// Discovery process started
204    DiscoveryStarted {
205        /// Service types being searched for
206        service_types: Vec<ServiceType>,
207        /// Protocols being used
208        protocols: Vec<ProtocolType>,
209    },
210    /// Discovery process completed
211    DiscoveryCompleted {
212        /// Number of services found
213        services_found: usize,
214        /// Time taken for discovery
215        duration: Duration,
216    },
217    /// Discovery process failed
218    DiscoveryFailed {
219        /// Error message
220        error: String,
221        /// Service types that failed
222        service_types: Vec<ServiceType>,
223    },
224}
225
226impl ServiceEvent {
227    /// Create a new service event
228    pub fn new(service: ServiceInfo) -> Self {
229        Self::New(service)
230    }
231
232    /// Create an updated service event
233    pub fn updated(service: ServiceInfo) -> Self {
234        Self::Updated(service)
235    }
236
237    /// Create a removed service event
238    pub fn removed(service: ServiceInfo) -> Self {
239        Self::Removed(service)
240    }
241
242    /// Create a verification failed event
243    pub fn verification_failed(service: ServiceInfo) -> Self {
244        Self::VerificationFailed(service)
245    }
246
247    /// Create a discovery started event
248    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    /// Create a discovery completed event
259    pub fn discovery_completed(services_found: usize, duration: Duration) -> Self {
260        Self::DiscoveryCompleted {
261            services_found,
262            duration,
263        }
264    }
265
266    /// Create a discovery failed event
267    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    /// Get the service info if this event contains one
275    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    /// Check if this is a positive event (new or updated service)
286    pub fn is_positive(&self) -> bool {
287        matches!(self, Self::New(_) | Self::Updated(_) | Self::DiscoveryCompleted { .. })
288    }
289
290    /// Check if this is a negative event (removed service or failure)
291    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}