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
92impl ClientError {
93 pub fn is_retryable(&self) -> bool {
95 match self {
96 #[cfg(feature = "http-client")]
97 ClientError::Http(e) => e.is_timeout() || e.is_connect(),
98 #[cfg(feature = "grpc")]
99 ClientError::Grpc(_) => true, ClientError::Server { status, .. } => *status >= 500,
101 ClientError::Connection(_) => true,
102 ClientError::Timeout => true,
103 _ => false,
104 }
105 }
106
107 pub fn is_not_found(&self) -> bool {
109 match self {
110 ClientError::Server { status, code, .. } => {
111 *status == 404
112 || matches!(
113 code,
114 Some(ServerErrorCode::NamespaceNotFound)
115 | Some(ServerErrorCode::VectorNotFound)
116 )
117 }
118 ClientError::NamespaceNotFound(_) => true,
119 ClientError::VectorNotFound(_) => true,
120 _ => false,
121 }
122 }
123
124 pub fn is_auth_error(&self) -> bool {
126 matches!(self, ClientError::Authorization { .. })
127 || matches!(self, ClientError::Server { status: 401, .. })
128 }
129}