1use std::{
2 fmt::Display,
3 net::{IpAddr, Ipv4Addr, SocketAddr, ToSocketAddrs},
4};
5
6use clap::{Parser, ValueEnum};
7use serde::{Deserialize, Serialize};
8use strum::Display;
9use thiserror::Error;
10use url::{ParseError, Url};
11
12#[derive(Serialize, Deserialize, Display, ValueEnum, Debug, Clone, PartialEq, Eq, Hash)]
13#[serde(rename_all = "lowercase")]
14#[strum(serialize_all = "lowercase")]
15pub enum Network {
16 Local,
17 Dev,
18 Main,
19}
20
21impl Network {
22 pub fn to_mm_rpc(&self) -> Connection {
23 match self {
24 Network::Local => Connection::try_from_str("ws://127.0.0.1:8080").unwrap(),
25 Network::Dev => Connection::try_from_str("ws://devnet.fermah.xyz:8080").unwrap(),
26 Network::Main => Connection::try_from_str("ws://mainnet.fermah.xyz:8080").unwrap(),
27 }
28 }
29
30 pub fn to_mm_p2p(&self) -> Connection {
31 match self {
32 Network::Local => Connection::try_from_str("127.0.0.1:8888").unwrap(),
33 Network::Dev => Connection::try_from_str("http://devnet.fermah.xyz:8888").unwrap(),
34 Network::Main => Connection::try_from_str("http://mainnet.fermah.xyz:8888").unwrap(),
35 }
36 }
37}
38
39#[derive(
40 Serialize, Deserialize, Display, ValueEnum, Default, Debug, Copy, Clone, PartialEq, Eq, Hash,
41)]
42#[serde(rename_all = "lowercase")]
43#[strum(serialize_all = "lowercase")]
44pub enum ConnectionProtocol {
45 #[default]
46 Ws,
47 Wss,
48 Http,
49 Https,
50 File,
51}
52
53#[derive(Error, Debug)]
54pub enum ConnectionParseError {
55 #[error("io error: {0}")]
56 Io(#[from] std::io::Error),
57
58 #[error("address does not resolve to a host: {0}")]
59 Resolution(String),
60
61 #[error("url parse error: {0}")]
62 Url(#[from] ParseError),
63
64 #[error("invalid url: {0}")]
65 InvalidUrl(String),
66}
67
68#[derive(Serialize, Deserialize, Parser, Copy, Clone, Debug)]
70pub struct Connection {
71 pub proto: Option<ConnectionProtocol>,
72 pub host: IpAddr,
73 pub port: u16,
74}
75
76impl Default for Connection {
77 fn default() -> Self {
78 Self {
79 proto: None,
80 host: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
81 port: 8080,
82 }
83 }
84}
85
86impl Connection {
87 pub fn try_from_str(value: &str) -> Result<Self, ConnectionParseError> {
88 let (proto, host, port) = if let Ok(url) = Url::parse(value) {
89 let host = url
90 .host_str()
91 .ok_or(ConnectionParseError::InvalidUrl(value.to_string()))?;
92 let port = url.port().unwrap_or(80);
93
94 if let Ok(ip) = host.parse::<IpAddr>() {
96 let proto = ConnectionProtocol::from_str(url.scheme(), true).ok();
97 return Ok(Connection {
98 proto,
99 host: ip,
100 port,
101 });
102 }
103
104 (Some(url.scheme().to_string()), host.to_string(), port)
105 } else if let Ok(socket_addr) = value.parse::<SocketAddr>() {
106 return Ok(Connection {
107 proto: None,
108 host: socket_addr.ip(),
109 port: socket_addr.port(),
110 });
111 } else {
112 return Err(ConnectionParseError::Resolution(value.to_string()));
113 };
114
115 let addresses = (host.as_str(), port).to_socket_addrs()?;
117 let addr = addresses
118 .last()
119 .ok_or(ConnectionParseError::Resolution(value.to_string()))?;
120
121 Ok(Connection {
122 proto: proto.and_then(|p| ConnectionProtocol::from_str(&p, true).ok()),
123 host: addr.ip(),
124 port: addr.port(),
125 })
126 }
127}
128
129impl TryFrom<&str> for Connection {
130 type Error = ConnectionParseError;
131
132 fn try_from(value: &str) -> Result<Self, Self::Error> {
133 Connection::try_from_str(value)
134 }
135}
136
137impl From<Connection> for Url {
138 fn from(conn: Connection) -> Self {
139 Url::parse(&format!(
140 "{}://{}:{}",
141 conn.proto.unwrap_or_default(),
142 conn.host,
143 conn.port
144 ))
145 .unwrap()
146 }
147}
148
149impl From<Connection> for SocketAddr {
150 fn from(value: Connection) -> Self {
151 SocketAddr::new(value.host, value.port)
152 }
153}
154
155impl From<SocketAddr> for Connection {
156 fn from(value: SocketAddr) -> Self {
157 Connection {
158 proto: None,
159 host: value.ip(),
160 port: value.port(),
161 }
162 }
163}
164
165impl Display for Connection {
166 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
167 write!(
168 f,
169 "{}://{}:{}",
170 self.proto.unwrap_or_default(),
171 self.host,
172 self.port
173 )
174 }
175}