simulator-client 0.7.2

Async WebSocket client for the Solana simulator backtest API
Documentation
use std::{error::Error, fmt::Write, time::Duration};

use simulator_api::{BacktestError, BacktestResponse};
use thiserror::Error;
use tokio_tungstenite::tungstenite;

pub type BacktestClientResult<T> = Result<T, BacktestClientError>;

/// Format an error with its full `source()` chain joined by `: `.
///
/// `Display` on most error types (notably `reqwest::Error`,
/// `tokio_tungstenite::Error`, and `solana_client::client_error::Error`) only
/// emits the top-level reason, hiding the cause. Use this when logging so the
/// underlying DNS / TLS / IO detail is preserved.
pub(crate) fn err_chain(e: &(dyn Error + 'static)) -> String {
    let mut out = e.to_string();
    let mut src = e.source();
    while let Some(s) = src {
        let _ = write!(&mut out, ": {s}");
        src = s.source();
    }
    out
}

#[derive(Debug, Error)]
pub enum BacktestClientError {
    #[error("invalid params: {message}")]
    InvalidParams { message: String },

    #[error("invalid API key header value: {0}")]
    InvalidApiKeyHeader(#[from] tungstenite::http::header::InvalidHeaderValue),

    #[error("failed to build websocket request for {url}: {source}")]
    BuildRequest {
        url: String,
        #[source]
        source: Box<tungstenite::Error>,
    },

    #[error("websocket connect to {url} failed: {source}")]
    Connect {
        url: String,
        #[source]
        source: Box<tungstenite::Error>,
    },

    #[error("timeout while {action} after {duration:?}")]
    Timeout {
        action: &'static str,
        duration: Duration,
    },

    #[error("failed to serialize request: {source}")]
    SerializeRequest {
        #[source]
        source: serde_json::Error,
    },

    #[error("failed to deserialize response: {source}; raw={raw}")]
    DeserializeResponse {
        raw: String,
        #[source]
        source: serde_json::Error,
    },

    #[error("remote error: {0}")]
    Remote(#[from] BacktestError),

    #[error("websocket closed: {reason}")]
    Closed { reason: String },

    #[error("unexpected response while {context}: {response:?}")]
    UnexpectedResponse {
        context: &'static str,
        response: Box<BacktestResponse>,
    },

    #[error("websocket error while {action}: {source}")]
    WebSocket {
        action: &'static str,
        #[source]
        source: Box<tungstenite::Error>,
    },

    #[error("HTTP request to {url} failed: {source}")]
    Http {
        url: String,
        #[source]
        source: Box<dyn std::error::Error + Send + Sync>,
    },
}