use std::error::Error;
#[cfg(feature = "tonic")]
mod tonic;
#[cfg(feature = "tonic")]
pub use tonic::*;
#[cfg(feature = "sqlx")]
mod sqlx;
#[cfg(feature = "sqlx")]
pub use sqlx::*;
#[cfg(feature = "validator")]
mod validator;
#[cfg(feature = "validator")]
pub use validator::*;
pub fn source_chain_contains(
source: &(dyn Error + 'static),
mut predicate: impl FnMut(&(dyn Error + 'static)) -> bool,
) -> bool {
let mut current = Some(source);
while let Some(err) = current {
if predicate(err) {
return true;
}
current = err.source();
}
false
}
#[derive(PartialEq, Debug, Clone, Copy)]
pub enum ErrorCodes {
Success = 0,
Cancelled = 1,
Unknown = 2,
InvalidArgument = 3,
DeadlineExceeded = 4,
NotFound = 5,
AlreadyExists = 6,
PermissionDenied = 7,
ResourceExhausted = 8,
FailedPrecondition = 9,
Aborted = 10,
OutOfRange = 11,
Unimplemented = 12,
Internal = 13,
Unavailable = 14,
DataLoss = 15,
Unauthenticated = 16,
VersionMismatch = 17,
UnprocessableEntity = 18,
}
impl ErrorCodes {
pub fn name(&self) -> &'static str {
match self {
ErrorCodes::InvalidArgument => "InvalidArgumentError",
ErrorCodes::NotFound => "NotFoundError",
ErrorCodes::Internal => "InternalError",
ErrorCodes::VersionMismatch => "VersionMismatchError",
_ => "ChromaError",
}
}
}
#[cfg(feature = "http")]
impl From<ErrorCodes> for http::StatusCode {
fn from(error_code: ErrorCodes) -> Self {
match error_code {
ErrorCodes::Success => http::StatusCode::OK,
ErrorCodes::Cancelled => http::StatusCode::BAD_REQUEST,
ErrorCodes::Unknown => http::StatusCode::INTERNAL_SERVER_ERROR,
ErrorCodes::InvalidArgument => http::StatusCode::BAD_REQUEST,
ErrorCodes::DeadlineExceeded => http::StatusCode::GATEWAY_TIMEOUT,
ErrorCodes::NotFound => http::StatusCode::NOT_FOUND,
ErrorCodes::AlreadyExists => http::StatusCode::CONFLICT,
ErrorCodes::PermissionDenied => http::StatusCode::FORBIDDEN,
ErrorCodes::ResourceExhausted => http::StatusCode::TOO_MANY_REQUESTS,
ErrorCodes::FailedPrecondition => http::StatusCode::PRECONDITION_FAILED,
ErrorCodes::Aborted => http::StatusCode::BAD_REQUEST,
ErrorCodes::OutOfRange => http::StatusCode::BAD_REQUEST,
ErrorCodes::Unimplemented => http::StatusCode::NOT_IMPLEMENTED,
ErrorCodes::Internal => http::StatusCode::INTERNAL_SERVER_ERROR,
ErrorCodes::Unavailable => http::StatusCode::SERVICE_UNAVAILABLE,
ErrorCodes::DataLoss => http::StatusCode::INTERNAL_SERVER_ERROR,
ErrorCodes::Unauthenticated => http::StatusCode::UNAUTHORIZED,
ErrorCodes::VersionMismatch => http::StatusCode::INTERNAL_SERVER_ERROR,
ErrorCodes::UnprocessableEntity => http::StatusCode::UNPROCESSABLE_ENTITY,
}
}
}
#[cfg(feature = "http")]
impl From<http::StatusCode> for ErrorCodes {
fn from(value: http::StatusCode) -> Self {
match value {
http::StatusCode::OK => ErrorCodes::Success,
http::StatusCode::BAD_REQUEST => ErrorCodes::InvalidArgument,
http::StatusCode::UNAUTHORIZED => ErrorCodes::Unauthenticated,
http::StatusCode::FORBIDDEN => ErrorCodes::PermissionDenied,
http::StatusCode::NOT_FOUND => ErrorCodes::NotFound,
http::StatusCode::CONFLICT => ErrorCodes::AlreadyExists,
http::StatusCode::TOO_MANY_REQUESTS => ErrorCodes::ResourceExhausted,
http::StatusCode::INTERNAL_SERVER_ERROR => ErrorCodes::Internal,
http::StatusCode::SERVICE_UNAVAILABLE => ErrorCodes::Unavailable,
http::StatusCode::NOT_IMPLEMENTED => ErrorCodes::Unimplemented,
http::StatusCode::GATEWAY_TIMEOUT => ErrorCodes::DeadlineExceeded,
http::StatusCode::PRECONDITION_FAILED => ErrorCodes::FailedPrecondition,
http::StatusCode::UNPROCESSABLE_ENTITY => ErrorCodes::UnprocessableEntity,
_ => ErrorCodes::Unknown,
}
}
}
pub trait ChromaError: Error + Send {
fn code(&self) -> ErrorCodes;
fn boxed(self) -> Box<dyn ChromaError>
where
Self: Sized + 'static,
{
Box::new(self)
}
fn should_trace_error(&self) -> bool {
true
}
}
impl Error for Box<dyn ChromaError> {}
impl ChromaError for Box<dyn ChromaError> {
fn code(&self) -> ErrorCodes {
self.as_ref().code()
}
}
impl ChromaError for std::io::Error {
fn code(&self) -> ErrorCodes {
ErrorCodes::Unknown
}
}
#[cfg(test)]
mod tests {
use std::{cell::Cell, error::Error, fmt};
use super::source_chain_contains;
#[derive(Debug)]
struct TestError {
message: &'static str,
source: Option<Box<dyn Error + Send + Sync + 'static>>,
}
impl TestError {
fn new(message: &'static str) -> Self {
Self {
message,
source: None,
}
}
fn with_source(message: &'static str, source: impl Error + Send + Sync + 'static) -> Self {
Self {
message,
source: Some(Box::new(source)),
}
}
}
impl fmt::Display for TestError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.message)
}
}
impl Error for TestError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
self.source
.as_deref()
.map(|err| err as &(dyn Error + 'static))
}
}
#[test]
fn source_chain_contains_matches_root_error() {
let err = TestError::new("root");
assert!(source_chain_contains(&err, |candidate| {
candidate.to_string() == "root"
}));
}
#[test]
fn source_chain_contains_matches_nested_source_error() {
let err = TestError::with_source(
"root",
TestError::with_source("middle", TestError::new("leaf")),
);
assert!(source_chain_contains(&err, |candidate| {
candidate.to_string() == "leaf"
}));
}
#[test]
fn source_chain_contains_returns_false_when_no_error_matches() {
let err = TestError::with_source(
"root",
TestError::with_source("middle", TestError::new("leaf")),
);
assert!(!source_chain_contains(&err, |candidate| {
candidate.to_string() == "missing"
}));
}
#[test]
fn source_chain_contains_stops_after_first_match() {
let err = TestError::with_source(
"root",
TestError::with_source("middle", TestError::new("leaf")),
);
let visits = Cell::new(0);
assert!(source_chain_contains(&err, |candidate| {
visits.set(visits.get() + 1);
candidate.to_string() == "middle"
}));
assert_eq!(2, visits.get());
}
}