synthiaresearch 0.1.2

Rust SDK for the Synthia API
Documentation
//! Errors.
//!
//! The peer SDKs signal failure by throwing: JS throws `Error`/`HttpError`,
//! Python raises `httpx.HTTPStatusError` / `RuntimeError` / `TimeoutError` /
//! `TypeError`. Rust returns `Result`, which is the one deliberate divergence
//! that shows up in every signature — see the divergences table in
//! `docs/reference/rust-api.md`.
//!
//! The variants mirror the peers' failure modes one-for-one so a message
//! written against the JS SDK still reads true here.

use std::fmt;

#[derive(Debug)]
#[non_exhaustive]
pub enum Error {
    /// A non-2xx response. Mirrors the JS `HttpError` message format exactly:
    /// `{METHOD} {path} failed with {status}: {body}`.
    Http {
        status: u16,
        body: String,
        method: String,
        path: String,
    },
    /// No HTTP response arrived (connect/read timeout, reset). The equivalent
    /// of the JS `TypeError` "fetch failed" check and Python's
    /// `httpx.TransportError` — this is what turn recovery treats as transient.
    Transport(reqwest::Error),
    /// A `wait()` deadline elapsed: `{kind} {id} still running after {secs}s`.
    Timeout {
        kind: String,
        id: String,
        secs: f64,
    },
    /// A job finished in a non-succeeded state: `{kind} {id} failed: {error}`.
    Failed {
        kind: String,
        id: String,
        error: Option<String>,
    },
    /// Bad arguments — the analogue of Python's `TypeError` guards
    /// (`seed_ids` with `dataset=`, a non-text probe reply, bad seed content).
    Invalid(String),
    /// Everything else the peers raise as a bare `RuntimeError`/`Error`:
    /// an invalid API key, no datasets yet, a server that ignored `seed_ids`.
    Api(String),
    Json(serde_json::Error),
    Io(std::io::Error),
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Error::Http {
                status,
                body,
                method,
                path,
            } => write!(f, "{method} {path} failed with {status}: {body}"),
            Error::Transport(e) => write!(f, "request failed: {e}"),
            Error::Timeout { kind, id, secs } => {
                write!(f, "{kind} {id} still running after {secs}s")
            }
            Error::Failed { kind, id, error } => write!(
                f,
                "{kind} {id} failed: {}",
                error.as_deref().unwrap_or("None")
            ),
            Error::Invalid(m) | Error::Api(m) => write!(f, "{m}"),
            Error::Json(e) => write!(f, "invalid JSON from the API: {e}"),
            Error::Io(e) => write!(f, "{e}"),
        }
    }
}

impl std::error::Error for Error {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Error::Transport(e) => Some(e),
            Error::Json(e) => Some(e),
            Error::Io(e) => Some(e),
            _ => None,
        }
    }
}

impl From<reqwest::Error> for Error {
    fn from(e: reqwest::Error) -> Self {
        Error::Transport(e)
    }
}

impl From<serde_json::Error> for Error {
    fn from(e: serde_json::Error) -> Self {
        Error::Json(e)
    }
}

impl From<std::io::Error> for Error {
    fn from(e: std::io::Error) -> Self {
        Error::Io(e)
    }
}

impl Error {
    /// Whether a failed turn post may have been a lost response rather than a
    /// rejected write — the gate on check-then-act turn recovery. Mirrors the
    /// JS `#recoverTurn` predicate (`TypeError`, or a retryable `HttpError`)
    /// and Python's `_recover_turn`.
    pub(crate) fn is_transient(&self) -> bool {
        match self {
            Error::Transport(_) => true,
            Error::Http { status, .. } => crate::client::RETRYABLE_STATUS.contains(status),
            _ => false,
        }
    }

    pub fn status(&self) -> Option<u16> {
        match self {
            Error::Http { status, .. } => Some(*status),
            _ => None,
        }
    }
}

pub type Result<T> = std::result::Result<T, Error>;