Skip to main content

jupiter_api_rs/generated/tx/
client.rs

1//! Generated HTTP client for regular API requests
2//!
3//! This file contains the HTTP client implementation for GET, POST, etc.
4//! Do not edit manually - regenerate using the appropriate script.
5//! Generated by openapi-to-rust v0.12.2. Source OpenAPI document: openapi/transaction.yaml
6#![allow(clippy::format_in_format_args)]
7#![allow(clippy::let_unit_value)]
8use super::types::*;
9use thiserror::Error;
10/// The generated validation-problem profile based on RFC 9457.
11/// The distinctive namespace avoids collisions with user schemas.
12pub 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/// Transport-level errors: failures where no safely inspectable
35/// HTTP response is available to the caller.
36///
37/// HTTP responses with non-2xx status codes are surfaced as
38/// [`ApiError`] inside [`ApiOpError::Api`], not here, so callers can
39/// always inspect status, headers, and the raw body when the server
40/// actually responded.
41#[derive(Error, Debug)]
42pub enum HttpError {
43    /// Network or connection error (from reqwest)
44    #[error("Network error: {0}")]
45    Network(#[from] reqwest::Error),
46    /// Middleware error (from reqwest-middleware)
47    #[error("Middleware error: {0}")]
48    Middleware(#[from] reqwest_middleware::Error),
49    /// Request serialization error
50    #[error("Failed to serialize request: {0}")]
51    Serialization(String),
52    /// Authentication error
53    #[error("Authentication error: {0}")]
54    Auth(String),
55    /// Request timeout
56    #[error("Request timeout")]
57    Timeout,
58    /// A response body exceeded the configured in-memory limit
59    #[error("Response body exceeded configured limit of {limit} bytes")]
60    ResponseTooLarge { limit: usize },
61    /// Invalid configuration
62    #[error("Configuration error: {0}")]
63    Config(String),
64    /// Generic error
65    #[error("{0}")]
66    Other(String),
67}
68impl HttpError {
69    /// Create a serialization error
70    pub fn serialization_error(error: impl std::fmt::Display) -> Self {
71        Self::Serialization(error.to_string())
72    }
73    /// Check if this transport error is retryable
74    pub fn is_retryable(&self) -> bool {
75        matches!(self, Self::Network(_) | Self::Middleware(_) | Self::Timeout)
76    }
77}
78/// Envelope returned for any HTTP response that we received but
79/// couldn't (or didn't) treat as a successful typed result.
80///
81/// Includes both non-2xx responses and 2xx responses whose body
82/// failed to deserialize into the expected success type. `status`,
83/// `headers`, and `raw_body` preserve what the server actually sent,
84/// while `body` is a convenient lossy UTF-8 rendering. `typed`
85/// carries the parsed per-operation error variant
86/// when the body matched a declared schema. Formatting the error
87/// limits only the displayed body preview; the public fields
88/// retain the complete response and parsing details.
89#[derive(Debug, Clone)]
90pub struct ApiError<E> {
91    pub status: u16,
92    pub headers: reqwest::header::HeaderMap,
93    pub body: String,
94    /// Exact response bytes before lossy UTF-8 conversion.
95    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    /// Retry guidance for the response. Mirrors the previous
118    /// HttpError logic for backwards-compatible retry middleware.
119    pub fn is_retryable(&self) -> bool {
120        matches!(self.status, 429 | 500 | 502 | 503 | 504)
121    }
122    /// Decode the generated RFC 9457 validation-problem profile
123    /// without replacing a documented per-operation error in `typed`.
124    ///
125    /// Returns `None` unless the response's `Content-Type` is
126    /// `application/problem+json`, which is how RFC 9457 identifies
127    /// a problem document. Most third-party APIs return their
128    /// errors as plain `application/json`, so this yields `None`
129    /// against them by design — use `typed` for a documented
130    /// per-operation error body, or `body` for the raw payload.
131    /// Servers generated by this tool always emit the problem
132    /// media type, so this succeeds against them.
133    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/// Result error type returned by every generated operation method.
165///
166/// `Transport` covers failures where we never got an inspectable
167/// response (network, timeout, middleware, request-side
168/// serialization). `Api` covers any case where the server *did*
169/// respond — the envelope always carries status + headers + raw
170/// body even when the typed deserialize fails.
171#[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    /// Returns the API envelope when this is an `Api` variant.
180    pub fn api(&self) -> Option<&ApiError<E>> {
181        match self {
182            Self::Api(e) => Some(e),
183            Self::Transport(_) => None,
184        }
185    }
186    /// True when the underlying error came from the server (i.e.
187    /// any `Api` variant) rather than the transport layer.
188    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}
202/// Result alias for transport-only error paths (e.g. helpers that
203/// don't have a per-operation error type). Generated operation
204/// methods use [`ApiOpError`] directly.
205pub type HttpResult<T> = Result<T, HttpError>;
206use reqwest_middleware::{ClientBuilder, ClientWithMiddleware};
207use std::collections::BTreeMap;
208/// Default upper bound for any response body buffered in memory.
209pub const DEFAULT_MAX_RESPONSE_BODY_BYTES: usize = 8 * 1024 * 1024;
210/// HTTP client for making API requests
211#[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    /// Create a new HTTP client with default configuration
235    pub fn new() -> Self {
236        Self::with_config(true)
237    }
238    /// Create a new HTTP client with custom configuration
239    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    /// Set the base URL for all requests
256    pub fn with_base_url(mut self, base_url: impl Into<String>) -> Self {
257        self.base_url = base_url.into();
258        self
259    }
260    /// Set the API key for authentication
261    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    /// Set the maximum number of response-body bytes buffered in memory.
266    pub fn with_max_response_body_bytes(mut self, limit: usize) -> Self {
267        self.max_response_body_bytes = limit;
268        self
269    }
270    /// Add a custom header to all requests
271    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    /// Add multiple custom headers
276    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///Typed error responses for `sendTransaction`. One variant per declared non-2xx response.
302#[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    /// Replace the complete request body.
314    #[must_use]
315    pub fn request(mut self, request: SendTransactionRequest) -> Self {
316        self.request = Some(request);
317        self
318    }
319    /// Send the request through the existing flat operation method.
320    pub async fn send(
321        self,
322    ) -> Result<SendTransactionResponse, ApiOpError<SendTransactionApiError>> {
323        self.client.send_transaction(self.request).await
324    }
325}
326impl HttpClient {
327    /// Send a transaction
328    ///
329    /// `tx.jup.ag` is a Solana RPC-compatible endpoint (compatible where possible) that forwards signed transactions to the Solana cluster through Jupiter's landing infrastructure. It implements the standard Solana [`sendTransaction`](https://solana.com/docs/rpc/http/sendtransaction) method, so any Solana client can point at it. A success response means the transaction was accepted and forwarded, not that it landed; confirm landing on your own RPC.
330    ///
331    /// It is send-only: `getLatestBlockhash`, `simulateTransaction`, and confirmation queries are not served. Keep your own RPC for those.
332    ///
333    /// **Authentication:** requires your Jupiter API key in the `x-api-key` header.
334    ///
335    /// **Validation:**
336    /// - Transaction must be a valid signed Solana transaction with all required signatures
337    /// - Transaction must contain a SOL transfer of >= 1,000,000 lamports (0.001 SOL) to one of the 16 tip receiver accounts
338    /// - Transaction must not exceed the Solana transaction size limit
339    ///
340    /// **Tip receiver accounts (16 Jupiter V6 program authorities):**
341    ///
342    /// `GGztQqQ6pCPaJQnNpXBgELr5cs3WwDakRbh1iEMzjgSJ`, `2MFoS3MPtvyQ4Wh4M9pdfPjz6UhVoNbFbGJAskCPCj3h`, `BQ72nSv9f3PRyRKCBnHLVrerrv37CYTHm5h3s9VSGQDV`, `6U91aKa8pmMxkJwBCfPTmUEfZi6dHe7DcFq2ALvB2tbB`, `4xDsmeTWPNjgSVSS1VTfzFq3iHZhp77ffPkAmkZkdu71`, `CapuXNQoDviLvU1PxFiizLgPNQCxrsag1uMeyk6zLVps`, `9nnLbotNTcUhvbrsA6Mdkx45Sm82G35zo28AqUvjExn8`, `6LXutJvKUw8Q5ue2gCgKHQdAN4suWW8awzFVC6XCguFx`, `HFqp6ErWHY6Uzhj8rFyjYuDya2mXUpYEk8VW75K9PSiY`, `DSN3j1ykL3obAVNv7ZX49VsFCPe4LqzxHnmtLiPwY6xg`, `69yhtoJR4JYPPABZcSNkzuqbaFbwHsCkja1sP1Q2aVT5`, `HU23r7UoZbqTUuh3vA7emAGztFtqwTeVips789vqxxBw`, `3LoAYHuSd7Gh8d7RTFnhvYtiTiefdZ5ByamU42vkzd76`, `3CgvbiM3op4vjrrjH2zcrQUwsqh5veNVRjFCB9N6sRoD`, `GP8StUXNYSZjPikyRsvkTbvRV1GBxMErb59cpeCJnDf1`, `7iWnBRRhBCiNXXPhqiGzvvBkKrvFSWqqmxRyu9VyYBxE`
343    ///
344    /// Randomise which account you send to across transactions to reduce write-lock contention.
345    ///
346    /// `POST /`
347    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}