monocoque_core/
endpoint.rs1use std::fmt;
6use std::net::SocketAddr;
7use std::path::PathBuf;
8use std::str::FromStr;
9
10#[derive(Debug, Clone, PartialEq, Eq, Hash)]
12pub enum Endpoint {
13 Tcp(SocketAddr),
15 #[cfg(unix)]
17 Ipc(PathBuf),
18 Inproc(String),
20}
21
22impl Endpoint {
23 pub fn parse(s: &str) -> Result<Self, EndpointError> {
49 s.parse()
50 }
51
52 #[must_use]
54 pub const fn is_tcp(&self) -> bool {
55 matches!(self, Self::Tcp(_))
56 }
57
58 #[cfg(unix)]
60 #[must_use]
61 pub const fn is_ipc(&self) -> bool {
62 matches!(self, Self::Ipc(_))
63 }
64
65 #[must_use]
67 pub const fn is_inproc(&self) -> bool {
68 matches!(self, Self::Inproc(_))
69 }
70}
71
72impl FromStr for Endpoint {
73 type Err = EndpointError;
74
75 fn from_str(s: &str) -> Result<Self, Self::Err> {
76 if let Some(addr) = s.strip_prefix("tcp://") {
77 let socket_addr = addr
78 .parse::<SocketAddr>()
79 .map_err(|_| EndpointError::InvalidTcpAddress(addr.to_string()))?;
80 Ok(Self::Tcp(socket_addr))
81 } else if let Some(path) = s.strip_prefix("ipc://") {
82 #[cfg(unix)]
83 {
84 if !path.starts_with('/') {
85 return Err(EndpointError::InvalidScheme(s.to_string()));
86 }
87 Ok(Self::Ipc(PathBuf::from(path)))
88 }
89 #[cfg(not(unix))]
90 {
91 Err(EndpointError::IpcNotSupported)
92 }
93 } else if let Some(name) = s.strip_prefix("inproc://") {
94 if name.is_empty() {
95 Err(EndpointError::InvalidInprocName(
96 "inproc name cannot be empty".to_string(),
97 ))
98 } else {
99 Ok(Self::Inproc(name.to_string()))
100 }
101 } else {
102 Err(EndpointError::InvalidScheme(s.to_string()))
103 }
104 }
105}
106
107impl fmt::Display for Endpoint {
108 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109 match self {
110 Self::Tcp(addr) => write!(f, "tcp://{addr}"),
111 #[cfg(unix)]
112 Self::Ipc(path) => write!(f, "ipc://{}", path.display()),
113 Self::Inproc(name) => write!(f, "inproc://{name}"),
114 }
115 }
116}
117
118#[derive(Debug, thiserror::Error)]
120pub enum EndpointError {
121 #[error("Invalid scheme in endpoint: {0} (expected tcp://, ipc://, or inproc://)")]
122 InvalidScheme(String),
123
124 #[error("Invalid TCP address: {0}")]
125 InvalidTcpAddress(String),
126
127 #[error("Invalid inproc name: {0}")]
128 InvalidInprocName(String),
129
130 #[error("IPC transport not supported on this platform")]
131 IpcNotSupported,
132
133 #[error("I/O error: {0}")]
134 Io(#[from] std::io::Error),
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 #[test]
142 fn test_parse_tcp_ipv4() {
143 let endpoint = Endpoint::parse("tcp://127.0.0.1:5555").unwrap();
144 assert!(matches!(endpoint, Endpoint::Tcp(_)));
145 assert_eq!(endpoint.to_string(), "tcp://127.0.0.1:5555");
146 }
147
148 #[test]
149 fn test_parse_tcp_ipv6() {
150 let endpoint = Endpoint::parse("tcp://[::1]:5555").unwrap();
151 assert!(matches!(endpoint, Endpoint::Tcp(_)));
152 }
153
154 #[cfg(unix)]
155 #[test]
156 fn test_parse_ipc() {
157 let endpoint = Endpoint::parse("ipc:///tmp/test.sock").unwrap();
158 assert!(matches!(endpoint, Endpoint::Ipc(_)));
159 assert_eq!(endpoint.to_string(), "ipc:///tmp/test.sock");
160 }
161
162 #[cfg(unix)]
163 #[test]
164 fn test_invalid_ipc_relative_path() {
165 let result = Endpoint::parse("ipc://relative.sock");
166 assert!(matches!(result, Err(EndpointError::InvalidScheme(_))));
167 }
168
169 #[test]
170 fn test_invalid_scheme() {
171 let result = Endpoint::parse("http://127.0.0.1:5555");
172 assert!(matches!(result, Err(EndpointError::InvalidScheme(_))));
173 }
174
175 #[test]
176 fn test_invalid_tcp_address() {
177 let result = Endpoint::parse("tcp://invalid:port");
178 assert!(matches!(result, Err(EndpointError::InvalidTcpAddress(_))));
179 }
180
181 #[test]
182 fn test_parse_inproc() {
183 let endpoint = Endpoint::parse("inproc://my-endpoint").unwrap();
184 assert!(matches!(endpoint, Endpoint::Inproc(_)));
185 assert_eq!(endpoint.to_string(), "inproc://my-endpoint");
186 }
187
188 #[test]
189 fn test_invalid_inproc_empty() {
190 let result = Endpoint::parse("inproc://");
191 assert!(matches!(result, Err(EndpointError::InvalidInprocName(_))));
192 }
193}