1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
use crate::error::{HeliusError, Result};
use reqwest::{Client, Method, RequestBuilder, Response, StatusCode, Url};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::fmt::Debug;
use std::sync::Arc;
/// The SDK version, sourced from Cargo.toml at compile time
pub const SDK_VERSION: &str = env!("CARGO_PKG_VERSION");
/// The User-Agent header value for SDK requests
/// Format: "helius-rust-sdk/{version} (server)"
pub const SDK_USER_AGENT: &str = concat!("helius-rust-sdk/", env!("CARGO_PKG_VERSION"), " (server)");
/// Manages HTTP requests for the `Helius` client
///
/// This struct is responsible for sending HTTP requests and handling responses. It encapsulates details
/// of the `reqwest::Client` to provide a simplified interface for making requests and processing responses
#[derive(Clone)]
pub struct RequestHandler {
pub http_client: Arc<Client>,
}
impl RequestHandler {
/// Creates a new instance of `RequestHandler`
///
/// # Arguments
/// * `client` - A shared instance of a `reqwest::Client`
pub fn new(client: Arc<Client>) -> Result<Self> {
Ok(Self { http_client: client })
}
/// Asynchronously sends a specified HTTP request using the `RequestBuilder`
///
/// # Arguments
/// * `request_builder` - Configured `RequestBuilder` for a specific HTTP request
///
/// # Returns
/// A `Result` wrapping a `reqwest::Response` if the request is send and received successfully
///
/// # Errors
/// Returns `HeliusError::Network` if there is an issue sending the request
async fn send_request(&self, request_builder: RequestBuilder) -> Result<Response> {
request_builder.send().await.map_err(HeliusError::Network)
}
/// Sends an HTTP request and processes the response to deserialize into a specified generic type
///
/// # Type Parameters
/// * `R` - The type of the request body, which must implement `Serialize`
/// * `T` - The expected type of the response, which must implement `Deserialize`
///
/// # Arguments
/// * `method` - The HTTP method to be used for the request
/// * `url` - The URL to which the request is sent
/// * `body` - An optional request body, serialized as JSON if provided
///
/// # Returns
/// A `Result` wrapping the deserialized response data if the response is successful
///
/// # Errors
/// Returns an error if the request fails at any stage, including network errors, serialization errors
/// or if the response status is not successful
pub async fn send<R, T>(&self, method: Method, url: Url, body: Option<&R>) -> Result<T>
where
R: Serialize + ?Sized + Send + Sync + Debug,
T: for<'de> Deserialize<'de> + Default,
{
let mut request_builder: RequestBuilder = self.http_client.request(method, url);
request_builder = request_builder.header("User-Agent", SDK_USER_AGENT);
if let Some(body) = body {
request_builder = request_builder.json(body);
}
let response: Response = self.send_request(request_builder).await?;
self.handle_response(response).await
}
/// Handles the Response for a given HTTP request, attempting to deserialize the response body into the requested type
///
/// # Type Parameters
/// * `T` - The type to which the response body should be deserialized
///
/// # Arguments
/// * `response` - The `reqwest::Response` received from the HTTP request
///
/// # Returns
/// A `Result` wrapping the deserialized data if the response is parsed successfully
///
/// # Errors
/// Returns an error if deserialization fails or if the response status indicates a failure (e.g., 404 Not Found)
async fn handle_response<T: for<'de> Deserialize<'de> + Default>(&self, response: Response) -> Result<T> {
let status: StatusCode = response.status();
let path: String = response.url().path().to_string();
let body_text: String = response.text().await.unwrap_or_default();
if status.is_success() {
if body_text.is_empty() {
return Ok(T::default());
}
// simd-json parses faster than serde_json on large payloads (DAS, getProgramAccountsV2,
// parsed transaction history) but mutates its input buffer in place, so we hand it
// owned bytes via `String::into_bytes`. Falls through to a `serde_json` retry on
// error to surface the more descriptive parser diagnostics for debugging.
let mut body_bytes: Vec<u8> = body_text.into_bytes();
match simd_json::serde::from_slice::<T>(&mut body_bytes) {
Ok(data) => Ok(data),
Err(simd_err) => match serde_json::from_slice::<T>(&body_bytes) {
Ok(data) => Ok(data),
Err(serde_err) => {
let raw: String = String::from_utf8_lossy(&body_bytes).into_owned();
log::error!("Deserialization error (simd-json): {}", simd_err);
log::error!("Deserialization error (serde_json): {}", serde_err);
log::debug!("Raw JSON: {}", raw);
Err(HeliusError::from(serde_err))
}
},
}
} else {
let body_json: serde_json::Result<Value> = serde_json::from_str(&body_text);
match body_json {
Ok(body) => {
let error_message = match body["error"].clone() {
Value::Object(error_value) => error_value
.into_iter()
.map(|(k, v)| format!("{}: {}", k, v))
.collect::<Vec<String>>()
.join(", ")
.to_string(),
Value::String(error_value) => error_value,
_ => "Unknown error".to_string(),
};
Err(HeliusError::from_response_status(status, path, error_message))
}
Err(_) => Err(HeliusError::from_response_status(status, path, body_text)),
}
}
}
}