jupiter_api_rs/generated/tx/
client.rs1#![allow(clippy::format_in_format_args)]
7#![allow(clippy::let_unit_value)]
8use super::types::*;
9use thiserror::Error;
10pub mod openapi_to_rust_problem {
13 #[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
14 pub struct ProblemDetails {
15 #[serde(rename = "type")]
16 pub type_uri: String,
17 pub title: String,
18 pub status: u16,
19 pub code: String,
20 #[serde(default)]
21 pub errors: Vec<InvalidParameter>,
22 #[serde(default, skip_serializing_if = "Option::is_none")]
23 pub detail: Option<String>,
24 #[serde(default, skip_serializing_if = "Option::is_none")]
25 pub instance: Option<String>,
26 }
27 #[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
28 pub struct InvalidParameter {
29 pub code: String,
30 pub location: String,
31 pub message: String,
32 }
33}
34#[derive(Error, Debug)]
42pub enum HttpError {
43 #[error("Network error: {0}")]
45 Network(#[from] reqwest::Error),
46 #[error("Middleware error: {0}")]
48 Middleware(#[from] reqwest_middleware::Error),
49 #[error("Failed to serialize request: {0}")]
51 Serialization(String),
52 #[error("Authentication error: {0}")]
54 Auth(String),
55 #[error("Request timeout")]
57 Timeout,
58 #[error("Response body exceeded configured limit of {limit} bytes")]
60 ResponseTooLarge { limit: usize },
61 #[error("Configuration error: {0}")]
63 Config(String),
64 #[error("{0}")]
66 Other(String),
67}
68impl HttpError {
69 pub fn serialization_error(error: impl std::fmt::Display) -> Self {
71 Self::Serialization(error.to_string())
72 }
73 pub fn is_retryable(&self) -> bool {
75 matches!(self, Self::Network(_) | Self::Middleware(_) | Self::Timeout)
76 }
77}
78#[derive(Debug, Clone)]
90pub struct ApiError<E> {
91 pub status: u16,
92 pub headers: reqwest::header::HeaderMap,
93 pub body: String,
94 pub raw_body: Vec<u8>,
96 pub typed: Option<E>,
97 pub parse_error: Option<String>,
98}
99const API_ERROR_BODY_DISPLAY_LIMIT: usize = 500;
100const API_ERROR_BODY_TRUNCATION_MARKER: &str = "... [truncated]";
101fn display_api_error_body(body: &str) -> std::borrow::Cow<'_, str> {
102 let Some((end, _)) = body.char_indices().nth(API_ERROR_BODY_DISPLAY_LIMIT) else {
103 return std::borrow::Cow::Borrowed(body);
104 };
105 let mut displayed = String::with_capacity(end + API_ERROR_BODY_TRUNCATION_MARKER.len());
106 displayed.push_str(&body[..end]);
107 displayed.push_str(API_ERROR_BODY_TRUNCATION_MARKER);
108 std::borrow::Cow::Owned(displayed)
109}
110impl<E> ApiError<E> {
111 pub fn is_client_error(&self) -> bool {
112 (400..500).contains(&self.status)
113 }
114 pub fn is_server_error(&self) -> bool {
115 (500..600).contains(&self.status)
116 }
117 pub fn is_retryable(&self) -> bool {
120 matches!(self.status, 429 | 500 | 502 | 503 | 504)
121 }
122 pub fn problem_details(&self) -> Option<openapi_to_rust_problem::ProblemDetails> {
134 let content_type = self
135 .headers
136 .get(reqwest::header::CONTENT_TYPE)?
137 .to_str()
138 .ok()?;
139 let media_type = content_type.split(';').next()?.trim();
140 if !media_type.eq_ignore_ascii_case("application/problem+json") {
141 return None;
142 }
143 serde_json::from_str(&self.body).ok()
144 }
145}
146impl<E: std::fmt::Debug> std::fmt::Display for ApiError<E> {
147 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148 write!(
149 f,
150 "API error {}: {}",
151 self.status,
152 display_api_error_body(&self.body)
153 )?;
154 if let Some(typed) = &self.typed {
155 write!(f, "; typed: {typed:?}")?;
156 }
157 if let Some(parse_error) = &self.parse_error {
158 write!(f, "; parse error: {parse_error}")?;
159 }
160 Ok(())
161 }
162}
163impl<E: std::fmt::Debug> std::error::Error for ApiError<E> {}
164#[derive(Debug, Error)]
172pub enum ApiOpError<E: std::fmt::Debug> {
173 #[error(transparent)]
174 Transport(#[from] HttpError),
175 #[error(transparent)]
176 Api(ApiError<E>),
177}
178impl<E: std::fmt::Debug> ApiOpError<E> {
179 pub fn api(&self) -> Option<&ApiError<E>> {
181 match self {
182 Self::Api(e) => Some(e),
183 Self::Transport(_) => None,
184 }
185 }
186 pub fn is_api_error(&self) -> bool {
189 matches!(self, Self::Api(_))
190 }
191}
192impl<E: std::fmt::Debug> From<reqwest::Error> for ApiOpError<E> {
193 fn from(e: reqwest::Error) -> Self {
194 Self::Transport(HttpError::Network(e))
195 }
196}
197impl<E: std::fmt::Debug> From<reqwest_middleware::Error> for ApiOpError<E> {
198 fn from(e: reqwest_middleware::Error) -> Self {
199 Self::Transport(HttpError::Middleware(e))
200 }
201}
202pub type HttpResult<T> = Result<T, HttpError>;
206use reqwest_middleware::{ClientBuilder, ClientWithMiddleware};
207use std::collections::BTreeMap;
208pub const DEFAULT_MAX_RESPONSE_BODY_BYTES: usize = 8 * 1024 * 1024;
210#[derive(Clone)]
212pub struct HttpClient {
213 base_url: String,
214 api_key: Option<String>,
215 http_client: ClientWithMiddleware,
216 custom_headers: BTreeMap<String, String>,
217 max_response_body_bytes: usize,
218}
219async fn __read_bounded_response_body(
220 mut response: reqwest::Response,
221 limit: usize,
222) -> Result<Vec<u8>, HttpError> {
223 let mut body = Vec::new();
224 while let Some(chunk) = response.chunk().await.map_err(HttpError::Network)? {
225 let next_len = body.len().checked_add(chunk.len());
226 if next_len.is_none_or(|next_len| next_len > limit) {
227 return Err(HttpError::ResponseTooLarge { limit });
228 }
229 body.extend_from_slice(&chunk);
230 }
231 Ok(body)
232}
233impl HttpClient {
234 pub fn new() -> Self {
236 Self::with_config(true)
237 }
238 pub fn with_config(enable_tracing: bool) -> Self {
240 let reqwest_client = reqwest::Client::new();
241 let mut client_builder = ClientBuilder::new(reqwest_client);
242 if enable_tracing {
243 use reqwest_tracing::TracingMiddleware;
244 client_builder = client_builder.with(TracingMiddleware::default());
245 }
246 let http_client = client_builder.build();
247 Self {
248 base_url: "https://tx.jup.ag".to_string(),
249 api_key: None,
250 http_client,
251 custom_headers: BTreeMap::new(),
252 max_response_body_bytes: 8388608usize,
253 }
254 }
255 pub fn with_base_url(mut self, base_url: impl Into<String>) -> Self {
257 self.base_url = base_url.into();
258 self
259 }
260 pub fn with_api_key(mut self, api_key: impl Into<String>) -> Self {
262 self.api_key = Some(api_key.into());
263 self
264 }
265 pub fn with_max_response_body_bytes(mut self, limit: usize) -> Self {
267 self.max_response_body_bytes = limit;
268 self
269 }
270 pub fn with_header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
272 self.custom_headers.insert(name.into(), value.into());
273 self
274 }
275 pub fn with_headers(mut self, headers: BTreeMap<String, String>) -> Self {
277 self.custom_headers.extend(headers);
278 self
279 }
280}
281impl Default for HttpClient {
282 fn default() -> Self {
283 Self::new()
284 }
285}
286fn __pct_encode_path_segment(s: &str) -> String {
287 let mut out = String::with_capacity(s.len());
288 for &b in s.as_bytes() {
289 match b {
290 b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'.' | b'_' | b'~' => {
291 out.push(b as char);
292 }
293 _ => {
294 out.push('%');
295 out.push_str(&format!("{:02X}", b));
296 }
297 }
298 }
299 out
300}
301#[derive(Debug, Clone)]
303pub enum SendTransactionApiError {
304 Status401(SendTransactionResponse401),
305}
306#[doc = concat!("Additive request builder for `", "sendTransaction", "`.")]
307#[must_use]
308pub struct SendTransactionBuilder<'a> {
309 client: &'a HttpClient,
310 request: Option<SendTransactionRequest>,
311}
312impl<'a> SendTransactionBuilder<'a> {
313 #[must_use]
315 pub fn request(mut self, request: SendTransactionRequest) -> Self {
316 self.request = Some(request);
317 self
318 }
319 pub async fn send(
321 self,
322 ) -> Result<SendTransactionResponse, ApiOpError<SendTransactionApiError>> {
323 self.client.send_transaction(self.request).await
324 }
325}
326impl HttpClient {
327 pub async fn send_transaction(
348 &self,
349 request: Option<SendTransactionRequest>,
350 ) -> Result<SendTransactionResponse, ApiOpError<SendTransactionApiError>> {
351 let request_url = format!("{}{}", self.base_url, "/");
352 let mut req = self.http_client.post(request_url);
353 if let Some(request) = request {
354 req = req
355 .body(serde_json::to_vec(&request).map_err(HttpError::serialization_error)?)
356 .header("content-type", "application/json");
357 } else {
358 req = req.header(reqwest::header::CONTENT_LENGTH, "0");
359 }
360 if let Some(api_key) = &self.api_key {
361 req = req.header("x-api-key", api_key.as_str());
362 }
363 for (name, value) in &self.custom_headers {
364 if !name.eq_ignore_ascii_case("accept") {
365 req = req.header(name, value);
366 }
367 }
368 req = req.header(reqwest::header::ACCEPT, "application/json");
369 let response = req.send().await?;
370 let status = response.status();
371 let status_code = status.as_u16();
372 let headers = response.headers().clone();
373 let body_bytes =
374 __read_bounded_response_body(response, self.max_response_body_bytes).await?;
375 let raw_body = body_bytes;
376 let body_text = String::from_utf8_lossy(&raw_body).into_owned();
377 if false || status_code == 200u16 {
378 match serde_json::from_str(&body_text) {
379 Ok(body) => Ok(body),
380 Err(e) => Err(ApiOpError::Api(ApiError {
381 status: status_code,
382 headers: headers,
383 body: body_text,
384 raw_body,
385 typed: None,
386 parse_error: Some(format!("failed to deserialize 2xx response body: {}", e)),
387 })),
388 }
389 } else if status.is_success() {
390 Err(ApiOpError::Api(ApiError {
391 status: status_code,
392 headers,
393 body: body_text,
394 raw_body,
395 typed: None,
396 parse_error: Some(format!(
397 "unexpected successful status {}; generated return type selects `{}`",
398 status_code, "200",
399 )),
400 }))
401 } else {
402 let typed: Option<SendTransactionApiError>;
403 let parse_error: Option<String>;
404 match status_code {
405 401u16 => match serde_json::from_str::<SendTransactionResponse401>(&body_text) {
406 Ok(v) => {
407 typed = Some(SendTransactionApiError::Status401(v));
408 parse_error = None;
409 }
410 Err(e) => {
411 typed = None;
412 parse_error = Some(e.to_string());
413 }
414 },
415 _ => {
416 typed = None;
417 parse_error = None;
418 }
419 }
420 Err(ApiOpError::Api(ApiError {
421 status: status_code,
422 headers,
423 body: body_text,
424 raw_body,
425 typed,
426 parse_error,
427 }))
428 }
429 }
430 #[doc = concat!("Start an additive builder for `", "sendTransaction", "`.")]
431 pub fn send_transaction_builder(&self) -> SendTransactionBuilder<'_> {
432 SendTransactionBuilder {
433 client: self,
434 request: None,
435 }
436 }
437}