1use alloc::string::{String, ToString};
2use core::fmt;
3
4use miden_protocol::address::NetworkId;
5
6#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
14pub struct Endpoint {
15 protocol: String,
17 host: String,
19 port: Option<u16>,
21}
22
23impl Endpoint {
24 pub(crate) const MIDEN_NODE_PORT: u16 = 57291;
25
26 pub const fn new(protocol: String, host: String, port: Option<u16>) -> Self {
34 Self { protocol, host, port }
35 }
36
37 pub fn mainnet() -> Self {
39 Self::new("https".into(), "rpc.mainnet.miden.io".into(), None)
40 }
41
42 pub fn testnet() -> Self {
44 Self::new("https".into(), "rpc.testnet.miden.io".into(), None)
45 }
46
47 pub fn devnet() -> Self {
49 Self::new("https".into(), "rpc.devnet.miden.io".into(), None)
50 }
51
52 pub fn localhost() -> Self {
54 Self::new("http".into(), "localhost".into(), Some(Self::MIDEN_NODE_PORT))
55 }
56
57 pub fn protocol(&self) -> &str {
58 &self.protocol
59 }
60
61 pub fn host(&self) -> &str {
62 &self.host
63 }
64
65 pub fn port(&self) -> Option<u16> {
66 self.port
67 }
68
69 pub fn to_network_id(&self) -> NetworkId {
70 if self == &Endpoint::mainnet() {
71 NetworkId::Mainnet
72 } else if self == &Endpoint::testnet() {
73 NetworkId::Testnet
74 } else if self == &Endpoint::devnet() {
75 NetworkId::Devnet
76 } else if self == &Endpoint::localhost() {
77 NetworkId::new("mlcl").expect("mlcl should be a valid network ID")
79 } else {
80 NetworkId::new("mcst").expect("mcst should be a valid network ID")
82 }
83 }
84}
85
86impl fmt::Display for Endpoint {
87 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88 match self.port {
89 Some(port) => write!(f, "{}://{}:{}", self.protocol, self.host, port),
90 None => write!(f, "{}://{}", self.protocol, self.host),
91 }
92 }
93}
94
95impl Default for Endpoint {
96 fn default() -> Self {
97 Self::localhost()
98 }
99}
100
101impl TryFrom<&str> for Endpoint {
102 type Error = String;
103
104 fn try_from(endpoint: &str) -> Result<Self, Self::Error> {
105 let protocol_separator_index = endpoint.find("://");
106 let port_separator_index = endpoint.rfind(':');
107
108 let port_separator_index = if port_separator_index == protocol_separator_index {
111 None
112 } else {
113 port_separator_index
114 };
115
116 let (protocol, hostname, port) = match (protocol_separator_index, port_separator_index) {
117 (Some(protocol_idx), Some(port_idx)) => {
118 let (protocol_and_hostname, port) = endpoint.split_at(port_idx);
119 let port = port[1..]
120 .trim_end_matches('/')
121 .parse::<u16>()
122 .map_err(|err| err.to_string())?;
123
124 let (protocol, hostname) = protocol_and_hostname.split_at(protocol_idx);
125 let hostname = &hostname[3..];
127
128 (protocol, hostname, Some(port))
129 },
130 (Some(protocol_idx), None) => {
131 let (protocol, hostname) = endpoint.split_at(protocol_idx);
132 let hostname = &hostname[3..];
134
135 (protocol, hostname, None)
136 },
137 (None, Some(port_idx)) => {
138 let (hostname, port) = endpoint.split_at(port_idx);
139 let port = port[1..]
140 .trim_end_matches('/')
141 .parse::<u16>()
142 .map_err(|err| err.to_string())?;
143
144 ("https", hostname, Some(port))
145 },
146 (None, None) => ("https", endpoint, None),
147 };
148
149 let hostname = hostname.trim_end_matches('/');
150
151 if protocol.is_empty() {
152 return Err("endpoint protocol cannot be empty".to_string());
153 }
154
155 if hostname.is_empty() {
156 return Err("endpoint host cannot be empty".to_string());
157 }
158
159 Ok(Endpoint::new(protocol.to_string(), hostname.to_string(), port))
160 }
161}
162
163#[cfg(test)]
164mod test {
165 use alloc::string::ToString;
166
167 use miden_protocol::address::NetworkId;
168
169 use crate::rpc::Endpoint;
170
171 #[test]
172 fn network_id_of_known_endpoints() {
173 assert_eq!(Endpoint::mainnet().to_network_id(), NetworkId::Mainnet);
174 assert_eq!(Endpoint::testnet().to_network_id(), NetworkId::Testnet);
175 assert_eq!(Endpoint::devnet().to_network_id(), NetworkId::Devnet);
176 assert_eq!(Endpoint::localhost().to_network_id().as_str(), "mlcl");
177 }
178
179 #[test]
180 fn network_id_of_parsed_mainnet_endpoint() {
181 let endpoint = Endpoint::try_from(Endpoint::mainnet().to_string().as_str()).unwrap();
184 assert_eq!(endpoint.to_network_id(), NetworkId::Mainnet);
185 }
186
187 #[test]
188 fn network_id_of_custom_endpoint() {
189 let endpoint = Endpoint::try_from("https://rpc.mainnet.miden.io:8080").unwrap();
191 assert_eq!(endpoint.to_network_id().as_str(), "mcst");
192 }
193
194 #[test]
195 fn endpoint_parsing_with_hostname_only() {
196 let endpoint = Endpoint::try_from("some.test.domain").unwrap();
197 let expected_endpoint = Endpoint {
198 protocol: "https".to_string(),
199 host: "some.test.domain".to_string(),
200 port: None,
201 };
202
203 assert_eq!(endpoint, expected_endpoint);
204 }
205
206 #[test]
207 fn endpoint_parsing_with_ip() {
208 let endpoint = Endpoint::try_from("192.168.0.1").unwrap();
209 let expected_endpoint = Endpoint {
210 protocol: "https".to_string(),
211 host: "192.168.0.1".to_string(),
212 port: None,
213 };
214
215 assert_eq!(endpoint, expected_endpoint);
216 }
217
218 #[test]
219 fn endpoint_parsing_with_port() {
220 let endpoint = Endpoint::try_from("some.test.domain:8000").unwrap();
221 let expected_endpoint = Endpoint {
222 protocol: "https".to_string(),
223 host: "some.test.domain".to_string(),
224 port: Some(8000),
225 };
226
227 assert_eq!(endpoint, expected_endpoint);
228 }
229
230 #[test]
231 fn endpoint_parsing_with_ip_and_port() {
232 let endpoint = Endpoint::try_from("192.168.0.1:8000").unwrap();
233 let expected_endpoint = Endpoint {
234 protocol: "https".to_string(),
235 host: "192.168.0.1".to_string(),
236 port: Some(8000),
237 };
238
239 assert_eq!(endpoint, expected_endpoint);
240 }
241
242 #[test]
243 fn endpoint_parsing_with_protocol() {
244 let endpoint = Endpoint::try_from("hkttp://some.test.domain").unwrap();
245 let expected_endpoint = Endpoint {
246 protocol: "hkttp".to_string(),
247 host: "some.test.domain".to_string(),
248 port: None,
249 };
250
251 assert_eq!(endpoint, expected_endpoint);
252 }
253
254 #[test]
255 fn endpoint_parsing_with_protocol_and_ip() {
256 let endpoint = Endpoint::try_from("http://192.168.0.1").unwrap();
257 let expected_endpoint = Endpoint {
258 protocol: "http".to_string(),
259 host: "192.168.0.1".to_string(),
260 port: None,
261 };
262
263 assert_eq!(endpoint, expected_endpoint);
264 }
265
266 #[test]
267 fn endpoint_parsing_with_both_protocol_and_port() {
268 let endpoint = Endpoint::try_from("http://some.test.domain:8080").unwrap();
269 let expected_endpoint = Endpoint {
270 protocol: "http".to_string(),
271 host: "some.test.domain".to_string(),
272 port: Some(8080),
273 };
274
275 assert_eq!(endpoint, expected_endpoint);
276 }
277
278 #[test]
279 fn endpoint_parsing_with_ip_and_protocol_and_port() {
280 let endpoint = Endpoint::try_from("http://192.168.0.1:8080").unwrap();
281 let expected_endpoint = Endpoint {
282 protocol: "http".to_string(),
283 host: "192.168.0.1".to_string(),
284 port: Some(8080),
285 };
286
287 assert_eq!(endpoint, expected_endpoint);
288 }
289
290 #[test]
291 fn endpoint_parsing_should_fail_for_invalid_port() {
292 let endpoint = Endpoint::try_from("some.test.domain:8000/hello");
293 assert!(endpoint.is_err());
294 }
295
296 #[test]
297 fn endpoint_parsing_should_fail_for_empty_protocol() {
298 let endpoint = Endpoint::try_from("://some.test.domain:8000");
299 assert!(endpoint.is_err());
300 }
301
302 #[test]
303 fn endpoint_parsing_should_fail_for_empty_host() {
304 let endpoint = Endpoint::try_from("https://:8000");
305 assert!(endpoint.is_err());
306 }
307
308 #[test]
309 fn endpoint_parsing_with_final_forward_slash() {
310 let endpoint = Endpoint::try_from("https://some.test.domain:8000/").unwrap();
311 let expected_endpoint = Endpoint {
312 protocol: "https".to_string(),
313 host: "some.test.domain".to_string(),
314 port: Some(8000),
315 };
316
317 assert_eq!(endpoint, expected_endpoint);
318 }
319
320 #[test]
321 fn endpoint_parsing_with_protocol_and_final_forward_slash_no_port() {
322 let endpoint = Endpoint::try_from("http://some.test.domain/").unwrap();
323 let expected_endpoint = Endpoint {
324 protocol: "http".to_string(),
325 host: "some.test.domain".to_string(),
326 port: None,
327 };
328
329 assert_eq!(endpoint, expected_endpoint);
330 }
331}