1use serde::{Deserialize, Serialize};
4use thiserror::Error;
5
6pub type Result<T> = std::result::Result<T, ClientError>;
8
9#[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#[derive(Error, Debug)]
33pub enum ClientError {
34 #[cfg(feature = "http-client")]
36 #[error("HTTP request failed: {0}")]
37 Http(#[from] reqwest::Error),
38
39 #[cfg(feature = "grpc")]
41 #[error("gRPC request failed: {0}")]
42 Grpc(String),
43
44 #[error("JSON error: {0}")]
46 Json(#[from] serde_json::Error),
47
48 #[error("Server error ({status}): {message}")]
50 Server {
51 status: u16,
53 message: String,
55 #[doc = "Typed error code from the server"]
56 code: Option<ServerErrorCode>,
57 },
58
59 #[error("Authorization failed ({status}): {message}")]
61 Authorization {
62 status: u16,
63 message: String,
64 code: Option<ServerErrorCode>,
65 },
66
67 #[error("Invalid configuration: {0}")]
69 Config(String),
70
71 #[error("Namespace not found: {0}")]
73 NamespaceNotFound(String),
74
75 #[error("Vector not found: {0}")]
77 VectorNotFound(String),
78
79 #[error("Invalid URL: {0}")]
81 InvalidUrl(String),
82
83 #[error("Connection failed: {0}")]
85 Connection(String),
86
87 #[error("Request timeout")]
89 Timeout,
90
91 #[error("Rate limit exceeded — retry after {retry_after:?}")]
93 RateLimitExceeded {
94 retry_after: Option<u64>,
96 },
97}
98
99impl ClientError {
100 pub fn is_retryable(&self) -> bool {
102 match self {
103 #[cfg(feature = "http-client")]
104 ClientError::Http(_) => true,
108 #[cfg(feature = "grpc")]
109 ClientError::Grpc(_) => true, ClientError::Server { status, .. } => *status >= 500 || *status == 408,
111 ClientError::Connection(_) => true,
112 ClientError::Timeout => true,
113 ClientError::RateLimitExceeded { .. } => true,
114 _ => false,
115 }
116 }
117
118 pub fn is_not_found(&self) -> bool {
120 match self {
121 ClientError::Server { status, code, .. } => {
122 *status == 404
123 || matches!(
124 code,
125 Some(ServerErrorCode::NamespaceNotFound)
126 | Some(ServerErrorCode::VectorNotFound)
127 )
128 }
129 ClientError::NamespaceNotFound(_) => true,
130 ClientError::VectorNotFound(_) => true,
131 _ => false,
132 }
133 }
134
135 pub fn is_auth_error(&self) -> bool {
137 matches!(self, ClientError::Authorization { .. })
138 || matches!(self, ClientError::Server { status: 401, .. })
139 }
140}