rs_zero/discovery/
instance.rs1use std::{collections::BTreeMap, fmt, net::SocketAddr, str::FromStr};
2
3use serde::{Deserialize, Serialize};
4
5use crate::discovery::{DiscoveryError, DiscoveryResult};
6
7#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
9pub struct InstanceEndpoint {
10 host: String,
11 port: u16,
12}
13
14impl InstanceEndpoint {
15 pub fn new(host: impl Into<String>, port: u16) -> DiscoveryResult<Self> {
17 let host = host.into();
18 if host.trim().is_empty() {
19 return Err(DiscoveryError::InvalidEndpoint { endpoint: host });
20 }
21 Ok(Self { host, port })
22 }
23
24 pub fn host(&self) -> &str {
26 &self.host
27 }
28
29 pub fn port(&self) -> u16 {
31 self.port
32 }
33
34 pub fn authority(&self) -> String {
36 format!("{}:{}", self.host, self.port)
37 }
38
39 pub fn from_socket_addr(addr: SocketAddr) -> Self {
41 Self {
42 host: addr.ip().to_string(),
43 port: addr.port(),
44 }
45 }
46}
47
48impl fmt::Display for InstanceEndpoint {
49 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
50 write!(formatter, "{}:{}", self.host, self.port)
51 }
52}
53
54impl FromStr for InstanceEndpoint {
55 type Err = DiscoveryError;
56
57 fn from_str(value: &str) -> Result<Self, Self::Err> {
58 let (host, port) =
59 value
60 .rsplit_once(':')
61 .ok_or_else(|| DiscoveryError::InvalidEndpoint {
62 endpoint: value.to_string(),
63 })?;
64 let port = port.parse().map_err(|_| DiscoveryError::InvalidEndpoint {
65 endpoint: value.to_string(),
66 })?;
67 Self::new(host, port)
68 }
69}
70
71#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
73pub struct ServiceInstance {
74 pub service: String,
76 pub id: String,
78 pub endpoint: InstanceEndpoint,
80 pub metadata: BTreeMap<String, String>,
82 pub healthy: bool,
84 pub weight: u32,
86}
87
88impl ServiceInstance {
89 pub fn new(
91 service: impl Into<String>,
92 id: impl Into<String>,
93 endpoint: InstanceEndpoint,
94 ) -> Self {
95 Self {
96 service: service.into(),
97 id: id.into(),
98 endpoint,
99 metadata: BTreeMap::new(),
100 healthy: true,
101 weight: 1,
102 }
103 }
104
105 pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
107 self.metadata.insert(key.into(), value.into());
108 self
109 }
110
111 pub fn with_health(mut self, healthy: bool) -> Self {
113 self.healthy = healthy;
114 self
115 }
116
117 pub fn with_weight(mut self, weight: u32) -> Self {
119 self.weight = weight.max(1);
120 self
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::{InstanceEndpoint, ServiceInstance};
127
128 #[test]
129 fn endpoint_parses_authority() {
130 let endpoint: InstanceEndpoint = "127.0.0.1:8080".parse().expect("endpoint");
131 assert_eq!(endpoint.host(), "127.0.0.1");
132 assert_eq!(endpoint.port(), 8080);
133 assert_eq!(endpoint.authority(), "127.0.0.1:8080");
134 }
135
136 #[test]
137 fn service_instance_defaults_are_healthy() {
138 let endpoint = InstanceEndpoint::new("localhost", 9000).expect("endpoint");
139 let instance = ServiceInstance::new("user", "user-1", endpoint).with_weight(0);
140 assert!(instance.healthy);
141 assert_eq!(instance.weight, 1);
142 }
143}