use crate::openapi::apis::{Error as OpenApiError, ResponseContent};
use anyhow::Error as AnyhowError;
use reqwest::{self, StatusCode};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum PineconeError {
#[error("Unknown response error: status: {status}, message: {message}")]
UnknownResponseError {
status: StatusCode,
message: String,
},
#[error("Action forbidden error: {source}")]
ActionForbiddenError {
source: WrappedResponseContent,
},
#[error("API key missing error: {message}")]
APIKeyMissingError {
message: String,
},
#[error("Invalid headers error: {message}")]
InvalidHeadersError {
message: String,
},
#[error("Timeout error: {message}")]
TimeoutError {
message: String,
},
#[error("Connection error: {source}")]
ConnectionError {
source: AnyhowError,
},
#[error("Reqwest error: {source}")]
ReqwestError {
source: AnyhowError,
},
#[error("Serde error: {source}")]
SerdeError {
source: AnyhowError,
},
#[error("IO error: {message}")]
IoError {
message: String,
},
#[error("Bad request error: {source}")]
BadRequestError {
source: WrappedResponseContent,
},
#[error("Unauthorized error: {source}")]
UnauthorizedError {
source: WrappedResponseContent,
},
#[error("Pod quota exceeded error: {source}")]
PodQuotaExceededError {
source: WrappedResponseContent,
},
#[error("Collections quota exceeded error: {source}")]
CollectionsQuotaExceededError {
source: WrappedResponseContent,
},
#[error("Invalid cloud error: {source}")]
InvalidCloudError {
source: WrappedResponseContent,
},
#[error("Invalid region error: {source}")]
InvalidRegionError {
source: WrappedResponseContent,
},
#[error("Invalid configuration error: {message}")]
InvalidConfigurationError {
message: String,
},
#[error("Collection not found error: {source}")]
CollectionNotFoundError {
source: WrappedResponseContent,
},
#[error("Index not found error: {source}")]
IndexNotFoundError {
source: WrappedResponseContent,
},
#[error("Resource already exists error: {source}")]
ResourceAlreadyExistsError {
source: WrappedResponseContent,
},
#[error("Unprocessable entity error: {source}")]
UnprocessableEntityError {
source: WrappedResponseContent,
},
#[error("Pending collection error: {source}")]
PendingCollectionError {
source: WrappedResponseContent,
},
#[error("Internal server error: {source}")]
InternalServerError {
source: WrappedResponseContent,
},
#[error("Data plane error: {status}")]
DataPlaneError {
status: tonic::Status,
},
#[error("Inference error: {status}")]
InferenceError {
status: tonic::Status,
},
}
impl<T> From<OpenApiError<T>> for PineconeError {
fn from(error: OpenApiError<T>) -> Self {
match error {
OpenApiError::Reqwest(inner) => PineconeError::ReqwestError {
source: inner.into(),
},
OpenApiError::Serde(inner) => PineconeError::SerdeError {
source: inner.into(),
},
OpenApiError::Io(inner) => PineconeError::IoError {
message: inner.to_string(),
},
OpenApiError::ResponseError(inner) => handle_response_error(inner.into()),
}
}
}
fn handle_response_error(source: WrappedResponseContent) -> PineconeError {
let status = source.status;
let message = source.content.clone();
match status {
StatusCode::BAD_REQUEST => PineconeError::BadRequestError { source },
StatusCode::UNAUTHORIZED => PineconeError::UnauthorizedError { source },
StatusCode::FORBIDDEN => parse_forbidden_error(source, message),
StatusCode::NOT_FOUND => parse_not_found_error(source, message),
StatusCode::CONFLICT => PineconeError::ResourceAlreadyExistsError { source },
StatusCode::PRECONDITION_FAILED => PineconeError::PendingCollectionError { source },
StatusCode::UNPROCESSABLE_ENTITY => PineconeError::UnprocessableEntityError { source },
StatusCode::INTERNAL_SERVER_ERROR => PineconeError::InternalServerError { source },
_ => PineconeError::UnknownResponseError { status, message },
}
}
fn parse_not_found_error(source: WrappedResponseContent, message: String) -> PineconeError {
if message.contains("Index") {
PineconeError::IndexNotFoundError { source }
} else if message.contains("Collection") {
PineconeError::CollectionNotFoundError { source }
} else if message.contains("region") {
PineconeError::InvalidRegionError { source }
} else if message.contains("cloud") {
PineconeError::InvalidCloudError { source }
} else {
PineconeError::InternalServerError { source }
}
}
fn parse_forbidden_error(source: WrappedResponseContent, message: String) -> PineconeError {
if message.contains("Deletion protection") {
PineconeError::ActionForbiddenError { source }
} else if message.contains("index") {
PineconeError::PodQuotaExceededError { source }
} else if message.contains("Collection") {
PineconeError::CollectionsQuotaExceededError { source }
} else {
PineconeError::InternalServerError { source }
}
}
#[derive(Debug)]
pub struct WrappedResponseContent {
pub status: reqwest::StatusCode,
pub content: String,
}
impl<T> From<ResponseContent<T>> for WrappedResponseContent {
fn from(rc: ResponseContent<T>) -> Self {
WrappedResponseContent {
status: rc.status,
content: rc.content,
}
}
}
impl std::error::Error for WrappedResponseContent {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}
impl std::fmt::Display for WrappedResponseContent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "status: {} content: {}", self.status, self.content)
}
}
#[cfg(test)]
mod tests {
use super::PineconeError;
use tokio;
fn assert_send_sync<T: Send + Sync>() {}
#[tokio::test]
async fn test_pinecone_error_is_send_sync() {
assert_send_sync::<PineconeError>();
}
}