From f05b513f6b7cd50fb3bfb69d30dd31dc5c3c6b72 Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Fri, 19 Apr 2024 03:54:52 -0400 Subject: [PATCH] Request Handloooorrrr --- Cargo.toml | 2 +- src/client.rs | 15 ++++++++++++- src/config.rs | 1 + src/error.rs | 23 +++++++++++++++++++- src/lib.rs | 3 ++- src/request_handler.rs | 48 ++++++++++++++++++++++++++++++++++++++++++ src/rpc.rs | 1 - src/rpc_client.rs | 19 +++++++++++++++++ 8 files changed, 107 insertions(+), 5 deletions(-) create mode 100644 src/request_handler.rs delete mode 100644 src/rpc.rs create mode 100644 src/rpc_client.rs diff --git a/Cargo.toml b/Cargo.toml index 5a203d7..fa07e71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ documentation = "https://docs.rs/helius-sdk" readme = "README.md" [dependencies] -reqwest = "0.12.3" +reqwest = { version = "0.12.3", features = ["json"] } serde = "1.0.198" serde_json = "1.0.116" solana-sdk = "1.18.11" diff --git a/src/client.rs b/src/client.rs index 76a7135..9099005 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,7 +1,9 @@ #![allow(dead_code)] +use std::sync::Arc; use crate::config::Config; use crate::error::Result; +use crate::rpc_client::RpcClient; use crate::types::Cluster; use reqwest::Client; @@ -9,13 +11,24 @@ use reqwest::Client; pub struct Helius { pub config: Config, pub client: Client, + pub rpc_client: Arc, } impl Helius { pub fn new(api_key: &str, cluster: Cluster) -> Result { let config: Config = Config::new(api_key, cluster)?; let client: Client = Client::new(); + let rpc_client: RpcClient = Arc::new(RpcClient::new(Arc::new(client.clone()), config.clone())); - Ok(Helius { config, client }) + Ok(Helius { + config, + client, + rpc_client, + }) + } + + /// Provides a thread-safe way to access RPC functionalities + pub fn rpc(&self) -> Arc { + self.rpc_client.clone() } } diff --git a/src/config.rs b/src/config.rs index 4903425..bb9a841 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,7 @@ use crate::error::{HeliusError, Result}; use crate::types::{Cluster, HeliusEndpoints}; +#[derive(Clone)] pub struct Config { pub api_key: String, pub cluster: Cluster, diff --git a/src/error.rs b/src/error.rs index 297e3b4..5d4ed62 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,5 +1,4 @@ use reqwest::{Error as ReqwestError, StatusCode}; -use serde_json::Error as SerdeError; use thiserror::Error; #[derive(Debug, Error)] @@ -13,9 +12,18 @@ pub enum HeliusError { #[error("Invalid input: {0}")] InvalidInput(String), + #[error("Network error: {0}")] + Network(ReqwestError), + + #[error("Not found: {text}")] + NotFound { text: String }, + #[error("Too many requests made to {path}")] RateLimitExceeded { path: String }, + #[error("Serialization / Deserialization error: {0}")] + SerdeJson(ReqwestError), + #[error("Unauthorized access to {path}: {text}")] Unauthorized { path: String, text: String }, @@ -23,5 +31,18 @@ pub enum HeliusError { Unknown { code: StatusCode, text: String }, } +impl HeliusError { + pub fn from_response_status(status: StatusCode, path: String, text: String) -> Self { + match status { + StatusCode::BAD_REQUEST => HeliusError::BadRequest { path, text }, + StatusCode::UNAUTHORIZED | StatusCode::FORBIDDEN => HeliusError::Unauthorized { path, text }, + StatusCode::NOT_FOUND => HeliusError::NotFound { text }, + StatusCode::INTERNAL_SERVER_ERROR => HeliusError::InternalError { code: status, text }, + StatusCode::TOO_MANY_REQUESTS => HeliusError::RateLimitExceeded { path }, + _ => HeliusError::Unknown { code: status, text }, + } + } +} + // Handy type alias pub type Result = std::result::Result; diff --git a/src/lib.rs b/src/lib.rs index 1f2f28b..7285f7e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,8 @@ pub mod das_api; pub mod error; pub mod factory; pub mod mint_api; -pub mod rpc; +pub mod request_handler; +pub mod rpc_client; pub mod types; pub mod utils; pub mod webhook; diff --git a/src/request_handler.rs b/src/request_handler.rs new file mode 100644 index 0000000..cd7cb87 --- /dev/null +++ b/src/request_handler.rs @@ -0,0 +1,48 @@ +use crate::error::{HeliusError, Result}; +use reqwest::{Client, Method, RequestBuilder, Response, StatusCode, Url}; +use serde::{Deserialize, Serialize}; +use std::fmt::Debug; +use std::sync::Arc; + +#[derive(Clone)] +pub struct RequestHandler { + pub http_client: Arc, +} + +impl RequestHandler { + pub fn new(client: Arc) -> Result { + Ok(Self { http_client: client }) + } + + async fn send_request(&self, request_builder: RequestBuilder) -> Result { + let response: Response = request_builder.send().await.map_err(|e| HeliusError::Network(e))?; + Ok(response) + } + + pub async fn send(&self, method: Method, url: Url, body: Option<&R>) -> Result + where + R: Serialize + ?Sized + Send + Sync + Debug, + T: for<'de> Deserialize<'de> + Default, + { + let mut request_builder: RequestBuilder = self.http_client.request(method, url.clone()); + + if let Some(body) = body { + request_builder = request_builder.json(body); + } + + let response: Response = self.send_request(request_builder).await?; + let path: String = url.path().to_string(); + self.handle_response(path, response).await + } + + async fn handle_response Deserialize<'de>>(&self, path: String, response: Response) -> Result { + let status: StatusCode = response.status(); + + if status == StatusCode::OK || status == StatusCode::CREATED { + response.json::().await.map_err(HeliusError::SerdeJson) + } else { + let error_text: String = response.text().await.unwrap_or_default(); + Err(HeliusError::from_response_status(status, path, error_text)) + } + } +} diff --git a/src/rpc.rs b/src/rpc.rs deleted file mode 100644 index 8b13789..0000000 --- a/src/rpc.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/rpc_client.rs b/src/rpc_client.rs new file mode 100644 index 0000000..98a9da8 --- /dev/null +++ b/src/rpc_client.rs @@ -0,0 +1,19 @@ +use std::sync::Arc; + +use crate::config::Config; +use crate::error::Result; +use crate::request_handler::RequestHandler; + +use reqwest::Client; + +pub struct RpcClient { + pub handler: RequestHandler, + pub config: Arc, +} + +impl RpcClient { + pub fn new(client: Arc, config: Arc) -> Result { + let handler: RequestHandler = RequestHandler::new(client)?; + Ok(RpcClient { handler, config }) + } +}