Skip to main content

fermah_common/types/
network.rs

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/// Represents a parsed remote connection using a protocol.
69#[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            // Check if host is an IP address
95            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        // Resolve the host if it's not an IP address
116        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}