jevapi 0.1.0

An asynchronous, typed Rust client for the TypeSafe AI evaluation API
Documentation
//! Errors from configuration, request validation, transport, and the service.

use reqwest::{StatusCode, header::HeaderMap};

/// A request field failed local validation.
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error("{path}: {message}")]
pub struct ValidationError {
    /// The invalid field, using dotted paths with quoted map keys.
    pub path: String,
    /// A description of the violated constraint.
    pub message: String,
}

impl ValidationError {
    pub(crate) fn new(path: impl Into<String>, message: impl Into<String>) -> Self {
        Self {
            path: path.into(),
            message: message.into(),
        }
    }
}

/// A non-successful HTTP response.
///
/// The service does not specify a fixed error JSON schema, so the response body
/// is retained as text, including non-JSON errors from proxies. `Display` omits
/// the body because it may contain application data.
#[derive(Debug, Clone, thiserror::Error)]
#[error("TypeSafe API returned HTTP {status}")]
pub struct ApiError {
    /// HTTP status, including the service's non-standard 529 status.
    pub status: StatusCode,
    /// Response headers, including request identifiers and retry hints if sent.
    pub headers: HeaderMap,
    /// Response body decoded as UTF-8, replacing invalid sequences.
    pub body: String,
}

impl ApiError {
    /// Parse the error body without assuming a service-specific schema.
    pub fn json(&self) -> Result<serde_json::Value, serde_json::Error> {
        serde_json::from_str(&self.body)
    }

    /// Whether the API documentation recommends retrying this status.
    pub fn is_retryable(&self) -> bool {
        matches!(self.status.as_u16(), 429 | 529)
    }
}

/// Errors returned by this crate.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
    /// Invalid client settings or a missing API key.
    #[error("invalid client configuration: {0}")]
    Configuration(String),
    /// The request failed validation before any network activity.
    #[error(transparent)]
    Validation(#[from] ValidationError),
    /// A connection, TLS, or response-body I/O error.
    #[error("HTTP transport failed: {0}")]
    Transport(#[from] reqwest::Error),
    /// The server returned a non-successful HTTP response.
    #[error(transparent)]
    Api(#[from] Box<ApiError>),
    /// A successful response did not match the documented JSON schema.
    #[error("failed to decode the evaluation response: {0}")]
    Decode(#[source] serde_json::Error),
    /// The overall deadline expired, including HTTP attempts and retry delays.
    #[error("evaluation timed out")]
    Timeout,
}