mirror of
https://github.com/meilisearch/MeiliSearch
synced 2025-07-15 13:58:36 +02:00
Integrate deserr on the most important routes
This commit is contained in:
parent
839b05c43d
commit
50ce0409bc
21 changed files with 1582 additions and 657 deletions
|
@ -1,8 +1,14 @@
|
|||
use std::convert::Infallible;
|
||||
use std::num::ParseIntError;
|
||||
|
||||
use actix_web::web::Data;
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use deserr::{
|
||||
DeserializeError, DeserializeFromValue, ErrorKind, IntoValue, MergeWithError, ValuePointerRef,
|
||||
};
|
||||
use index_scheduler::IndexScheduler;
|
||||
use log::debug;
|
||||
use meilisearch_types::error::ResponseError;
|
||||
use meilisearch_types::error::{unwrap_any, Code, ErrorCode, ResponseError};
|
||||
use meilisearch_types::index_uid::IndexUid;
|
||||
use meilisearch_types::milli::{self, FieldDistribution, Index};
|
||||
use meilisearch_types::tasks::KindWithContent;
|
||||
|
@ -14,6 +20,8 @@ use super::{Pagination, SummarizedTaskView};
|
|||
use crate::analytics::Analytics;
|
||||
use crate::extractors::authentication::policies::*;
|
||||
use crate::extractors::authentication::{AuthenticationError, GuardedData};
|
||||
use crate::extractors::json::ValidatedJson;
|
||||
use crate::extractors::query_parameters::QueryParameter;
|
||||
use crate::extractors::sequential_extractor::SeqHandler;
|
||||
|
||||
pub mod documents;
|
||||
|
@ -66,7 +74,7 @@ impl IndexView {
|
|||
|
||||
pub async fn list_indexes(
|
||||
index_scheduler: GuardedData<ActionPolicy<{ actions::INDEXES_GET }>, Data<IndexScheduler>>,
|
||||
paginate: web::Query<Pagination>,
|
||||
paginate: QueryParameter<Pagination, ListIndexesDeserrError>,
|
||||
) -> Result<HttpResponse, ResponseError> {
|
||||
let search_rules = &index_scheduler.filters().search_rules;
|
||||
let indexes: Vec<_> = index_scheduler.indexes()?;
|
||||
|
@ -82,8 +90,68 @@ pub async fn list_indexes(
|
|||
Ok(HttpResponse::Ok().json(ret))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
#[derive(Debug)]
|
||||
pub struct ListIndexesDeserrError {
|
||||
error: String,
|
||||
code: Code,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ListIndexesDeserrError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.error)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for ListIndexesDeserrError {}
|
||||
impl ErrorCode for ListIndexesDeserrError {
|
||||
fn error_code(&self) -> Code {
|
||||
self.code
|
||||
}
|
||||
}
|
||||
|
||||
impl MergeWithError<ListIndexesDeserrError> for ListIndexesDeserrError {
|
||||
fn merge(
|
||||
_self_: Option<Self>,
|
||||
other: ListIndexesDeserrError,
|
||||
_merge_location: ValuePointerRef,
|
||||
) -> Result<Self, Self> {
|
||||
Err(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl deserr::DeserializeError for ListIndexesDeserrError {
|
||||
fn error<V: IntoValue>(
|
||||
_self_: Option<Self>,
|
||||
error: ErrorKind<V>,
|
||||
location: ValuePointerRef,
|
||||
) -> Result<Self, Self> {
|
||||
let code = match location.last_field() {
|
||||
Some("offset") => Code::InvalidIndexLimit,
|
||||
Some("limit") => Code::InvalidIndexOffset,
|
||||
_ => Code::BadRequest,
|
||||
};
|
||||
let error = unwrap_any(deserr::serde_json::JsonError::error(None, error, location)).0;
|
||||
|
||||
Err(ListIndexesDeserrError { error, code })
|
||||
}
|
||||
}
|
||||
|
||||
impl MergeWithError<ParseIntError> for ListIndexesDeserrError {
|
||||
fn merge(
|
||||
_self_: Option<Self>,
|
||||
other: ParseIntError,
|
||||
merge_location: ValuePointerRef,
|
||||
) -> Result<Self, Self> {
|
||||
ListIndexesDeserrError::error::<Infallible>(
|
||||
None,
|
||||
ErrorKind::Unexpected { msg: other.to_string() },
|
||||
merge_location,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeserializeFromValue, Debug)]
|
||||
#[deserr(rename_all = camelCase, deny_unknown_fields)]
|
||||
pub struct IndexCreateRequest {
|
||||
uid: String,
|
||||
primary_key: Option<String>,
|
||||
|
@ -91,7 +159,7 @@ pub struct IndexCreateRequest {
|
|||
|
||||
pub async fn create_index(
|
||||
index_scheduler: GuardedData<ActionPolicy<{ actions::INDEXES_CREATE }>, Data<IndexScheduler>>,
|
||||
body: web::Json<IndexCreateRequest>,
|
||||
body: ValidatedJson<IndexCreateRequest, CreateIndexesDeserrError>,
|
||||
req: HttpRequest,
|
||||
analytics: web::Data<dyn Analytics>,
|
||||
) -> Result<HttpResponse, ResponseError> {
|
||||
|
@ -116,11 +184,58 @@ pub async fn create_index(
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
pub struct CreateIndexesDeserrError {
|
||||
error: String,
|
||||
code: Code,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for CreateIndexesDeserrError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.error)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for CreateIndexesDeserrError {}
|
||||
impl ErrorCode for CreateIndexesDeserrError {
|
||||
fn error_code(&self) -> Code {
|
||||
self.code
|
||||
}
|
||||
}
|
||||
|
||||
impl MergeWithError<CreateIndexesDeserrError> for CreateIndexesDeserrError {
|
||||
fn merge(
|
||||
_self_: Option<Self>,
|
||||
other: CreateIndexesDeserrError,
|
||||
_merge_location: ValuePointerRef,
|
||||
) -> Result<Self, Self> {
|
||||
Err(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl deserr::DeserializeError for CreateIndexesDeserrError {
|
||||
fn error<V: IntoValue>(
|
||||
_self_: Option<Self>,
|
||||
error: ErrorKind<V>,
|
||||
location: ValuePointerRef,
|
||||
) -> Result<Self, Self> {
|
||||
let code = match location.last_field() {
|
||||
Some("uid") => Code::InvalidIndexUid,
|
||||
Some("primaryKey") => Code::InvalidIndexPrimaryKey,
|
||||
None if matches!(error, ErrorKind::MissingField { field } if field == "uid") => {
|
||||
Code::MissingIndexUid
|
||||
}
|
||||
_ => Code::BadRequest,
|
||||
};
|
||||
let error = unwrap_any(deserr::serde_json::JsonError::error(None, error, location)).0;
|
||||
|
||||
Err(CreateIndexesDeserrError { error, code })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeserializeFromValue, Debug)]
|
||||
#[deserr(rename_all = camelCase, deny_unknown_fields)]
|
||||
pub struct UpdateIndexRequest {
|
||||
uid: Option<String>,
|
||||
primary_key: Option<String>,
|
||||
}
|
||||
|
||||
|
@ -139,7 +254,7 @@ pub async fn get_index(
|
|||
pub async fn update_index(
|
||||
index_scheduler: GuardedData<ActionPolicy<{ actions::INDEXES_UPDATE }>, Data<IndexScheduler>>,
|
||||
path: web::Path<String>,
|
||||
body: web::Json<UpdateIndexRequest>,
|
||||
body: ValidatedJson<UpdateIndexRequest, UpdateIndexesDeserrError>,
|
||||
req: HttpRequest,
|
||||
analytics: web::Data<dyn Analytics>,
|
||||
) -> Result<HttpResponse, ResponseError> {
|
||||
|
@ -147,7 +262,7 @@ pub async fn update_index(
|
|||
let body = body.into_inner();
|
||||
analytics.publish(
|
||||
"Index Updated".to_string(),
|
||||
json!({ "primary_key": body.primary_key}),
|
||||
json!({ "primary_key": body.primary_key }),
|
||||
Some(&req),
|
||||
);
|
||||
|
||||
|
@ -163,6 +278,51 @@ pub async fn update_index(
|
|||
Ok(HttpResponse::Accepted().json(task))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UpdateIndexesDeserrError {
|
||||
error: String,
|
||||
code: Code,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for UpdateIndexesDeserrError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.error)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for UpdateIndexesDeserrError {}
|
||||
impl ErrorCode for UpdateIndexesDeserrError {
|
||||
fn error_code(&self) -> Code {
|
||||
self.code
|
||||
}
|
||||
}
|
||||
|
||||
impl MergeWithError<UpdateIndexesDeserrError> for UpdateIndexesDeserrError {
|
||||
fn merge(
|
||||
_self_: Option<Self>,
|
||||
other: UpdateIndexesDeserrError,
|
||||
_merge_location: ValuePointerRef,
|
||||
) -> Result<Self, Self> {
|
||||
Err(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl deserr::DeserializeError for UpdateIndexesDeserrError {
|
||||
fn error<V: IntoValue>(
|
||||
_self_: Option<Self>,
|
||||
error: ErrorKind<V>,
|
||||
location: ValuePointerRef,
|
||||
) -> Result<Self, Self> {
|
||||
let code = match location.last_field() {
|
||||
Some("primaryKey") => Code::InvalidIndexPrimaryKey,
|
||||
_ => Code::BadRequest,
|
||||
};
|
||||
let error = unwrap_any(deserr::serde_json::JsonError::error(None, error, location)).0;
|
||||
|
||||
Err(UpdateIndexesDeserrError { error, code })
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn delete_index(
|
||||
index_scheduler: GuardedData<ActionPolicy<{ actions::INDEXES_DELETE }>, Data<IndexScheduler>>,
|
||||
index_uid: web::Path<String>,
|
||||
|
|
|
@ -1,21 +1,25 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use actix_web::web::Data;
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use index_scheduler::IndexScheduler;
|
||||
use log::debug;
|
||||
use meilisearch_auth::IndexSearchRules;
|
||||
use meilisearch_types::error::ResponseError;
|
||||
use serde::Deserialize;
|
||||
use serde_cs::vec::CS;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::analytics::{Analytics, SearchAggregator};
|
||||
use crate::extractors::authentication::policies::*;
|
||||
use crate::extractors::authentication::GuardedData;
|
||||
use crate::extractors::json::ValidatedJson;
|
||||
use crate::extractors::query_parameters::QueryParameter;
|
||||
use crate::extractors::sequential_extractor::SeqHandler;
|
||||
use crate::routes::from_string_to_option;
|
||||
use crate::search::{
|
||||
perform_search, MatchingStrategy, SearchQuery, DEFAULT_CROP_LENGTH, DEFAULT_CROP_MARKER,
|
||||
DEFAULT_HIGHLIGHT_POST_TAG, DEFAULT_HIGHLIGHT_PRE_TAG, DEFAULT_SEARCH_LIMIT,
|
||||
DEFAULT_SEARCH_OFFSET,
|
||||
perform_search, MatchingStrategy, SearchDeserError, SearchQuery, DEFAULT_CROP_LENGTH,
|
||||
DEFAULT_CROP_MARKER, DEFAULT_HIGHLIGHT_POST_TAG, DEFAULT_HIGHLIGHT_PRE_TAG,
|
||||
DEFAULT_SEARCH_LIMIT, DEFAULT_SEARCH_OFFSET,
|
||||
};
|
||||
|
||||
pub fn configure(cfg: &mut web::ServiceConfig) {
|
||||
|
@ -26,33 +30,35 @@ pub fn configure(cfg: &mut web::ServiceConfig) {
|
|||
);
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
#[derive(Debug, deserr::DeserializeFromValue)]
|
||||
#[deserr(rename_all = camelCase, deny_unknown_fields)]
|
||||
pub struct SearchQueryGet {
|
||||
q: Option<String>,
|
||||
#[serde(default = "DEFAULT_SEARCH_OFFSET")]
|
||||
#[deserr(default = DEFAULT_SEARCH_OFFSET(), from(&String) = FromStr::from_str -> std::num::ParseIntError)]
|
||||
offset: usize,
|
||||
#[serde(default = "DEFAULT_SEARCH_LIMIT")]
|
||||
#[deserr(default = DEFAULT_SEARCH_LIMIT(), from(&String) = FromStr::from_str -> std::num::ParseIntError)]
|
||||
limit: usize,
|
||||
#[deserr(from(&String) = from_string_to_option -> std::num::ParseIntError)]
|
||||
page: Option<usize>,
|
||||
#[deserr(from(&String) = from_string_to_option -> std::num::ParseIntError)]
|
||||
hits_per_page: Option<usize>,
|
||||
attributes_to_retrieve: Option<CS<String>>,
|
||||
attributes_to_crop: Option<CS<String>>,
|
||||
#[serde(default = "DEFAULT_CROP_LENGTH")]
|
||||
#[deserr(default = DEFAULT_CROP_LENGTH(), from(&String) = FromStr::from_str -> std::num::ParseIntError)]
|
||||
crop_length: usize,
|
||||
attributes_to_highlight: Option<CS<String>>,
|
||||
filter: Option<String>,
|
||||
sort: Option<String>,
|
||||
#[serde(default = "Default::default")]
|
||||
#[deserr(default, from(&String) = FromStr::from_str -> std::str::ParseBoolError)]
|
||||
show_matches_position: bool,
|
||||
facets: Option<CS<String>>,
|
||||
#[serde(default = "DEFAULT_HIGHLIGHT_PRE_TAG")]
|
||||
#[deserr(default = DEFAULT_HIGHLIGHT_PRE_TAG())]
|
||||
highlight_pre_tag: String,
|
||||
#[serde(default = "DEFAULT_HIGHLIGHT_POST_TAG")]
|
||||
#[deserr(default = DEFAULT_HIGHLIGHT_POST_TAG())]
|
||||
highlight_post_tag: String,
|
||||
#[serde(default = "DEFAULT_CROP_MARKER")]
|
||||
#[deserr(default = DEFAULT_CROP_MARKER())]
|
||||
crop_marker: String,
|
||||
#[serde(default)]
|
||||
#[deserr(default)]
|
||||
matching_strategy: MatchingStrategy,
|
||||
}
|
||||
|
||||
|
@ -136,7 +142,7 @@ fn fix_sort_query_parameters(sort_query: &str) -> Vec<String> {
|
|||
pub async fn search_with_url_query(
|
||||
index_scheduler: GuardedData<ActionPolicy<{ actions::SEARCH }>, Data<IndexScheduler>>,
|
||||
index_uid: web::Path<String>,
|
||||
params: web::Query<SearchQueryGet>,
|
||||
params: QueryParameter<SearchQueryGet, SearchDeserError>,
|
||||
req: HttpRequest,
|
||||
analytics: web::Data<dyn Analytics>,
|
||||
) -> Result<HttpResponse, ResponseError> {
|
||||
|
@ -168,7 +174,7 @@ pub async fn search_with_url_query(
|
|||
pub async fn search_with_post(
|
||||
index_scheduler: GuardedData<ActionPolicy<{ actions::SEARCH }>, Data<IndexScheduler>>,
|
||||
index_uid: web::Path<String>,
|
||||
params: web::Json<SearchQuery>,
|
||||
params: ValidatedJson<SearchQuery, SearchDeserError>,
|
||||
req: HttpRequest,
|
||||
analytics: web::Data<dyn Analytics>,
|
||||
) -> Result<HttpResponse, ResponseError> {
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
use std::fmt;
|
||||
|
||||
use actix_web::web::Data;
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use deserr::{IntoValue, ValuePointerRef};
|
||||
use index_scheduler::IndexScheduler;
|
||||
use log::debug;
|
||||
use meilisearch_types::error::ResponseError;
|
||||
use meilisearch_types::error::{unwrap_any, Code, ErrorCode, ResponseError};
|
||||
use meilisearch_types::index_uid::IndexUid;
|
||||
use meilisearch_types::settings::{settings, Settings, Unchecked};
|
||||
use meilisearch_types::tasks::KindWithContent;
|
||||
|
@ -11,6 +14,7 @@ use serde_json::json;
|
|||
use crate::analytics::Analytics;
|
||||
use crate::extractors::authentication::policies::*;
|
||||
use crate::extractors::authentication::GuardedData;
|
||||
use crate::extractors::json::ValidatedJson;
|
||||
use crate::routes::SummarizedTaskView;
|
||||
|
||||
#[macro_export]
|
||||
|
@ -39,7 +43,7 @@ macro_rules! make_setting_route {
|
|||
>,
|
||||
index_uid: web::Path<String>,
|
||||
) -> Result<HttpResponse, ResponseError> {
|
||||
let new_settings = Settings { $attr: Setting::Reset, ..Default::default() };
|
||||
let new_settings = Settings { $attr: Setting::Reset.into(), ..Default::default() };
|
||||
|
||||
let allow_index_creation = index_scheduler.filters().allow_index_creation;
|
||||
let index_uid = IndexUid::try_from(index_uid.into_inner())?.into_inner();
|
||||
|
@ -74,8 +78,8 @@ macro_rules! make_setting_route {
|
|||
|
||||
let new_settings = Settings {
|
||||
$attr: match body {
|
||||
Some(inner_body) => Setting::Set(inner_body),
|
||||
None => Setting::Reset,
|
||||
Some(inner_body) => Setting::Set(inner_body).into(),
|
||||
None => Setting::Reset.into(),
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -208,7 +212,7 @@ make_setting_route!(
|
|||
"TypoTolerance Updated".to_string(),
|
||||
json!({
|
||||
"typo_tolerance": {
|
||||
"enabled": setting.as_ref().map(|s| !matches!(s.enabled, Setting::Set(false))),
|
||||
"enabled": setting.as_ref().map(|s| !matches!(s.enabled.into(), Setting::Set(false))),
|
||||
"disable_on_attributes": setting
|
||||
.as_ref()
|
||||
.and_then(|s| s.disable_on_attributes.as_ref().set().map(|m| !m.is_empty())),
|
||||
|
@ -424,10 +428,66 @@ generate_configure!(
|
|||
faceting
|
||||
);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SettingsDeserrError {
|
||||
error: String,
|
||||
code: Code,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for SettingsDeserrError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.error)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for SettingsDeserrError {}
|
||||
impl ErrorCode for SettingsDeserrError {
|
||||
fn error_code(&self) -> Code {
|
||||
self.code
|
||||
}
|
||||
}
|
||||
|
||||
impl deserr::MergeWithError<SettingsDeserrError> for SettingsDeserrError {
|
||||
fn merge(
|
||||
_self_: Option<Self>,
|
||||
other: SettingsDeserrError,
|
||||
_merge_location: ValuePointerRef,
|
||||
) -> Result<Self, Self> {
|
||||
Err(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl deserr::DeserializeError for SettingsDeserrError {
|
||||
fn error<V: IntoValue>(
|
||||
_self_: Option<Self>,
|
||||
error: deserr::ErrorKind<V>,
|
||||
location: ValuePointerRef,
|
||||
) -> Result<Self, Self> {
|
||||
let error = unwrap_any(deserr::serde_json::JsonError::error(None, error, location)).0;
|
||||
|
||||
let code = match location.first_field() {
|
||||
Some("displayedAttributes") => Code::InvalidSettingsDisplayedAttributes,
|
||||
Some("searchableAttributes") => Code::InvalidSettingsSearchableAttributes,
|
||||
Some("filterableAttributes") => Code::InvalidSettingsFilterableAttributes,
|
||||
Some("sortableAttributes") => Code::InvalidSettingsSortableAttributes,
|
||||
Some("rankingRules") => Code::InvalidSettingsRankingRules,
|
||||
Some("stopWords") => Code::InvalidSettingsStopWords,
|
||||
Some("synonyms") => Code::InvalidSettingsSynonyms,
|
||||
Some("distinctAttribute") => Code::InvalidSettingsDistinctAttribute,
|
||||
Some("typoTolerance") => Code::InvalidSettingsTypoTolerance,
|
||||
Some("faceting") => Code::InvalidSettingsFaceting,
|
||||
Some("pagination") => Code::InvalidSettingsPagination,
|
||||
_ => Code::BadRequest,
|
||||
};
|
||||
|
||||
Err(SettingsDeserrError { error, code })
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn update_all(
|
||||
index_scheduler: GuardedData<ActionPolicy<{ actions::SETTINGS_UPDATE }>, Data<IndexScheduler>>,
|
||||
index_uid: web::Path<String>,
|
||||
body: web::Json<Settings<Unchecked>>,
|
||||
body: ValidatedJson<Settings<Unchecked>, SettingsDeserrError>,
|
||||
req: HttpRequest,
|
||||
analytics: web::Data<dyn Analytics>,
|
||||
) -> Result<HttpResponse, ResponseError> {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue