use std::fmt; use std::error; use actix_http::ResponseBuilder; use actix_web as aweb; use actix_web::http::StatusCode; use serde_json::json; use actix_web::error::JsonPayloadError; use meilisearch_error::{ErrorCode, Code}; #[derive(Debug)] pub struct ResponseError { inner: Box, } impl fmt::Display for ResponseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.inner.fmt(f) } } impl From for ResponseError { fn from(error: Error) -> ResponseError { ResponseError { inner: Box::new(error) } } } #[derive(Debug)] pub enum Error { BadParameter(String, String), BadRequest(String), CreateIndex(String), DocumentNotFound(String), IndexNotFound(String), Internal(String), InvalidIndexUid, InvalidToken(String), Maintenance, MissingAuthorizationHeader, MissingHeader(String), NotFound(String), OpenIndex(String), RetrieveDocument(u32, String), SearchDocuments(String), PayloadTooLarge, UnsupportedMediaType, } impl error::Error for Error {} impl ErrorCode for Error { fn error_code(&self) -> Code { use Error::*; match self { BadParameter(_, _) => Code::BadParameter, BadRequest(_) => Code::BadRequest, CreateIndex(_) => Code::CreateIndex, DocumentNotFound(_) => Code::DocumentNotFound, IndexNotFound(_) => Code::IndexNotFound, Internal(_) => Code::Internal, InvalidIndexUid => Code::InvalidIndexUid, InvalidToken(_) => Code::InvalidToken, Maintenance => Code::Maintenance, MissingAuthorizationHeader => Code::MissingAuthorizationHeader, MissingHeader(_) => Code::MissingHeader, NotFound(_) => Code::NotFound, OpenIndex(_) => Code::OpenIndex, RetrieveDocument(_, _) => Code::RetrieveDocument, SearchDocuments(_) => Code::SearchDocuments, PayloadTooLarge => Code::PayloadTooLarge, UnsupportedMediaType => Code::UnsupportedMediaType, } } } #[derive(Debug)] pub enum FacetCountError { AttributeNotSet(String), SyntaxError(String), UnexpectedToken { found: String, expected: &'static [&'static str] }, NoFacetSet, } impl error::Error for FacetCountError {} impl ErrorCode for FacetCountError { fn error_code(&self) -> Code { Code::BadRequest } } impl FacetCountError { pub fn unexpected_token(found: impl ToString, expected: &'static [&'static str]) -> FacetCountError { let found = found.to_string(); FacetCountError::UnexpectedToken { expected, found } } } impl From for FacetCountError { fn from(other: serde_json::error::Error) -> FacetCountError { FacetCountError::SyntaxError(other.to_string()) } } impl fmt::Display for FacetCountError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use FacetCountError::*; match self { AttributeNotSet(attr) => write!(f, "attribute {} is not set as facet", attr), SyntaxError(msg) => write!(f, "syntax error: {}", msg), UnexpectedToken { expected, found } => write!(f, "unexpected {} found, expected {:?}", found, expected), NoFacetSet => write!(f, "can't perform facet count, as no facet is set"), } } } impl Error { pub fn internal(err: impl fmt::Display) -> Error { Error::Internal(err.to_string()) } pub fn bad_request(err: impl fmt::Display) -> Error { Error::BadRequest(err.to_string()) } pub fn missing_authorization_header() -> Error { Error::MissingAuthorizationHeader } pub fn invalid_token(err: impl fmt::Display) -> Error { Error::InvalidToken(err.to_string()) } pub fn not_found(err: impl fmt::Display) -> Error { Error::NotFound(err.to_string()) } pub fn index_not_found(err: impl fmt::Display) -> Error { Error::IndexNotFound(err.to_string()) } pub fn document_not_found(err: impl fmt::Display) -> Error { Error::DocumentNotFound(err.to_string()) } pub fn missing_header(err: impl fmt::Display) -> Error { Error::MissingHeader(err.to_string()) } pub fn bad_parameter(param: impl fmt::Display, err: impl fmt::Display) -> Error { Error::BadParameter(param.to_string(), err.to_string()) } pub fn open_index(err: impl fmt::Display) -> Error { Error::OpenIndex(err.to_string()) } pub fn create_index(err: impl fmt::Display) -> Error { Error::CreateIndex(err.to_string()) } pub fn invalid_index_uid() -> Error { Error::InvalidIndexUid } pub fn maintenance() -> Error { Error::Maintenance } pub fn retrieve_document(doc_id: u32, err: impl fmt::Display) -> Error { Error::RetrieveDocument(doc_id, err.to_string()) } pub fn search_documents(err: impl fmt::Display) -> Error { Error::SearchDocuments(err.to_string()) } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::BadParameter(param, err) => write!(f, "Url parameter {} error: {}", param, err), Self::BadRequest(err) => f.write_str(err), Self::CreateIndex(err) => write!(f, "Impossible to create index; {}", err), Self::DocumentNotFound(document_id) => write!(f, "Document with id {} not found", document_id), Self::IndexNotFound(index_uid) => write!(f, "Index {} not found", index_uid), Self::Internal(err) => f.write_str(err), Self::InvalidIndexUid => f.write_str("Index must have a valid uid; Index uid can be of type integer or string only composed of alphanumeric characters, hyphens (-) and underscores (_)."), Self::InvalidToken(err) => write!(f, "Invalid API key: {}", err), Self::Maintenance => f.write_str("Server is in maintenance, please try again later"), Self::MissingAuthorizationHeader => f.write_str("You must have an authorization token"), Self::MissingHeader(header) => write!(f, "Header {} is missing", header), Self::NotFound(err) => write!(f, "{} not found", err), Self::OpenIndex(err) => write!(f, "Impossible to open index; {}", err), Self::RetrieveDocument(id, err) => write!(f, "impossible to retrieve the document with id: {}; {}", id, err), Self::SearchDocuments(err) => write!(f, "impossible to search documents; {}", err), Self::PayloadTooLarge => f.write_str("Payload to large"), Self::UnsupportedMediaType => f.write_str("Unsupported media type"), } } } impl aweb::error::ResponseError for ResponseError { fn error_response(&self) -> aweb::HttpResponse { let error_code = self.inner.error_code().internal(); ResponseBuilder::new(self.status_code()).json(json!({ "message": self.to_string(), "errorCode": error_code, "errorLink": format!("docs.meilisearch.come/error/{}", error_code), })) } fn status_code(&self) -> StatusCode { self.inner.error_code().http() } } impl From for ResponseError { fn from(err: meilisearch_core::Error) -> ResponseError { ResponseError { inner: Box::new(err) } } } impl From for ResponseError { fn from(err: meilisearch_schema::Error) -> ResponseError { ResponseError { inner: Box::new(err) } } } impl From for Error { fn from(err: actix_http::Error) -> Error { Error::Internal(err.to_string()) } } impl From for ResponseError { fn from(err: FacetCountError) -> ResponseError { ResponseError { inner: Box::new(err) } } } impl From for Error { fn from(err: JsonPayloadError) -> Error { match err { JsonPayloadError::Deserialize(err) => Error::BadRequest(format!("Invalid JSON: {}", err)), JsonPayloadError::Overflow => Error::PayloadTooLarge, JsonPayloadError::ContentType => Error::UnsupportedMediaType, JsonPayloadError::Payload(err) => Error::BadRequest(format!("Problem while decoding the request: {}", err)), } } } pub fn json_error_handler(err: JsonPayloadError) -> ResponseError { let error = Error::from(err); error.into() }