Skip to main content

dakera_client/
error.rs

1//! Error types for the Dakera client SDK
2
3use serde::{Deserialize, Serialize};
4use thiserror::Error;
5
6/// Result type alias for Dakera client operations
7pub type Result<T> = std::result::Result<T, ClientError>;
8
9/// Typed error codes from the Dakera server API
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
11#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
12pub enum ServerErrorCode {
13    NamespaceNotFound,
14    VectorNotFound,
15    DimensionMismatch,
16    EmptyVector,
17    InvalidRequest,
18    StorageError,
19    InternalError,
20    QuotaExceeded,
21    ServiceUnavailable,
22    AuthenticationRequired,
23    InvalidApiKey,
24    ApiKeyExpired,
25    InsufficientScope,
26    NamespaceAccessDenied,
27    #[serde(other)]
28    Unknown,
29}
30
31/// Errors that can occur when using the Dakera client
32#[derive(Error, Debug)]
33pub enum ClientError {
34    /// HTTP request failed
35    #[cfg(feature = "http-client")]
36    #[error("HTTP request failed: {0}")]
37    Http(#[from] reqwest::Error),
38
39    /// gRPC request failed
40    #[cfg(feature = "grpc")]
41    #[error("gRPC request failed: {0}")]
42    Grpc(String),
43
44    /// JSON serialization/deserialization failed
45    #[error("JSON error: {0}")]
46    Json(#[from] serde_json::Error),
47
48    /// Server returned an error response
49    #[error("Server error ({status}): {message}")]
50    Server {
51        /// HTTP status code
52        status: u16,
53        /// Error message from server
54        message: String,
55        #[doc = "Typed error code from the server"]
56        code: Option<ServerErrorCode>,
57    },
58
59    /// 403 Forbidden — insufficient scope or namespace access denied
60    #[error("Authorization failed ({status}): {message}")]
61    Authorization {
62        status: u16,
63        message: String,
64        code: Option<ServerErrorCode>,
65    },
66
67    /// Invalid configuration
68    #[error("Invalid configuration: {0}")]
69    Config(String),
70
71    /// Namespace not found
72    #[error("Namespace not found: {0}")]
73    NamespaceNotFound(String),
74
75    /// Vector not found
76    #[error("Vector not found: {0}")]
77    VectorNotFound(String),
78
79    /// Invalid URL
80    #[error("Invalid URL: {0}")]
81    InvalidUrl(String),
82
83    /// Connection failed
84    #[error("Connection failed: {0}")]
85    Connection(String),
86
87    /// Timeout
88    #[error("Request timeout")]
89    Timeout,
90
91    /// Rate limit exceeded (HTTP 429)
92    #[error("Rate limit exceeded — retry after {retry_after:?}")]
93    RateLimitExceeded {
94        /// Value of the `Retry-After` response header in seconds, if present.
95        retry_after: Option<u64>,
96    },
97}
98
99impl ClientError {
100    /// Check if the error is retryable
101    pub fn is_retryable(&self) -> bool {
102        match self {
103            #[cfg(feature = "http-client")]
104            ClientError::Http(e) => e.is_timeout() || e.is_connect(),
105            #[cfg(feature = "grpc")]
106            ClientError::Grpc(_) => true, // gRPC errors are generally retryable
107            ClientError::Server { status, .. } => *status >= 500,
108            ClientError::Connection(_) => true,
109            ClientError::Timeout => true,
110            ClientError::RateLimitExceeded { .. } => true,
111            _ => false,
112        }
113    }
114
115    /// Check if the error is a not found error
116    pub fn is_not_found(&self) -> bool {
117        match self {
118            ClientError::Server { status, code, .. } => {
119                *status == 404
120                    || matches!(
121                        code,
122                        Some(ServerErrorCode::NamespaceNotFound)
123                            | Some(ServerErrorCode::VectorNotFound)
124                    )
125            }
126            ClientError::NamespaceNotFound(_) => true,
127            ClientError::VectorNotFound(_) => true,
128            _ => false,
129        }
130    }
131
132    /// Check if the error is an authorization/authentication error
133    pub fn is_auth_error(&self) -> bool {
134        matches!(self, ClientError::Authorization { .. })
135            || matches!(self, ClientError::Server { status: 401, .. })
136    }
137}