1use std::error::Error;
5
6#[cfg(feature = "tonic")]
7mod tonic;
8#[cfg(feature = "tonic")]
9pub use tonic::*;
10
11#[cfg(feature = "sqlx")]
12mod sqlx;
13#[cfg(feature = "sqlx")]
14pub use sqlx::*;
15
16#[cfg(feature = "validator")]
17mod validator;
18#[cfg(feature = "validator")]
19pub use validator::*;
20
21pub fn source_chain_contains(
23 source: &(dyn Error + 'static),
24 mut predicate: impl FnMut(&(dyn Error + 'static)) -> bool,
25) -> bool {
26 let mut current = Some(source);
27 while let Some(err) = current {
28 if predicate(err) {
29 return true;
30 }
31 current = err.source();
32 }
33 false
34}
35
36#[derive(PartialEq, Debug, Clone, Copy)]
37pub enum ErrorCodes {
38 Success = 0,
40 Cancelled = 1,
42 Unknown = 2,
44 InvalidArgument = 3,
46 DeadlineExceeded = 4,
48 NotFound = 5,
50 AlreadyExists = 6,
52 PermissionDenied = 7,
54 ResourceExhausted = 8,
56 FailedPrecondition = 9,
58 Aborted = 10,
60 OutOfRange = 11,
62 Unimplemented = 12,
64 Internal = 13,
66 Unavailable = 14,
68 DataLoss = 15,
70 Unauthenticated = 16,
72 VersionMismatch = 17,
74 UnprocessableEntity = 18,
76}
77
78impl ErrorCodes {
79 pub fn name(&self) -> &'static str {
80 match self {
81 ErrorCodes::InvalidArgument => "InvalidArgumentError",
82 ErrorCodes::NotFound => "NotFoundError",
83 ErrorCodes::Internal => "InternalError",
84 ErrorCodes::VersionMismatch => "VersionMismatchError",
85 _ => "ChromaError",
86 }
87 }
88}
89
90#[cfg(feature = "http")]
91impl From<ErrorCodes> for http::StatusCode {
92 fn from(error_code: ErrorCodes) -> Self {
93 match error_code {
94 ErrorCodes::Success => http::StatusCode::OK,
95 ErrorCodes::Cancelled => http::StatusCode::BAD_REQUEST,
96 ErrorCodes::Unknown => http::StatusCode::INTERNAL_SERVER_ERROR,
97 ErrorCodes::InvalidArgument => http::StatusCode::BAD_REQUEST,
98 ErrorCodes::DeadlineExceeded => http::StatusCode::GATEWAY_TIMEOUT,
99 ErrorCodes::NotFound => http::StatusCode::NOT_FOUND,
100 ErrorCodes::AlreadyExists => http::StatusCode::CONFLICT,
101 ErrorCodes::PermissionDenied => http::StatusCode::FORBIDDEN,
102 ErrorCodes::ResourceExhausted => http::StatusCode::TOO_MANY_REQUESTS,
103 ErrorCodes::FailedPrecondition => http::StatusCode::PRECONDITION_FAILED,
104 ErrorCodes::Aborted => http::StatusCode::BAD_REQUEST,
105 ErrorCodes::OutOfRange => http::StatusCode::BAD_REQUEST,
106 ErrorCodes::Unimplemented => http::StatusCode::NOT_IMPLEMENTED,
107 ErrorCodes::Internal => http::StatusCode::INTERNAL_SERVER_ERROR,
108 ErrorCodes::Unavailable => http::StatusCode::SERVICE_UNAVAILABLE,
109 ErrorCodes::DataLoss => http::StatusCode::INTERNAL_SERVER_ERROR,
110 ErrorCodes::Unauthenticated => http::StatusCode::UNAUTHORIZED,
111 ErrorCodes::VersionMismatch => http::StatusCode::INTERNAL_SERVER_ERROR,
112 ErrorCodes::UnprocessableEntity => http::StatusCode::UNPROCESSABLE_ENTITY,
113 }
114 }
115}
116
117#[cfg(feature = "http")]
118impl From<http::StatusCode> for ErrorCodes {
119 fn from(value: http::StatusCode) -> Self {
120 match value {
121 http::StatusCode::OK => ErrorCodes::Success,
122 http::StatusCode::BAD_REQUEST => ErrorCodes::InvalidArgument,
123 http::StatusCode::UNAUTHORIZED => ErrorCodes::Unauthenticated,
124 http::StatusCode::FORBIDDEN => ErrorCodes::PermissionDenied,
125 http::StatusCode::NOT_FOUND => ErrorCodes::NotFound,
126 http::StatusCode::CONFLICT => ErrorCodes::AlreadyExists,
127 http::StatusCode::TOO_MANY_REQUESTS => ErrorCodes::ResourceExhausted,
128 http::StatusCode::INTERNAL_SERVER_ERROR => ErrorCodes::Internal,
129 http::StatusCode::SERVICE_UNAVAILABLE => ErrorCodes::Unavailable,
130 http::StatusCode::NOT_IMPLEMENTED => ErrorCodes::Unimplemented,
131 http::StatusCode::GATEWAY_TIMEOUT => ErrorCodes::DeadlineExceeded,
132 http::StatusCode::PRECONDITION_FAILED => ErrorCodes::FailedPrecondition,
133 http::StatusCode::UNPROCESSABLE_ENTITY => ErrorCodes::UnprocessableEntity,
134 _ => ErrorCodes::Unknown,
135 }
136 }
137}
138
139pub trait ChromaError: Error + Send {
140 fn code(&self) -> ErrorCodes;
141 fn boxed(self) -> Box<dyn ChromaError>
142 where
143 Self: Sized + 'static,
144 {
145 Box::new(self)
146 }
147 fn should_trace_error(&self) -> bool {
148 true
149 }
150}
151
152impl Error for Box<dyn ChromaError> {}
153
154impl ChromaError for Box<dyn ChromaError> {
155 fn code(&self) -> ErrorCodes {
156 self.as_ref().code()
157 }
158}
159
160impl ChromaError for std::io::Error {
161 fn code(&self) -> ErrorCodes {
162 ErrorCodes::Unknown
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use std::{cell::Cell, error::Error, fmt};
169
170 use super::source_chain_contains;
171
172 #[derive(Debug)]
173 struct TestError {
174 message: &'static str,
175 source: Option<Box<dyn Error + Send + Sync + 'static>>,
176 }
177
178 impl TestError {
179 fn new(message: &'static str) -> Self {
180 Self {
181 message,
182 source: None,
183 }
184 }
185
186 fn with_source(message: &'static str, source: impl Error + Send + Sync + 'static) -> Self {
187 Self {
188 message,
189 source: Some(Box::new(source)),
190 }
191 }
192 }
193
194 impl fmt::Display for TestError {
195 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
196 write!(f, "{}", self.message)
197 }
198 }
199
200 impl Error for TestError {
201 fn source(&self) -> Option<&(dyn Error + 'static)> {
202 self.source
203 .as_deref()
204 .map(|err| err as &(dyn Error + 'static))
205 }
206 }
207
208 #[test]
209 fn source_chain_contains_matches_root_error() {
210 let err = TestError::new("root");
211
212 assert!(source_chain_contains(&err, |candidate| {
213 candidate.to_string() == "root"
214 }));
215 }
216
217 #[test]
218 fn source_chain_contains_matches_nested_source_error() {
219 let err = TestError::with_source(
220 "root",
221 TestError::with_source("middle", TestError::new("leaf")),
222 );
223
224 assert!(source_chain_contains(&err, |candidate| {
225 candidate.to_string() == "leaf"
226 }));
227 }
228
229 #[test]
230 fn source_chain_contains_returns_false_when_no_error_matches() {
231 let err = TestError::with_source(
232 "root",
233 TestError::with_source("middle", TestError::new("leaf")),
234 );
235
236 assert!(!source_chain_contains(&err, |candidate| {
237 candidate.to_string() == "missing"
238 }));
239 }
240
241 #[test]
242 fn source_chain_contains_stops_after_first_match() {
243 let err = TestError::with_source(
244 "root",
245 TestError::with_source("middle", TestError::new("leaf")),
246 );
247 let visits = Cell::new(0);
248
249 assert!(source_chain_contains(&err, |candidate| {
250 visits.set(visits.get() + 1);
251 candidate.to_string() == "middle"
252 }));
253 assert_eq!(2, visits.get());
254 }
255}