Integrate deserr on the most important routes

This commit is contained in:
Tamo 2022-12-14 13:00:43 +01:00
parent 839b05c43d
commit 50ce0409bc
No known key found for this signature in database
GPG key ID: 20CD8020AFA88D69
21 changed files with 1582 additions and 657 deletions

View file

@ -5,6 +5,7 @@ authors = ["marin <postma.marin@protonmail.com>"]
edition = "2021"
[dependencies]
deserr = { path = "../../deserr", features = ["serde-json"] }
actix-web = { version = "4.2.1", default-features = false }
anyhow = "1.0.65"
convert_case = "0.6.0"

View file

@ -119,9 +119,13 @@ pub enum Code {
// index related error
CreateIndex,
IndexAlreadyExists,
InvalidIndexPrimaryKey,
IndexNotFound,
InvalidIndexUid,
MissingIndexUid,
InvalidMinWordLengthForTypo,
InvalidIndexLimit,
InvalidIndexOffset,
DuplicateIndexFound,
@ -138,6 +142,55 @@ pub enum Code {
Filter,
Sort,
// Invalid swap-indexes
InvalidSwapIndexes,
// Invalid settings update request
InvalidSettingsDisplayedAttributes,
InvalidSettingsSearchableAttributes,
InvalidSettingsFilterableAttributes,
InvalidSettingsSortableAttributes,
InvalidSettingsRankingRules,
InvalidSettingsStopWords,
InvalidSettingsSynonyms,
InvalidSettingsDistinctAttribute,
InvalidSettingsTypoTolerance,
InvalidSettingsFaceting,
InvalidSettingsPagination,
// Invalid search request
InvalidSearchQ,
InvalidSearchOffset,
InvalidSearchLimit,
InvalidSearchPage,
InvalidSearchHitsPerPage,
InvalidSearchAttributesToRetrieve,
InvalidSearchAttributesToCrop,
InvalidSearchCropLength,
InvalidSearchAttributesToHighlight,
InvalidSearchShowMatchesPosition,
InvalidSearchFilter,
InvalidSearchSort,
InvalidSearchFacets,
InvalidSearchHighlightPreTag,
InvalidSearchHighlightPostTag,
InvalidSearchCropMarker,
InvalidSearchMatchingStrategy,
// Related to the tasks
InvalidTaskUids,
InvalidTaskTypes,
InvalidTaskStatuses,
InvalidTaskCanceledBy,
InvalidTaskLimit,
InvalidTaskFrom,
InvalidTaskBeforeEnqueuedAt,
InvalidTaskAfterEnqueuedAt,
InvalidTaskBeforeStartedAt,
InvalidTaskAfterStartedAt,
InvalidTaskBeforeFinishedAt,
InvalidTaskAfterFinishedAt,
BadParameter,
BadRequest,
DatabaseSizeLimitReached,
@ -150,11 +203,6 @@ pub enum Code {
MissingAuthorizationHeader,
MissingMasterKey,
DumpNotFound,
InvalidTaskDateFilter,
InvalidTaskStatusesFilter,
InvalidTaskTypesFilter,
InvalidTaskCanceledByFilter,
InvalidTaskUidsFilter,
TaskNotFound,
TaskDeletionWithEmptyQuery,
TaskCancelationWithEmptyQuery,
@ -209,6 +257,12 @@ impl Code {
// thrown when requesting an unexisting index
IndexNotFound => ErrCode::invalid("index_not_found", StatusCode::NOT_FOUND),
InvalidIndexUid => ErrCode::invalid("invalid_index_uid", StatusCode::BAD_REQUEST),
MissingIndexUid => ErrCode::invalid("missing_index_uid", StatusCode::BAD_REQUEST),
InvalidIndexPrimaryKey => {
ErrCode::invalid("invalid_index_primary_key", StatusCode::BAD_REQUEST)
}
InvalidIndexLimit => ErrCode::invalid("invalid_index_limit", StatusCode::BAD_REQUEST),
InvalidIndexOffset => ErrCode::invalid("invalid_index_offset", StatusCode::BAD_REQUEST),
// invalid state error
InvalidState => ErrCode::internal("invalid_state", StatusCode::INTERNAL_SERVER_ERROR),
@ -259,21 +313,6 @@ impl Code {
MissingMasterKey => {
ErrCode::authentication("missing_master_key", StatusCode::UNAUTHORIZED)
}
InvalidTaskDateFilter => {
ErrCode::invalid("invalid_task_date_filter", StatusCode::BAD_REQUEST)
}
InvalidTaskUidsFilter => {
ErrCode::invalid("invalid_task_uids_filter", StatusCode::BAD_REQUEST)
}
InvalidTaskStatusesFilter => {
ErrCode::invalid("invalid_task_statuses_filter", StatusCode::BAD_REQUEST)
}
InvalidTaskTypesFilter => {
ErrCode::invalid("invalid_task_types_filter", StatusCode::BAD_REQUEST)
}
InvalidTaskCanceledByFilter => {
ErrCode::invalid("invalid_task_canceled_by_filter", StatusCode::BAD_REQUEST)
}
TaskNotFound => ErrCode::invalid("task_not_found", StatusCode::NOT_FOUND),
TaskDeletionWithEmptyQuery => {
ErrCode::invalid("missing_task_filters", StatusCode::BAD_REQUEST)
@ -336,6 +375,116 @@ impl Code {
DuplicateIndexFound => {
ErrCode::invalid("duplicate_index_found", StatusCode::BAD_REQUEST)
}
InvalidSwapIndexes => ErrCode::invalid("invalid_swap_indexes", StatusCode::BAD_REQUEST),
InvalidSettingsDisplayedAttributes => {
ErrCode::invalid("invalid_settings_displayed_attributes", StatusCode::BAD_REQUEST)
}
InvalidSettingsSearchableAttributes => {
ErrCode::invalid("invalid_settings_searchable_attributes", StatusCode::BAD_REQUEST)
}
InvalidSettingsFilterableAttributes => {
ErrCode::invalid("invalid_settings_filterable_attributes", StatusCode::BAD_REQUEST)
}
InvalidSettingsSortableAttributes => {
ErrCode::invalid("invalid_settings_sortable_attributes", StatusCode::BAD_REQUEST)
}
InvalidSettingsRankingRules => {
ErrCode::invalid("invalid_settings_ranking_rules", StatusCode::BAD_REQUEST)
}
InvalidSettingsStopWords => {
ErrCode::invalid("invalid_settings_stop_words", StatusCode::BAD_REQUEST)
}
InvalidSettingsSynonyms => {
ErrCode::invalid("invalid_settings_synonyms", StatusCode::BAD_REQUEST)
}
InvalidSettingsDistinctAttribute => {
ErrCode::invalid("invalid_settings_distinct_attribute", StatusCode::BAD_REQUEST)
}
InvalidSettingsTypoTolerance => {
ErrCode::invalid("invalid_settings_typo_tolerance", StatusCode::BAD_REQUEST)
}
InvalidSettingsFaceting => {
ErrCode::invalid("invalid_settings_faceting", StatusCode::BAD_REQUEST)
}
InvalidSettingsPagination => {
ErrCode::invalid("invalid_settings_pagination", StatusCode::BAD_REQUEST)
}
InvalidSearchQ => ErrCode::invalid("invalid_search_q", StatusCode::BAD_REQUEST),
InvalidSearchOffset => {
ErrCode::invalid("invalid_search_offset", StatusCode::BAD_REQUEST)
}
InvalidSearchLimit => ErrCode::invalid("invalid_search_limit", StatusCode::BAD_REQUEST),
InvalidSearchPage => ErrCode::invalid("invalid_search_page", StatusCode::BAD_REQUEST),
InvalidSearchHitsPerPage => {
ErrCode::invalid("invalid_search_hits_per_page", StatusCode::BAD_REQUEST)
}
InvalidSearchAttributesToRetrieve => {
ErrCode::invalid("invalid_search_attributes_to_retrieve", StatusCode::BAD_REQUEST)
}
InvalidSearchAttributesToCrop => {
ErrCode::invalid("invalid_search_attributes_to_crop", StatusCode::BAD_REQUEST)
}
InvalidSearchCropLength => {
ErrCode::invalid("invalid_search_crop_length", StatusCode::BAD_REQUEST)
}
InvalidSearchAttributesToHighlight => {
ErrCode::invalid("invalid_search_attributes_to_highlight", StatusCode::BAD_REQUEST)
}
InvalidSearchShowMatchesPosition => {
ErrCode::invalid("invalid_search_show_matches_position", StatusCode::BAD_REQUEST)
}
InvalidSearchFilter => {
ErrCode::invalid("invalid_search_filter", StatusCode::BAD_REQUEST)
}
InvalidSearchSort => ErrCode::invalid("invalid_search_sort", StatusCode::BAD_REQUEST),
InvalidSearchFacets => {
ErrCode::invalid("invalid_search_facets", StatusCode::BAD_REQUEST)
}
InvalidSearchHighlightPreTag => {
ErrCode::invalid("invalid_search_highlight_pre_tag", StatusCode::BAD_REQUEST)
}
InvalidSearchHighlightPostTag => {
ErrCode::invalid("invalid_search_highlight_post_tag", StatusCode::BAD_REQUEST)
}
InvalidSearchCropMarker => {
ErrCode::invalid("invalid_search_crop_marker", StatusCode::BAD_REQUEST)
}
InvalidSearchMatchingStrategy => {
ErrCode::invalid("invalid_search_matching_strategy", StatusCode::BAD_REQUEST)
}
// Related to the tasks
InvalidTaskUids => ErrCode::invalid("invalid_task_uids", StatusCode::BAD_REQUEST),
InvalidTaskTypes => ErrCode::invalid("invalid_task_types", StatusCode::BAD_REQUEST),
InvalidTaskStatuses => {
ErrCode::invalid("invalid_task_statuses", StatusCode::BAD_REQUEST)
}
InvalidTaskCanceledBy => {
ErrCode::invalid("invalid_task_canceled_by", StatusCode::BAD_REQUEST)
}
InvalidTaskLimit => ErrCode::invalid("invalid_task_limit", StatusCode::BAD_REQUEST),
InvalidTaskFrom => ErrCode::invalid("invalid_task_from", StatusCode::BAD_REQUEST),
InvalidTaskBeforeEnqueuedAt => {
ErrCode::invalid("invalid_task_before_enqueued_at", StatusCode::BAD_REQUEST)
}
InvalidTaskAfterEnqueuedAt => {
ErrCode::invalid("invalid_task_after_enqueued_at", StatusCode::BAD_REQUEST)
}
InvalidTaskBeforeStartedAt => {
ErrCode::invalid("invalid_task_before_started_at", StatusCode::BAD_REQUEST)
}
InvalidTaskAfterStartedAt => {
ErrCode::invalid("invalid_task_after_started_at", StatusCode::BAD_REQUEST)
}
InvalidTaskBeforeFinishedAt => {
ErrCode::invalid("invalid_task_before_finished_at", StatusCode::BAD_REQUEST)
}
InvalidTaskAfterFinishedAt => {
ErrCode::invalid("invalid_task_after_finished_at", StatusCode::BAD_REQUEST)
}
}
}
@ -476,6 +625,13 @@ impl ErrorCode for io::Error {
}
}
pub fn unwrap_any<T>(any: Result<T, T>) -> T {
match any {
Ok(any) => any,
Err(any) => any,
}
}
#[cfg(feature = "test-traits")]
mod strategy {
use proptest::strategy::Strategy;

View file

@ -2,10 +2,10 @@ use std::collections::{BTreeMap, BTreeSet};
use std::marker::PhantomData;
use std::num::NonZeroUsize;
use deserr::{DeserializeError, DeserializeFromValue};
use fst::IntoStreamer;
use milli::update::Setting;
use milli::{Index, DEFAULT_VALUES_PER_FACET};
use serde::{Deserialize, Serialize, Serializer};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
/// The maximimum number of results that the engine
/// will be able to return in one search call.
@ -27,16 +27,135 @@ where
.serialize(s)
}
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
pub enum Setting<T> {
Set(T),
Reset,
NotSet,
}
impl<T> Default for Setting<T> {
fn default() -> Self {
Self::NotSet
}
}
impl<T> From<Setting<T>> for milli::update::Setting<T> {
fn from(value: Setting<T>) -> Self {
match value {
Setting::Set(x) => milli::update::Setting::Set(x),
Setting::Reset => milli::update::Setting::Reset,
Setting::NotSet => milli::update::Setting::NotSet,
}
}
}
impl<T> From<milli::update::Setting<T>> for Setting<T> {
fn from(value: milli::update::Setting<T>) -> Self {
match value {
milli::update::Setting::Set(x) => Setting::Set(x),
milli::update::Setting::Reset => Setting::Reset,
milli::update::Setting::NotSet => Setting::NotSet,
}
}
}
impl<T> Setting<T> {
pub fn set(self) -> Option<T> {
match self {
Self::Set(value) => Some(value),
_ => None,
}
}
pub const fn as_ref(&self) -> Setting<&T> {
match *self {
Self::Set(ref value) => Setting::Set(value),
Self::Reset => Setting::Reset,
Self::NotSet => Setting::NotSet,
}
}
pub const fn is_not_set(&self) -> bool {
matches!(self, Self::NotSet)
}
/// If `Self` is `Reset`, then map self to `Set` with the provided `val`.
pub fn or_reset(self, val: T) -> Self {
match self {
Self::Reset => Self::Set(val),
otherwise => otherwise,
}
}
}
impl<T: Serialize> Serialize for Setting<T> {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Self::Set(value) => Some(value),
// Usually not_set isn't serialized by setting skip_serializing_if field attribute
Self::NotSet | Self::Reset => None,
}
.serialize(serializer)
}
}
impl<'de, T: Deserialize<'de>> Deserialize<'de> for Setting<T> {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Deserialize::deserialize(deserializer).map(|x| match x {
Some(x) => Self::Set(x),
None => Self::Reset, // Reset is forced by sending null value
})
}
}
impl<T, E> DeserializeFromValue<E> for Setting<T>
where
T: DeserializeFromValue<E>,
E: DeserializeError,
{
fn deserialize_from_value<V: deserr::IntoValue>(
value: deserr::Value<V>,
location: deserr::ValuePointerRef,
) -> Result<Self, E> {
match value {
deserr::Value::Null => Ok(Setting::Reset),
_ => T::deserialize_from_value(value, location).map(Setting::Set),
}
}
fn default() -> Option<Self> {
Some(Self::NotSet)
}
}
#[derive(Clone, Default, Debug, Serialize, PartialEq, Eq)]
pub struct Checked;
#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct Unchecked;
impl<E> DeserializeFromValue<E> for Unchecked
where
E: DeserializeError,
{
fn deserialize_from_value<V: deserr::IntoValue>(
_value: deserr::Value<V>,
_location: deserr::ValuePointerRef,
) -> Result<Self, E> {
unreachable!()
}
}
#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, DeserializeFromValue)]
#[serde(deny_unknown_fields)]
#[serde(rename_all = "camelCase")]
#[deserr(rename_all = camelCase, deny_unknown_fields)]
pub struct MinWordSizeTyposSetting {
#[cfg_attr(test, proptest(strategy = "test::setting_strategy()"))]
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
@ -47,9 +166,10 @@ pub struct MinWordSizeTyposSetting {
}
#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, DeserializeFromValue)]
#[serde(deny_unknown_fields)]
#[serde(rename_all = "camelCase")]
#[deserr(rename_all = camelCase, deny_unknown_fields)]
pub struct TypoSettings {
#[cfg_attr(test, proptest(strategy = "test::setting_strategy()"))]
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
@ -66,9 +186,10 @@ pub struct TypoSettings {
}
#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, DeserializeFromValue)]
#[serde(deny_unknown_fields)]
#[serde(rename_all = "camelCase")]
#[deserr(rename_all = camelCase, deny_unknown_fields)]
pub struct FacetingSettings {
#[cfg_attr(test, proptest(strategy = "test::setting_strategy()"))]
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
@ -76,9 +197,10 @@ pub struct FacetingSettings {
}
#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, DeserializeFromValue)]
#[serde(deny_unknown_fields)]
#[serde(rename_all = "camelCase")]
#[deserr(rename_all = camelCase, deny_unknown_fields)]
pub struct PaginationSettings {
#[cfg_attr(test, proptest(strategy = "test::setting_strategy()"))]
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
@ -88,10 +210,11 @@ pub struct PaginationSettings {
/// Holds all the settings for an index. `T` can either be `Checked` if they represents settings
/// whose validity is guaranteed, or `Unchecked` if they need to be validated. In the later case, a
/// call to `check` will return a `Settings<Checked>` from a `Settings<Unchecked>`.
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, DeserializeFromValue)]
#[serde(deny_unknown_fields)]
#[serde(rename_all = "camelCase")]
#[serde(bound(serialize = "T: Serialize", deserialize = "T: Deserialize<'static>"))]
#[deserr(rename_all = camelCase, deny_unknown_fields)]
#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
pub struct Settings<T> {
#[serde(