use crate::{Error, Result};
use anyhow::anyhow;
use std::{
fmt,
net::{AddrParseError, SocketAddrV4},
};
use url::Url;
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd)]
pub struct WSAddress {
domain: String,
port: Option<u16>,
}
impl WSAddress {
const DEFAULT_PORT: u16 = 9944;
const LOCALHOST: &'static str = "ws://127.0.0.1";
const WSS_DEFAULT_PORT: u16 = 443;
const VARA_TESTNET: &'static str = "wss://testnet.vara.network";
const VARA: &'static str = "wss://rpc.vara.network";
pub fn new(domain: impl AsRef<str>, port: impl Into<Option<u16>>) -> Self {
Self {
domain: domain.as_ref().into(),
port: port.into(),
}
}
pub fn try_new(domain: impl AsRef<str>, port: impl Into<Option<u16>>) -> Result<Self> {
let domain = domain.as_ref().to_string();
let port = port.into();
let url = Url::parse(domain.as_ref())?;
let valid_domain = matches!(url.scheme(), "ws" | "wss")
&& !url.cannot_be_a_base()
&& url.has_host()
&& url.port().is_none()
&& url.query().is_none()
&& url.fragment().is_none();
if !valid_domain {
return Err(Error::WSDomainInvalid);
}
Ok(Self { domain, port })
}
pub fn dev() -> Self {
Self::dev_with_port(Self::DEFAULT_PORT)
}
pub fn dev_with_port(port: u16) -> Self {
Self::new(Self::LOCALHOST, port)
}
pub fn vara_testnet() -> Self {
Self::new(Self::VARA_TESTNET, Self::WSS_DEFAULT_PORT)
}
pub fn vara() -> Self {
Self::new(Self::VARA, None)
}
pub fn url(&self) -> String {
if let Some(port) = self.port {
format!("{}:{port}", self.domain)
} else {
self.domain.to_string()
}
}
}
impl fmt::Debug for WSAddress {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
impl fmt::Display for WSAddress {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.url())
}
}
impl From<SocketAddrV4> for WSAddress {
fn from(addr: SocketAddrV4) -> Self {
let tls = addr.port() == 443;
let scheme_prefix = if tls { "wss" } else { "ws" }.to_string() + "://";
Self::new(scheme_prefix + &addr.ip().to_string(), addr.port())
}
}
impl TryInto<SocketAddrV4> for WSAddress {
type Error = Error;
fn try_into(self) -> Result<SocketAddrV4, Self::Error> {
let domain = self.domain.split("://").collect::<Vec<_>>();
let (ip, mb_port) = if domain.len() != 2 {
return Err(anyhow!("Invalid domain").into());
} else {
match domain[0] {
"ws" => (domain[1], 80),
"wss" => (domain[1], 443),
_ => return Err(anyhow!("Invalid scheme").into()),
}
};
Ok(format!("{}:{}", ip, self.port.unwrap_or(mb_port))
.parse()
.map_err(|e: AddrParseError| anyhow!(e))?)
}
}