use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum Error {
#[error(transparent)]
Meilisearch(#[from] MeilisearchError),
#[error("The Meilisearch server can't be reached.")]
UnreachableServer,
#[error("Error parsing response JSON: {}", .0)]
ParseError(#[from] serde_json::Error),
#[error("A task did not succeed in time.")]
Timeout,
#[error("Unable to generate a valid HTTP request. It probably comes from an invalid API key.")]
InvalidRequest,
#[error("The provided api_key is invalid.")]
TenantTokensInvalidApiKey,
#[error("The provided expires_at is already expired.")]
TenantTokensExpiredSignature,
#[error("Impossible to generate the token, jsonwebtoken encountered an error: {}", .0)]
InvalidTenantToken(#[from] jsonwebtoken::errors::Error),
#[cfg(not(target_arch = "wasm32"))]
#[error("HTTP request failed: {}", .0)]
HttpError(isahc::Error),
#[cfg(target_arch = "wasm32")]
#[error("HTTP request failed: {}", .0)]
HttpError(String),
#[error("Internal Error: could not parse the query parameters: {}", .0)]
Yaup(#[from] yaup::Error),
#[cfg(not(target_arch = "wasm32"))]
#[error("The uid of the token has bit an uuid4 format: {}", .0)]
Uuid(#[from] uuid::Error),
#[error("The uid provided to the token is not of version uuidv4")]
InvalidUuid4Version,
}
#[derive(Debug, Clone, Deserialize, Error)]
#[serde(rename_all = "camelCase")]
#[error("Meilisearch {}: {}: {}. {}", .error_type, .error_code, .error_message, .error_link)]
pub struct MeilisearchError {
#[serde(rename = "message")]
pub error_message: String,
#[serde(rename = "code")]
pub error_code: ErrorCode,
#[serde(rename = "type")]
pub error_type: ErrorType,
#[serde(rename = "link")]
pub error_link: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum ErrorType {
InvalidRequest,
Internal,
Auth,
#[serde(other)]
Unknown,
}
impl std::fmt::Display for ErrorType {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
write!(
fmt,
"{}",
serde_json::to_value(self).unwrap().as_str().unwrap()
)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum ErrorCode {
IndexCreationFailed,
IndexAlreadyExists,
IndexNotFound,
InvalidIndexUid,
InvalidState,
PrimaryKeyInferenceFailed,
IndexPrimaryKeyAlreadyPresent,
InvalidRankingRule,
InvalidStoreFile,
MaxFieldsLimitExceeded,
MissingDocumentId,
InvalidDocumentId,
InvalidFilter,
InvalidSort,
BadParameter,
BadRequest,
DatabaseSizeLimitReached,
DocumentNotFound,
InternalError,
InvalidGeoField,
InvalidApiKey,
MissingAuthorizationHeader,
TaskNotFound,
DumpNotFound,
MssingMasterKey,
NoSpaceLeftOnDevice,
PayloadTooLarge,
UnretrievableDocument,
SearchError,
UnsupportedMediaType,
DumpAlreadyProcessing,
DumpProcessFailed,
MissingContentType,
MalformedPayload,
InvalidContentType,
MissingPayload,
MissingParameter,
InvalidApiKeyDescription,
InvalidApiKeyActions,
InvalidApiKeyIndexes,
InvalidApiKeyExpiresAt,
ApiKeyNotFound,
InvalidTaskTypesFilter,
InvalidTaskStatusesFilter,
InvalidTaskCanceledByFilter,
InvalidTaskUidsFilter,
InvalidTaskDateFilter,
MissingTaskFilters,
#[serde(other)]
Unknown,
}
impl std::fmt::Display for ErrorCode {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
write!(
fmt,
"{}",
serde_json::to_value(self).unwrap().as_str().unwrap()
)
}
}
#[cfg(not(target_arch = "wasm32"))]
impl From<isahc::Error> for Error {
fn from(error: isahc::Error) -> Error {
if error.kind() == isahc::error::ErrorKind::ConnectionFailed {
Error::UnreachableServer
} else {
Error::HttpError(error)
}
}
}
#[cfg(test)]
mod test {
use super::*;
use jsonwebtoken::errors::ErrorKind::InvalidToken;
use uuid::Uuid;
#[test]
fn test_meilisearch_error() {
let error: MeilisearchError = serde_json::from_str(
r#"
{
"message": "The cool error message.",
"code": "index_creation_failed",
"type": "internal",
"link": "https://the best link eveer"
}"#,
)
.unwrap();
assert_eq!(error.error_message, "The cool error message.");
assert_eq!(error.error_code, ErrorCode::IndexCreationFailed);
assert_eq!(error.error_type, ErrorType::Internal);
assert_eq!(error.error_link, "https://the best link eveer");
let error: MeilisearchError = serde_json::from_str(
r#"
{
"message": "",
"code": "An unknown error",
"type": "An unknown type",
"link": ""
}"#,
)
.unwrap();
assert_eq!(error.error_code, ErrorCode::Unknown);
assert_eq!(error.error_type, ErrorType::Unknown);
}
#[test]
fn test_error_message_parsing() {
let error: MeilisearchError = serde_json::from_str(
r#"
{
"message": "The cool error message.",
"code": "index_creation_failed",
"type": "internal",
"link": "https://the best link eveer"
}"#,
)
.unwrap();
assert_eq!(error.to_string(), ("Meilisearch internal: index_creation_failed: The cool error message.. https://the best link eveer"));
let error = Error::UnreachableServer;
assert_eq!(
error.to_string(),
"The Meilisearch server can't be reached."
);
let error = Error::Timeout;
assert_eq!(error.to_string(), "A task did not succeed in time.");
let error = Error::InvalidRequest;
assert_eq!(
error.to_string(),
"Unable to generate a valid HTTP request. It probably comes from an invalid API key."
);
let error = Error::TenantTokensInvalidApiKey;
assert_eq!(error.to_string(), "The provided api_key is invalid.");
let error = Error::TenantTokensExpiredSignature;
assert_eq!(
error.to_string(),
"The provided expires_at is already expired."
);
let error = Error::InvalidUuid4Version;
assert_eq!(
error.to_string(),
"The uid provided to the token is not of version uuidv4"
);
let error = Error::Uuid(Uuid::parse_str("67e55044").unwrap_err());
assert_eq!(error.to_string(), "The uid of the token has bit an uuid4 format: invalid length: expected length 32 for simple format, found 8");
let data = r#"
{
"name": "John Doe"
"age": 43,
}"#;
let error = Error::ParseError(serde_json::from_str::<String>(data).unwrap_err());
assert_eq!(
error.to_string(),
"Error parsing response JSON: invalid type: map, expected a string at line 2 column 8"
);
let error = Error::HttpError(isahc::post("test_url", "test_body").unwrap_err());
assert_eq!(
error.to_string(),
"HTTP request failed: failed to resolve host name"
);
let error = Error::InvalidTenantToken(jsonwebtoken::errors::Error::from(InvalidToken));
assert_eq!(
error.to_string(),
"Impossible to generate the token, jsonwebtoken encountered an error: InvalidToken"
);
let error = Error::Yaup(yaup::error::Error::Custom("Test yaup error".to_string()));
assert_eq!(
error.to_string(),
"Internal Error: could not parse the query parameters: Test yaup error"
);
}
}