//! Generated HTTP client for regular API requests
//!
//! This file contains the HTTP client implementation for GET, POST, etc.
//! Do not edit manually - regenerate using the appropriate script.
//! Generated by openapi-to-rust v0.12.2. Source OpenAPI document: openapi/transaction.yaml
#![allow(clippy::format_in_format_args)]
#![allow(clippy::let_unit_value)]
use super::types::*;
use thiserror::Error;
/// The generated validation-problem profile based on RFC 9457.
/// The distinctive namespace avoids collisions with user schemas.
pub mod openapi_to_rust_problem {
#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
pub struct ProblemDetails {
#[serde(rename = "type")]
pub type_uri: String,
pub title: String,
pub status: u16,
pub code: String,
#[serde(default)]
pub errors: Vec<InvalidParameter>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub detail: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub instance: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
pub struct InvalidParameter {
pub code: String,
pub location: String,
pub message: String,
}
}
/// Transport-level errors: failures where no safely inspectable
/// HTTP response is available to the caller.
///
/// HTTP responses with non-2xx status codes are surfaced as
/// [`ApiError`] inside [`ApiOpError::Api`], not here, so callers can
/// always inspect status, headers, and the raw body when the server
/// actually responded.
#[derive(Error, Debug)]
pub enum HttpError {
/// Network or connection error (from reqwest)
#[error("Network error: {0}")]
Network(#[from] reqwest::Error),
/// Middleware error (from reqwest-middleware)
#[error("Middleware error: {0}")]
Middleware(#[from] reqwest_middleware::Error),
/// Request serialization error
#[error("Failed to serialize request: {0}")]
Serialization(String),
/// Authentication error
#[error("Authentication error: {0}")]
Auth(String),
/// Request timeout
#[error("Request timeout")]
Timeout,
/// A response body exceeded the configured in-memory limit
#[error("Response body exceeded configured limit of {limit} bytes")]
ResponseTooLarge { limit: usize },
/// Invalid configuration
#[error("Configuration error: {0}")]
Config(String),
/// Generic error
#[error("{0}")]
Other(String),
}
impl HttpError {
/// Create a serialization error
pub fn serialization_error(error: impl std::fmt::Display) -> Self {
Self::Serialization(error.to_string())
}
/// Check if this transport error is retryable
pub fn is_retryable(&self) -> bool {
matches!(self, Self::Network(_) | Self::Middleware(_) | Self::Timeout)
}
}
/// Envelope returned for any HTTP response that we received but
/// couldn't (or didn't) treat as a successful typed result.
///
/// Includes both non-2xx responses and 2xx responses whose body
/// failed to deserialize into the expected success type. `status`,
/// `headers`, and `raw_body` preserve what the server actually sent,
/// while `body` is a convenient lossy UTF-8 rendering. `typed`
/// carries the parsed per-operation error variant
/// when the body matched a declared schema. Formatting the error
/// limits only the displayed body preview; the public fields
/// retain the complete response and parsing details.
#[derive(Debug, Clone)]
pub struct ApiError<E> {
pub status: u16,
pub headers: reqwest::header::HeaderMap,
pub body: String,
/// Exact response bytes before lossy UTF-8 conversion.
pub raw_body: Vec<u8>,
pub typed: Option<E>,
pub parse_error: Option<String>,
}
const API_ERROR_BODY_DISPLAY_LIMIT: usize = 500;
const API_ERROR_BODY_TRUNCATION_MARKER: &str = "... [truncated]";
fn display_api_error_body(body: &str) -> std::borrow::Cow<'_, str> {
let Some((end, _)) = body.char_indices().nth(API_ERROR_BODY_DISPLAY_LIMIT) else {
return std::borrow::Cow::Borrowed(body);
};
let mut displayed = String::with_capacity(end + API_ERROR_BODY_TRUNCATION_MARKER.len());
displayed.push_str(&body[..end]);
displayed.push_str(API_ERROR_BODY_TRUNCATION_MARKER);
std::borrow::Cow::Owned(displayed)
}
impl<E> ApiError<E> {
pub fn is_client_error(&self) -> bool {
(400..500).contains(&self.status)
}
pub fn is_server_error(&self) -> bool {
(500..600).contains(&self.status)
}
/// Retry guidance for the response. Mirrors the previous
/// HttpError logic for backwards-compatible retry middleware.
pub fn is_retryable(&self) -> bool {
matches!(self.status, 429 | 500 | 502 | 503 | 504)
}
/// Decode the generated RFC 9457 validation-problem profile
/// without replacing a documented per-operation error in `typed`.
///
/// Returns `None` unless the response's `Content-Type` is
/// `application/problem+json`, which is how RFC 9457 identifies
/// a problem document. Most third-party APIs return their
/// errors as plain `application/json`, so this yields `None`
/// against them by design — use `typed` for a documented
/// per-operation error body, or `body` for the raw payload.
/// Servers generated by this tool always emit the problem
/// media type, so this succeeds against them.
pub fn problem_details(&self) -> Option<openapi_to_rust_problem::ProblemDetails> {
let content_type = self
.headers
.get(reqwest::header::CONTENT_TYPE)?
.to_str()
.ok()?;
let media_type = content_type.split(';').next()?.trim();
if !media_type.eq_ignore_ascii_case("application/problem+json") {
return None;
}
serde_json::from_str(&self.body).ok()
}
}
impl<E: std::fmt::Debug> std::fmt::Display for ApiError<E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"API error {}: {}",
self.status,
display_api_error_body(&self.body)
)?;
if let Some(typed) = &self.typed {
write!(f, "; typed: {typed:?}")?;
}
if let Some(parse_error) = &self.parse_error {
write!(f, "; parse error: {parse_error}")?;
}
Ok(())
}
}
impl<E: std::fmt::Debug> std::error::Error for ApiError<E> {}
/// Result error type returned by every generated operation method.
///
/// `Transport` covers failures where we never got an inspectable
/// response (network, timeout, middleware, request-side
/// serialization). `Api` covers any case where the server *did*
/// respond — the envelope always carries status + headers + raw
/// body even when the typed deserialize fails.
#[derive(Debug, Error)]
pub enum ApiOpError<E: std::fmt::Debug> {
#[error(transparent)]
Transport(#[from] HttpError),
#[error(transparent)]
Api(ApiError<E>),
}
impl<E: std::fmt::Debug> ApiOpError<E> {
/// Returns the API envelope when this is an `Api` variant.
pub fn api(&self) -> Option<&ApiError<E>> {
match self {
Self::Api(e) => Some(e),
Self::Transport(_) => None,
}
}
/// True when the underlying error came from the server (i.e.
/// any `Api` variant) rather than the transport layer.
pub fn is_api_error(&self) -> bool {
matches!(self, Self::Api(_))
}
}
impl<E: std::fmt::Debug> From<reqwest::Error> for ApiOpError<E> {
fn from(e: reqwest::Error) -> Self {
Self::Transport(HttpError::Network(e))
}
}
impl<E: std::fmt::Debug> From<reqwest_middleware::Error> for ApiOpError<E> {
fn from(e: reqwest_middleware::Error) -> Self {
Self::Transport(HttpError::Middleware(e))
}
}
/// Result alias for transport-only error paths (e.g. helpers that
/// don't have a per-operation error type). Generated operation
/// methods use [`ApiOpError`] directly.
pub type HttpResult<T> = Result<T, HttpError>;
use reqwest_middleware::{ClientBuilder, ClientWithMiddleware};
use std::collections::BTreeMap;
/// Default upper bound for any response body buffered in memory.
pub const DEFAULT_MAX_RESPONSE_BODY_BYTES: usize = 8 * 1024 * 1024;
/// HTTP client for making API requests
#[derive(Clone)]
pub struct HttpClient {
base_url: String,
api_key: Option<String>,
http_client: ClientWithMiddleware,
custom_headers: BTreeMap<String, String>,
max_response_body_bytes: usize,
}
async fn __read_bounded_response_body(
mut response: reqwest::Response,
limit: usize,
) -> Result<Vec<u8>, HttpError> {
let mut body = Vec::new();
while let Some(chunk) = response.chunk().await.map_err(HttpError::Network)? {
let next_len = body.len().checked_add(chunk.len());
if next_len.is_none_or(|next_len| next_len > limit) {
return Err(HttpError::ResponseTooLarge { limit });
}
body.extend_from_slice(&chunk);
}
Ok(body)
}
impl HttpClient {
/// Create a new HTTP client with default configuration
pub fn new() -> Self {
Self::with_config(true)
}
/// Create a new HTTP client with custom configuration
pub fn with_config(enable_tracing: bool) -> Self {
let reqwest_client = reqwest::Client::new();
let mut client_builder = ClientBuilder::new(reqwest_client);
if enable_tracing {
use reqwest_tracing::TracingMiddleware;
client_builder = client_builder.with(TracingMiddleware::default());
}
let http_client = client_builder.build();
Self {
base_url: "https://tx.jup.ag".to_string(),
api_key: None,
http_client,
custom_headers: BTreeMap::new(),
max_response_body_bytes: 8388608usize,
}
}
/// Set the base URL for all requests
pub fn with_base_url(mut self, base_url: impl Into<String>) -> Self {
self.base_url = base_url.into();
self
}
/// Set the API key for authentication
pub fn with_api_key(mut self, api_key: impl Into<String>) -> Self {
self.api_key = Some(api_key.into());
self
}
/// Set the maximum number of response-body bytes buffered in memory.
pub fn with_max_response_body_bytes(mut self, limit: usize) -> Self {
self.max_response_body_bytes = limit;
self
}
/// Add a custom header to all requests
pub fn with_header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
self.custom_headers.insert(name.into(), value.into());
self
}
/// Add multiple custom headers
pub fn with_headers(mut self, headers: BTreeMap<String, String>) -> Self {
self.custom_headers.extend(headers);
self
}
}
impl Default for HttpClient {
fn default() -> Self {
Self::new()
}
}
fn __pct_encode_path_segment(s: &str) -> String {
let mut out = String::with_capacity(s.len());
for &b in s.as_bytes() {
match b {
b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'.' | b'_' | b'~' => {
out.push(b as char);
}
_ => {
out.push('%');
out.push_str(&format!("{:02X}", b));
}
}
}
out
}
///Typed error responses for `sendTransaction`. One variant per declared non-2xx response.
#[derive(Debug, Clone)]
pub enum SendTransactionApiError {
Status401(SendTransactionResponse401),
}
#[doc = concat!("Additive request builder for `", "sendTransaction", "`.")]
#[must_use]
pub struct SendTransactionBuilder<'a> {
client: &'a HttpClient,
request: Option<SendTransactionRequest>,
}
impl<'a> SendTransactionBuilder<'a> {
/// Replace the complete request body.
#[must_use]
pub fn request(mut self, request: SendTransactionRequest) -> Self {
self.request = Some(request);
self
}
/// Send the request through the existing flat operation method.
pub async fn send(
self,
) -> Result<SendTransactionResponse, ApiOpError<SendTransactionApiError>> {
self.client.send_transaction(self.request).await
}
}
impl HttpClient {
/// Send a transaction
///
/// `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.
///
/// It is send-only: `getLatestBlockhash`, `simulateTransaction`, and confirmation queries are not served. Keep your own RPC for those.
///
/// **Authentication:** requires your Jupiter API key in the `x-api-key` header.
///
/// **Validation:**
/// - Transaction must be a valid signed Solana transaction with all required signatures
/// - Transaction must contain a SOL transfer of >= 1,000,000 lamports (0.001 SOL) to one of the 16 tip receiver accounts
/// - Transaction must not exceed the Solana transaction size limit
///
/// **Tip receiver accounts (16 Jupiter V6 program authorities):**
///
/// `GGztQqQ6pCPaJQnNpXBgELr5cs3WwDakRbh1iEMzjgSJ`, `2MFoS3MPtvyQ4Wh4M9pdfPjz6UhVoNbFbGJAskCPCj3h`, `BQ72nSv9f3PRyRKCBnHLVrerrv37CYTHm5h3s9VSGQDV`, `6U91aKa8pmMxkJwBCfPTmUEfZi6dHe7DcFq2ALvB2tbB`, `4xDsmeTWPNjgSVSS1VTfzFq3iHZhp77ffPkAmkZkdu71`, `CapuXNQoDviLvU1PxFiizLgPNQCxrsag1uMeyk6zLVps`, `9nnLbotNTcUhvbrsA6Mdkx45Sm82G35zo28AqUvjExn8`, `6LXutJvKUw8Q5ue2gCgKHQdAN4suWW8awzFVC6XCguFx`, `HFqp6ErWHY6Uzhj8rFyjYuDya2mXUpYEk8VW75K9PSiY`, `DSN3j1ykL3obAVNv7ZX49VsFCPe4LqzxHnmtLiPwY6xg`, `69yhtoJR4JYPPABZcSNkzuqbaFbwHsCkja1sP1Q2aVT5`, `HU23r7UoZbqTUuh3vA7emAGztFtqwTeVips789vqxxBw`, `3LoAYHuSd7Gh8d7RTFnhvYtiTiefdZ5ByamU42vkzd76`, `3CgvbiM3op4vjrrjH2zcrQUwsqh5veNVRjFCB9N6sRoD`, `GP8StUXNYSZjPikyRsvkTbvRV1GBxMErb59cpeCJnDf1`, `7iWnBRRhBCiNXXPhqiGzvvBkKrvFSWqqmxRyu9VyYBxE`
///
/// Randomise which account you send to across transactions to reduce write-lock contention.
///
/// `POST /`
pub async fn send_transaction(
&self,
request: Option<SendTransactionRequest>,
) -> Result<SendTransactionResponse, ApiOpError<SendTransactionApiError>> {
let request_url = format!("{}{}", self.base_url, "/");
let mut req = self.http_client.post(request_url);
if let Some(request) = request {
req = req
.body(serde_json::to_vec(&request).map_err(HttpError::serialization_error)?)
.header("content-type", "application/json");
} else {
req = req.header(reqwest::header::CONTENT_LENGTH, "0");
}
if let Some(api_key) = &self.api_key {
req = req.header("x-api-key", api_key.as_str());
}
for (name, value) in &self.custom_headers {
if !name.eq_ignore_ascii_case("accept") {
req = req.header(name, value);
}
}
req = req.header(reqwest::header::ACCEPT, "application/json");
let response = req.send().await?;
let status = response.status();
let status_code = status.as_u16();
let headers = response.headers().clone();
let body_bytes =
__read_bounded_response_body(response, self.max_response_body_bytes).await?;
let raw_body = body_bytes;
let body_text = String::from_utf8_lossy(&raw_body).into_owned();
if false || status_code == 200u16 {
match serde_json::from_str(&body_text) {
Ok(body) => Ok(body),
Err(e) => Err(ApiOpError::Api(ApiError {
status: status_code,
headers: headers,
body: body_text,
raw_body,
typed: None,
parse_error: Some(format!("failed to deserialize 2xx response body: {}", e)),
})),
}
} else if status.is_success() {
Err(ApiOpError::Api(ApiError {
status: status_code,
headers,
body: body_text,
raw_body,
typed: None,
parse_error: Some(format!(
"unexpected successful status {}; generated return type selects `{}`",
status_code, "200",
)),
}))
} else {
let typed: Option<SendTransactionApiError>;
let parse_error: Option<String>;
match status_code {
401u16 => match serde_json::from_str::<SendTransactionResponse401>(&body_text) {
Ok(v) => {
typed = Some(SendTransactionApiError::Status401(v));
parse_error = None;
}
Err(e) => {
typed = None;
parse_error = Some(e.to_string());
}
},
_ => {
typed = None;
parse_error = None;
}
}
Err(ApiOpError::Api(ApiError {
status: status_code,
headers,
body: body_text,
raw_body,
typed,
parse_error,
}))
}
}
#[doc = concat!("Start an additive builder for `", "sendTransaction", "`.")]
pub fn send_transaction_builder(&self) -> SendTransactionBuilder<'_> {
SendTransactionBuilder {
client: self,
request: None,
}
}
}