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