mirror of
https://github.com/meilisearch/MeiliSearch
synced 2025-07-03 11:57:07 +02:00
Add a new meilisearch_types
crate
Move `meilisearch_error` to `meilisearch_types::error` Move `meilisearch_lib::index_resolver::IndexUid` to `meilisearch_types::index_uid` Add a new `InvalidIndexUid` error in `meilisearch_types::index_uid`
This commit is contained in:
parent
b9b32d65a8
commit
36cb09eb25
45 changed files with 172 additions and 157 deletions
365
meilisearch-types/src/error.rs
Normal file
365
meilisearch-types/src/error.rs
Normal file
|
@ -0,0 +1,365 @@
|
|||
use std::fmt;
|
||||
|
||||
use actix_web::{self as aweb, http::StatusCode, HttpResponseBuilder};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[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
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ErrorCode: std::error::Error {
|
||||
fn error_code(&self) -> Code;
|
||||
|
||||
/// returns the HTTP status code associated with the error
|
||||
fn http_status(&self) -> StatusCode {
|
||||
self.error_code().http()
|
||||
}
|
||||
|
||||
/// returns the doc url associated 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 {
|
||||
InternalError => write!(f, "internal"),
|
||||
InvalidRequestError => write!(f, "invalid_request"),
|
||||
AuthenticationError => write!(f, "auth"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
|
||||
pub enum Code {
|
||||
// index related error
|
||||
CreateIndex,
|
||||
IndexAlreadyExists,
|
||||
IndexNotFound,
|
||||
InvalidIndexUid,
|
||||
InvalidMinWordLengthForTypo,
|
||||
|
||||
// invalid state error
|
||||
InvalidState,
|
||||
MissingPrimaryKey,
|
||||
PrimaryKeyAlreadyPresent,
|
||||
|
||||
MaxFieldsLimitExceeded,
|
||||
MissingDocumentId,
|
||||
InvalidDocumentId,
|
||||
|
||||
Filter,
|
||||
Sort,
|
||||
|
||||
BadParameter,
|
||||
BadRequest,
|
||||
DatabaseSizeLimitReached,
|
||||
DocumentNotFound,
|
||||
Internal,
|
||||
InvalidGeoField,
|
||||
InvalidRankingRule,
|
||||
InvalidStore,
|
||||
InvalidToken,
|
||||
MissingAuthorizationHeader,
|
||||
NoSpaceLeftOnDevice,
|
||||
DumpNotFound,
|
||||
TaskNotFound,
|
||||
PayloadTooLarge,
|
||||
RetrieveDocument,
|
||||
SearchDocuments,
|
||||
UnsupportedMediaType,
|
||||
|
||||
DumpAlreadyInProgress,
|
||||
DumpProcessFailed,
|
||||
|
||||
InvalidContentType,
|
||||
MissingContentType,
|
||||
MalformedPayload,
|
||||
MissingPayload,
|
||||
|
||||
ApiKeyNotFound,
|
||||
MissingParameter,
|
||||
InvalidApiKeyActions,
|
||||
InvalidApiKeyIndexes,
|
||||
InvalidApiKeyExpiresAt,
|
||||
InvalidApiKeyDescription,
|
||||
InvalidApiKeyName,
|
||||
InvalidApiKeyUid,
|
||||
ImmutableField,
|
||||
ApiKeyAlreadyExists,
|
||||
}
|
||||
|
||||
impl Code {
|
||||
/// associate 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.
|
||||
CreateIndex => {
|
||||
ErrCode::internal("index_creation_failed", StatusCode::INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
IndexAlreadyExists => ErrCode::invalid("index_already_exists", StatusCode::CONFLICT),
|
||||
// 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
|
||||
MissingPrimaryKey => {
|
||||
ErrCode::invalid("primary_key_inference_failed", StatusCode::BAD_REQUEST)
|
||||
}
|
||||
// error thrown when trying to set an already existing primary key
|
||||
PrimaryKeyAlreadyPresent => {
|
||||
ErrCode::invalid("index_primary_key_already_exists", StatusCode::BAD_REQUEST)
|
||||
}
|
||||
// invalid ranking rule
|
||||
InvalidRankingRule => ErrCode::invalid("invalid_ranking_rule", StatusCode::BAD_REQUEST),
|
||||
|
||||
// invalid database
|
||||
InvalidStore => {
|
||||
ErrCode::internal("invalid_store_file", StatusCode::INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
|
||||
// invalid document
|
||||
MaxFieldsLimitExceeded => {
|
||||
ErrCode::invalid("max_fields_limit_exceeded", StatusCode::BAD_REQUEST)
|
||||
}
|
||||
MissingDocumentId => ErrCode::invalid("missing_document_id", StatusCode::BAD_REQUEST),
|
||||
InvalidDocumentId => ErrCode::invalid("invalid_document_id", StatusCode::BAD_REQUEST),
|
||||
|
||||
// error related to filters
|
||||
Filter => ErrCode::invalid("invalid_filter", StatusCode::BAD_REQUEST),
|
||||
// error related to sorts
|
||||
Sort => ErrCode::invalid("invalid_sort", StatusCode::BAD_REQUEST),
|
||||
|
||||
BadParameter => ErrCode::invalid("bad_parameter", StatusCode::BAD_REQUEST),
|
||||
BadRequest => ErrCode::invalid("bad_request", StatusCode::BAD_REQUEST),
|
||||
DatabaseSizeLimitReached => ErrCode::internal(
|
||||
"database_size_limit_reached",
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
),
|
||||
DocumentNotFound => ErrCode::invalid("document_not_found", StatusCode::NOT_FOUND),
|
||||
Internal => ErrCode::internal("internal", StatusCode::INTERNAL_SERVER_ERROR),
|
||||
InvalidGeoField => ErrCode::invalid("invalid_geo_field", StatusCode::BAD_REQUEST),
|
||||
InvalidToken => ErrCode::authentication("invalid_api_key", StatusCode::FORBIDDEN),
|
||||
MissingAuthorizationHeader => {
|
||||
ErrCode::authentication("missing_authorization_header", StatusCode::UNAUTHORIZED)
|
||||
}
|
||||
TaskNotFound => ErrCode::invalid("task_not_found", StatusCode::NOT_FOUND),
|
||||
DumpNotFound => ErrCode::invalid("dump_not_found", StatusCode::NOT_FOUND),
|
||||
NoSpaceLeftOnDevice => {
|
||||
ErrCode::internal("no_space_left_on_device", StatusCode::INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
PayloadTooLarge => ErrCode::invalid("payload_too_large", StatusCode::PAYLOAD_TOO_LARGE),
|
||||
RetrieveDocument => {
|
||||
ErrCode::internal("unretrievable_document", StatusCode::BAD_REQUEST)
|
||||
}
|
||||
SearchDocuments => ErrCode::internal("search_error", StatusCode::BAD_REQUEST),
|
||||
UnsupportedMediaType => {
|
||||
ErrCode::invalid("unsupported_media_type", StatusCode::UNSUPPORTED_MEDIA_TYPE)
|
||||
}
|
||||
|
||||
// error related to dump
|
||||
DumpAlreadyInProgress => {
|
||||
ErrCode::invalid("dump_already_processing", StatusCode::CONFLICT)
|
||||
}
|
||||
DumpProcessFailed => {
|
||||
ErrCode::internal("dump_process_failed", StatusCode::INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
MissingContentType => {
|
||||
ErrCode::invalid("missing_content_type", StatusCode::UNSUPPORTED_MEDIA_TYPE)
|
||||
}
|
||||
MalformedPayload => ErrCode::invalid("malformed_payload", StatusCode::BAD_REQUEST),
|
||||
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)
|
||||
}
|
||||
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),
|
||||
ImmutableField => ErrCode::invalid("immutable_field", StatusCode::BAD_REQUEST),
|
||||
InvalidMinWordLengthForTypo => {
|
||||
ErrCode::invalid("invalid_min_word_length_for_typo", StatusCode::BAD_REQUEST)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// return the HTTP status code associated 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 associated 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))
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
85
meilisearch-types/src/index_uid.rs
Normal file
85
meilisearch-types/src/index_uid.rs
Normal file
|
@ -0,0 +1,85 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// An index uid is composed of only ascii alphanumeric characters, - and _, between 1 and 400
|
||||
/// bytes long
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "test-traits", derive(proptest_derive::Arbitrary))]
|
||||
pub struct IndexUid(
|
||||
#[cfg_attr(feature = "test-traits", proptest(regex("[a-zA-Z0-9_-]{1,400}")))] String,
|
||||
);
|
||||
|
||||
impl IndexUid {
|
||||
pub fn new_unchecked(s: impl AsRef<str>) -> Self {
|
||||
Self(s.as_ref().to_string())
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> String {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Return a reference over the inner str.
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for IndexUid {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for IndexUid {
|
||||
type Error = IndexUidFormatError;
|
||||
|
||||
fn try_from(uid: String) -> Result<Self, Self::Error> {
|
||||
if !uid
|
||||
.chars()
|
||||
.all(|x| x.is_ascii_alphanumeric() || x == '-' || x == '_')
|
||||
|| uid.is_empty()
|
||||
|| uid.len() > 400
|
||||
{
|
||||
Err(IndexUidFormatError { invalid_uid: uid })
|
||||
} else {
|
||||
Ok(IndexUid(uid))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for IndexUid {
|
||||
type Err = IndexUidFormatError;
|
||||
|
||||
fn from_str(uid: &str) -> Result<IndexUid, IndexUidFormatError> {
|
||||
uid.to_string().try_into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IndexUid> for String {
|
||||
fn from(uid: IndexUid) -> Self {
|
||||
uid.into_inner()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct IndexUidFormatError {
|
||||
pub invalid_uid: String,
|
||||
}
|
||||
|
||||
impl fmt::Display for IndexUidFormatError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"invalid index uid `{}`, the uid must be an integer \
|
||||
or a string containing only alphanumeric characters \
|
||||
a-z A-Z 0-9, hyphens - and underscores _.",
|
||||
self.invalid_uid,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for IndexUidFormatError {}
|
2
meilisearch-types/src/lib.rs
Normal file
2
meilisearch-types/src/lib.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
pub mod error;
|
||||
pub mod index_uid;
|
Loading…
Add table
Add a link
Reference in a new issue