Skip to main content

anchor_client/
cluster.rs

1use {
2    anyhow::{anyhow, Result},
3    serde::{Deserialize, Serialize},
4    std::str::FromStr,
5    url::Url,
6};
7
8#[derive(Serialize, Deserialize, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
9pub enum Cluster {
10    Testnet,
11    Mainnet,
12    Devnet,
13    #[default]
14    Localnet,
15    Debug,
16    Custom(String, String),
17}
18
19impl FromStr for Cluster {
20    type Err = anyhow::Error;
21    fn from_str(s: &str) -> Result<Cluster> {
22        match s.to_lowercase().as_str() {
23            "t" | "testnet" => Ok(Cluster::Testnet),
24            "m" | "mainnet" => Ok(Cluster::Mainnet),
25            "d" | "devnet" => Ok(Cluster::Devnet),
26            "l" | "localnet" => Ok(Cluster::Localnet),
27            "g" | "debug" => Ok(Cluster::Debug),
28            _ if s.starts_with("http") => {
29                let http_url = s;
30
31                // Taken from:
32                // https://github.com/solana-labs/solana/blob/aea8f0df1610248d29d8ca3bc0d60e9fabc99e31/web3.js/src/util/url.ts
33
34                let mut ws_url = Url::parse(http_url)?;
35                if let Some(port) = ws_url.port() {
36                    ws_url
37                        .set_port(Some(port + 1))
38                        .map_err(|_| anyhow!("Unable to set port"))?;
39                }
40                if ws_url.scheme() == "https" {
41                    ws_url
42                        .set_scheme("wss")
43                        .map_err(|_| anyhow!("Unable to set scheme"))?;
44                } else {
45                    ws_url
46                        .set_scheme("ws")
47                        .map_err(|_| anyhow!("Unable to set scheme"))?;
48                }
49
50                Ok(Cluster::Custom(http_url.to_string(), ws_url.to_string()))
51            }
52            _ => Err(anyhow::Error::msg(
53                "Cluster must be one of [localnet, testnet, mainnet, devnet] or be an http or \
54                 https url\n",
55            )),
56        }
57    }
58}
59
60impl std::fmt::Display for Cluster {
61    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
62        let clust_str = match self {
63            Cluster::Testnet => "testnet",
64            Cluster::Mainnet => "mainnet",
65            Cluster::Devnet => "devnet",
66            Cluster::Localnet => "localnet",
67            Cluster::Debug => "debug",
68            Cluster::Custom(url, _ws_url) => url,
69        };
70        write!(f, "{clust_str}")
71    }
72}
73
74impl Cluster {
75    pub fn url(&self) -> &str {
76        match self {
77            Cluster::Devnet => "https://api.devnet.solana.com",
78            Cluster::Testnet => "https://api.testnet.solana.com",
79            Cluster::Mainnet => "https://api.mainnet-beta.solana.com",
80            Cluster::Localnet => "http://127.0.0.1:8899",
81            Cluster::Debug => "http://34.90.18.145:8899",
82            Cluster::Custom(url, _ws_url) => url,
83        }
84    }
85    pub fn ws_url(&self) -> &str {
86        match self {
87            Cluster::Devnet => "wss://api.devnet.solana.com",
88            Cluster::Testnet => "wss://api.testnet.solana.com",
89            Cluster::Mainnet => "wss://api.mainnet-beta.solana.com",
90            Cluster::Localnet => "ws://127.0.0.1:8900",
91            Cluster::Debug => "ws://34.90.18.145:8900",
92            Cluster::Custom(_url, ws_url) => ws_url,
93        }
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100
101    fn test_cluster(name: &str, cluster: Cluster) {
102        assert_eq!(Cluster::from_str(name).unwrap(), cluster);
103    }
104
105    #[test]
106    fn test_cluster_parse() {
107        test_cluster("testnet", Cluster::Testnet);
108        test_cluster("mainnet", Cluster::Mainnet);
109        test_cluster("devnet", Cluster::Devnet);
110        test_cluster("localnet", Cluster::Localnet);
111        test_cluster("debug", Cluster::Debug);
112    }
113
114    #[test]
115    #[should_panic]
116    fn test_cluster_bad_parse() {
117        let bad_url = "httq://my_custom_url.test.net";
118        Cluster::from_str(bad_url).unwrap();
119    }
120
121    #[test]
122    fn test_http_port() {
123        let url = "http://my-url.com:7000/";
124        let cluster = Cluster::from_str(url).unwrap();
125        assert_eq!(
126            Cluster::Custom(url.to_string(), "ws://my-url.com:7001/".to_string()),
127            cluster
128        );
129    }
130
131    #[test]
132    fn test_http_no_port() {
133        let url = "http://my-url.com/";
134        let cluster = Cluster::from_str(url).unwrap();
135        assert_eq!(
136            Cluster::Custom(url.to_string(), "ws://my-url.com/".to_string()),
137            cluster
138        );
139    }
140
141    #[test]
142    fn test_https_port() {
143        let url = "https://my-url.com:7000/";
144        let cluster = Cluster::from_str(url).unwrap();
145        assert_eq!(
146            Cluster::Custom(url.to_string(), "wss://my-url.com:7001/".to_string()),
147            cluster
148        );
149    }
150    #[test]
151    fn test_https_no_port() {
152        let url = "https://my-url.com/";
153        let cluster = Cluster::from_str(url).unwrap();
154        assert_eq!(
155            Cluster::Custom(url.to_string(), "wss://my-url.com/".to_string()),
156            cluster
157        );
158    }
159
160    #[test]
161    fn test_upper_case() {
162        let url = "http://my-url.com/FooBar";
163        let cluster = Cluster::from_str(url).unwrap();
164        assert_eq!(
165            Cluster::Custom(url.to_string(), "ws://my-url.com/FooBar".to_string()),
166            cluster
167        );
168    }
169}