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(e) => e.is_timeout() || e.is_connect(),
105 #[cfg(feature = "grpc")]
106 ClientError::Grpc(_) => true, ClientError::Server { status, .. } => *status >= 500,
108 ClientError::Connection(_) => true,
109 ClientError::Timeout => true,
110 ClientError::RateLimitExceeded { .. } => true,
111 _ => false,
112 }
113 }
114
115 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 pub fn is_auth_error(&self) -> bool {
134 matches!(self, ClientError::Authorization { .. })
135 || matches!(self, ClientError::Server { status: 401, .. })
136 }
137}