MeiliSearch/meilisearch-error/src/lib.rs

366 lines
12 KiB
Rust
Raw Normal View History

2021-03-10 13:46:49 +01:00
use std::fmt;
use actix_web::{self as aweb, http::StatusCode, HttpResponseBuilder};
2021-09-27 15:41:14 +02:00
use serde::{Deserialize, Serialize};
2021-03-10 13:46:49 +01:00
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "test-traits", derive(proptest_derive::Arbitrary))]
pub struct ResponseError {
#[serde(skip)]
#[cfg_attr(
feature = "test-traits",
proptest(strategy = "strategy::status_code_strategy()")
)]
code: StatusCode,
message: String,
#[serde(rename = "code")]
error_code: String,
#[serde(rename = "type")]
error_type: String,
#[serde(rename = "link")]
error_link: String,
}
impl ResponseError {
pub fn from_msg(message: String, code: Code) -> Self {
Self {
code: code.http(),
message,
error_code: code.err_code().error_name.to_string(),
error_type: code.type_(),
error_link: code.url(),
}
}
}
impl fmt::Display for ResponseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.message.fmt(f)
}
}
impl std::error::Error for ResponseError {}
impl<T> From<T> for ResponseError
where
T: ErrorCode,
{
fn from(other: T) -> Self {
Self {
code: other.http_status(),
message: other.to_string(),
error_code: other.error_name(),
error_type: other.error_type(),
error_link: other.error_url(),
}
}
}
impl aweb::error::ResponseError for ResponseError {
fn error_response(&self) -> aweb::HttpResponse {
let json = serde_json::to_vec(self).unwrap();
HttpResponseBuilder::new(self.status_code())
.content_type("application/json")
.body(json)
}
fn status_code(&self) -> StatusCode {
self.code
}
}
2021-03-10 13:46:49 +01:00
pub trait ErrorCode: std::error::Error {
fn error_code(&self) -> Code;
/// returns the HTTP status code ascociated with the error
fn http_status(&self) -> StatusCode {
self.error_code().http()
}
/// returns the doc url ascociated with the error
fn error_url(&self) -> String {
self.error_code().url()
}
/// returns error name, used as error code
fn error_name(&self) -> String {
self.error_code().name()
}
/// return the error type
fn error_type(&self) -> String {
self.error_code().type_()
}
}
#[allow(clippy::enum_variant_names)]
enum ErrorType {
InternalError,
InvalidRequestError,
AuthenticationError,
}
impl fmt::Display for ErrorType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use ErrorType::*;
match self {
2021-10-25 14:09:24 +02:00
InternalError => write!(f, "internal"),
InvalidRequestError => write!(f, "invalid_request"),
2021-10-28 16:57:48 +02:00
AuthenticationError => write!(f, "auth"),
2021-03-10 13:46:49 +01:00
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
2021-03-10 13:46:49 +01:00
pub enum Code {
// index related error
CreateIndex,
IndexAlreadyExists,
IndexNotFound,
InvalidIndexUid,
InvalidMinWordLengthForTypo,
2021-03-10 13:46:49 +01:00
// invalid state error
InvalidState,
MissingPrimaryKey,
PrimaryKeyAlreadyPresent,
MaxFieldsLimitExceeded,
MissingDocumentId,
2021-10-26 19:36:48 +02:00
InvalidDocumentId,
2021-03-10 13:46:49 +01:00
Filter,
Sort,
2021-03-10 13:46:49 +01:00
BadParameter,
BadRequest,
2021-11-03 14:25:49 +01:00
DatabaseSizeLimitReached,
2021-03-10 13:46:49 +01:00
DocumentNotFound,
Internal,
2021-09-27 15:41:14 +02:00
InvalidGeoField,
InvalidRankingRule,
2021-11-03 14:25:49 +01:00
InvalidStore,
2021-03-10 13:46:49 +01:00
InvalidToken,
MissingAuthorizationHeader,
2021-11-03 14:25:49 +01:00
NoSpaceLeftOnDevice,
DumpNotFound,
2021-10-26 19:36:48 +02:00
TaskNotFound,
2021-03-10 13:46:49 +01:00
PayloadTooLarge,
RetrieveDocument,
SearchDocuments,
UnsupportedMediaType,
DumpAlreadyInProgress,
DumpProcessFailed,
2021-09-30 10:26:30 +02:00
2021-09-30 11:17:42 +02:00
InvalidContentType,
2021-09-30 10:26:30 +02:00
MissingContentType,
2021-09-30 10:35:24 +02:00
MalformedPayload,
2021-09-30 11:29:27 +02:00
MissingPayload,
ApiKeyNotFound,
MissingParameter,
InvalidApiKeyActions,
InvalidApiKeyIndexes,
InvalidApiKeyExpiresAt,
InvalidApiKeyDescription,
2022-05-25 10:32:47 +02:00
InvalidApiKeyName,
InvalidApiKeyUid,
2022-06-01 14:11:56 +02:00
ImmutableField,
2022-05-25 10:32:47 +02:00
ApiKeyAlreadyExists,
2021-03-10 13:46:49 +01:00
}
impl Code {
/// ascociate a `Code` variant to the actual ErrCode
fn err_code(&self) -> ErrCode {
use Code::*;
match self {
// index related errors
// create index is thrown on internal error while creating an index.
2021-11-03 14:25:49 +01:00
CreateIndex => {
ErrCode::internal("index_creation_failed", StatusCode::INTERNAL_SERVER_ERROR)
}
2021-10-26 19:36:48 +02:00
IndexAlreadyExists => ErrCode::invalid("index_already_exists", StatusCode::CONFLICT),
2021-03-10 13:46:49 +01:00
// thrown when requesting an unexisting index
IndexNotFound => ErrCode::invalid("index_not_found", StatusCode::NOT_FOUND),
InvalidIndexUid => ErrCode::invalid("invalid_index_uid", StatusCode::BAD_REQUEST),
// invalid state error
InvalidState => ErrCode::internal("invalid_state", StatusCode::INTERNAL_SERVER_ERROR),
// thrown when no primary key has been set
2021-11-04 13:38:44 +01:00
MissingPrimaryKey => {
ErrCode::invalid("primary_key_inference_failed", StatusCode::BAD_REQUEST)
}
2021-03-10 13:46:49 +01:00
// error thrown when trying to set an already existing primary key
2021-03-15 18:11:10 +01:00
PrimaryKeyAlreadyPresent => {
2021-10-26 19:36:48 +02:00
ErrCode::invalid("index_primary_key_already_exists", StatusCode::BAD_REQUEST)
2021-03-15 18:11:10 +01:00
}
2021-09-27 15:41:14 +02:00
// invalid ranking rule
2021-11-04 13:38:44 +01:00
InvalidRankingRule => ErrCode::invalid("invalid_ranking_rule", StatusCode::BAD_REQUEST),
2021-03-10 13:46:49 +01:00
2021-11-03 14:25:49 +01:00
// invalid database
InvalidStore => {
ErrCode::internal("invalid_store_file", StatusCode::INTERNAL_SERVER_ERROR)
}
2021-03-10 13:46:49 +01:00
// invalid document
2021-03-15 18:11:10 +01:00
MaxFieldsLimitExceeded => {
ErrCode::invalid("max_fields_limit_exceeded", StatusCode::BAD_REQUEST)
}
2021-03-10 13:46:49 +01:00
MissingDocumentId => ErrCode::invalid("missing_document_id", StatusCode::BAD_REQUEST),
2021-10-26 19:36:48 +02:00
InvalidDocumentId => ErrCode::invalid("invalid_document_id", StatusCode::BAD_REQUEST),
2021-03-10 13:46:49 +01:00
// error related to filters
Filter => ErrCode::invalid("invalid_filter", StatusCode::BAD_REQUEST),
// error related to sorts
Sort => ErrCode::invalid("invalid_sort", StatusCode::BAD_REQUEST),
2021-03-10 13:46:49 +01:00
BadParameter => ErrCode::invalid("bad_parameter", StatusCode::BAD_REQUEST),
BadRequest => ErrCode::invalid("bad_request", StatusCode::BAD_REQUEST),
2021-11-03 14:25:49 +01:00
DatabaseSizeLimitReached => ErrCode::internal(
"database_size_limit_reached",
StatusCode::INTERNAL_SERVER_ERROR,
),
2021-03-10 13:46:49 +01:00
DocumentNotFound => ErrCode::invalid("document_not_found", StatusCode::NOT_FOUND),
Internal => ErrCode::internal("internal", StatusCode::INTERNAL_SERVER_ERROR),
2021-11-03 14:25:49 +01:00
InvalidGeoField => ErrCode::invalid("invalid_geo_field", StatusCode::BAD_REQUEST),
2021-10-28 15:56:57 +02:00
InvalidToken => ErrCode::authentication("invalid_api_key", StatusCode::FORBIDDEN),
2021-03-15 18:11:10 +01:00
MissingAuthorizationHeader => {
ErrCode::authentication("missing_authorization_header", StatusCode::UNAUTHORIZED)
}
2021-10-26 19:36:48 +02:00
TaskNotFound => ErrCode::invalid("task_not_found", StatusCode::NOT_FOUND),
2021-11-03 14:25:49 +01:00
DumpNotFound => ErrCode::invalid("dump_not_found", StatusCode::NOT_FOUND),
NoSpaceLeftOnDevice => {
ErrCode::internal("no_space_left_on_device", StatusCode::INTERNAL_SERVER_ERROR)
}
2021-03-10 13:46:49 +01:00
PayloadTooLarge => ErrCode::invalid("payload_too_large", StatusCode::PAYLOAD_TOO_LARGE),
2021-03-15 18:11:10 +01:00
RetrieveDocument => {
ErrCode::internal("unretrievable_document", StatusCode::BAD_REQUEST)
}
2021-03-10 13:46:49 +01:00
SearchDocuments => ErrCode::internal("search_error", StatusCode::BAD_REQUEST),
2021-03-15 18:11:10 +01:00
UnsupportedMediaType => {
ErrCode::invalid("unsupported_media_type", StatusCode::UNSUPPORTED_MEDIA_TYPE)
}
2021-03-10 13:46:49 +01:00
// error related to dump
2021-03-15 18:11:10 +01:00
DumpAlreadyInProgress => {
2021-11-03 14:25:49 +01:00
ErrCode::invalid("dump_already_processing", StatusCode::CONFLICT)
2021-03-15 18:11:10 +01:00
}
DumpProcessFailed => {
ErrCode::internal("dump_process_failed", StatusCode::INTERNAL_SERVER_ERROR)
}
2021-09-30 11:29:27 +02:00
MissingContentType => {
ErrCode::invalid("missing_content_type", StatusCode::UNSUPPORTED_MEDIA_TYPE)
}
2021-09-30 11:17:42 +02:00
MalformedPayload => ErrCode::invalid("malformed_payload", StatusCode::BAD_REQUEST),
2021-09-30 11:29:27 +02:00
InvalidContentType => {
ErrCode::invalid("invalid_content_type", StatusCode::UNSUPPORTED_MEDIA_TYPE)
}
MissingPayload => ErrCode::invalid("missing_payload", StatusCode::BAD_REQUEST),
// error related to keys
ApiKeyNotFound => ErrCode::invalid("api_key_not_found", StatusCode::NOT_FOUND),
MissingParameter => ErrCode::invalid("missing_parameter", StatusCode::BAD_REQUEST),
InvalidApiKeyActions => {
ErrCode::invalid("invalid_api_key_actions", StatusCode::BAD_REQUEST)
}
InvalidApiKeyIndexes => {
ErrCode::invalid("invalid_api_key_indexes", StatusCode::BAD_REQUEST)
}
InvalidApiKeyExpiresAt => {
ErrCode::invalid("invalid_api_key_expires_at", StatusCode::BAD_REQUEST)
}
InvalidApiKeyDescription => {
ErrCode::invalid("invalid_api_key_description", StatusCode::BAD_REQUEST)
}
2022-05-25 10:32:47 +02:00
InvalidApiKeyName => ErrCode::invalid("invalid_api_key_name", StatusCode::BAD_REQUEST),
InvalidApiKeyUid => ErrCode::invalid("invalid_api_key_uid", StatusCode::BAD_REQUEST),
ApiKeyAlreadyExists => ErrCode::invalid("api_key_already_exists", StatusCode::CONFLICT),
2022-06-01 14:11:56 +02:00
ImmutableField => ErrCode::invalid("immutable_field", StatusCode::BAD_REQUEST),
InvalidMinWordLengthForTypo => {
ErrCode::invalid("invalid_min_word_length_for_typo", StatusCode::BAD_REQUEST)
}
2021-03-10 13:46:49 +01:00
}
}
/// return the HTTP status code ascociated with the `Code`
fn http(&self) -> StatusCode {
self.err_code().status_code
}
/// return error name, used as error code
fn name(&self) -> String {
self.err_code().error_name.to_string()
}
/// return the error type
fn type_(&self) -> String {
self.err_code().error_type.to_string()
}
/// return the doc url ascociated with the error
fn url(&self) -> String {
format!("https://docs.meilisearch.com/errors#{}", self.name())
}
}
/// Internal structure providing a convenient way to create error codes
struct ErrCode {
status_code: StatusCode,
error_type: ErrorType,
error_name: &'static str,
}
impl ErrCode {
fn authentication(error_name: &'static str, status_code: StatusCode) -> ErrCode {
ErrCode {
status_code,
error_name,
error_type: ErrorType::AuthenticationError,
}
}
fn internal(error_name: &'static str, status_code: StatusCode) -> ErrCode {
ErrCode {
status_code,
error_name,
error_type: ErrorType::InternalError,
}
}
fn invalid(error_name: &'static str, status_code: StatusCode) -> ErrCode {
ErrCode {
status_code,
error_name,
error_type: ErrorType::InvalidRequestError,
}
}
}
#[cfg(feature = "test-traits")]
mod strategy {
use proptest::strategy::Strategy;
use super::*;
pub(super) fn status_code_strategy() -> impl Strategy<Value = StatusCode> {
(100..999u16).prop_map(|i| StatusCode::from_u16(i).unwrap())
}
}
#[macro_export]
macro_rules! internal_error {
($target:ty : $($other:path), *) => {
$(
impl From<$other> for $target {
fn from(other: $other) -> Self {
Self::Internal(Box::new(other))
}
}
)*
}
}