spiral-sdk 0.1.11

Rust SDK for Spiral resource and table APIs
Documentation
//! Errors produced by SDK configuration, control-plane management and table operations.

use std::fmt::Display;

use reqwest::{Error as HttpError, StatusCode, Url};
use spiral_contract::call::CallError;
use uuid::Uuid;

use crate::api::apps::AppsError;
use crate::api::authn::AuthnError;
use crate::api::authorization::AuthorizationError;
use crate::api::catalog::{CatalogError, PublicationConflict};
use crate::api::connections::ConnectionsError;
use crate::api::enrichments::EnrichmentsError;
use crate::api::locations::LocationsError;
use crate::api::organization::OrganizationError;
use crate::api::project::ProjectError;
use crate::api::storage::StorageError;
use crate::api::tables::TablesError;
use crate::api::tasks::TasksError;
use crate::api::usage::UsageError;
use crate::api::vector_indexes::VectorIndexesError;
use crate::api::workers::WorkersError;
use crate::api::workloads::WorkloadsError;
use crate::auth::CredentialError;
use crate::catalog_types::ProjectCommitId;
#[cfg(feature = "tables")]
use crate::scandal::error::ScandalError;
#[cfg(feature = "tables")]
use crate::scandal::types::PreparedWriteReceipt;

/// A Rust SDK operation failed.
#[derive(Debug, thiserror::Error)]
pub enum Error {
    /// A credential source could not supply a usable credential.
    #[error(transparent)]
    Credentials(#[from] CredentialError),
    /// An HTTP request or response failed.
    #[error(transparent)]
    Http(#[from] HttpError),
    /// A control-plane request was rejected.
    #[error("{url} returned {status}: {body}")]
    Rejected {
        url: Url,
        status: StatusCode,
        body: String,
    },
    /// An application or installation operation failed, retaining its typed service error.
    #[error(transparent)]
    Applications(Box<CallError<AppsError>>),
    /// A typed organization service call failed.
    #[error(transparent)]
    Organization(Box<CallError<OrganizationError>>),
    /// A typed project service call failed.
    #[error(transparent)]
    Project(Box<CallError<ProjectError>>),
    /// A typed authorization service call failed.
    #[error(transparent)]
    Authorization(Box<CallError<AuthorizationError>>),
    /// A typed workloads service call failed.
    #[error(transparent)]
    Workloads(Box<CallError<WorkloadsError>>),
    /// A typed locations service call failed.
    #[error(transparent)]
    Location(Box<CallError<LocationsError>>),
    /// A typed tasks service call failed.
    #[error(transparent)]
    Tasks(Box<CallError<TasksError>>),
    /// A typed vector indexes service call failed.
    #[error(transparent)]
    VectorIndex(Box<CallError<VectorIndexesError>>),
    /// A typed storage service call failed.
    #[error(transparent)]
    Storage(Box<CallError<StorageError>>),
    /// A typed enrichments service call failed.
    #[error(transparent)]
    Enrichments(Box<CallError<EnrichmentsError>>),
    /// A typed workers service call failed.
    #[error(transparent)]
    Workers(Box<CallError<WorkersError>>),
    /// A typed authn service call failed.
    #[error(transparent)]
    Authn(Box<CallError<AuthnError>>),
    /// A typed usage service call failed.
    #[error(transparent)]
    Usage(Box<CallError<UsageError>>),
    /// No credential source supplied a usable credential.
    #[error(
        "no Spiral credentials found; configure a bearer token, workload credentials, or a credential provider"
    )]
    CredentialsNotFound,
    /// A remote response violated its protocol.
    #[error("invalid service response: {0}")]
    Protocol(String),
    /// A typed Arbor resource-service operation failed.
    #[error(transparent)]
    Arbor(Box<CallError<TablesError>>),
    /// A typed Connection service operation failed.
    #[error(transparent)]
    Connection(Box<CallError<ConnectionsError>>),
    /// A resource path has no binding at the selected commit.
    #[error("resource not found: {0}")]
    NotFound(String),
    /// A commit was not published because its branch moved past the pinned head.
    #[error("the branch moved to {current} before publication")]
    BranchMoved { current: ProjectCommitId },
    /// A commit was not published because a staged change conflicts with the branch head.
    #[error("a staged change conflicts with the branch head")]
    Conflict(Box<PublicationConflict>),
    /// Publication may have committed. Match [`PublishError::Unknown`](crate::PublishError)
    /// before converting, to recover or publish the same frozen commit.
    #[error("publication outcome unknown for {operation_id}: {message}")]
    OutcomeUnknown { operation_id: Uuid, message: String },
    /// Resource registration may have succeeded. Register the receipt again with
    /// [`Table::recover_write`](crate::Table::recover_write) for a table's next version, or
    /// [`Client::register_prepared`](crate::Client::register_prepared) for a new table.
    #[cfg(feature = "tables")]
    #[error("table registration outcome unknown: {message}")]
    RegistrationUnknown {
        receipt: PreparedWriteReceipt,
        message: String,
    },
    /// Client configuration is invalid.
    #[error("invalid SDK configuration: {0}")]
    InvalidConfiguration(String),
    /// A scandal operation failed.
    #[error(transparent)]
    #[cfg(feature = "tables")]
    Scandal(ScandalError),
    /// A Task finished without succeeding.
    #[error("Task {task_id} failed: {message}")]
    TaskFailed {
        task_id: Uuid,
        code: Option<String>,
        message: String,
    },
    /// A Task was cancelled before it succeeded.
    #[error("Task {task_id} was cancelled")]
    TaskCancelled { task_id: Uuid },
    /// The client stopped waiting; the Task may still be running.
    #[error("timed out waiting for Task {task_id}")]
    TaskWaitTimeout { task_id: Uuid },
    /// A project catalog operation failed.
    #[error(transparent)]
    Catalog(Box<CallError<CatalogError>>),
    /// Arrow data could not be encoded or decoded.
    #[error("Arrow stream failed: {0}")]
    Arrow(String),
}

impl Error {
    pub(crate) fn invalid(error: impl Display) -> Self {
        Self::InvalidConfiguration(error.to_string())
    }

    #[cfg(feature = "tables")]
    pub(crate) fn arrow(error: impl Display) -> Self {
        Self::Arrow(error.to_string())
    }
}

#[cfg(feature = "tables")]
impl From<ScandalError> for Error {
    fn from(error: ScandalError) -> Self {
        Self::Scandal(error)
    }
}

impl From<CallError<AppsError>> for Error {
    fn from(error: CallError<AppsError>) -> Self {
        Self::Applications(Box::new(error))
    }
}

impl From<reqwest_middleware::Error> for Error {
    fn from(error: reqwest_middleware::Error) -> Self {
        match error {
            reqwest_middleware::Error::Reqwest(error) => Self::Http(error),
            reqwest_middleware::Error::Middleware(error) => Self::Protocol(error.to_string()),
        }
    }
}

macro_rules! service_error_conversion {
    ($variant:ident, $error:ty) => {
        impl From<CallError<$error>> for Error {
            fn from(error: CallError<$error>) -> Self {
                Self::$variant(Box::new(error))
            }
        }
    };
}
service_error_conversion!(Arbor, TablesError);
service_error_conversion!(Catalog, CatalogError);
service_error_conversion!(Connection, ConnectionsError);
service_error_conversion!(Organization, OrganizationError);
service_error_conversion!(Project, ProjectError);
service_error_conversion!(Authorization, AuthorizationError);
service_error_conversion!(Workloads, WorkloadsError);
service_error_conversion!(Location, LocationsError);
service_error_conversion!(Tasks, TasksError);
service_error_conversion!(VectorIndex, VectorIndexesError);
service_error_conversion!(Storage, StorageError);
service_error_conversion!(Enrichments, EnrichmentsError);
service_error_conversion!(Workers, WorkersError);
service_error_conversion!(Authn, AuthnError);
service_error_conversion!(Usage, UsageError);