98 lines
2.8 KiB
Rust
98 lines
2.8 KiB
Rust
#[cfg(not(target_arch = "wasm32"))]
|
|
use std::time::Duration;
|
|
|
|
use reqwest::StatusCode;
|
|
use thiserror::Error;
|
|
use url::Url;
|
|
|
|
// Re-export request types
|
|
pub use nym_explorer_api_requests::{PrettyDetailedGatewayBond, PrettyDetailedMixNodeBond};
|
|
|
|
// Paths
|
|
const API_VERSION: &str = "v1";
|
|
const MIXNODES: &str = "mix-nodes";
|
|
const GATEWAYS: &str = "gateways";
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
const REQUEST_TIMEOUT: Duration = Duration::from_secs(5);
|
|
|
|
#[derive(Debug, Error)]
|
|
pub enum ExplorerApiError {
|
|
#[error("REST request error: {0}")]
|
|
ReqwestError(#[from] reqwest::Error),
|
|
|
|
#[error("URL parse error: {0}")]
|
|
UrlParseError(#[from] url::ParseError),
|
|
|
|
#[error("not found")]
|
|
NotFound,
|
|
|
|
#[error("request failure: {0}")]
|
|
RequestFailure(String),
|
|
}
|
|
|
|
pub struct ExplorerClient {
|
|
url: Url,
|
|
client: reqwest::Client,
|
|
}
|
|
|
|
impl ExplorerClient {
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
pub fn new(url: url::Url) -> Result<Self, ExplorerApiError> {
|
|
let client = reqwest::Client::builder()
|
|
.timeout(REQUEST_TIMEOUT)
|
|
.build()?;
|
|
Ok(Self { client, url })
|
|
}
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
pub fn new(url: url::Url) -> Result<Self, ExplorerApiError> {
|
|
let client = reqwest::Client::builder().build()?;
|
|
Ok(Self { client, url })
|
|
}
|
|
|
|
async fn send_get_request(
|
|
&self,
|
|
paths: &[&str],
|
|
) -> Result<reqwest::Response, ExplorerApiError> {
|
|
let url = combine_url(self.url.clone(), paths)?;
|
|
log::trace!("Sending GET request {url:?}");
|
|
Ok(self.client.get(url).send().await?)
|
|
}
|
|
|
|
async fn query_explorer_api<T>(&self, paths: &[&str]) -> Result<T, ExplorerApiError>
|
|
where
|
|
T: std::fmt::Debug,
|
|
T: for<'a> serde::Deserialize<'a>,
|
|
{
|
|
let response = self.send_get_request(paths).await?;
|
|
if response.status().is_success() {
|
|
let res = response.json::<T>().await?;
|
|
log::trace!("Got response: {res:?}");
|
|
Ok(res)
|
|
} else if response.status() == StatusCode::NOT_FOUND {
|
|
Err(ExplorerApiError::NotFound)
|
|
} else {
|
|
Err(ExplorerApiError::RequestFailure(response.text().await?))
|
|
}
|
|
}
|
|
|
|
pub async fn get_mixnodes(&self) -> Result<Vec<PrettyDetailedMixNodeBond>, ExplorerApiError> {
|
|
self.query_explorer_api(&[API_VERSION, MIXNODES]).await
|
|
}
|
|
|
|
pub async fn get_gateways(&self) -> Result<Vec<PrettyDetailedGatewayBond>, ExplorerApiError> {
|
|
self.query_explorer_api(&[API_VERSION, GATEWAYS]).await
|
|
}
|
|
}
|
|
|
|
fn combine_url(mut base_url: Url, paths: &[&str]) -> Result<Url, ExplorerApiError> {
|
|
{
|
|
let mut segments = base_url.path_segments_mut().expect("failed to parse url");
|
|
for path in paths {
|
|
segments.push(path);
|
|
}
|
|
}
|
|
Ok(base_url)
|
|
}
|