use url::Url;
use crate::error::{Result, ZeraError};
pub type AmountInput = String;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RpcProtocol {
Http,
Https,
}
pub type Protocol = RpcProtocol;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RpcConfig {
pub endpoint: Option<String>,
pub host: Option<String>,
pub port: Option<u16>,
pub protocol: RpcProtocol,
pub timeout_ms: u64,
pub max_retries: u32,
pub retry_delay_ms: u64,
pub reject_unauthorized: bool,
pub fallback_to_http: bool,
pub fallback_port: u16,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ResolvedRpcEndpoint {
pub base_url: String,
pub hostname: String,
}
impl Default for RpcConfig {
fn default() -> Self {
Self {
endpoint: None,
host: Some("testnet.zera.network".to_string()),
port: None,
protocol: RpcProtocol::Https,
timeout_ms: 10_000,
max_retries: 2,
retry_delay_ms: 500,
reject_unauthorized: true,
fallback_to_http: true,
fallback_port: 80,
}
}
}
impl RpcConfig {
pub fn resolve_endpoint(&self) -> Result<ResolvedRpcEndpoint> {
if let Some(endpoint) = &self.endpoint {
let parsed = Url::parse(endpoint).map_err(|error| {
ZeraError::InvalidConfig(format!("Invalid endpoint URL \"{endpoint}\": {error}"))
})?;
let scheme = parsed.scheme();
if scheme != "http" && scheme != "https" {
return Err(ZeraError::InvalidConfig(format!(
"Endpoint must use http or https: {endpoint}"
)));
}
let host = parsed.host_str().ok_or_else(|| {
ZeraError::InvalidConfig(format!("Endpoint missing host: {endpoint}"))
})?;
let normalized = endpoint.trim_end_matches('/').to_string();
if normalized.is_empty() {
return Err(ZeraError::InvalidConfig(
"Endpoint cannot be empty".to_string(),
));
}
return Ok(ResolvedRpcEndpoint {
base_url: normalized,
hostname: host.to_string(),
});
}
let host = self
.host
.as_deref()
.filter(|host| !host.trim().is_empty())
.ok_or_else(|| ZeraError::InvalidConfig("Host is required".to_string()))?;
let scheme = match self.protocol {
RpcProtocol::Http => "http",
RpcProtocol::Https => "https",
};
let default_port = if matches!(self.protocol, RpcProtocol::Https) {
443
} else {
80
};
let port = self.port.unwrap_or(default_port);
Ok(ResolvedRpcEndpoint {
base_url: format!("{scheme}://{host}:{port}"),
hostname: host.to_string(),
})
}
}