use thiserror::Error;
#[derive(Debug, Error)]
pub enum UrlError {
#[error("cannot derive WebSocket URL from `{url}`: expected http:// or https:// scheme")]
InvalidScheme { url: String },
}
pub fn http_to_ws_url(url: &str) -> Result<String, UrlError> {
let scheme = if url.starts_with("https://") {
"wss"
} else if url.starts_with("http://") {
"ws"
} else {
return Err(UrlError::InvalidScheme {
url: url.to_string(),
});
};
let rest = url.split_once("://").map(|x| x.1).unwrap_or(url);
Ok(format!("{scheme}://{rest}"))
}
pub fn http_base_from_ws_url(ws_url: &str) -> String {
let http = ws_url
.replacen("wss://", "https://", 1)
.replacen("ws://", "http://", 1);
if let Some(start) = http.find("://").map(|i| i + 3)
&& let Some(slash) = http[start..].find('/')
{
return http[..start + slash].to_string();
}
http
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn http_to_ws_converts_http() {
assert_eq!(
http_to_ws_url("http://localhost:8899").unwrap(),
"ws://localhost:8899"
);
}
#[test]
fn http_to_ws_converts_https() {
assert_eq!(
http_to_ws_url("https://api.mainnet-beta.solana.com").unwrap(),
"wss://api.mainnet-beta.solana.com"
);
}
#[test]
fn http_to_ws_rejects_other_schemes() {
assert!(matches!(
http_to_ws_url("ws://example.com"),
Err(UrlError::InvalidScheme { .. })
));
assert!(matches!(
http_to_ws_url("ftp://example.com"),
Err(UrlError::InvalidScheme { .. })
));
assert!(matches!(
http_to_ws_url("example.com"),
Err(UrlError::InvalidScheme { .. })
));
}
#[test]
fn http_base_strips_path() {
assert_eq!(
http_base_from_ws_url("wss://host:8900/backtest"),
"https://host:8900"
);
assert_eq!(
http_base_from_ws_url("ws://localhost:8900/backtest"),
"http://localhost:8900"
);
}
#[test]
fn http_base_no_path() {
assert_eq!(
http_base_from_ws_url("wss://host:8900"),
"https://host:8900"
);
}
}