bindist 0.1.0

Rust client library for the BinDist Customer API
Documentation
use serde::{Deserialize, Serialize};

/// Result type used throughout the crate.
pub type Result<T> = std::result::Result<T, Error>;

/// Error returned by the API inside a `{"success": false, "error": {...}}`
/// envelope, or synthesized from a non-2xx response that did not carry one.
///
/// For synthesized errors (auth middleware, reverse proxy, rate limiter,
/// gateway timeout, ...) `code` is derived from the HTTP status
/// (`unauthorized`, `forbidden`, `not_found`, `rate_limited`, `server_error`,
/// `http_error`), `http_status` holds the underlying status code, and
/// `message` is extracted from a bare `{"message":"..."}`/`{"error":"..."}`
/// body or falls back to the status text.
#[derive(Debug, Clone, Deserialize, Serialize, thiserror::Error)]
#[error("{code}: {message}")]
pub struct ApiError {
    pub code: String,
    pub message: String,
    #[serde(skip, default)]
    pub http_status: Option<u16>,
}

#[derive(Debug, thiserror::Error)]
pub enum Error {
    /// The API returned a non-success response (either an error envelope or
    /// a non-2xx status).
    #[error(transparent)]
    Api(#[from] ApiError),

    /// HTTP transport failure (connection, TLS, timeout, ...).
    #[error("http transport error: {0}")]
    Http(#[from] reqwest::Error),

    /// Failed to build a URL from the configured base.
    #[error("invalid url: {0}")]
    InvalidUrl(String),

    /// Failed to decode a successful response body.
    #[error("failed to decode response: {0}")]
    Decode(#[from] serde_json::Error),

    /// A successful response arrived without the expected `data` field.
    #[error("response missing data field")]
    MissingData,
}

impl ApiError {
    pub(crate) fn with_http_status(mut self, status: u16) -> Self {
        if self.http_status.is_none() {
            self.http_status = Some(status);
        }
        self
    }
}