gmsol_solana_utils/
cluster.rs1use std::str::FromStr;
5
6use url::Url;
7
8#[cfg(client)]
9use solana_client::nonblocking::rpc_client::RpcClient;
10
11#[cfg(client)]
12use solana_sdk::commitment_config::CommitmentConfig;
13
14#[derive(Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17pub enum Cluster {
18 Testnet,
20 Mainnet,
22 Devnet,
24 #[default]
26 Localnet,
27 Debug,
29 Custom(String, String),
31}
32
33impl FromStr for Cluster {
34 type Err = crate::Error;
35 fn from_str(s: &str) -> crate::Result<Cluster> {
36 match s.to_lowercase().as_str() {
37 "t" | "testnet" => Ok(Cluster::Testnet),
38 "m" | "mainnet" => Ok(Cluster::Mainnet),
39 "d" | "devnet" => Ok(Cluster::Devnet),
40 "l" | "localnet" => Ok(Cluster::Localnet),
41 "g" | "debug" => Ok(Cluster::Debug),
42 _ if s.starts_with("http") => {
43 let http_url = s;
44
45 let mut ws_url = Url::parse(http_url)?;
49 if let Some(port) = ws_url.port() {
50 ws_url.set_port(Some(port + 1))
51 .map_err(|_| crate::Error::ParseCluster("Unable to set port"))?;
52 }
53 if ws_url.scheme() == "https" {
54 ws_url.set_scheme("wss")
55 .map_err(|_| crate::Error::ParseCluster("Unable to set scheme"))?;
56 } else {
57 ws_url.set_scheme("ws")
58 .map_err(|_| crate::Error::ParseCluster("Unable to set scheme"))?;
59 }
60
61
62 Ok(Cluster::Custom(http_url.to_string(), ws_url.to_string()))
63 }
64 _ => Err(crate::Error::ParseCluster(
65 "Cluster must be one of [localnet, testnet, mainnet, devnet] or be an http or https url\n",
66 )),
67 }
68 }
69}
70
71impl std::fmt::Display for Cluster {
72 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
73 let clust_str = match self {
74 Cluster::Testnet => "testnet",
75 Cluster::Mainnet => "mainnet",
76 Cluster::Devnet => "devnet",
77 Cluster::Localnet => "localnet",
78 Cluster::Debug => "debug",
79 Cluster::Custom(url, _ws_url) => url,
80 };
81 write!(f, "{clust_str}")
82 }
83}
84
85impl Cluster {
86 pub fn url(&self) -> &str {
88 match self {
89 Cluster::Devnet => "https://api.devnet.solana.com",
90 Cluster::Testnet => "https://api.testnet.solana.com",
91 Cluster::Mainnet => "https://api.mainnet-beta.solana.com",
92 Cluster::Localnet => "http://127.0.0.1:8899",
93 Cluster::Debug => "http://34.90.18.145:8899",
94 Cluster::Custom(url, _ws_url) => url,
95 }
96 }
97
98 pub fn ws_url(&self) -> &str {
100 match self {
101 Cluster::Devnet => "wss://api.devnet.solana.com",
102 Cluster::Testnet => "wss://api.testnet.solana.com",
103 Cluster::Mainnet => "wss://api.mainnet-beta.solana.com",
104 Cluster::Localnet => "ws://127.0.0.1:8900",
105 Cluster::Debug => "ws://34.90.18.145:8900",
106 Cluster::Custom(_url, ws_url) => ws_url,
107 }
108 }
109
110 #[cfg(client)]
112 pub fn rpc(&self, commitment: CommitmentConfig) -> RpcClient {
113 RpcClient::new_with_commitment(self.url().to_string(), commitment)
114 }
115}
116
117#[cfg(feature = "anchor")]
118impl From<Cluster> for anchor_client::Cluster {
119 fn from(cluster: Cluster) -> Self {
120 match cluster {
121 Cluster::Testnet => Self::Testnet,
122 Cluster::Mainnet => Self::Mainnet,
123 Cluster::Devnet => Self::Devnet,
124 Cluster::Localnet => Self::Localnet,
125 Cluster::Debug => Self::Debug,
126 Cluster::Custom(url, ws_url) => Self::Custom(url, ws_url),
127 }
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134
135 fn test_cluster(name: &str, cluster: Cluster) {
136 assert_eq!(Cluster::from_str(name).unwrap(), cluster);
137 }
138
139 #[test]
140 fn test_cluster_parse() {
141 test_cluster("testnet", Cluster::Testnet);
142 test_cluster("mainnet", Cluster::Mainnet);
143 test_cluster("devnet", Cluster::Devnet);
144 test_cluster("localnet", Cluster::Localnet);
145 test_cluster("debug", Cluster::Debug);
146 }
147
148 #[test]
149 #[should_panic]
150 fn test_cluster_bad_parse() {
151 let bad_url = "httq://my_custom_url.test.net";
152 Cluster::from_str(bad_url).unwrap();
153 }
154
155 #[test]
156 fn test_http_port() {
157 let url = "http://my-url.com:7000/";
158 let cluster = Cluster::from_str(url).unwrap();
159 assert_eq!(
160 Cluster::Custom(url.to_string(), "ws://my-url.com:7001/".to_string()),
161 cluster
162 );
163 }
164
165 #[test]
166 fn test_http_no_port() {
167 let url = "http://my-url.com/";
168 let cluster = Cluster::from_str(url).unwrap();
169 assert_eq!(
170 Cluster::Custom(url.to_string(), "ws://my-url.com/".to_string()),
171 cluster
172 );
173 }
174
175 #[test]
176 fn test_https_port() {
177 let url = "https://my-url.com:7000/";
178 let cluster = Cluster::from_str(url).unwrap();
179 assert_eq!(
180 Cluster::Custom(url.to_string(), "wss://my-url.com:7001/".to_string()),
181 cluster
182 );
183 }
184 #[test]
185 fn test_https_no_port() {
186 let url = "https://my-url.com/";
187 let cluster = Cluster::from_str(url).unwrap();
188 assert_eq!(
189 Cluster::Custom(url.to_string(), "wss://my-url.com/".to_string()),
190 cluster
191 );
192 }
193
194 #[test]
195 fn test_upper_case() {
196 let url = "http://my-url.com/FooBar";
197 let cluster = Cluster::from_str(url).unwrap();
198 assert_eq!(
199 Cluster::Custom(url.to_string(), "ws://my-url.com/FooBar".to_string()),
200 cluster
201 );
202 }
203}