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 Ok(Endpoint::new(protocol.to_string(), hostname.to_string(), port))
143 }
144}
145
146#[cfg(test)]
147mod test {
148 use alloc::string::ToString;
149
150 use crate::rpc::Endpoint;
151
152 #[test]
153 fn endpoint_parsing_with_hostname_only() {
154 let endpoint = Endpoint::try_from("some.test.domain").unwrap();
155 let expected_endpoint = Endpoint {
156 protocol: "https".to_string(),
157 host: "some.test.domain".to_string(),
158 port: None,
159 };
160
161 assert_eq!(endpoint, expected_endpoint);
162 }
163
164 #[test]
165 fn endpoint_parsing_with_ip() {
166 let endpoint = Endpoint::try_from("192.168.0.1").unwrap();
167 let expected_endpoint = Endpoint {
168 protocol: "https".to_string(),
169 host: "192.168.0.1".to_string(),
170 port: None,
171 };
172
173 assert_eq!(endpoint, expected_endpoint);
174 }
175
176 #[test]
177 fn endpoint_parsing_with_port() {
178 let endpoint = Endpoint::try_from("some.test.domain:8000").unwrap();
179 let expected_endpoint = Endpoint {
180 protocol: "https".to_string(),
181 host: "some.test.domain".to_string(),
182 port: Some(8000),
183 };
184
185 assert_eq!(endpoint, expected_endpoint);
186 }
187
188 #[test]
189 fn endpoint_parsing_with_ip_and_port() {
190 let endpoint = Endpoint::try_from("192.168.0.1:8000").unwrap();
191 let expected_endpoint = Endpoint {
192 protocol: "https".to_string(),
193 host: "192.168.0.1".to_string(),
194 port: Some(8000),
195 };
196
197 assert_eq!(endpoint, expected_endpoint);
198 }
199
200 #[test]
201 fn endpoint_parsing_with_protocol() {
202 let endpoint = Endpoint::try_from("hkttp://some.test.domain").unwrap();
203 let expected_endpoint = Endpoint {
204 protocol: "hkttp".to_string(),
205 host: "some.test.domain".to_string(),
206 port: None,
207 };
208
209 assert_eq!(endpoint, expected_endpoint);
210 }
211
212 #[test]
213 fn endpoint_parsing_with_protocol_and_ip() {
214 let endpoint = Endpoint::try_from("http://192.168.0.1").unwrap();
215 let expected_endpoint = Endpoint {
216 protocol: "http".to_string(),
217 host: "192.168.0.1".to_string(),
218 port: None,
219 };
220
221 assert_eq!(endpoint, expected_endpoint);
222 }
223
224 #[test]
225 fn endpoint_parsing_with_both_protocol_and_port() {
226 let endpoint = Endpoint::try_from("http://some.test.domain:8080").unwrap();
227 let expected_endpoint = Endpoint {
228 protocol: "http".to_string(),
229 host: "some.test.domain".to_string(),
230 port: Some(8080),
231 };
232
233 assert_eq!(endpoint, expected_endpoint);
234 }
235
236 #[test]
237 fn endpoint_parsing_with_ip_and_protocol_and_port() {
238 let endpoint = Endpoint::try_from("http://192.168.0.1:8080").unwrap();
239 let expected_endpoint = Endpoint {
240 protocol: "http".to_string(),
241 host: "192.168.0.1".to_string(),
242 port: Some(8080),
243 };
244
245 assert_eq!(endpoint, expected_endpoint);
246 }
247
248 #[test]
249 fn endpoint_parsing_should_fail_for_invalid_port() {
250 let endpoint = Endpoint::try_from("some.test.domain:8000/hello");
251 assert!(endpoint.is_err());
252 }
253
254 #[test]
255 fn endpoint_parsing_with_final_forward_slash() {
256 let endpoint = Endpoint::try_from("https://some.test.domain:8000/").unwrap();
257 let expected_endpoint = Endpoint {
258 protocol: "https".to_string(),
259 host: "some.test.domain".to_string(),
260 port: Some(8000),
261 };
262
263 assert_eq!(endpoint, expected_endpoint);
264 }
265}