mirror of
https://github.com/meilisearch/meilisearch.git
synced 2024-11-22 10:07:40 +08:00
Refactor query parameter deserialisation logic
This commit is contained in:
parent
49ddaaef49
commit
9194508a0f
31
Cargo.lock
generated
31
Cargo.lock
generated
@ -1026,7 +1026,18 @@ dependencies = [
|
|||||||
name = "deserr"
|
name = "deserr"
|
||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"deserr-internal",
|
"deserr-internal 0.1.4",
|
||||||
|
"serde-cs",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deserr"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "86290491a2b5c21a1a5083da8dae831006761258fabd5617309c3eebc5f89468"
|
||||||
|
dependencies = [
|
||||||
|
"deserr-internal 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde-cs",
|
"serde-cs",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
]
|
]
|
||||||
@ -1041,6 +1052,18 @@ dependencies = [
|
|||||||
"syn 1.0.107",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deserr-internal"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7131de1c27581bc376a22166c9f570be91b76cb096be2f6aecf224c27bf7c49a"
|
||||||
|
dependencies = [
|
||||||
|
"convert_case 0.5.0",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deunicode"
|
name = "deunicode"
|
||||||
version = "1.3.3"
|
version = "1.3.3"
|
||||||
@ -2300,7 +2323,7 @@ dependencies = [
|
|||||||
"cargo_toml",
|
"cargo_toml",
|
||||||
"clap 4.0.32",
|
"clap 4.0.32",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"deserr",
|
"deserr 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"dump",
|
"dump",
|
||||||
"either",
|
"either",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
@ -2391,7 +2414,7 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"convert_case 0.6.0",
|
"convert_case 0.6.0",
|
||||||
"csv",
|
"csv",
|
||||||
"deserr",
|
"deserr 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"either",
|
"either",
|
||||||
"enum-iterator",
|
"enum-iterator",
|
||||||
"file-store",
|
"file-store",
|
||||||
@ -2451,7 +2474,7 @@ dependencies = [
|
|||||||
"concat-arrays",
|
"concat-arrays",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"csv",
|
"csv",
|
||||||
"deserr",
|
"deserr 0.1.4",
|
||||||
"either",
|
"either",
|
||||||
"filter-parser",
|
"filter-parser",
|
||||||
"flatten-serde-json",
|
"flatten-serde-json",
|
||||||
|
@ -3,7 +3,6 @@ pub mod error;
|
|||||||
mod store;
|
mod store;
|
||||||
|
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::ops::Deref;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@ -86,15 +85,13 @@ impl AuthController {
|
|||||||
key.indexes
|
key.indexes
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|index| {
|
.filter_map(|index| {
|
||||||
search_rules.get_index_search_rules(index.deref()).map(
|
search_rules.get_index_search_rules(&format!("{index}")).map(
|
||||||
|index_search_rules| {
|
|index_search_rules| (index.to_string(), Some(index_search_rules)),
|
||||||
(String::from(index), Some(index_search_rules))
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
),
|
),
|
||||||
None => SearchRules::Set(key.indexes.into_iter().map(String::from).collect()),
|
None => SearchRules::Set(key.indexes.into_iter().map(|x| x.to_string()).collect()),
|
||||||
};
|
};
|
||||||
} else if let Some(search_rules) = search_rules {
|
} else if let Some(search_rules) = search_rules {
|
||||||
filters.search_rules = search_rules;
|
filters.search_rules = search_rules;
|
||||||
|
@ -3,7 +3,6 @@ use std::cmp::Reverse;
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::convert::{TryFrom, TryInto};
|
use std::convert::{TryFrom, TryInto};
|
||||||
use std::fs::create_dir_all;
|
use std::fs::create_dir_all;
|
||||||
use std::ops::Deref;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::str;
|
use std::str;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -135,7 +134,7 @@ impl HeedAuthStore {
|
|||||||
for index in key.indexes.iter() {
|
for index in key.indexes.iter() {
|
||||||
db.put(
|
db.put(
|
||||||
&mut wtxn,
|
&mut wtxn,
|
||||||
&(&uid, &action, Some(index.deref().as_bytes())),
|
&(&uid, &action, Some(index.to_string().as_bytes())),
|
||||||
&key.expires_at,
|
&key.expires_at,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ actix-web = { version = "4.2.1", default-features = false }
|
|||||||
anyhow = "1.0.65"
|
anyhow = "1.0.65"
|
||||||
convert_case = "0.6.0"
|
convert_case = "0.6.0"
|
||||||
csv = "1.1.6"
|
csv = "1.1.6"
|
||||||
deserr = { path = "/Users/meilisearch/Documents/deserr" }
|
deserr = "0.1.4"
|
||||||
either = { version = "1.6.1", features = ["serde"] }
|
either = { version = "1.6.1", features = ["serde"] }
|
||||||
enum-iterator = "1.1.3"
|
enum-iterator = "1.1.3"
|
||||||
file-store = { path = "../file-store" }
|
file-store = { path = "../file-store" }
|
||||||
|
315
meilisearch-types/src/deserr/error_messages.rs
Normal file
315
meilisearch-types/src/deserr/error_messages.rs
Normal file
@ -0,0 +1,315 @@
|
|||||||
|
/*!
|
||||||
|
This module implements the error messages of deserialization errors.
|
||||||
|
|
||||||
|
We try to:
|
||||||
|
1. Give a human-readable description of where the error originated.
|
||||||
|
2. Use the correct terms depending on the format of the request (json/query param)
|
||||||
|
3. Categorise the type of the error (e.g. missing field, wrong value type, unexpected error, etc.)
|
||||||
|
*/
|
||||||
|
use deserr::{ErrorKind, IntoValue, ValueKind, ValuePointerRef};
|
||||||
|
|
||||||
|
use super::{DeserrJsonError, DeserrQueryParamError};
|
||||||
|
use crate::error::ErrorCode;
|
||||||
|
|
||||||
|
/// Return a description of the given location in a Json, preceded by the given article.
|
||||||
|
/// e.g. `at .key1[8].key2`. If the location is the origin, the given article will not be
|
||||||
|
/// included in the description.
|
||||||
|
pub fn location_json_description(location: ValuePointerRef, article: &str) -> String {
|
||||||
|
fn rec(location: ValuePointerRef) -> String {
|
||||||
|
match location {
|
||||||
|
ValuePointerRef::Origin => String::new(),
|
||||||
|
ValuePointerRef::Key { key, prev } => rec(*prev) + "." + key,
|
||||||
|
ValuePointerRef::Index { index, prev } => format!("{}[{index}]", rec(*prev)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match location {
|
||||||
|
ValuePointerRef::Origin => String::new(),
|
||||||
|
_ => {
|
||||||
|
format!("{article} `{}`", rec(location))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a description of the list of value kinds for a Json payload.
|
||||||
|
fn value_kinds_description_json(kinds: &[ValueKind]) -> String {
|
||||||
|
// Rank each value kind so that they can be sorted (and deduplicated)
|
||||||
|
// Having a predictable order helps with pattern matching
|
||||||
|
fn order(kind: &ValueKind) -> u8 {
|
||||||
|
match kind {
|
||||||
|
ValueKind::Null => 0,
|
||||||
|
ValueKind::Boolean => 1,
|
||||||
|
ValueKind::Integer => 2,
|
||||||
|
ValueKind::NegativeInteger => 3,
|
||||||
|
ValueKind::Float => 4,
|
||||||
|
ValueKind::String => 5,
|
||||||
|
ValueKind::Sequence => 6,
|
||||||
|
ValueKind::Map => 7,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Return a description of a single value kind, preceded by an article
|
||||||
|
fn single_description(kind: &ValueKind) -> &'static str {
|
||||||
|
match kind {
|
||||||
|
ValueKind::Null => "null",
|
||||||
|
ValueKind::Boolean => "a boolean",
|
||||||
|
ValueKind::Integer => "a positive integer",
|
||||||
|
ValueKind::NegativeInteger => "an integer",
|
||||||
|
ValueKind::Float => "a number",
|
||||||
|
ValueKind::String => "a string",
|
||||||
|
ValueKind::Sequence => "an array",
|
||||||
|
ValueKind::Map => "an object",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description_rec(kinds: &[ValueKind], count_items: &mut usize, message: &mut String) {
|
||||||
|
let (msg_part, rest): (_, &[ValueKind]) = match kinds {
|
||||||
|
[] => (String::new(), &[]),
|
||||||
|
[ValueKind::Integer | ValueKind::NegativeInteger, ValueKind::Float, rest @ ..] => {
|
||||||
|
("a number".to_owned(), rest)
|
||||||
|
}
|
||||||
|
[ValueKind::Integer, ValueKind::NegativeInteger, ValueKind::Float, rest @ ..] => {
|
||||||
|
("a number".to_owned(), rest)
|
||||||
|
}
|
||||||
|
[ValueKind::Integer, ValueKind::NegativeInteger, rest @ ..] => {
|
||||||
|
("an integer".to_owned(), rest)
|
||||||
|
}
|
||||||
|
[a] => (single_description(a).to_owned(), &[]),
|
||||||
|
[a, rest @ ..] => (single_description(a).to_owned(), rest),
|
||||||
|
};
|
||||||
|
|
||||||
|
if rest.is_empty() {
|
||||||
|
if *count_items == 0 {
|
||||||
|
message.push_str(&msg_part);
|
||||||
|
} else if *count_items == 1 {
|
||||||
|
message.push_str(&format!(" or {msg_part}"));
|
||||||
|
} else {
|
||||||
|
message.push_str(&format!(", or {msg_part}"));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if *count_items == 0 {
|
||||||
|
message.push_str(&msg_part);
|
||||||
|
} else {
|
||||||
|
message.push_str(&format!(", {msg_part}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
*count_items += 1;
|
||||||
|
description_rec(rest, count_items, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut kinds = kinds.to_owned();
|
||||||
|
kinds.sort_by_key(order);
|
||||||
|
kinds.dedup();
|
||||||
|
|
||||||
|
if kinds.is_empty() {
|
||||||
|
// Should not happen ideally
|
||||||
|
"a different value".to_owned()
|
||||||
|
} else {
|
||||||
|
let mut message = String::new();
|
||||||
|
description_rec(kinds.as_slice(), &mut 0, &mut message);
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the JSON string of the value preceded by a description of its kind
|
||||||
|
fn value_description_with_kind_json(v: &serde_json::Value) -> String {
|
||||||
|
match v.kind() {
|
||||||
|
ValueKind::Null => "null".to_owned(),
|
||||||
|
kind => {
|
||||||
|
format!(
|
||||||
|
"{}: `{}`",
|
||||||
|
value_kinds_description_json(&[kind]),
|
||||||
|
serde_json::to_string(v).unwrap()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: Default + ErrorCode> deserr::DeserializeError for DeserrJsonError<C> {
|
||||||
|
fn error<V: IntoValue>(
|
||||||
|
_self_: Option<Self>,
|
||||||
|
error: deserr::ErrorKind<V>,
|
||||||
|
location: ValuePointerRef,
|
||||||
|
) -> Result<Self, Self> {
|
||||||
|
let mut message = String::new();
|
||||||
|
|
||||||
|
message.push_str(&match error {
|
||||||
|
ErrorKind::IncorrectValueKind { actual, accepted } => {
|
||||||
|
let expected = value_kinds_description_json(accepted);
|
||||||
|
let received = value_description_with_kind_json(&serde_json::Value::from(actual));
|
||||||
|
|
||||||
|
let location = location_json_description(location, " at");
|
||||||
|
|
||||||
|
format!("Invalid value type{location}: expected {expected}, but found {received}")
|
||||||
|
}
|
||||||
|
ErrorKind::MissingField { field } => {
|
||||||
|
let location = location_json_description(location, " inside");
|
||||||
|
format!("Missing field `{field}`{location}")
|
||||||
|
}
|
||||||
|
ErrorKind::UnknownKey { key, accepted } => {
|
||||||
|
let location = location_json_description(location, " inside");
|
||||||
|
format!(
|
||||||
|
"Unknown field `{}`{location}: expected one of {}",
|
||||||
|
key,
|
||||||
|
accepted
|
||||||
|
.iter()
|
||||||
|
.map(|accepted| format!("`{}`", accepted))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", ")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ErrorKind::UnknownValue { value, accepted } => {
|
||||||
|
let location = location_json_description(location, " at");
|
||||||
|
format!(
|
||||||
|
"Unknown value `{}`{location}: expected one of {}",
|
||||||
|
value,
|
||||||
|
accepted
|
||||||
|
.iter()
|
||||||
|
.map(|accepted| format!("`{}`", accepted))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", "),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ErrorKind::Unexpected { msg } => {
|
||||||
|
let location = location_json_description(location, " at");
|
||||||
|
format!("Invalid value{location}: {msg}")
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Err(DeserrJsonError::new(message, C::default().error_code()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a description of the given location in query parameters, preceded by the
|
||||||
|
/// given article. e.g. `at key5[2]`. If the location is the origin, the given article
|
||||||
|
/// will not be included in the description.
|
||||||
|
pub fn location_query_param_description(location: ValuePointerRef, article: &str) -> String {
|
||||||
|
fn rec(location: ValuePointerRef) -> String {
|
||||||
|
match location {
|
||||||
|
ValuePointerRef::Origin => String::new(),
|
||||||
|
ValuePointerRef::Key { key, prev } => {
|
||||||
|
if matches!(prev, ValuePointerRef::Origin) {
|
||||||
|
key.to_owned()
|
||||||
|
} else {
|
||||||
|
rec(*prev) + "." + key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ValuePointerRef::Index { index, prev } => format!("{}[{index}]", rec(*prev)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match location {
|
||||||
|
ValuePointerRef::Origin => String::new(),
|
||||||
|
_ => {
|
||||||
|
format!("{article} `{}`", rec(location))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: Default + ErrorCode> deserr::DeserializeError for DeserrQueryParamError<C> {
|
||||||
|
fn error<V: IntoValue>(
|
||||||
|
_self_: Option<Self>,
|
||||||
|
error: deserr::ErrorKind<V>,
|
||||||
|
location: ValuePointerRef,
|
||||||
|
) -> Result<Self, Self> {
|
||||||
|
let mut message = String::new();
|
||||||
|
|
||||||
|
message.push_str(&match error {
|
||||||
|
ErrorKind::IncorrectValueKind { actual, accepted } => {
|
||||||
|
let expected = value_kinds_description_query_param(accepted);
|
||||||
|
let received = value_description_with_kind_query_param(actual);
|
||||||
|
|
||||||
|
let location = location_query_param_description(location, " for parameter");
|
||||||
|
|
||||||
|
format!("Invalid value type{location}: expected {expected}, but found {received}")
|
||||||
|
}
|
||||||
|
ErrorKind::MissingField { field } => {
|
||||||
|
let location = location_query_param_description(location, " inside");
|
||||||
|
format!("Missing parameter `{field}`{location}")
|
||||||
|
}
|
||||||
|
ErrorKind::UnknownKey { key, accepted } => {
|
||||||
|
let location = location_query_param_description(location, " inside");
|
||||||
|
format!(
|
||||||
|
"Unknown parameter `{}`{location}: expected one of {}",
|
||||||
|
key,
|
||||||
|
accepted
|
||||||
|
.iter()
|
||||||
|
.map(|accepted| format!("`{}`", accepted))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", ")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ErrorKind::UnknownValue { value, accepted } => {
|
||||||
|
let location = location_query_param_description(location, " for parameter");
|
||||||
|
format!(
|
||||||
|
"Unknown value `{}`{location}: expected one of {}",
|
||||||
|
value,
|
||||||
|
accepted
|
||||||
|
.iter()
|
||||||
|
.map(|accepted| format!("`{}`", accepted))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", "),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ErrorKind::Unexpected { msg } => {
|
||||||
|
let location = location_query_param_description(location, " in parameter");
|
||||||
|
format!("Invalid value{location}: {msg}")
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Err(DeserrQueryParamError::new(message, C::default().error_code()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a description of the list of value kinds for query parameters
|
||||||
|
/// Since query parameters are always treated as strings, we always return
|
||||||
|
/// "a string" for now.
|
||||||
|
fn value_kinds_description_query_param(_accepted: &[ValueKind]) -> String {
|
||||||
|
"a string".to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value_description_with_kind_query_param<V: IntoValue>(actual: deserr::Value<V>) -> String {
|
||||||
|
match actual {
|
||||||
|
deserr::Value::Null => "null".to_owned(),
|
||||||
|
deserr::Value::Boolean(x) => format!("a boolean: `{x}`"),
|
||||||
|
deserr::Value::Integer(x) => format!("an integer: `{x}`"),
|
||||||
|
deserr::Value::NegativeInteger(x) => {
|
||||||
|
format!("an integer: `{x}`")
|
||||||
|
}
|
||||||
|
deserr::Value::Float(x) => {
|
||||||
|
format!("a number: `{x}`")
|
||||||
|
}
|
||||||
|
deserr::Value::String(x) => {
|
||||||
|
format!("a string: `{x}`")
|
||||||
|
}
|
||||||
|
deserr::Value::Sequence(_) => "multiple values".to_owned(),
|
||||||
|
deserr::Value::Map(_) => "multiple parameters".to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use deserr::ValueKind;
|
||||||
|
|
||||||
|
use crate::deserr::error_messages::value_kinds_description_json;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_value_kinds_description_json() {
|
||||||
|
insta::assert_display_snapshot!(value_kinds_description_json(&[]), @"a different value");
|
||||||
|
|
||||||
|
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::Boolean]), @"a boolean");
|
||||||
|
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::Integer]), @"a positive integer");
|
||||||
|
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::NegativeInteger]), @"an integer");
|
||||||
|
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::Integer]), @"a positive integer");
|
||||||
|
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::String]), @"a string");
|
||||||
|
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::Sequence]), @"an array");
|
||||||
|
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::Map]), @"an object");
|
||||||
|
|
||||||
|
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::Integer, ValueKind::Boolean]), @"a boolean or a positive integer");
|
||||||
|
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::Null, ValueKind::Integer]), @"null or a positive integer");
|
||||||
|
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::Sequence, ValueKind::NegativeInteger]), @"an integer or an array");
|
||||||
|
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::Integer, ValueKind::Float]), @"a number");
|
||||||
|
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::Integer, ValueKind::Float, ValueKind::NegativeInteger]), @"a number");
|
||||||
|
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::Integer, ValueKind::Float, ValueKind::NegativeInteger, ValueKind::Null]), @"null or a number");
|
||||||
|
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::Boolean, ValueKind::Integer, ValueKind::Float, ValueKind::NegativeInteger, ValueKind::Null]), @"null, a boolean, or a number");
|
||||||
|
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::Null, ValueKind::Boolean, ValueKind::Integer, ValueKind::Float, ValueKind::NegativeInteger, ValueKind::Null]), @"null, a boolean, or a number");
|
||||||
|
}
|
||||||
|
}
|
134
meilisearch-types/src/deserr/mod.rs
Normal file
134
meilisearch-types/src/deserr/mod.rs
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
use std::convert::Infallible;
|
||||||
|
use std::fmt;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
use deserr::{DeserializeError, MergeWithError, ValuePointerRef};
|
||||||
|
|
||||||
|
use crate::error::deserr_codes::{self, *};
|
||||||
|
use crate::error::{
|
||||||
|
unwrap_any, Code, DeserrParseBoolError, DeserrParseIntError, ErrorCode, InvalidTaskDateError,
|
||||||
|
ParseOffsetDateTimeError,
|
||||||
|
};
|
||||||
|
use crate::index_uid::IndexUidFormatError;
|
||||||
|
use crate::tasks::{ParseTaskKindError, ParseTaskStatusError};
|
||||||
|
|
||||||
|
pub mod error_messages;
|
||||||
|
pub mod query_params;
|
||||||
|
|
||||||
|
/// Marker type for the Json format
|
||||||
|
pub struct DeserrJson;
|
||||||
|
/// Marker type for the Query Parameter format
|
||||||
|
pub struct DeserrQueryParam;
|
||||||
|
|
||||||
|
pub type DeserrJsonError<C = deserr_codes::BadRequest> = DeserrError<DeserrJson, C>;
|
||||||
|
pub type DeserrQueryParamError<C = deserr_codes::BadRequest> = DeserrError<DeserrQueryParam, C>;
|
||||||
|
|
||||||
|
/// A request deserialization error.
|
||||||
|
///
|
||||||
|
/// The first generic paramater is a marker type describing the format of the request: either json (e.g. [`DeserrJson`] or [`DeserrQueryParam`]).
|
||||||
|
/// The second generic parameter is the default error code for the deserialization error, in case it is not given.
|
||||||
|
pub struct DeserrError<Format, C: Default + ErrorCode> {
|
||||||
|
pub msg: String,
|
||||||
|
pub code: Code,
|
||||||
|
_phantom: PhantomData<(Format, C)>,
|
||||||
|
}
|
||||||
|
impl<Format, C: Default + ErrorCode> DeserrError<Format, C> {
|
||||||
|
pub fn new(msg: String, code: Code) -> Self {
|
||||||
|
Self { msg, code, _phantom: PhantomData }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<Format, C: Default + ErrorCode> std::fmt::Debug for DeserrError<Format, C> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("DeserrError").field("msg", &self.msg).field("code", &self.code).finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Format, C: Default + ErrorCode> std::fmt::Display for DeserrError<Format, C> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Format, C: Default + ErrorCode> std::error::Error for DeserrError<Format, C> {}
|
||||||
|
impl<Format, C: Default + ErrorCode> ErrorCode for DeserrError<Format, C> {
|
||||||
|
fn error_code(&self) -> Code {
|
||||||
|
self.code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For now, we don't accumulate errors. Only one deserialisation error is ever returned at a time.
|
||||||
|
impl<Format, C1: Default + ErrorCode, C2: Default + ErrorCode>
|
||||||
|
MergeWithError<DeserrError<Format, C2>> for DeserrError<Format, C1>
|
||||||
|
{
|
||||||
|
fn merge(
|
||||||
|
_self_: Option<Self>,
|
||||||
|
other: DeserrError<Format, C2>,
|
||||||
|
_merge_location: ValuePointerRef,
|
||||||
|
) -> Result<Self, Self> {
|
||||||
|
Err(DeserrError { msg: other.msg, code: other.code, _phantom: PhantomData })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Format, C: Default + ErrorCode> MergeWithError<Infallible> for DeserrError<Format, C> {
|
||||||
|
fn merge(
|
||||||
|
_self_: Option<Self>,
|
||||||
|
_other: Infallible,
|
||||||
|
_merge_location: ValuePointerRef,
|
||||||
|
) -> Result<Self, Self> {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement a convenience function to build a `missing_field` error
|
||||||
|
macro_rules! make_missing_field_convenience_builder {
|
||||||
|
($err_code:ident, $fn_name:ident) => {
|
||||||
|
impl DeserrJsonError<$err_code> {
|
||||||
|
pub fn $fn_name(field: &str, location: ValuePointerRef) -> Self {
|
||||||
|
let x = unwrap_any(Self::error::<Infallible>(
|
||||||
|
None,
|
||||||
|
deserr::ErrorKind::MissingField { field },
|
||||||
|
location,
|
||||||
|
));
|
||||||
|
Self { msg: x.msg, code: $err_code.error_code(), _phantom: PhantomData }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
make_missing_field_convenience_builder!(MissingIndexUid, missing_index_uid);
|
||||||
|
make_missing_field_convenience_builder!(MissingApiKeyActions, missing_api_key_actions);
|
||||||
|
make_missing_field_convenience_builder!(MissingApiKeyExpiresAt, missing_api_key_expires_at);
|
||||||
|
make_missing_field_convenience_builder!(MissingApiKeyIndexes, missing_api_key_indexes);
|
||||||
|
make_missing_field_convenience_builder!(MissingSwapIndexes, missing_swap_indexes);
|
||||||
|
|
||||||
|
// Integrate a sub-error into a [`DeserrError`] by taking its error message but using
|
||||||
|
// the default error code (C) from `Self`
|
||||||
|
macro_rules! merge_with_error_impl_take_error_message {
|
||||||
|
($err_type:ty) => {
|
||||||
|
impl<Format, C: Default + ErrorCode> MergeWithError<$err_type> for DeserrError<Format, C>
|
||||||
|
where
|
||||||
|
DeserrError<Format, C>: deserr::DeserializeError,
|
||||||
|
{
|
||||||
|
fn merge(
|
||||||
|
_self_: Option<Self>,
|
||||||
|
other: $err_type,
|
||||||
|
merge_location: ValuePointerRef,
|
||||||
|
) -> Result<Self, Self> {
|
||||||
|
DeserrError::<Format, C>::error::<Infallible>(
|
||||||
|
None,
|
||||||
|
deserr::ErrorKind::Unexpected { msg: other.to_string() },
|
||||||
|
merge_location,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// All these errors can be merged into a `DeserrError`
|
||||||
|
merge_with_error_impl_take_error_message!(DeserrParseIntError);
|
||||||
|
merge_with_error_impl_take_error_message!(DeserrParseBoolError);
|
||||||
|
merge_with_error_impl_take_error_message!(uuid::Error);
|
||||||
|
merge_with_error_impl_take_error_message!(InvalidTaskDateError);
|
||||||
|
merge_with_error_impl_take_error_message!(ParseOffsetDateTimeError);
|
||||||
|
merge_with_error_impl_take_error_message!(ParseTaskKindError);
|
||||||
|
merge_with_error_impl_take_error_message!(ParseTaskStatusError);
|
||||||
|
merge_with_error_impl_take_error_message!(IndexUidFormatError);
|
115
meilisearch-types/src/deserr/query_params.rs
Normal file
115
meilisearch-types/src/deserr/query_params.rs
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
/*!
|
||||||
|
This module provides helper traits, types, and functions to deserialize query parameters.
|
||||||
|
|
||||||
|
The source of the problem is that query parameters only give us a string to work with.
|
||||||
|
This means `deserr` is never given a sequence or numbers, and thus the default deserialization
|
||||||
|
code for common types such as `usize` or `Vec<T>` does not work. To work around it, we create a
|
||||||
|
wrapper type called `Param<T>`, which is deserialised using the `from_query_param` method of the trait
|
||||||
|
`FromQueryParameter`.
|
||||||
|
|
||||||
|
We also use other helper types such as `CS` (i.e. comma-separated) from `serde_cs` as well as
|
||||||
|
`StarOr`, `OptionStarOr`, and `OptionStarOrList`.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::convert::Infallible;
|
||||||
|
use std::ops::Deref;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use deserr::{DeserializeError, DeserializeFromValue, MergeWithError, ValueKind};
|
||||||
|
|
||||||
|
use super::{DeserrParseBoolError, DeserrParseIntError};
|
||||||
|
use crate::error::unwrap_any;
|
||||||
|
use crate::index_uid::IndexUid;
|
||||||
|
use crate::tasks::{Kind, Status};
|
||||||
|
|
||||||
|
/// A wrapper type indicating that the inner value should be
|
||||||
|
/// deserialised from a query parameter string.
|
||||||
|
///
|
||||||
|
/// Note that if the field is optional, it is better to use
|
||||||
|
/// `Option<Param<T>>` instead of `Param<Option<T>>`.
|
||||||
|
#[derive(Default, Debug, Clone, Copy)]
|
||||||
|
pub struct Param<T>(pub T);
|
||||||
|
|
||||||
|
impl<T> Deref for Param<T> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, E> DeserializeFromValue<E> for Param<T>
|
||||||
|
where
|
||||||
|
E: DeserializeError + MergeWithError<T::Err>,
|
||||||
|
T: FromQueryParameter,
|
||||||
|
{
|
||||||
|
fn deserialize_from_value<V: deserr::IntoValue>(
|
||||||
|
value: deserr::Value<V>,
|
||||||
|
location: deserr::ValuePointerRef,
|
||||||
|
) -> Result<Self, E> {
|
||||||
|
match value {
|
||||||
|
deserr::Value::String(s) => match T::from_query_param(&s) {
|
||||||
|
Ok(x) => Ok(Param(x)),
|
||||||
|
Err(e) => Err(unwrap_any(E::merge(None, e, location))),
|
||||||
|
},
|
||||||
|
_ => Err(unwrap_any(E::error(
|
||||||
|
None,
|
||||||
|
deserr::ErrorKind::IncorrectValueKind {
|
||||||
|
actual: value,
|
||||||
|
accepted: &[ValueKind::String],
|
||||||
|
},
|
||||||
|
location,
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a value from a query parameter string.
|
||||||
|
///
|
||||||
|
/// This trait is functionally equivalent to `FromStr`.
|
||||||
|
/// Having a separate trait trait allows us to return better
|
||||||
|
/// deserializatio error messages.
|
||||||
|
pub trait FromQueryParameter: Sized {
|
||||||
|
type Err;
|
||||||
|
fn from_query_param(p: &str) -> Result<Self, Self::Err>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implement `FromQueryParameter` for the given type using its `FromStr`
|
||||||
|
/// trait implementation.
|
||||||
|
macro_rules! impl_from_query_param_from_str {
|
||||||
|
($type:ty) => {
|
||||||
|
impl FromQueryParameter for $type {
|
||||||
|
type Err = <$type as FromStr>::Err;
|
||||||
|
fn from_query_param(p: &str) -> Result<Self, Self::Err> {
|
||||||
|
p.parse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
impl_from_query_param_from_str!(Kind);
|
||||||
|
impl_from_query_param_from_str!(Status);
|
||||||
|
impl_from_query_param_from_str!(IndexUid);
|
||||||
|
|
||||||
|
/// Implement `FromQueryParameter` for the given type using its `FromStr`
|
||||||
|
/// trait implementation, replacing the returned error with a struct
|
||||||
|
/// that wraps the original query parameter.
|
||||||
|
macro_rules! impl_from_query_param_wrap_original_value_in_error {
|
||||||
|
($type:ty, $err_type:path) => {
|
||||||
|
impl FromQueryParameter for $type {
|
||||||
|
type Err = $err_type;
|
||||||
|
fn from_query_param(p: &str) -> Result<Self, Self::Err> {
|
||||||
|
p.parse().map_err(|_| $err_type(p.to_owned()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
impl_from_query_param_wrap_original_value_in_error!(usize, DeserrParseIntError);
|
||||||
|
impl_from_query_param_wrap_original_value_in_error!(u32, DeserrParseIntError);
|
||||||
|
impl_from_query_param_wrap_original_value_in_error!(bool, DeserrParseBoolError);
|
||||||
|
|
||||||
|
impl FromQueryParameter for String {
|
||||||
|
type Err = Infallible;
|
||||||
|
fn from_query_param(p: &str) -> Result<Self, Infallible> {
|
||||||
|
Ok(p.to_owned())
|
||||||
|
}
|
||||||
|
}
|
@ -1,30 +1,17 @@
|
|||||||
use std::convert::Infallible;
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::str::FromStr;
|
|
||||||
use std::{fmt, io};
|
use std::{fmt, io};
|
||||||
|
|
||||||
use actix_web::http::StatusCode;
|
use actix_web::http::StatusCode;
|
||||||
use actix_web::{self as aweb, HttpResponseBuilder};
|
use actix_web::{self as aweb, HttpResponseBuilder};
|
||||||
use aweb::rt::task::JoinError;
|
use aweb::rt::task::JoinError;
|
||||||
use convert_case::Casing;
|
use convert_case::Casing;
|
||||||
use deserr::{DeserializeError, ErrorKind, IntoValue, MergeWithError, ValueKind, ValuePointerRef};
|
|
||||||
use milli::heed::{Error as HeedError, MdbError};
|
use milli::heed::{Error as HeedError, MdbError};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_cs::vec::CS;
|
|
||||||
|
|
||||||
use crate::star_or::StarOr;
|
|
||||||
|
|
||||||
use self::deserr_codes::{
|
|
||||||
InvalidSwapIndexes, MissingApiKeyActions, MissingApiKeyExpiresAt, MissingApiKeyIndexes,
|
|
||||||
MissingIndexUid, MissingSwapIndexes,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[cfg_attr(feature = "test-traits", derive(proptest_derive::Arbitrary))]
|
#[cfg_attr(feature = "test-traits", derive(proptest_derive::Arbitrary))]
|
||||||
pub struct ResponseError {
|
pub struct ResponseError {
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
#[cfg_attr(feature = "test-traits", proptest(strategy = "strategy::status_code_strategy()"))]
|
|
||||||
code: StatusCode,
|
code: StatusCode,
|
||||||
message: String,
|
message: String,
|
||||||
#[serde(rename = "code")]
|
#[serde(rename = "code")]
|
||||||
@ -43,7 +30,7 @@ impl ResponseError {
|
|||||||
Self {
|
Self {
|
||||||
code: code.http(),
|
code: code.http(),
|
||||||
message,
|
message,
|
||||||
error_code: code.err_code().error_name,
|
error_code: code.name(),
|
||||||
error_type: code.type_(),
|
error_type: code.type_(),
|
||||||
error_link: code.url(),
|
error_link: code.url(),
|
||||||
}
|
}
|
||||||
@ -104,9 +91,9 @@ pub trait ErrorCode {
|
|||||||
|
|
||||||
#[allow(clippy::enum_variant_names)]
|
#[allow(clippy::enum_variant_names)]
|
||||||
enum ErrorType {
|
enum ErrorType {
|
||||||
InternalError,
|
Internal,
|
||||||
InvalidRequestError,
|
InvalidRequest,
|
||||||
AuthenticationError,
|
Auth,
|
||||||
System,
|
System,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,14 +102,24 @@ impl fmt::Display for ErrorType {
|
|||||||
use ErrorType::*;
|
use ErrorType::*;
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
InternalError => write!(f, "internal"),
|
Internal => write!(f, "internal"),
|
||||||
InvalidRequestError => write!(f, "invalid_request"),
|
InvalidRequest => write!(f, "invalid_request"),
|
||||||
AuthenticationError => write!(f, "auth"),
|
Auth => write!(f, "auth"),
|
||||||
System => write!(f, "system"),
|
System => write!(f, "system"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Implement all the error codes.
|
||||||
|
///
|
||||||
|
/// 1. Make an enum `Code` where each error code is a variant
|
||||||
|
/// 2. Implement the `http`, `name`, and `type_` method on the enum
|
||||||
|
/// 3. Make a unit type for each error code in the module `deserr_codes`.
|
||||||
|
///
|
||||||
|
/// The unit type's purpose is to be used as a marker type parameter, e.g.
|
||||||
|
/// `DeserrJsonError<MyErrorCode>`. It implements `Default` and `ErrorCode`,
|
||||||
|
/// so we can get a value of the `Code` enum with the correct variant by calling
|
||||||
|
/// `MyErrorCode::default().error_code()`.
|
||||||
macro_rules! make_error_codes {
|
macro_rules! make_error_codes {
|
||||||
($($code_ident:ident, $err_type:ident, $status:ident);*) => {
|
($($code_ident:ident, $err_type:ident, $status:ident);*) => {
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
@ -130,29 +127,31 @@ macro_rules! make_error_codes {
|
|||||||
$($code_ident),*
|
$($code_ident),*
|
||||||
}
|
}
|
||||||
impl Code {
|
impl Code {
|
||||||
/// associate a `Code` variant to the actual ErrCode
|
|
||||||
fn err_code(&self) -> ErrCode {
|
|
||||||
match self {
|
|
||||||
$(
|
|
||||||
Code::$code_ident => {
|
|
||||||
ErrCode::$err_type( stringify!($code_ident).to_case(convert_case::Case::Snake), StatusCode::$status)
|
|
||||||
}
|
|
||||||
)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// return the HTTP status code associated with the `Code`
|
/// return the HTTP status code associated with the `Code`
|
||||||
fn http(&self) -> StatusCode {
|
fn http(&self) -> StatusCode {
|
||||||
self.err_code().status_code
|
match self {
|
||||||
|
$(
|
||||||
|
Code::$code_ident => StatusCode::$status
|
||||||
|
),*
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// return error name, used as error code
|
/// return error name, used as error code
|
||||||
fn name(&self) -> String {
|
fn name(&self) -> String {
|
||||||
self.err_code().error_name.to_string()
|
match self {
|
||||||
|
$(
|
||||||
|
Code::$code_ident => stringify!($code_ident).to_case(convert_case::Case::Snake)
|
||||||
|
),*
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// return the error type
|
/// return the error type
|
||||||
fn type_(&self) -> String {
|
fn type_(&self) -> String {
|
||||||
self.err_code().error_type.to_string()
|
match self {
|
||||||
|
$(
|
||||||
|
Code::$code_ident => ErrorType::$err_type.to_string()
|
||||||
|
),*
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// return the doc url associated with the error
|
/// return the doc url associated with the error
|
||||||
@ -177,144 +176,121 @@ macro_rules! make_error_codes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// An exhaustive list of all the error codes used by meilisearch.
|
||||||
make_error_codes! {
|
make_error_codes! {
|
||||||
ApiKeyAlreadyExists , invalid , CONFLICT ;
|
ApiKeyAlreadyExists , InvalidRequest , CONFLICT ;
|
||||||
ApiKeyNotFound , invalid , NOT_FOUND ;
|
ApiKeyNotFound , InvalidRequest , NOT_FOUND ;
|
||||||
BadParameter , invalid , BAD_REQUEST;
|
BadParameter , InvalidRequest , BAD_REQUEST;
|
||||||
BadRequest , invalid , BAD_REQUEST;
|
BadRequest , InvalidRequest , BAD_REQUEST;
|
||||||
DatabaseSizeLimitReached , internal , INTERNAL_SERVER_ERROR;
|
DatabaseSizeLimitReached , Internal , INTERNAL_SERVER_ERROR;
|
||||||
DocumentNotFound , invalid , NOT_FOUND;
|
DocumentNotFound , InvalidRequest , NOT_FOUND;
|
||||||
DumpAlreadyProcessing , invalid , CONFLICT;
|
DumpAlreadyProcessing , InvalidRequest , CONFLICT;
|
||||||
DumpNotFound , invalid , NOT_FOUND;
|
DumpNotFound , InvalidRequest , NOT_FOUND;
|
||||||
DumpProcessFailed , internal , INTERNAL_SERVER_ERROR;
|
DumpProcessFailed , Internal , INTERNAL_SERVER_ERROR;
|
||||||
DuplicateIndexFound , invalid , BAD_REQUEST;
|
DuplicateIndexFound , InvalidRequest , BAD_REQUEST;
|
||||||
ImmutableApiKeyUid , invalid , BAD_REQUEST;
|
ImmutableApiKeyActions , InvalidRequest , BAD_REQUEST;
|
||||||
ImmutableApiKeyKey , invalid , BAD_REQUEST;
|
ImmutableApiKeyCreatedAt , InvalidRequest , BAD_REQUEST;
|
||||||
ImmutableApiKeyActions , invalid , BAD_REQUEST;
|
ImmutableApiKeyExpiresAt , InvalidRequest , BAD_REQUEST;
|
||||||
ImmutableApiKeyIndexes , invalid , BAD_REQUEST;
|
ImmutableApiKeyIndexes , InvalidRequest , BAD_REQUEST;
|
||||||
ImmutableApiKeyExpiresAt , invalid , BAD_REQUEST;
|
ImmutableApiKeyKey , InvalidRequest , BAD_REQUEST;
|
||||||
ImmutableApiKeyCreatedAt , invalid , BAD_REQUEST;
|
ImmutableApiKeyUid , InvalidRequest , BAD_REQUEST;
|
||||||
ImmutableApiKeyUpdatedAt , invalid , BAD_REQUEST;
|
ImmutableApiKeyUpdatedAt , InvalidRequest , BAD_REQUEST;
|
||||||
ImmutableIndexUid , invalid , BAD_REQUEST;
|
ImmutableIndexCreatedAt , InvalidRequest , BAD_REQUEST;
|
||||||
ImmutableIndexCreatedAt , invalid , BAD_REQUEST;
|
ImmutableIndexUid , InvalidRequest , BAD_REQUEST;
|
||||||
ImmutableIndexUpdatedAt , invalid , BAD_REQUEST;
|
ImmutableIndexUpdatedAt , InvalidRequest , BAD_REQUEST;
|
||||||
IndexAlreadyExists , invalid , CONFLICT ;
|
IndexAlreadyExists , InvalidRequest , CONFLICT ;
|
||||||
IndexCreationFailed , internal , INTERNAL_SERVER_ERROR;
|
IndexCreationFailed , Internal , INTERNAL_SERVER_ERROR;
|
||||||
IndexNotFound , invalid , NOT_FOUND;
|
IndexNotFound , InvalidRequest , NOT_FOUND;
|
||||||
IndexPrimaryKeyAlreadyExists , invalid , BAD_REQUEST ;
|
IndexPrimaryKeyAlreadyExists , InvalidRequest , BAD_REQUEST ;
|
||||||
IndexPrimaryKeyNoCandidateFound , invalid , BAD_REQUEST ;
|
IndexPrimaryKeyMultipleCandidatesFound, InvalidRequest , BAD_REQUEST;
|
||||||
IndexPrimaryKeyMultipleCandidatesFound, invalid , BAD_REQUEST;
|
IndexPrimaryKeyNoCandidateFound , InvalidRequest , BAD_REQUEST ;
|
||||||
Internal , internal , INTERNAL_SERVER_ERROR ;
|
Internal , Internal , INTERNAL_SERVER_ERROR ;
|
||||||
InvalidApiKeyActions , invalid , BAD_REQUEST ;
|
InvalidApiKey , Auth , FORBIDDEN ;
|
||||||
InvalidApiKeyDescription , invalid , BAD_REQUEST ;
|
InvalidApiKeyActions , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidApiKeyExpiresAt , invalid , BAD_REQUEST ;
|
InvalidApiKeyDescription , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidApiKeyIndexes , invalid , BAD_REQUEST ;
|
InvalidApiKeyExpiresAt , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidApiKeyLimit , invalid , BAD_REQUEST ;
|
InvalidApiKeyIndexes , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidApiKeyName , invalid , BAD_REQUEST ;
|
InvalidApiKeyLimit , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidApiKeyOffset , invalid , BAD_REQUEST ;
|
InvalidApiKeyName , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidApiKeyUid , invalid , BAD_REQUEST ;
|
InvalidApiKeyOffset , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidApiKey , authentication, FORBIDDEN ;
|
InvalidApiKeyUid , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidContentType , invalid , UNSUPPORTED_MEDIA_TYPE ;
|
InvalidContentType , InvalidRequest , UNSUPPORTED_MEDIA_TYPE ;
|
||||||
InvalidDocumentFields , invalid , BAD_REQUEST ;
|
InvalidDocumentFields , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidDocumentGeoField , invalid , BAD_REQUEST ;
|
InvalidDocumentGeoField , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidDocumentId , invalid , BAD_REQUEST ;
|
InvalidDocumentId , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidDocumentLimit , invalid , BAD_REQUEST ;
|
InvalidDocumentLimit , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidDocumentOffset , invalid , BAD_REQUEST ;
|
InvalidDocumentOffset , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidIndexLimit , invalid , BAD_REQUEST ;
|
InvalidIndexLimit , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidIndexOffset , invalid , BAD_REQUEST ;
|
InvalidIndexOffset , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidIndexPrimaryKey , invalid , BAD_REQUEST ;
|
InvalidIndexPrimaryKey , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidIndexUid , invalid , BAD_REQUEST ;
|
InvalidIndexUid , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidMinWordLengthForTypo , invalid , BAD_REQUEST ;
|
InvalidMinWordLengthForTypo , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSearchAttributesToCrop , invalid , BAD_REQUEST ;
|
InvalidSearchAttributesToCrop , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSearchAttributesToHighlight , invalid , BAD_REQUEST ;
|
InvalidSearchAttributesToHighlight , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSearchAttributesToRetrieve , invalid , BAD_REQUEST ;
|
InvalidSearchAttributesToRetrieve , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSearchCropLength , invalid , BAD_REQUEST ;
|
InvalidSearchCropLength , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSearchCropMarker , invalid , BAD_REQUEST ;
|
InvalidSearchCropMarker , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSearchFacets , invalid , BAD_REQUEST ;
|
InvalidSearchFacets , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSearchFilter , invalid , BAD_REQUEST ;
|
InvalidSearchFilter , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSearchHighlightPostTag , invalid , BAD_REQUEST ;
|
InvalidSearchHighlightPostTag , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSearchHighlightPreTag , invalid , BAD_REQUEST ;
|
InvalidSearchHighlightPreTag , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSearchHitsPerPage , invalid , BAD_REQUEST ;
|
InvalidSearchHitsPerPage , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSearchLimit , invalid , BAD_REQUEST ;
|
InvalidSearchLimit , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSearchMatchingStrategy , invalid , BAD_REQUEST ;
|
InvalidSearchMatchingStrategy , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSearchOffset , invalid , BAD_REQUEST ;
|
InvalidSearchOffset , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSearchPage , invalid , BAD_REQUEST ;
|
InvalidSearchPage , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSearchQ , invalid , BAD_REQUEST ;
|
InvalidSearchQ , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSearchShowMatchesPosition , invalid , BAD_REQUEST ;
|
InvalidSearchShowMatchesPosition , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSearchSort , invalid , BAD_REQUEST ;
|
InvalidSearchSort , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSettingsDisplayedAttributes , invalid , BAD_REQUEST ;
|
InvalidSettingsDisplayedAttributes , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSettingsDistinctAttribute , invalid , BAD_REQUEST ;
|
InvalidSettingsDistinctAttribute , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSettingsFaceting , invalid , BAD_REQUEST ;
|
InvalidSettingsFaceting , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSettingsFilterableAttributes , invalid , BAD_REQUEST ;
|
InvalidSettingsFilterableAttributes , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSettingsPagination , invalid , BAD_REQUEST ;
|
InvalidSettingsPagination , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSettingsRankingRules , invalid , BAD_REQUEST ;
|
InvalidSettingsRankingRules , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSettingsSearchableAttributes , invalid , BAD_REQUEST ;
|
InvalidSettingsSearchableAttributes , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSettingsSortableAttributes , invalid , BAD_REQUEST ;
|
InvalidSettingsSortableAttributes , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSettingsStopWords , invalid , BAD_REQUEST ;
|
InvalidSettingsStopWords , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSettingsSynonyms , invalid , BAD_REQUEST ;
|
InvalidSettingsSynonyms , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSettingsTypoTolerance , invalid , BAD_REQUEST ;
|
InvalidSettingsTypoTolerance , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidState , internal , INTERNAL_SERVER_ERROR ;
|
InvalidState , Internal , INTERNAL_SERVER_ERROR ;
|
||||||
InvalidStoreFile , internal , INTERNAL_SERVER_ERROR ;
|
InvalidStoreFile , Internal , INTERNAL_SERVER_ERROR ;
|
||||||
InvalidSwapDuplicateIndexFound , invalid , BAD_REQUEST ;
|
InvalidSwapDuplicateIndexFound , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSwapIndexes , invalid , BAD_REQUEST ;
|
InvalidSwapIndexes , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidTaskAfterEnqueuedAt , invalid , BAD_REQUEST ;
|
InvalidTaskAfterEnqueuedAt , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidTaskAfterFinishedAt , invalid , BAD_REQUEST ;
|
InvalidTaskAfterFinishedAt , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidTaskAfterStartedAt , invalid , BAD_REQUEST ;
|
InvalidTaskAfterStartedAt , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidTaskBeforeEnqueuedAt , invalid , BAD_REQUEST ;
|
InvalidTaskBeforeEnqueuedAt , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidTaskBeforeFinishedAt , invalid , BAD_REQUEST ;
|
InvalidTaskBeforeFinishedAt , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidTaskBeforeStartedAt , invalid , BAD_REQUEST ;
|
InvalidTaskBeforeStartedAt , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidTaskCanceledBy , invalid , BAD_REQUEST ;
|
InvalidTaskCanceledBy , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidTaskFrom , invalid , BAD_REQUEST ;
|
InvalidTaskFrom , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidTaskLimit , invalid , BAD_REQUEST ;
|
InvalidTaskLimit , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidTaskStatuses , invalid , BAD_REQUEST ;
|
InvalidTaskStatuses , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidTaskTypes , invalid , BAD_REQUEST ;
|
InvalidTaskTypes , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidTaskUids , invalid , BAD_REQUEST ;
|
InvalidTaskUids , InvalidRequest , BAD_REQUEST ;
|
||||||
IoError , system , UNPROCESSABLE_ENTITY;
|
IoError , System , UNPROCESSABLE_ENTITY;
|
||||||
MalformedPayload , invalid , BAD_REQUEST ;
|
MalformedPayload , InvalidRequest , BAD_REQUEST ;
|
||||||
MaxFieldsLimitExceeded , invalid , BAD_REQUEST ;
|
MaxFieldsLimitExceeded , InvalidRequest , BAD_REQUEST ;
|
||||||
MissingApiKeyActions , invalid , BAD_REQUEST ;
|
MissingApiKeyActions , InvalidRequest , BAD_REQUEST ;
|
||||||
MissingApiKeyExpiresAt , invalid , BAD_REQUEST ;
|
MissingApiKeyExpiresAt , InvalidRequest , BAD_REQUEST ;
|
||||||
MissingApiKeyIndexes , invalid , BAD_REQUEST ;
|
MissingApiKeyIndexes , InvalidRequest , BAD_REQUEST ;
|
||||||
MissingAuthorizationHeader , authentication, UNAUTHORIZED ;
|
MissingAuthorizationHeader , Auth , UNAUTHORIZED ;
|
||||||
MissingContentType , invalid , UNSUPPORTED_MEDIA_TYPE ;
|
MissingContentType , InvalidRequest , UNSUPPORTED_MEDIA_TYPE ;
|
||||||
MissingDocumentId , invalid , BAD_REQUEST ;
|
MissingDocumentId , InvalidRequest , BAD_REQUEST ;
|
||||||
MissingIndexUid , invalid , BAD_REQUEST ;
|
MissingIndexUid , InvalidRequest , BAD_REQUEST ;
|
||||||
MissingMasterKey , authentication, UNAUTHORIZED ;
|
MissingMasterKey , Auth , UNAUTHORIZED ;
|
||||||
MissingPayload , invalid , BAD_REQUEST ;
|
MissingPayload , InvalidRequest , BAD_REQUEST ;
|
||||||
MissingSwapIndexes , invalid , BAD_REQUEST ;
|
MissingSwapIndexes , InvalidRequest , BAD_REQUEST ;
|
||||||
MissingTaskFilters , invalid , BAD_REQUEST ;
|
MissingTaskFilters , InvalidRequest , BAD_REQUEST ;
|
||||||
NoSpaceLeftOnDevice , system , UNPROCESSABLE_ENTITY;
|
NoSpaceLeftOnDevice , System , UNPROCESSABLE_ENTITY;
|
||||||
PayloadTooLarge , invalid , PAYLOAD_TOO_LARGE ;
|
PayloadTooLarge , InvalidRequest , PAYLOAD_TOO_LARGE ;
|
||||||
TaskNotFound , invalid , NOT_FOUND ;
|
TaskNotFound , InvalidRequest , NOT_FOUND ;
|
||||||
TooManyOpenFiles , system , UNPROCESSABLE_ENTITY ;
|
TooManyOpenFiles , System , UNPROCESSABLE_ENTITY ;
|
||||||
UnretrievableDocument , internal , BAD_REQUEST ;
|
UnretrievableDocument , Internal , BAD_REQUEST ;
|
||||||
UnretrievableErrorCode , invalid , BAD_REQUEST ;
|
UnretrievableErrorCode , InvalidRequest , BAD_REQUEST ;
|
||||||
UnsupportedMediaType , invalid , UNSUPPORTED_MEDIA_TYPE
|
UnsupportedMediaType , InvalidRequest , UNSUPPORTED_MEDIA_TYPE
|
||||||
}
|
|
||||||
|
|
||||||
/// Internal structure providing a convenient way to create error codes
|
|
||||||
struct ErrCode {
|
|
||||||
status_code: StatusCode,
|
|
||||||
error_type: ErrorType,
|
|
||||||
error_name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ErrCode {
|
|
||||||
fn authentication(error_name: String, status_code: StatusCode) -> ErrCode {
|
|
||||||
ErrCode { status_code, error_name, error_type: ErrorType::AuthenticationError }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn internal(error_name: String, status_code: StatusCode) -> ErrCode {
|
|
||||||
ErrCode { status_code, error_name, error_type: ErrorType::InternalError }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn invalid(error_name: String, status_code: StatusCode) -> ErrCode {
|
|
||||||
ErrCode { status_code, error_name, error_type: ErrorType::InvalidRequestError }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn system(error_name: String, status_code: StatusCode) -> ErrCode {
|
|
||||||
ErrCode { status_code, error_name, error_type: ErrorType::System }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ErrorCode for JoinError {
|
impl ErrorCode for JoinError {
|
||||||
@ -409,6 +385,7 @@ impl ErrorCode for io::Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Unwrap a result, either its Ok or Err value.
|
||||||
pub fn unwrap_any<T>(any: Result<T, T>) -> T {
|
pub fn unwrap_any<T>(any: Result<T, T>) -> T {
|
||||||
match any {
|
match any {
|
||||||
Ok(any) => any,
|
Ok(any) => any,
|
||||||
@ -416,501 +393,43 @@ pub fn unwrap_any<T>(any: Result<T, T>) -> T {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "test-traits")]
|
/// Deserialization when `deserr` cannot parse an API key date.
|
||||||
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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct DeserrJson;
|
|
||||||
pub struct DeserrQueryParam;
|
|
||||||
|
|
||||||
pub type DeserrJsonError<C = deserr_codes::BadRequest> = DeserrError<DeserrJson, C>;
|
|
||||||
pub type DeserrQueryParamError<C = deserr_codes::BadRequest> = DeserrError<DeserrQueryParam, C>;
|
|
||||||
|
|
||||||
pub struct DeserrError<Format, C: Default + ErrorCode> {
|
|
||||||
pub msg: String,
|
|
||||||
pub code: Code,
|
|
||||||
_phantom: PhantomData<(Format, C)>,
|
|
||||||
}
|
|
||||||
impl<Format, C: Default + ErrorCode> std::fmt::Debug for DeserrError<Format, C> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.debug_struct("DeserrError").field("msg", &self.msg).field("code", &self.code).finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Format, C: Default + ErrorCode> std::fmt::Display for DeserrError<Format, C> {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}", self.msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Format, C: Default + ErrorCode> std::error::Error for DeserrError<Format, C> {}
|
|
||||||
impl<Format, C: Default + ErrorCode> ErrorCode for DeserrError<Format, C> {
|
|
||||||
fn error_code(&self) -> Code {
|
|
||||||
self.code
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Format, C1: Default + ErrorCode, C2: Default + ErrorCode>
|
|
||||||
MergeWithError<DeserrError<Format, C2>> for DeserrError<Format, C1>
|
|
||||||
{
|
|
||||||
fn merge(
|
|
||||||
_self_: Option<Self>,
|
|
||||||
other: DeserrError<Format, C2>,
|
|
||||||
_merge_location: ValuePointerRef,
|
|
||||||
) -> Result<Self, Self> {
|
|
||||||
Err(DeserrError { msg: other.msg, code: other.code, _phantom: PhantomData })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DeserrJsonError<MissingIndexUid> {
|
|
||||||
pub fn missing_index_uid(field: &str, location: ValuePointerRef) -> Self {
|
|
||||||
let x = unwrap_any(Self::error::<Infallible>(
|
|
||||||
None,
|
|
||||||
deserr::ErrorKind::MissingField { field },
|
|
||||||
location,
|
|
||||||
));
|
|
||||||
Self { msg: x.msg, code: MissingIndexUid.error_code(), _phantom: PhantomData }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl DeserrJsonError<MissingApiKeyActions> {
|
|
||||||
pub fn missing_api_key_actions(field: &str, location: ValuePointerRef) -> Self {
|
|
||||||
let x = unwrap_any(Self::error::<Infallible>(
|
|
||||||
None,
|
|
||||||
deserr::ErrorKind::MissingField { field },
|
|
||||||
location,
|
|
||||||
));
|
|
||||||
Self { msg: x.msg, code: MissingApiKeyActions.error_code(), _phantom: PhantomData }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl DeserrJsonError<MissingApiKeyExpiresAt> {
|
|
||||||
pub fn missing_api_key_expires_at(field: &str, location: ValuePointerRef) -> Self {
|
|
||||||
let x = unwrap_any(Self::error::<Infallible>(
|
|
||||||
None,
|
|
||||||
deserr::ErrorKind::MissingField { field },
|
|
||||||
location,
|
|
||||||
));
|
|
||||||
Self { msg: x.msg, code: MissingApiKeyExpiresAt.error_code(), _phantom: PhantomData }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl DeserrJsonError<MissingApiKeyIndexes> {
|
|
||||||
pub fn missing_api_key_indexes(field: &str, location: ValuePointerRef) -> Self {
|
|
||||||
let x = unwrap_any(Self::error::<Infallible>(
|
|
||||||
None,
|
|
||||||
deserr::ErrorKind::MissingField { field },
|
|
||||||
location,
|
|
||||||
));
|
|
||||||
Self { msg: x.msg, code: MissingApiKeyIndexes.error_code(), _phantom: PhantomData }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DeserrJsonError<InvalidSwapIndexes> {
|
|
||||||
pub fn missing_swap_indexes_indexes(field: &str, location: ValuePointerRef) -> Self {
|
|
||||||
let x = unwrap_any(Self::error::<Infallible>(
|
|
||||||
None,
|
|
||||||
deserr::ErrorKind::MissingField { field },
|
|
||||||
location,
|
|
||||||
));
|
|
||||||
Self { msg: x.msg, code: MissingSwapIndexes.error_code(), _phantom: PhantomData }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the error happened in the root, then an empty string is returned.
|
|
||||||
pub fn location_json_description(location: ValuePointerRef, article: &str) -> String {
|
|
||||||
fn rec(location: ValuePointerRef) -> String {
|
|
||||||
match location {
|
|
||||||
ValuePointerRef::Origin => String::new(),
|
|
||||||
ValuePointerRef::Key { key, prev } => rec(*prev) + "." + key,
|
|
||||||
ValuePointerRef::Index { index, prev } => format!("{}[{index}]", rec(*prev)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
match location {
|
|
||||||
ValuePointerRef::Origin => String::new(),
|
|
||||||
_ => {
|
|
||||||
format!("{article} `{}`", rec(location))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn value_kinds_description_json(kinds: &[ValueKind]) -> String {
|
|
||||||
fn order(kind: &ValueKind) -> u8 {
|
|
||||||
match kind {
|
|
||||||
ValueKind::Null => 0,
|
|
||||||
ValueKind::Boolean => 1,
|
|
||||||
ValueKind::Integer => 2,
|
|
||||||
ValueKind::NegativeInteger => 3,
|
|
||||||
ValueKind::Float => 4,
|
|
||||||
ValueKind::String => 5,
|
|
||||||
ValueKind::Sequence => 6,
|
|
||||||
ValueKind::Map => 7,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn single_description(kind: &ValueKind) -> &'static str {
|
|
||||||
match kind {
|
|
||||||
ValueKind::Null => "null",
|
|
||||||
ValueKind::Boolean => "a boolean",
|
|
||||||
ValueKind::Integer => "a positive integer",
|
|
||||||
ValueKind::NegativeInteger => "an integer",
|
|
||||||
ValueKind::Float => "a number",
|
|
||||||
ValueKind::String => "a string",
|
|
||||||
ValueKind::Sequence => "an array",
|
|
||||||
ValueKind::Map => "an object",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn description_rec(kinds: &[ValueKind], count_items: &mut usize, message: &mut String) {
|
|
||||||
let (msg_part, rest): (_, &[ValueKind]) = match kinds {
|
|
||||||
[] => (String::new(), &[]),
|
|
||||||
[ValueKind::Integer | ValueKind::NegativeInteger, ValueKind::Float, rest @ ..] => {
|
|
||||||
("a number".to_owned(), rest)
|
|
||||||
}
|
|
||||||
[ValueKind::Integer, ValueKind::NegativeInteger, ValueKind::Float, rest @ ..] => {
|
|
||||||
("a number".to_owned(), rest)
|
|
||||||
}
|
|
||||||
[ValueKind::Integer, ValueKind::NegativeInteger, rest @ ..] => {
|
|
||||||
("an integer".to_owned(), rest)
|
|
||||||
}
|
|
||||||
[a] => (single_description(a).to_owned(), &[]),
|
|
||||||
[a, rest @ ..] => (single_description(a).to_owned(), rest),
|
|
||||||
};
|
|
||||||
|
|
||||||
if rest.is_empty() {
|
|
||||||
if *count_items == 0 {
|
|
||||||
message.push_str(&msg_part);
|
|
||||||
} else if *count_items == 1 {
|
|
||||||
message.push_str(&format!(" or {msg_part}"));
|
|
||||||
} else {
|
|
||||||
message.push_str(&format!(", or {msg_part}"));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if *count_items == 0 {
|
|
||||||
message.push_str(&msg_part);
|
|
||||||
} else {
|
|
||||||
message.push_str(&format!(", {msg_part}"));
|
|
||||||
}
|
|
||||||
|
|
||||||
*count_items += 1;
|
|
||||||
description_rec(rest, count_items, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut kinds = kinds.to_owned();
|
|
||||||
kinds.sort_by_key(order);
|
|
||||||
kinds.dedup();
|
|
||||||
|
|
||||||
if kinds.is_empty() {
|
|
||||||
"a different value".to_owned()
|
|
||||||
} else {
|
|
||||||
let mut message = String::new();
|
|
||||||
description_rec(kinds.as_slice(), &mut 0, &mut message);
|
|
||||||
message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn value_description_with_kind_json(v: &serde_json::Value) -> String {
|
|
||||||
match v.kind() {
|
|
||||||
ValueKind::Null => "null".to_owned(),
|
|
||||||
kind => {
|
|
||||||
format!(
|
|
||||||
"{}: `{}`",
|
|
||||||
value_kinds_description_json(&[kind]),
|
|
||||||
serde_json::to_string(v).unwrap()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<C: Default + ErrorCode> deserr::DeserializeError for DeserrJsonError<C> {
|
|
||||||
fn error<V: IntoValue>(
|
|
||||||
_self_: Option<Self>,
|
|
||||||
error: deserr::ErrorKind<V>,
|
|
||||||
location: ValuePointerRef,
|
|
||||||
) -> Result<Self, Self> {
|
|
||||||
let mut message = String::new();
|
|
||||||
|
|
||||||
message.push_str(&match error {
|
|
||||||
ErrorKind::IncorrectValueKind { actual, accepted } => {
|
|
||||||
let expected = value_kinds_description_json(accepted);
|
|
||||||
// if we're not able to get the value as a string then we print nothing.
|
|
||||||
let received = value_description_with_kind_json(&serde_json::Value::from(actual));
|
|
||||||
|
|
||||||
let location = location_json_description(location, " at");
|
|
||||||
|
|
||||||
format!("Invalid value type{location}: expected {expected}, but found {received}")
|
|
||||||
}
|
|
||||||
ErrorKind::MissingField { field } => {
|
|
||||||
// serde_json original message:
|
|
||||||
// Json deserialize error: missing field `lol` at line 1 column 2
|
|
||||||
let location = location_json_description(location, " inside");
|
|
||||||
format!("Missing field `{field}`{location}")
|
|
||||||
}
|
|
||||||
ErrorKind::UnknownKey { key, accepted } => {
|
|
||||||
let location = location_json_description(location, " inside");
|
|
||||||
format!(
|
|
||||||
"Unknown field `{}`{location}: expected one of {}",
|
|
||||||
key,
|
|
||||||
accepted
|
|
||||||
.iter()
|
|
||||||
.map(|accepted| format!("`{}`", accepted))
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join(", ")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
ErrorKind::UnknownValue { value, accepted } => {
|
|
||||||
let location = location_json_description(location, " at");
|
|
||||||
format!(
|
|
||||||
"Unknown value `{}`{location}: expected one of {}",
|
|
||||||
value,
|
|
||||||
accepted
|
|
||||||
.iter()
|
|
||||||
.map(|accepted| format!("`{}`", accepted))
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join(", "),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
ErrorKind::Unexpected { msg } => {
|
|
||||||
let location = location_json_description(location, " at");
|
|
||||||
// serde_json original message:
|
|
||||||
// The json payload provided is malformed. `trailing characters at line 1 column 19`.
|
|
||||||
format!("Invalid value{location}: {msg}")
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Err(DeserrJsonError {
|
|
||||||
msg: message,
|
|
||||||
code: C::default().error_code(),
|
|
||||||
_phantom: PhantomData,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the error happened in the root, then an empty string is returned.
|
|
||||||
pub fn location_query_param_description(location: ValuePointerRef, article: &str) -> String {
|
|
||||||
fn rec(location: ValuePointerRef) -> String {
|
|
||||||
match location {
|
|
||||||
ValuePointerRef::Origin => String::new(),
|
|
||||||
ValuePointerRef::Key { key, prev } => {
|
|
||||||
if matches!(prev, ValuePointerRef::Origin) {
|
|
||||||
key.to_owned()
|
|
||||||
} else {
|
|
||||||
rec(*prev) + "." + key
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ValuePointerRef::Index { index, prev } => format!("{}[{index}]", rec(*prev)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
match location {
|
|
||||||
ValuePointerRef::Origin => String::new(),
|
|
||||||
_ => {
|
|
||||||
format!("{article} `{}`", rec(location))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<C: Default + ErrorCode> deserr::DeserializeError for DeserrQueryParamError<C> {
|
|
||||||
fn error<V: IntoValue>(
|
|
||||||
_self_: Option<Self>,
|
|
||||||
error: deserr::ErrorKind<V>,
|
|
||||||
location: ValuePointerRef,
|
|
||||||
) -> Result<Self, Self> {
|
|
||||||
let mut message = String::new();
|
|
||||||
|
|
||||||
message.push_str(&match error {
|
|
||||||
ErrorKind::IncorrectValueKind { actual, accepted } => {
|
|
||||||
let expected = value_kinds_description_query_param(accepted);
|
|
||||||
// if we're not able to get the value as a string then we print nothing.
|
|
||||||
let received = value_description_with_kind_query_param(actual);
|
|
||||||
|
|
||||||
let location = location_query_param_description(location, " for parameter");
|
|
||||||
|
|
||||||
format!("Invalid value type{location}: expected {expected}, but found {received}")
|
|
||||||
}
|
|
||||||
ErrorKind::MissingField { field } => {
|
|
||||||
// serde_json original message:
|
|
||||||
// Json deserialize error: missing field `lol` at line 1 column 2
|
|
||||||
let location = location_query_param_description(location, " inside");
|
|
||||||
format!("Missing parameter `{field}`{location}")
|
|
||||||
}
|
|
||||||
ErrorKind::UnknownKey { key, accepted } => {
|
|
||||||
let location = location_query_param_description(location, " inside");
|
|
||||||
format!(
|
|
||||||
"Unknown parameter `{}`{location}: expected one of {}",
|
|
||||||
key,
|
|
||||||
accepted
|
|
||||||
.iter()
|
|
||||||
.map(|accepted| format!("`{}`", accepted))
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join(", ")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
ErrorKind::UnknownValue { value, accepted } => {
|
|
||||||
let location = location_query_param_description(location, " for parameter");
|
|
||||||
format!(
|
|
||||||
"Unknown value `{}`{location}: expected one of {}",
|
|
||||||
value,
|
|
||||||
accepted
|
|
||||||
.iter()
|
|
||||||
.map(|accepted| format!("`{}`", accepted))
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join(", "),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
ErrorKind::Unexpected { msg } => {
|
|
||||||
let location = location_query_param_description(location, " in parameter");
|
|
||||||
// serde_json original message:
|
|
||||||
// The json payload provided is malformed. `trailing characters at line 1 column 19`.
|
|
||||||
format!("Invalid value{location}: {msg}")
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Err(DeserrQueryParamError {
|
|
||||||
msg: message,
|
|
||||||
code: C::default().error_code(),
|
|
||||||
_phantom: PhantomData,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn value_kinds_description_query_param(_accepted: &[ValueKind]) -> String {
|
|
||||||
"a string".to_owned()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn value_description_with_kind_query_param<V: IntoValue>(actual: deserr::Value<V>) -> String {
|
|
||||||
match actual {
|
|
||||||
deserr::Value::Null => "null".to_owned(),
|
|
||||||
deserr::Value::Boolean(x) => format!("a boolean: `{x}`"),
|
|
||||||
deserr::Value::Integer(x) => format!("an integer: `{x}`"),
|
|
||||||
deserr::Value::NegativeInteger(x) => {
|
|
||||||
format!("an integer: `{x}`")
|
|
||||||
}
|
|
||||||
deserr::Value::Float(x) => {
|
|
||||||
format!("a number: `{x}`")
|
|
||||||
}
|
|
||||||
deserr::Value::String(x) => {
|
|
||||||
format!("a string: `{x}`")
|
|
||||||
}
|
|
||||||
deserr::Value::Sequence(_) => "multiple values".to_owned(),
|
|
||||||
deserr::Value::Map(_) => "multiple parameters".to_owned(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct DetailedParseIntError(String);
|
pub struct ParseOffsetDateTimeError(pub String);
|
||||||
impl fmt::Display for DetailedParseIntError {
|
impl fmt::Display for ParseOffsetDateTimeError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
writeln!(f, "`{original}` is not a valid date. It should follow the RFC 3339 format to represents a date or datetime in the future or specified as a null value. e.g. 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'.", original = self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserialization when `deserr` cannot parse a task date.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct InvalidTaskDateError(pub String);
|
||||||
|
impl std::fmt::Display for InvalidTaskDateError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "`{}` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format.", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserialization error when `deserr` cannot parse a String
|
||||||
|
/// into a bool.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DeserrParseBoolError(pub String);
|
||||||
|
impl fmt::Display for DeserrParseBoolError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "could not parse `{}` as a boolean, expected either `true` or `false`", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserialization error when `deserr` cannot parse a String
|
||||||
|
/// into an integer.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DeserrParseIntError(pub String);
|
||||||
|
impl fmt::Display for DeserrParseIntError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "could not parse `{}` as a positive integer", self.0)
|
write!(f, "could not parse `{}` as a positive integer", self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl std::error::Error for DetailedParseIntError {}
|
|
||||||
|
|
||||||
pub fn parse_u32_query_param(x: String) -> Result<u32, TakeErrorMessage<DetailedParseIntError>> {
|
|
||||||
x.parse::<u32>().map_err(|_e| TakeErrorMessage(DetailedParseIntError(x.to_owned())))
|
|
||||||
}
|
|
||||||
pub fn parse_usize_query_param(
|
|
||||||
x: String,
|
|
||||||
) -> Result<usize, TakeErrorMessage<DetailedParseIntError>> {
|
|
||||||
x.parse::<usize>().map_err(|_e| TakeErrorMessage(DetailedParseIntError(x.to_owned())))
|
|
||||||
}
|
|
||||||
pub fn parse_option_usize_query_param(
|
|
||||||
s: Option<String>,
|
|
||||||
) -> Result<Option<usize>, TakeErrorMessage<DetailedParseIntError>> {
|
|
||||||
if let Some(s) = s {
|
|
||||||
parse_usize_query_param(s).map(Some)
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn parse_option_u32_query_param(
|
|
||||||
s: Option<String>,
|
|
||||||
) -> Result<Option<u32>, TakeErrorMessage<DetailedParseIntError>> {
|
|
||||||
if let Some(s) = s {
|
|
||||||
parse_u32_query_param(s).map(Some)
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn parse_option_vec_u32_query_param(
|
|
||||||
s: Option<serde_cs::vec::CS<String>>,
|
|
||||||
) -> Result<Option<Vec<u32>>, TakeErrorMessage<DetailedParseIntError>> {
|
|
||||||
if let Some(s) = s {
|
|
||||||
s.into_iter()
|
|
||||||
.map(parse_u32_query_param)
|
|
||||||
.collect::<Result<Vec<u32>, TakeErrorMessage<DetailedParseIntError>>>()
|
|
||||||
.map(Some)
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn parse_option_cs_star_or<T: FromStr>(
|
|
||||||
s: Option<CS<StarOr<String>>>,
|
|
||||||
) -> Result<Option<Vec<T>>, TakeErrorMessage<T::Err>> {
|
|
||||||
if let Some(s) = s.and_then(fold_star_or) as Option<Vec<String>> {
|
|
||||||
s.into_iter()
|
|
||||||
.map(|s| T::from_str(&s))
|
|
||||||
.collect::<Result<Vec<T>, T::Err>>()
|
|
||||||
.map_err(TakeErrorMessage)
|
|
||||||
.map(Some)
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extracts the raw values from the `StarOr` types and
|
|
||||||
/// return None if a `StarOr::Star` is encountered.
|
|
||||||
pub fn fold_star_or<T, O>(content: impl IntoIterator<Item = StarOr<T>>) -> Option<O>
|
|
||||||
where
|
|
||||||
O: FromIterator<T>,
|
|
||||||
{
|
|
||||||
content
|
|
||||||
.into_iter()
|
|
||||||
.map(|value| match value {
|
|
||||||
StarOr::Star => None,
|
|
||||||
StarOr::Other(val) => Some(val),
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
pub struct TakeErrorMessage<T>(pub T);
|
|
||||||
|
|
||||||
impl<C: Default + ErrorCode, T> MergeWithError<TakeErrorMessage<T>> for DeserrJsonError<C>
|
|
||||||
where
|
|
||||||
T: std::error::Error,
|
|
||||||
{
|
|
||||||
fn merge(
|
|
||||||
_self_: Option<Self>,
|
|
||||||
other: TakeErrorMessage<T>,
|
|
||||||
merge_location: ValuePointerRef,
|
|
||||||
) -> Result<Self, Self> {
|
|
||||||
DeserrJsonError::error::<Infallible>(
|
|
||||||
None,
|
|
||||||
deserr::ErrorKind::Unexpected { msg: other.0.to_string() },
|
|
||||||
merge_location,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<C: Default + ErrorCode, T> MergeWithError<TakeErrorMessage<T>> for DeserrQueryParamError<C>
|
|
||||||
where
|
|
||||||
T: std::error::Error,
|
|
||||||
{
|
|
||||||
fn merge(
|
|
||||||
_self_: Option<Self>,
|
|
||||||
other: TakeErrorMessage<T>,
|
|
||||||
merge_location: ValuePointerRef,
|
|
||||||
) -> Result<Self, Self> {
|
|
||||||
DeserrQueryParamError::error::<Infallible>(
|
|
||||||
None,
|
|
||||||
deserr::ErrorKind::Unexpected { msg: other.0.to_string() },
|
|
||||||
merge_location,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! internal_error {
|
macro_rules! internal_error {
|
||||||
@ -924,32 +443,3 @@ macro_rules! internal_error {
|
|||||||
)*
|
)*
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use deserr::ValueKind;
|
|
||||||
|
|
||||||
use crate::error::value_kinds_description_json;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_value_kinds_description_json() {
|
|
||||||
insta::assert_display_snapshot!(value_kinds_description_json(&[]), @"a different value");
|
|
||||||
|
|
||||||
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::Boolean]), @"a boolean");
|
|
||||||
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::Integer]), @"a positive integer");
|
|
||||||
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::NegativeInteger]), @"an integer");
|
|
||||||
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::Integer]), @"a positive integer");
|
|
||||||
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::String]), @"a string");
|
|
||||||
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::Sequence]), @"an array");
|
|
||||||
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::Map]), @"an object");
|
|
||||||
|
|
||||||
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::Integer, ValueKind::Boolean]), @"a boolean or a positive integer");
|
|
||||||
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::Null, ValueKind::Integer]), @"null or a positive integer");
|
|
||||||
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::Sequence, ValueKind::NegativeInteger]), @"an integer or an array");
|
|
||||||
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::Integer, ValueKind::Float]), @"a number");
|
|
||||||
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::Integer, ValueKind::Float, ValueKind::NegativeInteger]), @"a number");
|
|
||||||
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::Integer, ValueKind::Float, ValueKind::NegativeInteger, ValueKind::Null]), @"null or a number");
|
|
||||||
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::Boolean, ValueKind::Integer, ValueKind::Float, ValueKind::NegativeInteger, ValueKind::Null]), @"null, a boolean, or a number");
|
|
||||||
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::Null, ValueKind::Boolean, ValueKind::Integer, ValueKind::Float, ValueKind::NegativeInteger, ValueKind::Null]), @"null, a boolean, or a number");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -29,6 +29,12 @@ impl IndexUid {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for IndexUid {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
fmt::Display::fmt(&self.0, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl std::ops::Deref for IndexUid {
|
impl std::ops::Deref for IndexUid {
|
||||||
type Target = str;
|
type Target = str;
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
use std::fmt::Display;
|
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use deserr::{DeserializeError, DeserializeFromValue, MergeWithError, ValuePointerRef};
|
use deserr::{DeserializeError, DeserializeFromValue, ValuePointerRef};
|
||||||
use enum_iterator::Sequence;
|
use enum_iterator::Sequence;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use time::format_description::well_known::Rfc3339;
|
use time::format_description::well_known::Rfc3339;
|
||||||
@ -10,31 +10,14 @@ use time::macros::{format_description, time};
|
|||||||
use time::{Date, OffsetDateTime, PrimitiveDateTime};
|
use time::{Date, OffsetDateTime, PrimitiveDateTime};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::error::deserr_codes::*;
|
use crate::deserr::DeserrJsonError;
|
||||||
use crate::error::{unwrap_any, Code, DeserrJsonError, ErrorCode, TakeErrorMessage};
|
use crate::error::{deserr_codes::*, ParseOffsetDateTimeError};
|
||||||
use crate::index_uid::{IndexUid, IndexUidFormatError};
|
use crate::error::{unwrap_any, Code};
|
||||||
|
use crate::index_uid::IndexUid;
|
||||||
use crate::star_or::StarOr;
|
use crate::star_or::StarOr;
|
||||||
|
|
||||||
pub type KeyId = Uuid;
|
pub type KeyId = Uuid;
|
||||||
|
|
||||||
impl<C: Default + ErrorCode> MergeWithError<IndexUidFormatError> for DeserrJsonError<C> {
|
|
||||||
fn merge(
|
|
||||||
_self_: Option<Self>,
|
|
||||||
other: IndexUidFormatError,
|
|
||||||
merge_location: deserr::ValuePointerRef,
|
|
||||||
) -> std::result::Result<Self, Self> {
|
|
||||||
DeserrJsonError::error::<Infallible>(
|
|
||||||
None,
|
|
||||||
deserr::ErrorKind::Unexpected { msg: other.to_string() },
|
|
||||||
merge_location,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_uuid_from_str(s: &str) -> Result<Uuid, TakeErrorMessage<uuid::Error>> {
|
|
||||||
Uuid::parse_str(s).map_err(TakeErrorMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, DeserializeFromValue)]
|
#[derive(Debug, DeserializeFromValue)]
|
||||||
#[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields)]
|
#[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields)]
|
||||||
pub struct CreateApiKey {
|
pub struct CreateApiKey {
|
||||||
@ -42,13 +25,13 @@ pub struct CreateApiKey {
|
|||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
#[deserr(default, error = DeserrJsonError<InvalidApiKeyName>)]
|
#[deserr(default, error = DeserrJsonError<InvalidApiKeyName>)]
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
#[deserr(default = Uuid::new_v4(), error = DeserrJsonError<InvalidApiKeyUid>, from(&String) = parse_uuid_from_str -> TakeErrorMessage<uuid::Error>)]
|
#[deserr(default = Uuid::new_v4(), error = DeserrJsonError<InvalidApiKeyUid>, from(&String) = Uuid::from_str -> uuid::Error)]
|
||||||
pub uid: KeyId,
|
pub uid: KeyId,
|
||||||
#[deserr(error = DeserrJsonError<InvalidApiKeyActions>, missing_field_error = DeserrJsonError::missing_api_key_actions)]
|
#[deserr(error = DeserrJsonError<InvalidApiKeyActions>, missing_field_error = DeserrJsonError::missing_api_key_actions)]
|
||||||
pub actions: Vec<Action>,
|
pub actions: Vec<Action>,
|
||||||
#[deserr(error = DeserrJsonError<InvalidApiKeyIndexes>, missing_field_error = DeserrJsonError::missing_api_key_indexes)]
|
#[deserr(error = DeserrJsonError<InvalidApiKeyIndexes>, missing_field_error = DeserrJsonError::missing_api_key_indexes)]
|
||||||
pub indexes: Vec<StarOr<IndexUid>>,
|
pub indexes: Vec<StarOr<IndexUid>>,
|
||||||
#[deserr(error = DeserrJsonError<InvalidApiKeyExpiresAt>, from(Option<String>) = parse_expiration_date -> TakeErrorMessage<ParseOffsetDateTimeError>, missing_field_error = DeserrJsonError::missing_api_key_expires_at)]
|
#[deserr(error = DeserrJsonError<InvalidApiKeyExpiresAt>, from(Option<String>) = parse_expiration_date -> ParseOffsetDateTimeError, missing_field_error = DeserrJsonError::missing_api_key_expires_at)]
|
||||||
pub expires_at: Option<OffsetDateTime>,
|
pub expires_at: Option<OffsetDateTime>,
|
||||||
}
|
}
|
||||||
impl CreateApiKey {
|
impl CreateApiKey {
|
||||||
@ -149,18 +132,9 @@ impl Key {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ParseOffsetDateTimeError(String);
|
|
||||||
impl Display for ParseOffsetDateTimeError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
writeln!(f, "`{original}` is not a valid date. It should follow the RFC 3339 format to represents a date or datetime in the future or specified as a null value. e.g. 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'.", original = self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl std::error::Error for ParseOffsetDateTimeError {}
|
|
||||||
|
|
||||||
fn parse_expiration_date(
|
fn parse_expiration_date(
|
||||||
string: Option<String>,
|
string: Option<String>,
|
||||||
) -> std::result::Result<Option<OffsetDateTime>, TakeErrorMessage<ParseOffsetDateTimeError>> {
|
) -> std::result::Result<Option<OffsetDateTime>, ParseOffsetDateTimeError> {
|
||||||
let Some(string) = string else {
|
let Some(string) = string else {
|
||||||
return Ok(None)
|
return Ok(None)
|
||||||
};
|
};
|
||||||
@ -186,12 +160,12 @@ fn parse_expiration_date(
|
|||||||
) {
|
) {
|
||||||
PrimitiveDateTime::new(date, time!(00:00)).assume_utc()
|
PrimitiveDateTime::new(date, time!(00:00)).assume_utc()
|
||||||
} else {
|
} else {
|
||||||
return Err(TakeErrorMessage(ParseOffsetDateTimeError(string)));
|
return Err(ParseOffsetDateTimeError(string));
|
||||||
};
|
};
|
||||||
if datetime > OffsetDateTime::now_utc() {
|
if datetime > OffsetDateTime::now_utc() {
|
||||||
Ok(Some(datetime))
|
Ok(Some(datetime))
|
||||||
} else {
|
} else {
|
||||||
Err(TakeErrorMessage(ParseOffsetDateTimeError(string)))
|
Err(ParseOffsetDateTimeError(string))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ pub mod settings;
|
|||||||
pub mod star_or;
|
pub mod star_or;
|
||||||
pub mod tasks;
|
pub mod tasks;
|
||||||
pub mod versioning;
|
pub mod versioning;
|
||||||
|
pub mod deserr;
|
||||||
pub use milli;
|
pub use milli;
|
||||||
pub use milli::{heed, Index};
|
pub use milli::{heed, Index};
|
||||||
pub use serde_cs;
|
pub use serde_cs;
|
||||||
|
@ -11,8 +11,9 @@ use milli::update::Setting;
|
|||||||
use milli::{Criterion, CriterionError, Index, DEFAULT_VALUES_PER_FACET};
|
use milli::{Criterion, CriterionError, Index, DEFAULT_VALUES_PER_FACET};
|
||||||
use serde::{Deserialize, Serialize, Serializer};
|
use serde::{Deserialize, Serialize, Serializer};
|
||||||
|
|
||||||
|
use crate::deserr::DeserrJsonError;
|
||||||
use crate::error::deserr_codes::*;
|
use crate::error::deserr_codes::*;
|
||||||
use crate::error::{unwrap_any, DeserrJsonError};
|
use crate::error::{unwrap_any};
|
||||||
|
|
||||||
/// The maximimum number of results that the engine
|
/// The maximimum number of results that the engine
|
||||||
/// will be able to return in one search call.
|
/// will be able to return in one search call.
|
||||||
|
@ -1,13 +1,9 @@
|
|||||||
use std::fmt::{Display, Formatter};
|
use std::{fmt, marker::PhantomData, str::FromStr};
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::ops::Deref;
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use deserr::{DeserializeError, DeserializeFromValue, MergeWithError, ValueKind};
|
use deserr::{DeserializeError, DeserializeFromValue, MergeWithError, ValueKind};
|
||||||
use serde::de::Visitor;
|
use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
|
||||||
|
|
||||||
use crate::error::unwrap_any;
|
use crate::{deserr::query_params::FromQueryParameter, error::unwrap_any};
|
||||||
|
|
||||||
/// A type that tries to match either a star (*) or
|
/// A type that tries to match either a star (*) or
|
||||||
/// any other thing that implements `FromStr`.
|
/// any other thing that implements `FromStr`.
|
||||||
@ -17,35 +13,6 @@ pub enum StarOr<T> {
|
|||||||
Other(T),
|
Other(T),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: DeserializeError, T> DeserializeFromValue<E> for StarOr<T>
|
|
||||||
where
|
|
||||||
T: FromStr,
|
|
||||||
E: MergeWithError<T::Err>,
|
|
||||||
{
|
|
||||||
fn deserialize_from_value<V: deserr::IntoValue>(
|
|
||||||
value: deserr::Value<V>,
|
|
||||||
location: deserr::ValuePointerRef,
|
|
||||||
) -> Result<Self, E> {
|
|
||||||
match value {
|
|
||||||
deserr::Value::String(v) => match v.as_str() {
|
|
||||||
"*" => Ok(StarOr::Star),
|
|
||||||
v => match FromStr::from_str(v) {
|
|
||||||
Ok(x) => Ok(StarOr::Other(x)),
|
|
||||||
Err(e) => Err(unwrap_any(E::merge(None, e, location))),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
_ => Err(unwrap_any(E::error::<V>(
|
|
||||||
None,
|
|
||||||
deserr::ErrorKind::IncorrectValueKind {
|
|
||||||
actual: value,
|
|
||||||
accepted: &[ValueKind::String],
|
|
||||||
},
|
|
||||||
location,
|
|
||||||
))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: FromStr> FromStr for StarOr<T> {
|
impl<T: FromStr> FromStr for StarOr<T> {
|
||||||
type Err = T::Err;
|
type Err = T::Err;
|
||||||
|
|
||||||
@ -57,23 +24,11 @@ impl<T: FromStr> FromStr for StarOr<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl<T: fmt::Display> fmt::Display for StarOr<T> {
|
||||||
impl<T: Deref<Target = str>> Deref for StarOr<T> {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
type Target = str;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
match self {
|
match self {
|
||||||
Self::Star => "*",
|
StarOr::Star => write!(f, "*"),
|
||||||
Self::Other(t) => t.deref(),
|
StarOr::Other(x) => fmt::Display::fmt(x, f),
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Into<String>> From<StarOr<T>> for String {
|
|
||||||
fn from(s: StarOr<T>) -> Self {
|
|
||||||
match s {
|
|
||||||
StarOr::Star => "*".to_string(),
|
|
||||||
StarOr::Other(t) => t.into(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -93,7 +48,7 @@ impl<T: PartialEq + Eq> Eq for StarOr<T> {}
|
|||||||
impl<'de, T, E> Deserialize<'de> for StarOr<T>
|
impl<'de, T, E> Deserialize<'de> for StarOr<T>
|
||||||
where
|
where
|
||||||
T: FromStr<Err = E>,
|
T: FromStr<Err = E>,
|
||||||
E: Display,
|
E: fmt::Display,
|
||||||
{
|
{
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
where
|
where
|
||||||
@ -109,11 +64,11 @@ where
|
|||||||
impl<'de, T, FE> Visitor<'de> for StarOrVisitor<T>
|
impl<'de, T, FE> Visitor<'de> for StarOrVisitor<T>
|
||||||
where
|
where
|
||||||
T: FromStr<Err = FE>,
|
T: FromStr<Err = FE>,
|
||||||
FE: Display,
|
FE: fmt::Display,
|
||||||
{
|
{
|
||||||
type Value = StarOr<T>;
|
type Value = StarOr<T>;
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> std::fmt::Result {
|
||||||
formatter.write_str("a string")
|
formatter.write_str("a string")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,7 +94,7 @@ where
|
|||||||
|
|
||||||
impl<T> Serialize for StarOr<T>
|
impl<T> Serialize for StarOr<T>
|
||||||
where
|
where
|
||||||
T: Deref<Target = str>,
|
T: ToString,
|
||||||
{
|
{
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where
|
where
|
||||||
@ -147,7 +102,222 @@ where
|
|||||||
{
|
{
|
||||||
match self {
|
match self {
|
||||||
StarOr::Star => serializer.serialize_str("*"),
|
StarOr::Star => serializer.serialize_str("*"),
|
||||||
StarOr::Other(other) => serializer.serialize_str(other.deref()),
|
StarOr::Other(other) => serializer.serialize_str(&other.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, E> DeserializeFromValue<E> for StarOr<T>
|
||||||
|
where
|
||||||
|
T: FromStr,
|
||||||
|
E: DeserializeError + MergeWithError<T::Err>,
|
||||||
|
{
|
||||||
|
fn deserialize_from_value<V: deserr::IntoValue>(
|
||||||
|
value: deserr::Value<V>,
|
||||||
|
location: deserr::ValuePointerRef,
|
||||||
|
) -> Result<Self, E> {
|
||||||
|
match value {
|
||||||
|
deserr::Value::String(v) => {
|
||||||
|
if v == "*" {
|
||||||
|
Ok(StarOr::Star)
|
||||||
|
} else {
|
||||||
|
match T::from_str(&v) {
|
||||||
|
Ok(parsed) => Ok(StarOr::Other(parsed)),
|
||||||
|
Err(e) => Err(unwrap_any(E::merge(None, e, location))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Err(unwrap_any(E::error::<V>(
|
||||||
|
None,
|
||||||
|
deserr::ErrorKind::IncorrectValueKind {
|
||||||
|
actual: value,
|
||||||
|
accepted: &[ValueKind::String],
|
||||||
|
},
|
||||||
|
location,
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A type representing the content of a query parameter that can either not exist,
|
||||||
|
/// be equal to a star (*), or another value
|
||||||
|
///
|
||||||
|
/// It is a convenient alternative to `Option<StarOr<T>>`.
|
||||||
|
#[derive(Debug, Default, Clone, Copy)]
|
||||||
|
pub enum OptionStarOr<T> {
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
Star,
|
||||||
|
Other(T),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> OptionStarOr<T> {
|
||||||
|
pub fn is_some(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::None => false,
|
||||||
|
Self::Star => false,
|
||||||
|
Self::Other(_) => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn merge_star_and_none(self) -> Option<T> {
|
||||||
|
match self {
|
||||||
|
Self::None | Self::Star => None,
|
||||||
|
Self::Other(x) => Some(x),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn try_map<U, E, F: Fn(T) -> Result<U, E>>(self, map_f: F) -> Result<OptionStarOr<U>, E> {
|
||||||
|
match self {
|
||||||
|
OptionStarOr::None => Ok(OptionStarOr::None),
|
||||||
|
OptionStarOr::Star => Ok(OptionStarOr::Star),
|
||||||
|
OptionStarOr::Other(x) => map_f(x).map(OptionStarOr::Other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> FromQueryParameter for OptionStarOr<T>
|
||||||
|
where
|
||||||
|
T: FromQueryParameter,
|
||||||
|
{
|
||||||
|
type Err = T::Err;
|
||||||
|
fn from_query_param(p: &str) -> Result<Self, Self::Err> {
|
||||||
|
match p {
|
||||||
|
"*" => Ok(OptionStarOr::Star),
|
||||||
|
s => T::from_query_param(s).map(OptionStarOr::Other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, E> DeserializeFromValue<E> for OptionStarOr<T>
|
||||||
|
where
|
||||||
|
E: DeserializeError + MergeWithError<T::Err>,
|
||||||
|
T: FromQueryParameter,
|
||||||
|
{
|
||||||
|
fn deserialize_from_value<V: deserr::IntoValue>(
|
||||||
|
value: deserr::Value<V>,
|
||||||
|
location: deserr::ValuePointerRef,
|
||||||
|
) -> Result<Self, E> {
|
||||||
|
match value {
|
||||||
|
deserr::Value::String(s) => match s.as_str() {
|
||||||
|
"*" => Ok(OptionStarOr::Star),
|
||||||
|
s => match T::from_query_param(s) {
|
||||||
|
Ok(x) => Ok(OptionStarOr::Other(x)),
|
||||||
|
Err(e) => Err(unwrap_any(E::merge(None, e, location))),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_ => Err(unwrap_any(E::error::<V>(
|
||||||
|
None,
|
||||||
|
deserr::ErrorKind::IncorrectValueKind {
|
||||||
|
actual: value,
|
||||||
|
accepted: &[ValueKind::String],
|
||||||
|
},
|
||||||
|
location,
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A type representing the content of a query parameter that can either not exist, be equal to a star (*), or represent a list of other values
|
||||||
|
#[derive(Debug, Default, Clone)]
|
||||||
|
pub enum OptionStarOrList<T> {
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
Star,
|
||||||
|
List(Vec<T>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> OptionStarOrList<T> {
|
||||||
|
pub fn is_some(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::None => false,
|
||||||
|
Self::Star => false,
|
||||||
|
Self::List(_) => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn map<U, F: Fn(T) -> U>(self, map_f: F) -> OptionStarOrList<U> {
|
||||||
|
match self {
|
||||||
|
Self::None => OptionStarOrList::None,
|
||||||
|
Self::Star => OptionStarOrList::Star,
|
||||||
|
Self::List(xs) => OptionStarOrList::List(xs.into_iter().map(map_f).collect()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn try_map<U, E, F: Fn(T) -> Result<U, E>>(
|
||||||
|
self,
|
||||||
|
map_f: F,
|
||||||
|
) -> Result<OptionStarOrList<U>, E> {
|
||||||
|
match self {
|
||||||
|
Self::None => Ok(OptionStarOrList::None),
|
||||||
|
Self::Star => Ok(OptionStarOrList::Star),
|
||||||
|
Self::List(xs) => {
|
||||||
|
xs.into_iter().map(map_f).collect::<Result<Vec<_>, _>>().map(OptionStarOrList::List)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn merge_star_and_none(self) -> Option<Vec<T>> {
|
||||||
|
match self {
|
||||||
|
Self::None | Self::Star => None,
|
||||||
|
Self::List(xs) => Some(xs),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn push(&mut self, el: T) {
|
||||||
|
match self {
|
||||||
|
Self::None => *self = Self::List(vec![el]),
|
||||||
|
Self::Star => (),
|
||||||
|
Self::List(xs) => xs.push(el),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, E> DeserializeFromValue<E> for OptionStarOrList<T>
|
||||||
|
where
|
||||||
|
E: DeserializeError + MergeWithError<T::Err>,
|
||||||
|
T: FromQueryParameter,
|
||||||
|
{
|
||||||
|
fn deserialize_from_value<V: deserr::IntoValue>(
|
||||||
|
value: deserr::Value<V>,
|
||||||
|
location: deserr::ValuePointerRef,
|
||||||
|
) -> Result<Self, E> {
|
||||||
|
match value {
|
||||||
|
deserr::Value::String(s) => {
|
||||||
|
let mut error = None;
|
||||||
|
let mut is_star = false;
|
||||||
|
// CS::<String>::from_str is infaillible
|
||||||
|
let cs = serde_cs::vec::CS::<String>::from_str(&s).unwrap();
|
||||||
|
let len_cs = cs.0.len();
|
||||||
|
let mut els = vec![];
|
||||||
|
for (i, el_str) in cs.into_iter().enumerate() {
|
||||||
|
if el_str == "*" {
|
||||||
|
is_star = true;
|
||||||
|
} else {
|
||||||
|
match T::from_query_param(&el_str) {
|
||||||
|
Ok(el) => {
|
||||||
|
els.push(el);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
let location =
|
||||||
|
if len_cs > 1 { location.push_index(i) } else { location };
|
||||||
|
error = Some(E::merge(error, e, location)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(error) = error {
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_star {
|
||||||
|
Ok(OptionStarOrList::Star)
|
||||||
|
} else {
|
||||||
|
Ok(OptionStarOrList::List(els))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Err(unwrap_any(E::error::<V>(
|
||||||
|
None,
|
||||||
|
deserr::ErrorKind::IncorrectValueKind {
|
||||||
|
actual: value,
|
||||||
|
accepted: &[ValueKind::String],
|
||||||
|
},
|
||||||
|
location,
|
||||||
|
))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use core::fmt;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::fmt::{Display, Write};
|
use std::fmt::{Display, Write};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
@ -9,7 +10,7 @@ use serde::{Deserialize, Serialize, Serializer};
|
|||||||
use time::{Duration, OffsetDateTime};
|
use time::{Duration, OffsetDateTime};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::error::{Code, ResponseError};
|
use crate::error::ResponseError;
|
||||||
use crate::keys::Key;
|
use crate::keys::Key;
|
||||||
use crate::settings::{Settings, Unchecked};
|
use crate::settings::{Settings, Unchecked};
|
||||||
use crate::InstanceUid;
|
use crate::InstanceUid;
|
||||||
@ -332,7 +333,7 @@ impl Display for Status {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for Status {
|
impl FromStr for Status {
|
||||||
type Err = ResponseError;
|
type Err = ParseTaskStatusError;
|
||||||
|
|
||||||
fn from_str(status: &str) -> Result<Self, Self::Err> {
|
fn from_str(status: &str) -> Result<Self, Self::Err> {
|
||||||
if status.eq_ignore_ascii_case("enqueued") {
|
if status.eq_ignore_ascii_case("enqueued") {
|
||||||
@ -346,21 +347,28 @@ impl FromStr for Status {
|
|||||||
} else if status.eq_ignore_ascii_case("canceled") {
|
} else if status.eq_ignore_ascii_case("canceled") {
|
||||||
Ok(Status::Canceled)
|
Ok(Status::Canceled)
|
||||||
} else {
|
} else {
|
||||||
Err(ResponseError::from_msg(
|
Err(ParseTaskStatusError(status.to_owned()))
|
||||||
format!(
|
|
||||||
"`{}` is not a valid task status. Available statuses are {}.",
|
|
||||||
status,
|
|
||||||
enum_iterator::all::<Status>()
|
|
||||||
.map(|s| format!("`{s}`"))
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join(", ")
|
|
||||||
),
|
|
||||||
Code::BadRequest,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ParseTaskStatusError(pub String);
|
||||||
|
impl fmt::Display for ParseTaskStatusError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"`{}` is not a valid task status. Available statuses are {}.",
|
||||||
|
self.0,
|
||||||
|
enum_iterator::all::<Status>()
|
||||||
|
.map(|s| format!("`{s}`"))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", ")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::error::Error for ParseTaskStatusError {}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Sequence)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Sequence)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub enum Kind {
|
pub enum Kind {
|
||||||
@ -412,7 +420,7 @@ impl Display for Kind {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl FromStr for Kind {
|
impl FromStr for Kind {
|
||||||
type Err = ResponseError;
|
type Err = ParseTaskKindError;
|
||||||
|
|
||||||
fn from_str(kind: &str) -> Result<Self, Self::Err> {
|
fn from_str(kind: &str) -> Result<Self, Self::Err> {
|
||||||
if kind.eq_ignore_ascii_case("indexCreation") {
|
if kind.eq_ignore_ascii_case("indexCreation") {
|
||||||
@ -438,25 +446,32 @@ impl FromStr for Kind {
|
|||||||
} else if kind.eq_ignore_ascii_case("snapshotCreation") {
|
} else if kind.eq_ignore_ascii_case("snapshotCreation") {
|
||||||
Ok(Kind::SnapshotCreation)
|
Ok(Kind::SnapshotCreation)
|
||||||
} else {
|
} else {
|
||||||
Err(ResponseError::from_msg(
|
Err(ParseTaskKindError(kind.to_owned()))
|
||||||
format!(
|
|
||||||
"`{}` is not a valid task type. Available types are {}.",
|
|
||||||
kind,
|
|
||||||
enum_iterator::all::<Kind>()
|
|
||||||
.map(|k| format!(
|
|
||||||
"`{}`",
|
|
||||||
// by default serde is going to insert `"` around the value.
|
|
||||||
serde_json::to_string(&k).unwrap().trim_matches('"')
|
|
||||||
))
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join(", ")
|
|
||||||
),
|
|
||||||
Code::BadRequest,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ParseTaskKindError(pub String);
|
||||||
|
impl fmt::Display for ParseTaskKindError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"`{}` is not a valid task type. Available types are {}.",
|
||||||
|
self.0,
|
||||||
|
enum_iterator::all::<Kind>()
|
||||||
|
.map(|k| format!(
|
||||||
|
"`{}`",
|
||||||
|
// by default serde is going to insert `"` around the value.
|
||||||
|
serde_json::to_string(&k).unwrap().trim_matches('"')
|
||||||
|
))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", ")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::error::Error for ParseTaskKindError {}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
|
||||||
pub enum Details {
|
pub enum Details {
|
||||||
DocumentAdditionOrUpdate { received_documents: u64, indexed_documents: Option<u64> },
|
DocumentAdditionOrUpdate { received_documents: u64, indexed_documents: Option<u64> },
|
||||||
|
@ -19,7 +19,7 @@ byte-unit = { version = "4.0.14", default-features = false, features = ["std", "
|
|||||||
bytes = "1.2.1"
|
bytes = "1.2.1"
|
||||||
clap = { version = "4.0.9", features = ["derive", "env"] }
|
clap = { version = "4.0.9", features = ["derive", "env"] }
|
||||||
crossbeam-channel = "0.5.6"
|
crossbeam-channel = "0.5.6"
|
||||||
deserr = { path = "/Users/meilisearch/Documents/deserr" }
|
deserr = "0.1.4"
|
||||||
dump = { path = "../dump" }
|
dump = { path = "../dump" }
|
||||||
either = "1.8.0"
|
either = "1.8.0"
|
||||||
env_logger = "0.9.1"
|
env_logger = "0.9.1"
|
||||||
|
@ -4,14 +4,15 @@ use actix_web::{web, HttpRequest, HttpResponse};
|
|||||||
use deserr::DeserializeFromValue;
|
use deserr::DeserializeFromValue;
|
||||||
use meilisearch_auth::error::AuthControllerError;
|
use meilisearch_auth::error::AuthControllerError;
|
||||||
use meilisearch_auth::AuthController;
|
use meilisearch_auth::AuthController;
|
||||||
use meilisearch_types::error::{deserr_codes::*, DeserrQueryParamError};
|
use meilisearch_types::deserr::query_params::Param;
|
||||||
use meilisearch_types::error::{Code, DeserrJsonError, ResponseError, TakeErrorMessage};
|
use meilisearch_types::deserr::{DeserrJsonError, DeserrQueryParamError};
|
||||||
|
use meilisearch_types::error::deserr_codes::*;
|
||||||
|
use meilisearch_types::error::{Code, ResponseError};
|
||||||
use meilisearch_types::keys::{Action, CreateApiKey, Key, PatchApiKey};
|
use meilisearch_types::keys::{Action, CreateApiKey, Key, PatchApiKey};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::indexes::search::parse_usize_take_error_message;
|
|
||||||
use super::PAGINATION_DEFAULT_LIMIT;
|
use super::PAGINATION_DEFAULT_LIMIT;
|
||||||
use crate::extractors::authentication::policies::*;
|
use crate::extractors::authentication::policies::*;
|
||||||
use crate::extractors::authentication::GuardedData;
|
use crate::extractors::authentication::GuardedData;
|
||||||
@ -50,20 +51,17 @@ pub async fn create_api_key(
|
|||||||
Ok(HttpResponse::Created().json(res))
|
Ok(HttpResponse::Created().json(res))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(DeserializeFromValue, Deserialize, Debug, Clone, Copy)]
|
#[derive(DeserializeFromValue, Debug, Clone, Copy)]
|
||||||
#[deserr(error = DeserrQueryParamError, rename_all = camelCase, deny_unknown_fields)]
|
#[deserr(error = DeserrQueryParamError, rename_all = camelCase, deny_unknown_fields)]
|
||||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
|
||||||
pub struct ListApiKeys {
|
pub struct ListApiKeys {
|
||||||
#[serde(default)]
|
#[deserr(default, error = DeserrQueryParamError<InvalidApiKeyOffset>)]
|
||||||
#[deserr(default, error = DeserrQueryParamError<InvalidApiKeyOffset>, from(&String) = parse_usize_take_error_message -> TakeErrorMessage<std::num::ParseIntError>)]
|
pub offset: Param<usize>,
|
||||||
pub offset: usize,
|
#[deserr(default = Param(PAGINATION_DEFAULT_LIMIT), error = DeserrQueryParamError<InvalidApiKeyLimit>)]
|
||||||
#[serde(default = "PAGINATION_DEFAULT_LIMIT")]
|
pub limit: Param<usize>,
|
||||||
#[deserr(default = PAGINATION_DEFAULT_LIMIT(), error = DeserrQueryParamError<InvalidApiKeyLimit>, from(&String) = parse_usize_take_error_message -> TakeErrorMessage<std::num::ParseIntError>)]
|
|
||||||
pub limit: usize,
|
|
||||||
}
|
}
|
||||||
impl ListApiKeys {
|
impl ListApiKeys {
|
||||||
fn as_pagination(self) -> Pagination {
|
fn as_pagination(self) -> Pagination {
|
||||||
Pagination { offset: self.offset, limit: self.limit }
|
Pagination { offset: self.offset.0, limit: self.limit.0 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,7 +170,7 @@ impl KeyView {
|
|||||||
key: generated_key,
|
key: generated_key,
|
||||||
uid: key.uid,
|
uid: key.uid,
|
||||||
actions: key.actions,
|
actions: key.actions,
|
||||||
indexes: key.indexes.into_iter().map(String::from).collect(),
|
indexes: key.indexes.into_iter().map(|x| x.to_string()).collect(),
|
||||||
expires_at: key.expires_at,
|
expires_at: key.expires_at,
|
||||||
created_at: key.created_at,
|
created_at: key.created_at,
|
||||||
updated_at: key.updated_at,
|
updated_at: key.updated_at,
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
use std::io::ErrorKind;
|
use std::io::ErrorKind;
|
||||||
use std::num::ParseIntError;
|
|
||||||
|
|
||||||
use actix_web::http::header::CONTENT_TYPE;
|
use actix_web::http::header::CONTENT_TYPE;
|
||||||
use actix_web::web::Data;
|
use actix_web::web::Data;
|
||||||
@ -9,14 +8,15 @@ use deserr::DeserializeFromValue;
|
|||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use index_scheduler::IndexScheduler;
|
use index_scheduler::IndexScheduler;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
|
use meilisearch_types::deserr::query_params::Param;
|
||||||
|
use meilisearch_types::deserr::{DeserrJsonError, DeserrQueryParamError};
|
||||||
use meilisearch_types::document_formats::{read_csv, read_json, read_ndjson, PayloadType};
|
use meilisearch_types::document_formats::{read_csv, read_json, read_ndjson, PayloadType};
|
||||||
use meilisearch_types::error::{deserr_codes::*, fold_star_or, DeserrQueryParamError};
|
use meilisearch_types::error::deserr_codes::*;
|
||||||
use meilisearch_types::error::{DeserrJsonError, ResponseError, TakeErrorMessage};
|
use meilisearch_types::error::ResponseError;
|
||||||
use meilisearch_types::heed::RoTxn;
|
use meilisearch_types::heed::RoTxn;
|
||||||
use meilisearch_types::index_uid::IndexUid;
|
use meilisearch_types::index_uid::IndexUid;
|
||||||
use meilisearch_types::milli::update::IndexDocumentsMethod;
|
use meilisearch_types::milli::update::IndexDocumentsMethod;
|
||||||
use meilisearch_types::serde_cs::vec::CS;
|
use meilisearch_types::star_or::OptionStarOrList;
|
||||||
use meilisearch_types::star_or::StarOr;
|
|
||||||
use meilisearch_types::tasks::KindWithContent;
|
use meilisearch_types::tasks::KindWithContent;
|
||||||
use meilisearch_types::{milli, Document, Index};
|
use meilisearch_types::{milli, Document, Index};
|
||||||
use mime::Mime;
|
use mime::Mime;
|
||||||
@ -27,7 +27,6 @@ use tempfile::tempfile;
|
|||||||
use tokio::fs::File;
|
use tokio::fs::File;
|
||||||
use tokio::io::{AsyncSeekExt, AsyncWriteExt, BufWriter};
|
use tokio::io::{AsyncSeekExt, AsyncWriteExt, BufWriter};
|
||||||
|
|
||||||
use super::search::parse_usize_take_error_message;
|
|
||||||
use crate::analytics::{Analytics, DocumentDeletionKind};
|
use crate::analytics::{Analytics, DocumentDeletionKind};
|
||||||
use crate::error::MeilisearchHttpError;
|
use crate::error::MeilisearchHttpError;
|
||||||
use crate::error::PayloadError::ReceivePayload;
|
use crate::error::PayloadError::ReceivePayload;
|
||||||
@ -36,7 +35,7 @@ use crate::extractors::authentication::GuardedData;
|
|||||||
use crate::extractors::payload::Payload;
|
use crate::extractors::payload::Payload;
|
||||||
use crate::extractors::query_parameters::QueryParameter;
|
use crate::extractors::query_parameters::QueryParameter;
|
||||||
use crate::extractors::sequential_extractor::SeqHandler;
|
use crate::extractors::sequential_extractor::SeqHandler;
|
||||||
use crate::routes::{PaginationView, SummarizedTaskView};
|
use crate::routes::{PaginationView, SummarizedTaskView, PAGINATION_DEFAULT_LIMIT};
|
||||||
|
|
||||||
static ACCEPTED_CONTENT_TYPE: Lazy<Vec<String>> = Lazy::new(|| {
|
static ACCEPTED_CONTENT_TYPE: Lazy<Vec<String>> = Lazy::new(|| {
|
||||||
vec!["application/json".to_string(), "application/x-ndjson".to_string(), "text/csv".to_string()]
|
vec!["application/json".to_string(), "application/x-ndjson".to_string(), "text/csv".to_string()]
|
||||||
@ -81,12 +80,11 @@ pub fn configure(cfg: &mut web::ServiceConfig) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug, DeserializeFromValue)]
|
#[derive(Debug, DeserializeFromValue)]
|
||||||
#[deserr(error = DeserrQueryParamError, rename_all = camelCase, deny_unknown_fields)]
|
#[deserr(error = DeserrQueryParamError, rename_all = camelCase, deny_unknown_fields)]
|
||||||
pub struct GetDocument {
|
pub struct GetDocument {
|
||||||
// TODO: strongly typed argument here
|
|
||||||
#[deserr(default, error = DeserrQueryParamError<InvalidDocumentFields>)]
|
#[deserr(default, error = DeserrQueryParamError<InvalidDocumentFields>)]
|
||||||
fields: Option<CS<StarOr<String>>>,
|
fields: OptionStarOrList<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_document(
|
pub async fn get_document(
|
||||||
@ -95,7 +93,7 @@ pub async fn get_document(
|
|||||||
params: QueryParameter<GetDocument, DeserrQueryParamError>,
|
params: QueryParameter<GetDocument, DeserrQueryParamError>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
let GetDocument { fields } = params.into_inner();
|
let GetDocument { fields } = params.into_inner();
|
||||||
let attributes_to_retrieve = fields.and_then(fold_star_or);
|
let attributes_to_retrieve = fields.merge_star_and_none();
|
||||||
|
|
||||||
let index = index_scheduler.index(&path.index_uid)?;
|
let index = index_scheduler.index(&path.index_uid)?;
|
||||||
let document = retrieve_document(&index, &path.document_id, attributes_to_retrieve)?;
|
let document = retrieve_document(&index, &path.document_id, attributes_to_retrieve)?;
|
||||||
@ -119,15 +117,15 @@ pub async fn delete_document(
|
|||||||
Ok(HttpResponse::Accepted().json(task))
|
Ok(HttpResponse::Accepted().json(task))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug, DeserializeFromValue)]
|
#[derive(Debug, DeserializeFromValue)]
|
||||||
#[deserr(error = DeserrQueryParamError, rename_all = camelCase, deny_unknown_fields)]
|
#[deserr(error = DeserrQueryParamError, rename_all = camelCase, deny_unknown_fields)]
|
||||||
pub struct BrowseQuery {
|
pub struct BrowseQuery {
|
||||||
#[deserr(default, error = DeserrQueryParamError<InvalidDocumentFields>, from(&String) = parse_usize_take_error_message -> TakeErrorMessage<ParseIntError>)]
|
#[deserr(default, error = DeserrQueryParamError<InvalidDocumentFields>)]
|
||||||
offset: usize,
|
offset: Param<usize>,
|
||||||
#[deserr(default = crate::routes::PAGINATION_DEFAULT_LIMIT(), error = DeserrQueryParamError<InvalidDocumentLimit>, from(&String) = parse_usize_take_error_message -> TakeErrorMessage<ParseIntError>)]
|
#[deserr(default = Param(PAGINATION_DEFAULT_LIMIT), error = DeserrQueryParamError<InvalidDocumentLimit>)]
|
||||||
limit: usize,
|
limit: Param<usize>,
|
||||||
#[deserr(default, error = DeserrQueryParamError<InvalidDocumentLimit>)]
|
#[deserr(default, error = DeserrQueryParamError<InvalidDocumentLimit>)]
|
||||||
fields: Option<CS<StarOr<String>>>,
|
fields: OptionStarOrList<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_all_documents(
|
pub async fn get_all_documents(
|
||||||
@ -137,12 +135,12 @@ pub async fn get_all_documents(
|
|||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
debug!("called with params: {:?}", params);
|
debug!("called with params: {:?}", params);
|
||||||
let BrowseQuery { limit, offset, fields } = params.into_inner();
|
let BrowseQuery { limit, offset, fields } = params.into_inner();
|
||||||
let attributes_to_retrieve = fields.and_then(fold_star_or);
|
let attributes_to_retrieve = fields.merge_star_and_none();
|
||||||
|
|
||||||
let index = index_scheduler.index(&index_uid)?;
|
let index = index_scheduler.index(&index_uid)?;
|
||||||
let (total, documents) = retrieve_documents(&index, offset, limit, attributes_to_retrieve)?;
|
let (total, documents) = retrieve_documents(&index, offset.0, limit.0, attributes_to_retrieve)?;
|
||||||
|
|
||||||
let ret = PaginationView::new(offset, limit, total as usize, documents);
|
let ret = PaginationView::new(offset.0, limit.0, total as usize, documents);
|
||||||
|
|
||||||
debug!("returns: {:?}", ret);
|
debug!("returns: {:?}", ret);
|
||||||
Ok(HttpResponse::Ok().json(ret))
|
Ok(HttpResponse::Ok().json(ret))
|
||||||
|
@ -5,8 +5,10 @@ use actix_web::{web, HttpRequest, HttpResponse};
|
|||||||
use deserr::{DeserializeError, DeserializeFromValue, ValuePointerRef};
|
use deserr::{DeserializeError, DeserializeFromValue, ValuePointerRef};
|
||||||
use index_scheduler::IndexScheduler;
|
use index_scheduler::IndexScheduler;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use meilisearch_types::error::{deserr_codes::*, unwrap_any, Code, DeserrQueryParamError};
|
use meilisearch_types::deserr::query_params::Param;
|
||||||
use meilisearch_types::error::{DeserrJsonError, ResponseError, TakeErrorMessage};
|
use meilisearch_types::deserr::{DeserrJsonError, DeserrQueryParamError};
|
||||||
|
use meilisearch_types::error::ResponseError;
|
||||||
|
use meilisearch_types::error::{deserr_codes::*, unwrap_any, Code};
|
||||||
use meilisearch_types::index_uid::IndexUid;
|
use meilisearch_types::index_uid::IndexUid;
|
||||||
use meilisearch_types::milli::{self, FieldDistribution, Index};
|
use meilisearch_types::milli::{self, FieldDistribution, Index};
|
||||||
use meilisearch_types::tasks::KindWithContent;
|
use meilisearch_types::tasks::KindWithContent;
|
||||||
@ -14,7 +16,6 @@ use serde::{Deserialize, Serialize};
|
|||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
use self::search::parse_usize_take_error_message;
|
|
||||||
use super::{Pagination, SummarizedTaskView, PAGINATION_DEFAULT_LIMIT};
|
use super::{Pagination, SummarizedTaskView, PAGINATION_DEFAULT_LIMIT};
|
||||||
use crate::analytics::Analytics;
|
use crate::analytics::Analytics;
|
||||||
use crate::extractors::authentication::policies::*;
|
use crate::extractors::authentication::policies::*;
|
||||||
@ -71,20 +72,17 @@ impl IndexView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(DeserializeFromValue, Deserialize, Debug, Clone, Copy)]
|
#[derive(DeserializeFromValue, Debug, Clone, Copy)]
|
||||||
#[deserr(error = DeserrQueryParamError, rename_all = camelCase, deny_unknown_fields)]
|
#[deserr(error = DeserrQueryParamError, rename_all = camelCase, deny_unknown_fields)]
|
||||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
|
||||||
pub struct ListIndexes {
|
pub struct ListIndexes {
|
||||||
#[serde(default)]
|
#[deserr(default, error = DeserrQueryParamError<InvalidIndexOffset>)]
|
||||||
#[deserr(default, error = DeserrQueryParamError<InvalidIndexOffset>, from(&String) = parse_usize_take_error_message -> TakeErrorMessage<std::num::ParseIntError>)]
|
pub offset: Param<usize>,
|
||||||
pub offset: usize,
|
#[deserr(default = Param(PAGINATION_DEFAULT_LIMIT), error = DeserrQueryParamError<InvalidIndexLimit>)]
|
||||||
#[serde(default = "PAGINATION_DEFAULT_LIMIT")]
|
pub limit: Param<usize>,
|
||||||
#[deserr(default = PAGINATION_DEFAULT_LIMIT(), error = DeserrQueryParamError<InvalidIndexLimit>, from(&String) = parse_usize_take_error_message -> TakeErrorMessage<std::num::ParseIntError>)]
|
|
||||||
pub limit: usize,
|
|
||||||
}
|
}
|
||||||
impl ListIndexes {
|
impl ListIndexes {
|
||||||
fn as_pagination(self) -> Pagination {
|
fn as_pagination(self) -> Pagination {
|
||||||
Pagination { offset: self.offset, limit: self.limit }
|
Pagination { offset: self.offset.0, limit: self.limit.0 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,15 +1,12 @@
|
|||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use actix_web::web::Data;
|
use actix_web::web::Data;
|
||||||
use actix_web::{web, HttpRequest, HttpResponse};
|
use actix_web::{web, HttpRequest, HttpResponse};
|
||||||
use index_scheduler::IndexScheduler;
|
use index_scheduler::IndexScheduler;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use meilisearch_auth::IndexSearchRules;
|
use meilisearch_auth::IndexSearchRules;
|
||||||
use meilisearch_types::error::{
|
use meilisearch_types::deserr::{DeserrQueryParamError, DeserrJsonError};
|
||||||
deserr_codes::*, parse_option_usize_query_param, parse_usize_query_param,
|
use meilisearch_types::deserr::query_params::Param;
|
||||||
DeserrQueryParamError, DetailedParseIntError,
|
use meilisearch_types::error::deserr_codes::*;
|
||||||
};
|
use meilisearch_types::error::ResponseError;
|
||||||
use meilisearch_types::error::{DeserrJsonError, ResponseError, TakeErrorMessage};
|
|
||||||
use meilisearch_types::serde_cs::vec::CS;
|
use meilisearch_types::serde_cs::vec::CS;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
@ -33,45 +30,33 @@ pub fn configure(cfg: &mut web::ServiceConfig) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_usize_take_error_message(
|
|
||||||
s: &str,
|
|
||||||
) -> Result<usize, TakeErrorMessage<std::num::ParseIntError>> {
|
|
||||||
usize::from_str(s).map_err(TakeErrorMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_bool_take_error_message(
|
|
||||||
s: &str,
|
|
||||||
) -> Result<bool, TakeErrorMessage<std::str::ParseBoolError>> {
|
|
||||||
s.parse().map_err(TakeErrorMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, deserr::DeserializeFromValue)]
|
#[derive(Debug, deserr::DeserializeFromValue)]
|
||||||
#[deserr(error = DeserrQueryParamError, rename_all = camelCase, deny_unknown_fields)]
|
#[deserr(error = DeserrQueryParamError, rename_all = camelCase, deny_unknown_fields)]
|
||||||
pub struct SearchQueryGet {
|
pub struct SearchQueryGet {
|
||||||
#[deserr(default, error = DeserrQueryParamError<InvalidSearchQ>)]
|
#[deserr(default, error = DeserrQueryParamError<InvalidSearchQ>)]
|
||||||
q: Option<String>,
|
q: Option<String>,
|
||||||
#[deserr(default = DEFAULT_SEARCH_OFFSET(), error = DeserrQueryParamError<InvalidSearchOffset>, from(String) = parse_usize_query_param -> TakeErrorMessage<DetailedParseIntError>)]
|
#[deserr(default = Param(DEFAULT_SEARCH_OFFSET()), error = DeserrQueryParamError<InvalidSearchOffset>)]
|
||||||
offset: usize,
|
offset: Param<usize>,
|
||||||
#[deserr(default = DEFAULT_SEARCH_LIMIT(), error = DeserrQueryParamError<InvalidSearchLimit>, from(String) = parse_usize_query_param -> TakeErrorMessage<DetailedParseIntError>)]
|
#[deserr(default = Param(DEFAULT_SEARCH_LIMIT()), error = DeserrQueryParamError<InvalidSearchLimit>)]
|
||||||
limit: usize,
|
limit: Param<usize>,
|
||||||
#[deserr(default, error = DeserrQueryParamError<InvalidSearchPage>, from(Option<String>) = parse_option_usize_query_param -> TakeErrorMessage<std::num::ParseIntError>)]
|
#[deserr(default, error = DeserrQueryParamError<InvalidSearchPage>)]
|
||||||
page: Option<usize>,
|
page: Option<Param<usize>>,
|
||||||
#[deserr(default, error = DeserrQueryParamError<InvalidSearchHitsPerPage>, from(Option<String>) = parse_option_usize_query_param -> TakeErrorMessage<std::num::ParseIntError>)]
|
#[deserr(default, error = DeserrQueryParamError<InvalidSearchHitsPerPage>)]
|
||||||
hits_per_page: Option<usize>,
|
hits_per_page: Option<Param<usize>>,
|
||||||
#[deserr(default, error = DeserrQueryParamError<InvalidSearchAttributesToRetrieve>)]
|
#[deserr(default, error = DeserrQueryParamError<InvalidSearchAttributesToRetrieve>)]
|
||||||
attributes_to_retrieve: Option<CS<String>>,
|
attributes_to_retrieve: Option<CS<String>>,
|
||||||
#[deserr(default, error = DeserrQueryParamError<InvalidSearchAttributesToCrop>)]
|
#[deserr(default, error = DeserrQueryParamError<InvalidSearchAttributesToCrop>)]
|
||||||
attributes_to_crop: Option<CS<String>>,
|
attributes_to_crop: Option<CS<String>>,
|
||||||
#[deserr(default = DEFAULT_CROP_LENGTH(), error = DeserrQueryParamError<InvalidSearchCropLength>, from(String) = parse_usize_query_param -> TakeErrorMessage<DetailedParseIntError>)]
|
#[deserr(default = Param(DEFAULT_CROP_LENGTH()), error = DeserrQueryParamError<InvalidSearchCropLength>)]
|
||||||
crop_length: usize,
|
crop_length: Param<usize>,
|
||||||
#[deserr(default, error = DeserrQueryParamError<InvalidSearchAttributesToHighlight>)]
|
#[deserr(default, error = DeserrQueryParamError<InvalidSearchAttributesToHighlight>)]
|
||||||
attributes_to_highlight: Option<CS<String>>,
|
attributes_to_highlight: Option<CS<String>>,
|
||||||
#[deserr(default, error = DeserrQueryParamError<InvalidSearchFilter>)]
|
#[deserr(default, error = DeserrQueryParamError<InvalidSearchFilter>)]
|
||||||
filter: Option<String>,
|
filter: Option<String>,
|
||||||
#[deserr(default, error = DeserrQueryParamError<InvalidSearchSort>)]
|
#[deserr(default, error = DeserrQueryParamError<InvalidSearchSort>)]
|
||||||
sort: Option<String>,
|
sort: Option<String>,
|
||||||
#[deserr(default, error = DeserrQueryParamError<InvalidSearchShowMatchesPosition>, from(&String) = parse_bool_take_error_message -> TakeErrorMessage<std::str::ParseBoolError>)]
|
#[deserr(default, error = DeserrQueryParamError<InvalidSearchShowMatchesPosition>)]
|
||||||
show_matches_position: bool,
|
show_matches_position: Param<bool>,
|
||||||
#[deserr(default, error = DeserrQueryParamError<InvalidSearchFacets>)]
|
#[deserr(default, error = DeserrQueryParamError<InvalidSearchFacets>)]
|
||||||
facets: Option<CS<String>>,
|
facets: Option<CS<String>>,
|
||||||
#[deserr( default = DEFAULT_HIGHLIGHT_PRE_TAG(), error = DeserrQueryParamError<InvalidSearchHighlightPreTag>)]
|
#[deserr( default = DEFAULT_HIGHLIGHT_PRE_TAG(), error = DeserrQueryParamError<InvalidSearchHighlightPreTag>)]
|
||||||
@ -96,17 +81,17 @@ impl From<SearchQueryGet> for SearchQuery {
|
|||||||
|
|
||||||
Self {
|
Self {
|
||||||
q: other.q,
|
q: other.q,
|
||||||
offset: other.offset,
|
offset: other.offset.0,
|
||||||
limit: other.limit,
|
limit: other.limit.0,
|
||||||
page: other.page,
|
page: other.page.as_deref().copied(),
|
||||||
hits_per_page: other.hits_per_page,
|
hits_per_page: other.hits_per_page.as_deref().copied(),
|
||||||
attributes_to_retrieve: other.attributes_to_retrieve.map(|o| o.into_iter().collect()),
|
attributes_to_retrieve: other.attributes_to_retrieve.map(|o| o.into_iter().collect()),
|
||||||
attributes_to_crop: other.attributes_to_crop.map(|o| o.into_iter().collect()),
|
attributes_to_crop: other.attributes_to_crop.map(|o| o.into_iter().collect()),
|
||||||
crop_length: other.crop_length,
|
crop_length: other.crop_length.0,
|
||||||
attributes_to_highlight: other.attributes_to_highlight.map(|o| o.into_iter().collect()),
|
attributes_to_highlight: other.attributes_to_highlight.map(|o| o.into_iter().collect()),
|
||||||
filter,
|
filter,
|
||||||
sort: other.sort.map(|attr| fix_sort_query_parameters(&attr)),
|
sort: other.sort.map(|attr| fix_sort_query_parameters(&attr)),
|
||||||
show_matches_position: other.show_matches_position,
|
show_matches_position: other.show_matches_position.0,
|
||||||
facets: other.facets.map(|o| o.into_iter().collect()),
|
facets: other.facets.map(|o| o.into_iter().collect()),
|
||||||
highlight_pre_tag: other.highlight_pre_tag,
|
highlight_pre_tag: other.highlight_pre_tag,
|
||||||
highlight_post_tag: other.highlight_post_tag,
|
highlight_post_tag: other.highlight_post_tag,
|
||||||
|
@ -2,7 +2,8 @@ use actix_web::web::Data;
|
|||||||
use actix_web::{web, HttpRequest, HttpResponse};
|
use actix_web::{web, HttpRequest, HttpResponse};
|
||||||
use index_scheduler::IndexScheduler;
|
use index_scheduler::IndexScheduler;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use meilisearch_types::error::{DeserrJsonError, ResponseError};
|
use meilisearch_types::deserr::DeserrJsonError;
|
||||||
|
use meilisearch_types::error::ResponseError;
|
||||||
use meilisearch_types::index_uid::IndexUid;
|
use meilisearch_types::index_uid::IndexUid;
|
||||||
use meilisearch_types::settings::{settings, RankingRuleView, Settings, Unchecked};
|
use meilisearch_types::settings::{settings, RankingRuleView, Settings, Unchecked};
|
||||||
use meilisearch_types::tasks::KindWithContent;
|
use meilisearch_types::tasks::KindWithContent;
|
||||||
@ -130,7 +131,7 @@ make_setting_route!(
|
|||||||
"/filterable-attributes",
|
"/filterable-attributes",
|
||||||
put,
|
put,
|
||||||
std::collections::BTreeSet<String>,
|
std::collections::BTreeSet<String>,
|
||||||
meilisearch_types::error::DeserrJsonError<
|
meilisearch_types::deserr::DeserrJsonError<
|
||||||
meilisearch_types::error::deserr_codes::InvalidSettingsFilterableAttributes,
|
meilisearch_types::error::deserr_codes::InvalidSettingsFilterableAttributes,
|
||||||
>,
|
>,
|
||||||
filterable_attributes,
|
filterable_attributes,
|
||||||
@ -156,7 +157,7 @@ make_setting_route!(
|
|||||||
"/sortable-attributes",
|
"/sortable-attributes",
|
||||||
put,
|
put,
|
||||||
std::collections::BTreeSet<String>,
|
std::collections::BTreeSet<String>,
|
||||||
meilisearch_types::error::DeserrJsonError<
|
meilisearch_types::deserr::DeserrJsonError<
|
||||||
meilisearch_types::error::deserr_codes::InvalidSettingsSortableAttributes,
|
meilisearch_types::error::deserr_codes::InvalidSettingsSortableAttributes,
|
||||||
>,
|
>,
|
||||||
sortable_attributes,
|
sortable_attributes,
|
||||||
@ -182,7 +183,7 @@ make_setting_route!(
|
|||||||
"/displayed-attributes",
|
"/displayed-attributes",
|
||||||
put,
|
put,
|
||||||
Vec<String>,
|
Vec<String>,
|
||||||
meilisearch_types::error::DeserrJsonError<
|
meilisearch_types::deserr::DeserrJsonError<
|
||||||
meilisearch_types::error::deserr_codes::InvalidSettingsDisplayedAttributes,
|
meilisearch_types::error::deserr_codes::InvalidSettingsDisplayedAttributes,
|
||||||
>,
|
>,
|
||||||
displayed_attributes,
|
displayed_attributes,
|
||||||
@ -208,7 +209,7 @@ make_setting_route!(
|
|||||||
"/typo-tolerance",
|
"/typo-tolerance",
|
||||||
patch,
|
patch,
|
||||||
meilisearch_types::settings::TypoSettings,
|
meilisearch_types::settings::TypoSettings,
|
||||||
meilisearch_types::error::DeserrJsonError<
|
meilisearch_types::deserr::DeserrJsonError<
|
||||||
meilisearch_types::error::deserr_codes::InvalidSettingsTypoTolerance,
|
meilisearch_types::error::deserr_codes::InvalidSettingsTypoTolerance,
|
||||||
>,
|
>,
|
||||||
typo_tolerance,
|
typo_tolerance,
|
||||||
@ -253,7 +254,7 @@ make_setting_route!(
|
|||||||
"/searchable-attributes",
|
"/searchable-attributes",
|
||||||
put,
|
put,
|
||||||
Vec<String>,
|
Vec<String>,
|
||||||
meilisearch_types::error::DeserrJsonError<
|
meilisearch_types::deserr::DeserrJsonError<
|
||||||
meilisearch_types::error::deserr_codes::InvalidSettingsSearchableAttributes,
|
meilisearch_types::error::deserr_codes::InvalidSettingsSearchableAttributes,
|
||||||
>,
|
>,
|
||||||
searchable_attributes,
|
searchable_attributes,
|
||||||
@ -279,7 +280,7 @@ make_setting_route!(
|
|||||||
"/stop-words",
|
"/stop-words",
|
||||||
put,
|
put,
|
||||||
std::collections::BTreeSet<String>,
|
std::collections::BTreeSet<String>,
|
||||||
meilisearch_types::error::DeserrJsonError<
|
meilisearch_types::deserr::DeserrJsonError<
|
||||||
meilisearch_types::error::deserr_codes::InvalidSettingsStopWords,
|
meilisearch_types::error::deserr_codes::InvalidSettingsStopWords,
|
||||||
>,
|
>,
|
||||||
stop_words,
|
stop_words,
|
||||||
@ -304,7 +305,7 @@ make_setting_route!(
|
|||||||
"/synonyms",
|
"/synonyms",
|
||||||
put,
|
put,
|
||||||
std::collections::BTreeMap<String, Vec<String>>,
|
std::collections::BTreeMap<String, Vec<String>>,
|
||||||
meilisearch_types::error::DeserrJsonError<
|
meilisearch_types::deserr::DeserrJsonError<
|
||||||
meilisearch_types::error::deserr_codes::InvalidSettingsSynonyms,
|
meilisearch_types::error::deserr_codes::InvalidSettingsSynonyms,
|
||||||
>,
|
>,
|
||||||
synonyms,
|
synonyms,
|
||||||
@ -329,7 +330,7 @@ make_setting_route!(
|
|||||||
"/distinct-attribute",
|
"/distinct-attribute",
|
||||||
put,
|
put,
|
||||||
String,
|
String,
|
||||||
meilisearch_types::error::DeserrJsonError<
|
meilisearch_types::deserr::DeserrJsonError<
|
||||||
meilisearch_types::error::deserr_codes::InvalidSettingsDistinctAttribute,
|
meilisearch_types::error::deserr_codes::InvalidSettingsDistinctAttribute,
|
||||||
>,
|
>,
|
||||||
distinct_attribute,
|
distinct_attribute,
|
||||||
@ -353,7 +354,7 @@ make_setting_route!(
|
|||||||
"/ranking-rules",
|
"/ranking-rules",
|
||||||
put,
|
put,
|
||||||
Vec<meilisearch_types::settings::RankingRuleView>,
|
Vec<meilisearch_types::settings::RankingRuleView>,
|
||||||
meilisearch_types::error::DeserrJsonError<
|
meilisearch_types::deserr::DeserrJsonError<
|
||||||
meilisearch_types::error::deserr_codes::InvalidSettingsRankingRules,
|
meilisearch_types::error::deserr_codes::InvalidSettingsRankingRules,
|
||||||
>,
|
>,
|
||||||
ranking_rules,
|
ranking_rules,
|
||||||
@ -384,7 +385,7 @@ make_setting_route!(
|
|||||||
"/faceting",
|
"/faceting",
|
||||||
patch,
|
patch,
|
||||||
meilisearch_types::settings::FacetingSettings,
|
meilisearch_types::settings::FacetingSettings,
|
||||||
meilisearch_types::error::DeserrJsonError<
|
meilisearch_types::deserr::DeserrJsonError<
|
||||||
meilisearch_types::error::deserr_codes::InvalidSettingsFaceting,
|
meilisearch_types::error::deserr_codes::InvalidSettingsFaceting,
|
||||||
>,
|
>,
|
||||||
faceting,
|
faceting,
|
||||||
@ -409,7 +410,7 @@ make_setting_route!(
|
|||||||
"/pagination",
|
"/pagination",
|
||||||
patch,
|
patch,
|
||||||
meilisearch_types::settings::PaginationSettings,
|
meilisearch_types::settings::PaginationSettings,
|
||||||
meilisearch_types::error::DeserrJsonError<
|
meilisearch_types::deserr::DeserrJsonError<
|
||||||
meilisearch_types::error::deserr_codes::InvalidSettingsPagination,
|
meilisearch_types::error::deserr_codes::InvalidSettingsPagination,
|
||||||
>,
|
>,
|
||||||
pagination,
|
pagination,
|
||||||
|
@ -41,7 +41,7 @@ where
|
|||||||
Ok(Some(input.parse()?))
|
Ok(Some(input.parse()?))
|
||||||
}
|
}
|
||||||
|
|
||||||
const PAGINATION_DEFAULT_LIMIT: fn() -> usize = || 20;
|
const PAGINATION_DEFAULT_LIMIT: usize = 20;
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
@ -2,8 +2,9 @@ use actix_web::web::Data;
|
|||||||
use actix_web::{web, HttpRequest, HttpResponse};
|
use actix_web::{web, HttpRequest, HttpResponse};
|
||||||
use deserr::DeserializeFromValue;
|
use deserr::DeserializeFromValue;
|
||||||
use index_scheduler::IndexScheduler;
|
use index_scheduler::IndexScheduler;
|
||||||
|
use meilisearch_types::deserr::DeserrJsonError;
|
||||||
use meilisearch_types::error::deserr_codes::InvalidSwapIndexes;
|
use meilisearch_types::error::deserr_codes::InvalidSwapIndexes;
|
||||||
use meilisearch_types::error::{DeserrJsonError, ResponseError};
|
use meilisearch_types::error::ResponseError;
|
||||||
use meilisearch_types::tasks::{IndexSwap, KindWithContent};
|
use meilisearch_types::tasks::{IndexSwap, KindWithContent};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
@ -22,7 +23,7 @@ pub fn configure(cfg: &mut web::ServiceConfig) {
|
|||||||
#[derive(DeserializeFromValue, Debug, Clone, PartialEq, Eq)]
|
#[derive(DeserializeFromValue, Debug, Clone, PartialEq, Eq)]
|
||||||
#[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields)]
|
#[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields)]
|
||||||
pub struct SwapIndexesPayload {
|
pub struct SwapIndexesPayload {
|
||||||
#[deserr(error = DeserrJsonError<InvalidSwapIndexes>, missing_field_error = DeserrJsonError::missing_swap_indexes_indexes)]
|
#[deserr(error = DeserrJsonError<InvalidSwapIndexes>, missing_field_error = DeserrJsonError::missing_swap_indexes)]
|
||||||
indexes: Vec<String>,
|
indexes: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,21 +2,17 @@ use actix_web::web::Data;
|
|||||||
use actix_web::{web, HttpRequest, HttpResponse};
|
use actix_web::{web, HttpRequest, HttpResponse};
|
||||||
use deserr::DeserializeFromValue;
|
use deserr::DeserializeFromValue;
|
||||||
use index_scheduler::{IndexScheduler, Query, TaskId};
|
use index_scheduler::{IndexScheduler, Query, TaskId};
|
||||||
use meilisearch_types::error::{
|
use meilisearch_types::deserr::query_params::Param;
|
||||||
deserr_codes::*, parse_option_cs_star_or, parse_option_u32_query_param,
|
use meilisearch_types::deserr::DeserrQueryParamError;
|
||||||
parse_option_vec_u32_query_param, DeserrQueryParamError, DetailedParseIntError,
|
use meilisearch_types::error::ResponseError;
|
||||||
TakeErrorMessage,
|
use meilisearch_types::error::{deserr_codes::*, InvalidTaskDateError};
|
||||||
};
|
|
||||||
use meilisearch_types::error::{parse_u32_query_param, ResponseError};
|
|
||||||
use meilisearch_types::index_uid::IndexUid;
|
use meilisearch_types::index_uid::IndexUid;
|
||||||
use meilisearch_types::serde_cs;
|
|
||||||
use meilisearch_types::settings::{Settings, Unchecked};
|
use meilisearch_types::settings::{Settings, Unchecked};
|
||||||
use meilisearch_types::star_or::StarOr;
|
use meilisearch_types::star_or::{OptionStarOr, OptionStarOrList};
|
||||||
use meilisearch_types::tasks::{
|
use meilisearch_types::tasks::{
|
||||||
serialize_duration, Details, IndexSwap, Kind, KindWithContent, Status, Task,
|
serialize_duration, Details, IndexSwap, Kind, KindWithContent, Status, Task,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::Serialize;
|
||||||
use serde_cs::vec::CS;
|
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use time::format_description::well_known::Rfc3339;
|
use time::format_description::well_known::Rfc3339;
|
||||||
use time::macros::format_description;
|
use time::macros::format_description;
|
||||||
@ -30,7 +26,7 @@ use crate::extractors::authentication::GuardedData;
|
|||||||
use crate::extractors::query_parameters::QueryParameter;
|
use crate::extractors::query_parameters::QueryParameter;
|
||||||
use crate::extractors::sequential_extractor::SeqHandler;
|
use crate::extractors::sequential_extractor::SeqHandler;
|
||||||
|
|
||||||
const DEFAULT_LIMIT: fn() -> u32 = || 20;
|
const DEFAULT_LIMIT: u32 = 20;
|
||||||
|
|
||||||
pub fn configure(cfg: &mut web::ServiceConfig) {
|
pub fn configure(cfg: &mut web::ServiceConfig) {
|
||||||
cfg.service(
|
cfg.service(
|
||||||
@ -169,62 +165,121 @@ impl From<Details> for DetailsView {
|
|||||||
#[derive(Debug, DeserializeFromValue)]
|
#[derive(Debug, DeserializeFromValue)]
|
||||||
#[deserr(error = DeserrQueryParamError, rename_all = camelCase, deny_unknown_fields)]
|
#[deserr(error = DeserrQueryParamError, rename_all = camelCase, deny_unknown_fields)]
|
||||||
pub struct TasksFilterQuery {
|
pub struct TasksFilterQuery {
|
||||||
#[deserr(default = DEFAULT_LIMIT(), error = DeserrQueryParamError<InvalidTaskLimit>, from(String) = parse_u32_query_param -> TakeErrorMessage<DetailedParseIntError>)]
|
#[deserr(default = Param(DEFAULT_LIMIT), error = DeserrQueryParamError<InvalidTaskLimit>)]
|
||||||
pub limit: u32,
|
pub limit: Param<u32>,
|
||||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskFrom>, from(Option<String>) = parse_option_u32_query_param -> TakeErrorMessage<DetailedParseIntError>)]
|
#[deserr(default, error = DeserrQueryParamError<InvalidTaskFrom>)]
|
||||||
pub from: Option<TaskId>,
|
pub from: Option<Param<TaskId>>,
|
||||||
|
|
||||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskUids>, from(Option<CS<String>>) = parse_option_vec_u32_query_param -> TakeErrorMessage<DetailedParseIntError>)]
|
#[deserr(default, error = DeserrQueryParamError<InvalidTaskUids>)]
|
||||||
pub uids: Option<Vec<u32>>,
|
pub uids: OptionStarOrList<u32>,
|
||||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskCanceledBy>, from(Option<CS<String>>) = parse_option_vec_u32_query_param -> TakeErrorMessage<DetailedParseIntError>)]
|
#[deserr(default, error = DeserrQueryParamError<InvalidTaskCanceledBy>)]
|
||||||
pub canceled_by: Option<Vec<u32>>,
|
pub canceled_by: OptionStarOrList<u32>,
|
||||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskTypes>, default = None, from(Option<CS<StarOr<String>>>) = parse_option_cs_star_or::<Kind> -> TakeErrorMessage<ResponseError>)]
|
#[deserr(default, error = DeserrQueryParamError<InvalidTaskTypes>)]
|
||||||
pub types: Option<Vec<Kind>>,
|
pub types: OptionStarOrList<Kind>,
|
||||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskStatuses>, default = None, from(Option<CS<StarOr<String>>>) = parse_option_cs_star_or::<Status> -> TakeErrorMessage<ResponseError>)]
|
#[deserr(default, error = DeserrQueryParamError<InvalidTaskStatuses>)]
|
||||||
pub statuses: Option<Vec<Status>>,
|
pub statuses: OptionStarOrList<Status>,
|
||||||
#[deserr(default, error = DeserrQueryParamError<InvalidIndexUid>, default = None, from(Option<CS<StarOr<String>>>) = parse_option_cs_star_or::<IndexUid> -> TakeErrorMessage<ResponseError>)]
|
#[deserr(default, error = DeserrQueryParamError<InvalidIndexUid>)]
|
||||||
pub index_uids: Option<Vec<IndexUid>>,
|
pub index_uids: OptionStarOrList<IndexUid>,
|
||||||
|
|
||||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskAfterEnqueuedAt>, default = None, from(Option<String>) = deserialize_date_after -> TakeErrorMessage<InvalidTaskDateError>)]
|
#[deserr(default, error = DeserrQueryParamError<InvalidTaskAfterEnqueuedAt>, from(OptionStarOr<String>) = deserialize_date_after -> InvalidTaskDateError)]
|
||||||
pub after_enqueued_at: Option<OffsetDateTime>,
|
pub after_enqueued_at: OptionStarOr<OffsetDateTime>,
|
||||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskBeforeEnqueuedAt>, default = None, from(Option<String>) = deserialize_date_before -> TakeErrorMessage<InvalidTaskDateError>)]
|
#[deserr(default, error = DeserrQueryParamError<InvalidTaskBeforeEnqueuedAt>, from(OptionStarOr<String>) = deserialize_date_before -> InvalidTaskDateError)]
|
||||||
pub before_enqueued_at: Option<OffsetDateTime>,
|
pub before_enqueued_at: OptionStarOr<OffsetDateTime>,
|
||||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskAfterStartedAt>, default = None, from(Option<String>) = deserialize_date_after -> TakeErrorMessage<InvalidTaskDateError>)]
|
#[deserr(default, error = DeserrQueryParamError<InvalidTaskAfterStartedAt>, from(OptionStarOr<String>) = deserialize_date_after -> InvalidTaskDateError)]
|
||||||
pub after_started_at: Option<OffsetDateTime>,
|
pub after_started_at: OptionStarOr<OffsetDateTime>,
|
||||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskBeforeStartedAt>, default = None, from(Option<String>) = deserialize_date_before -> TakeErrorMessage<InvalidTaskDateError>)]
|
#[deserr(default, error = DeserrQueryParamError<InvalidTaskBeforeStartedAt>, from(OptionStarOr<String>) = deserialize_date_before -> InvalidTaskDateError)]
|
||||||
pub before_started_at: Option<OffsetDateTime>,
|
pub before_started_at: OptionStarOr<OffsetDateTime>,
|
||||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskAfterFinishedAt>, default = None, from(Option<String>) = deserialize_date_after -> TakeErrorMessage<InvalidTaskDateError>)]
|
#[deserr(default, error = DeserrQueryParamError<InvalidTaskAfterFinishedAt>, from(OptionStarOr<String>) = deserialize_date_after -> InvalidTaskDateError)]
|
||||||
pub after_finished_at: Option<OffsetDateTime>,
|
pub after_finished_at: OptionStarOr<OffsetDateTime>,
|
||||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskBeforeFinishedAt>, default = None, from(Option<String>) = deserialize_date_before -> TakeErrorMessage<InvalidTaskDateError>)]
|
#[deserr(default, error = DeserrQueryParamError<InvalidTaskBeforeFinishedAt>, from(OptionStarOr<String>) = deserialize_date_before -> InvalidTaskDateError)]
|
||||||
pub before_finished_at: Option<OffsetDateTime>,
|
pub before_finished_at: OptionStarOr<OffsetDateTime>,
|
||||||
|
}
|
||||||
|
impl TasksFilterQuery {
|
||||||
|
fn into_query(self) -> Query {
|
||||||
|
Query {
|
||||||
|
limit: Some(self.limit.0),
|
||||||
|
from: self.from.as_deref().copied(),
|
||||||
|
statuses: self.statuses.merge_star_and_none(),
|
||||||
|
types: self.types.merge_star_and_none(),
|
||||||
|
index_uids: self.index_uids.map(|x| x.to_string()).merge_star_and_none(),
|
||||||
|
uids: self.uids.merge_star_and_none(),
|
||||||
|
canceled_by: self.canceled_by.merge_star_and_none(),
|
||||||
|
before_enqueued_at: self.before_enqueued_at.merge_star_and_none(),
|
||||||
|
after_enqueued_at: self.after_enqueued_at.merge_star_and_none(),
|
||||||
|
before_started_at: self.before_started_at.merge_star_and_none(),
|
||||||
|
after_started_at: self.after_started_at.merge_star_and_none(),
|
||||||
|
before_finished_at: self.before_finished_at.merge_star_and_none(),
|
||||||
|
after_finished_at: self.after_finished_at.merge_star_and_none(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug, DeserializeFromValue)]
|
impl TaskDeletionOrCancelationQuery {
|
||||||
|
fn is_empty(&self) -> bool {
|
||||||
|
matches!(
|
||||||
|
self,
|
||||||
|
TaskDeletionOrCancelationQuery {
|
||||||
|
uids: OptionStarOrList::None,
|
||||||
|
canceled_by: OptionStarOrList::None,
|
||||||
|
types: OptionStarOrList::None,
|
||||||
|
statuses: OptionStarOrList::None,
|
||||||
|
index_uids: OptionStarOrList::None,
|
||||||
|
after_enqueued_at: OptionStarOr::None,
|
||||||
|
before_enqueued_at: OptionStarOr::None,
|
||||||
|
after_started_at: OptionStarOr::None,
|
||||||
|
before_started_at: OptionStarOr::None,
|
||||||
|
after_finished_at: OptionStarOr::None,
|
||||||
|
before_finished_at: OptionStarOr::None
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, DeserializeFromValue)]
|
||||||
#[deserr(error = DeserrQueryParamError, rename_all = camelCase, deny_unknown_fields)]
|
#[deserr(error = DeserrQueryParamError, rename_all = camelCase, deny_unknown_fields)]
|
||||||
pub struct TaskDeletionOrCancelationQuery {
|
pub struct TaskDeletionOrCancelationQuery {
|
||||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskUids>, from(Option<CS<String>>) = parse_option_vec_u32_query_param -> TakeErrorMessage<DetailedParseIntError>)]
|
#[deserr(default, error = DeserrQueryParamError<InvalidTaskUids>)]
|
||||||
pub uids: Option<Vec<u32>>,
|
pub uids: OptionStarOrList<u32>,
|
||||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskCanceledBy>, from(Option<CS<String>>) = parse_option_vec_u32_query_param -> TakeErrorMessage<DetailedParseIntError>)]
|
#[deserr(default, error = DeserrQueryParamError<InvalidTaskCanceledBy>)]
|
||||||
pub canceled_by: Option<Vec<u32>>,
|
pub canceled_by: OptionStarOrList<u32>,
|
||||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskTypes>, default = None, from(Option<CS<StarOr<String>>>) = parse_option_cs_star_or::<Kind> -> TakeErrorMessage<ResponseError>)]
|
#[deserr(default, error = DeserrQueryParamError<InvalidTaskTypes>)]
|
||||||
pub types: Option<Vec<Kind>>,
|
pub types: OptionStarOrList<Kind>,
|
||||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskStatuses>, default = None, from(Option<CS<StarOr<String>>>) = parse_option_cs_star_or::<Status> -> TakeErrorMessage<ResponseError>)]
|
#[deserr(default, error = DeserrQueryParamError<InvalidTaskStatuses>)]
|
||||||
pub statuses: Option<Vec<Status>>,
|
pub statuses: OptionStarOrList<Status>,
|
||||||
#[deserr(default, error = DeserrQueryParamError<InvalidIndexUid>, default = None, from(Option<CS<StarOr<String>>>) = parse_option_cs_star_or::<IndexUid> -> TakeErrorMessage<ResponseError>)]
|
#[deserr(default, error = DeserrQueryParamError<InvalidIndexUid>)]
|
||||||
pub index_uids: Option<Vec<IndexUid>>,
|
pub index_uids: OptionStarOrList<IndexUid>,
|
||||||
|
|
||||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskAfterEnqueuedAt>, default = None, from(Option<String>) = deserialize_date_after -> TakeErrorMessage<InvalidTaskDateError>)]
|
#[deserr(default, error = DeserrQueryParamError<InvalidTaskAfterEnqueuedAt>, from(OptionStarOr<String>) = deserialize_date_after -> InvalidTaskDateError)]
|
||||||
pub after_enqueued_at: Option<OffsetDateTime>,
|
pub after_enqueued_at: OptionStarOr<OffsetDateTime>,
|
||||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskBeforeEnqueuedAt>, default = None, from(Option<String>) = deserialize_date_before -> TakeErrorMessage<InvalidTaskDateError>)]
|
#[deserr(default, error = DeserrQueryParamError<InvalidTaskBeforeEnqueuedAt>, from(OptionStarOr<String>) = deserialize_date_before -> InvalidTaskDateError)]
|
||||||
pub before_enqueued_at: Option<OffsetDateTime>,
|
pub before_enqueued_at: OptionStarOr<OffsetDateTime>,
|
||||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskAfterStartedAt>, default = None, from(Option<String>) = deserialize_date_after -> TakeErrorMessage<InvalidTaskDateError>)]
|
#[deserr(default, error = DeserrQueryParamError<InvalidTaskAfterStartedAt>, from(OptionStarOr<String>) = deserialize_date_after -> InvalidTaskDateError)]
|
||||||
pub after_started_at: Option<OffsetDateTime>,
|
pub after_started_at: OptionStarOr<OffsetDateTime>,
|
||||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskBeforeStartedAt>, default = None, from(Option<String>) = deserialize_date_before -> TakeErrorMessage<InvalidTaskDateError>)]
|
#[deserr(default, error = DeserrQueryParamError<InvalidTaskBeforeStartedAt>, from(OptionStarOr<String>) = deserialize_date_before -> InvalidTaskDateError)]
|
||||||
pub before_started_at: Option<OffsetDateTime>,
|
pub before_started_at: OptionStarOr<OffsetDateTime>,
|
||||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskAfterFinishedAt>, default = None, from(Option<String>) = deserialize_date_after -> TakeErrorMessage<InvalidTaskDateError>)]
|
#[deserr(default, error = DeserrQueryParamError<InvalidTaskAfterFinishedAt>, from(OptionStarOr<String>) = deserialize_date_after -> InvalidTaskDateError)]
|
||||||
pub after_finished_at: Option<OffsetDateTime>,
|
pub after_finished_at: OptionStarOr<OffsetDateTime>,
|
||||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskBeforeFinishedAt>, default = None, from(Option<String>) = deserialize_date_before -> TakeErrorMessage<InvalidTaskDateError>)]
|
#[deserr(default, error = DeserrQueryParamError<InvalidTaskBeforeFinishedAt>, from(OptionStarOr<String>) = deserialize_date_before -> InvalidTaskDateError)]
|
||||||
pub before_finished_at: Option<OffsetDateTime>,
|
pub before_finished_at: OptionStarOr<OffsetDateTime>,
|
||||||
|
}
|
||||||
|
impl TaskDeletionOrCancelationQuery {
|
||||||
|
fn into_query(self) -> Query {
|
||||||
|
Query {
|
||||||
|
limit: None,
|
||||||
|
from: None,
|
||||||
|
statuses: self.statuses.merge_star_and_none(),
|
||||||
|
types: self.types.merge_star_and_none(),
|
||||||
|
index_uids: self.index_uids.map(|x| x.to_string()).merge_star_and_none(),
|
||||||
|
uids: self.uids.merge_star_and_none(),
|
||||||
|
canceled_by: self.canceled_by.merge_star_and_none(),
|
||||||
|
before_enqueued_at: self.before_enqueued_at.merge_star_and_none(),
|
||||||
|
after_enqueued_at: self.after_enqueued_at.merge_star_and_none(),
|
||||||
|
before_started_at: self.before_started_at.merge_star_and_none(),
|
||||||
|
after_started_at: self.after_started_at.merge_star_and_none(),
|
||||||
|
before_finished_at: self.before_finished_at.merge_star_and_none(),
|
||||||
|
after_finished_at: self.after_finished_at.merge_star_and_none(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn cancel_tasks(
|
async fn cancel_tasks(
|
||||||
@ -233,57 +288,31 @@ async fn cancel_tasks(
|
|||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
analytics: web::Data<dyn Analytics>,
|
analytics: web::Data<dyn Analytics>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
let TaskDeletionOrCancelationQuery {
|
let params = params.into_inner();
|
||||||
types,
|
|
||||||
uids,
|
if params.is_empty() {
|
||||||
canceled_by,
|
return Err(index_scheduler::Error::TaskCancelationWithEmptyQuery.into());
|
||||||
statuses,
|
}
|
||||||
index_uids,
|
|
||||||
after_enqueued_at,
|
|
||||||
before_enqueued_at,
|
|
||||||
after_started_at,
|
|
||||||
before_started_at,
|
|
||||||
after_finished_at,
|
|
||||||
before_finished_at,
|
|
||||||
} = params.into_inner();
|
|
||||||
|
|
||||||
analytics.publish(
|
analytics.publish(
|
||||||
"Tasks Canceled".to_string(),
|
"Tasks Canceled".to_string(),
|
||||||
json!({
|
json!({
|
||||||
"filtered_by_uid": uids.is_some(),
|
"filtered_by_uid": params.uids.is_some(),
|
||||||
"filtered_by_index_uid": index_uids.is_some(),
|
"filtered_by_index_uid": params.index_uids.is_some(),
|
||||||
"filtered_by_type": types.is_some(),
|
"filtered_by_type": params.types.is_some(),
|
||||||
"filtered_by_status": statuses.is_some(),
|
"filtered_by_status": params.statuses.is_some(),
|
||||||
"filtered_by_canceled_by": canceled_by.is_some(),
|
"filtered_by_canceled_by": params.canceled_by.is_some(),
|
||||||
"filtered_by_before_enqueued_at": before_enqueued_at.is_some(),
|
"filtered_by_before_enqueued_at": params.before_enqueued_at.is_some(),
|
||||||
"filtered_by_after_enqueued_at": after_enqueued_at.is_some(),
|
"filtered_by_after_enqueued_at": params.after_enqueued_at.is_some(),
|
||||||
"filtered_by_before_started_at": before_started_at.is_some(),
|
"filtered_by_before_started_at": params.before_started_at.is_some(),
|
||||||
"filtered_by_after_started_at": after_started_at.is_some(),
|
"filtered_by_after_started_at": params.after_started_at.is_some(),
|
||||||
"filtered_by_before_finished_at": before_finished_at.is_some(),
|
"filtered_by_before_finished_at": params.before_finished_at.is_some(),
|
||||||
"filtered_by_after_finished_at": after_finished_at.is_some(),
|
"filtered_by_after_finished_at": params.after_finished_at.is_some(),
|
||||||
}),
|
}),
|
||||||
Some(&req),
|
Some(&req),
|
||||||
);
|
);
|
||||||
|
|
||||||
let query = Query {
|
let query = params.into_query();
|
||||||
limit: None,
|
|
||||||
from: None,
|
|
||||||
statuses,
|
|
||||||
types,
|
|
||||||
index_uids: index_uids.map(|xs| xs.into_iter().map(|s| s.to_string()).collect()),
|
|
||||||
uids,
|
|
||||||
canceled_by,
|
|
||||||
before_enqueued_at,
|
|
||||||
after_enqueued_at,
|
|
||||||
before_started_at,
|
|
||||||
after_started_at,
|
|
||||||
before_finished_at,
|
|
||||||
after_finished_at,
|
|
||||||
};
|
|
||||||
|
|
||||||
if query.is_empty() {
|
|
||||||
return Err(index_scheduler::Error::TaskCancelationWithEmptyQuery.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
let tasks = index_scheduler.get_task_ids_from_authorized_indexes(
|
let tasks = index_scheduler.get_task_ids_from_authorized_indexes(
|
||||||
&index_scheduler.read_txn()?,
|
&index_scheduler.read_txn()?,
|
||||||
@ -305,58 +334,30 @@ async fn delete_tasks(
|
|||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
analytics: web::Data<dyn Analytics>,
|
analytics: web::Data<dyn Analytics>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
let TaskDeletionOrCancelationQuery {
|
let params = params.into_inner();
|
||||||
types,
|
|
||||||
uids,
|
|
||||||
canceled_by,
|
|
||||||
statuses,
|
|
||||||
index_uids,
|
|
||||||
|
|
||||||
after_enqueued_at,
|
if params.is_empty() {
|
||||||
before_enqueued_at,
|
return Err(index_scheduler::Error::TaskDeletionWithEmptyQuery.into());
|
||||||
after_started_at,
|
}
|
||||||
before_started_at,
|
|
||||||
after_finished_at,
|
|
||||||
before_finished_at,
|
|
||||||
} = params.into_inner();
|
|
||||||
|
|
||||||
analytics.publish(
|
analytics.publish(
|
||||||
"Tasks Deleted".to_string(),
|
"Tasks Deleted".to_string(),
|
||||||
json!({
|
json!({
|
||||||
"filtered_by_uid": uids.is_some(),
|
"filtered_by_uid": params.uids.is_some(),
|
||||||
"filtered_by_index_uid": index_uids.is_some(),
|
"filtered_by_index_uid": params.index_uids.is_some(),
|
||||||
"filtered_by_type": types.is_some(),
|
"filtered_by_type": params.types.is_some(),
|
||||||
"filtered_by_status": statuses.is_some(),
|
"filtered_by_status": params.statuses.is_some(),
|
||||||
"filtered_by_canceled_by": canceled_by.is_some(),
|
"filtered_by_canceled_by": params.canceled_by.is_some(),
|
||||||
"filtered_by_before_enqueued_at": before_enqueued_at.is_some(),
|
"filtered_by_before_enqueued_at": params.before_enqueued_at.is_some(),
|
||||||
"filtered_by_after_enqueued_at": after_enqueued_at.is_some(),
|
"filtered_by_after_enqueued_at": params.after_enqueued_at.is_some(),
|
||||||
"filtered_by_before_started_at": before_started_at.is_some(),
|
"filtered_by_before_started_at": params.before_started_at.is_some(),
|
||||||
"filtered_by_after_started_at": after_started_at.is_some(),
|
"filtered_by_after_started_at": params.after_started_at.is_some(),
|
||||||
"filtered_by_before_finished_at": before_finished_at.is_some(),
|
"filtered_by_before_finished_at": params.before_finished_at.is_some(),
|
||||||
"filtered_by_after_finished_at": after_finished_at.is_some(),
|
"filtered_by_after_finished_at": params.after_finished_at.is_some(),
|
||||||
}),
|
}),
|
||||||
Some(&req),
|
Some(&req),
|
||||||
);
|
);
|
||||||
|
let query = params.into_query();
|
||||||
let query = Query {
|
|
||||||
limit: None,
|
|
||||||
from: None,
|
|
||||||
statuses,
|
|
||||||
types,
|
|
||||||
index_uids: index_uids.map(|xs| xs.into_iter().map(|s| s.to_string()).collect()),
|
|
||||||
uids,
|
|
||||||
canceled_by,
|
|
||||||
after_enqueued_at,
|
|
||||||
before_enqueued_at,
|
|
||||||
after_started_at,
|
|
||||||
before_started_at,
|
|
||||||
after_finished_at,
|
|
||||||
before_finished_at,
|
|
||||||
};
|
|
||||||
|
|
||||||
if query.is_empty() {
|
|
||||||
return Err(index_scheduler::Error::TaskDeletionWithEmptyQuery.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
let tasks = index_scheduler.get_task_ids_from_authorized_indexes(
|
let tasks = index_scheduler.get_task_ids_from_authorized_indexes(
|
||||||
&index_scheduler.read_txn()?,
|
&index_scheduler.read_txn()?,
|
||||||
@ -386,43 +387,13 @@ async fn get_tasks(
|
|||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
analytics: web::Data<dyn Analytics>,
|
analytics: web::Data<dyn Analytics>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
let params = params.into_inner();
|
let mut params = params.into_inner();
|
||||||
analytics.get_tasks(¶ms, &req);
|
analytics.get_tasks(¶ms, &req);
|
||||||
|
|
||||||
let TasksFilterQuery {
|
|
||||||
types,
|
|
||||||
uids,
|
|
||||||
canceled_by,
|
|
||||||
statuses,
|
|
||||||
index_uids,
|
|
||||||
limit,
|
|
||||||
from,
|
|
||||||
after_enqueued_at,
|
|
||||||
before_enqueued_at,
|
|
||||||
after_started_at,
|
|
||||||
before_started_at,
|
|
||||||
after_finished_at,
|
|
||||||
before_finished_at,
|
|
||||||
} = params;
|
|
||||||
|
|
||||||
// We +1 just to know if there is more after this "page" or not.
|
// We +1 just to know if there is more after this "page" or not.
|
||||||
let limit = limit.saturating_add(1);
|
params.limit.0 = params.limit.0.saturating_add(1);
|
||||||
|
let limit = params.limit.0;
|
||||||
let query = index_scheduler::Query {
|
let query = params.into_query();
|
||||||
limit: Some(limit),
|
|
||||||
from,
|
|
||||||
statuses,
|
|
||||||
types,
|
|
||||||
index_uids: index_uids.map(|xs| xs.into_iter().map(|s| s.to_string()).collect()),
|
|
||||||
uids,
|
|
||||||
canceled_by,
|
|
||||||
before_enqueued_at,
|
|
||||||
after_enqueued_at,
|
|
||||||
before_started_at,
|
|
||||||
after_started_at,
|
|
||||||
before_finished_at,
|
|
||||||
after_finished_at,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut tasks_results: Vec<TaskView> = index_scheduler
|
let mut tasks_results: Vec<TaskView> = index_scheduler
|
||||||
.get_tasks_from_authorized_indexes(
|
.get_tasks_from_authorized_indexes(
|
||||||
@ -488,7 +459,7 @@ pub enum DeserializeDateOption {
|
|||||||
pub fn deserialize_date(
|
pub fn deserialize_date(
|
||||||
value: &str,
|
value: &str,
|
||||||
option: DeserializeDateOption,
|
option: DeserializeDateOption,
|
||||||
) -> std::result::Result<OffsetDateTime, TakeErrorMessage<InvalidTaskDateError>> {
|
) -> std::result::Result<OffsetDateTime, InvalidTaskDateError> {
|
||||||
// We can't parse using time's rfc3339 format, since then we won't know what part of the
|
// We can't parse using time's rfc3339 format, since then we won't know what part of the
|
||||||
// datetime was not explicitly specified, and thus we won't be able to increment it to the
|
// datetime was not explicitly specified, and thus we won't be able to increment it to the
|
||||||
// next step.
|
// next step.
|
||||||
@ -510,45 +481,26 @@ pub fn deserialize_date(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(TakeErrorMessage(InvalidTaskDateError(value.to_owned())))
|
Err(InvalidTaskDateError(value.to_owned()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deserialize_date_before(
|
|
||||||
value: Option<String>,
|
|
||||||
) -> std::result::Result<Option<OffsetDateTime>, TakeErrorMessage<InvalidTaskDateError>> {
|
|
||||||
if let Some(value) = value {
|
|
||||||
let date = deserialize_date(&value, DeserializeDateOption::Before)?;
|
|
||||||
Ok(Some(date))
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn deserialize_date_after(
|
pub fn deserialize_date_after(
|
||||||
value: Option<String>,
|
value: OptionStarOr<String>,
|
||||||
) -> std::result::Result<Option<OffsetDateTime>, TakeErrorMessage<InvalidTaskDateError>> {
|
) -> std::result::Result<OptionStarOr<OffsetDateTime>, InvalidTaskDateError> {
|
||||||
if let Some(value) = value {
|
value.try_map(|x| deserialize_date(&x, DeserializeDateOption::After))
|
||||||
let date = deserialize_date(&value, DeserializeDateOption::After)?;
|
|
||||||
Ok(Some(date))
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
pub fn deserialize_date_before(
|
||||||
#[derive(Debug)]
|
value: OptionStarOr<String>,
|
||||||
pub struct InvalidTaskDateError(String);
|
) -> std::result::Result<OptionStarOr<OffsetDateTime>, InvalidTaskDateError> {
|
||||||
impl std::fmt::Display for InvalidTaskDateError {
|
value.try_map(|x| deserialize_date(&x, DeserializeDateOption::Before))
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "`{}` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format.", self.0)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
impl std::error::Error for InvalidTaskDateError {}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use deserr::DeserializeFromValue;
|
use deserr::DeserializeFromValue;
|
||||||
use meili_snap::snapshot;
|
use meili_snap::snapshot;
|
||||||
use meilisearch_types::error::DeserrQueryParamError;
|
use meilisearch_types::deserr::DeserrQueryParamError;
|
||||||
|
|
||||||
use crate::extractors::query_parameters::QueryParameter;
|
use crate::extractors::query_parameters::QueryParameter;
|
||||||
use crate::routes::tasks::{TaskDeletionOrCancelationQuery, TasksFilterQuery};
|
use crate::routes::tasks::{TaskDeletionOrCancelationQuery, TasksFilterQuery};
|
||||||
@ -566,65 +518,71 @@ mod tests {
|
|||||||
let params = "afterEnqueuedAt=2021-12-03&beforeEnqueuedAt=2021-12-03&afterStartedAt=2021-12-03&beforeStartedAt=2021-12-03&afterFinishedAt=2021-12-03&beforeFinishedAt=2021-12-03";
|
let params = "afterEnqueuedAt=2021-12-03&beforeEnqueuedAt=2021-12-03&afterStartedAt=2021-12-03&beforeStartedAt=2021-12-03&afterFinishedAt=2021-12-03&beforeFinishedAt=2021-12-03";
|
||||||
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
||||||
|
|
||||||
snapshot!(format!("{:?}", query.after_enqueued_at.unwrap()), @"2021-12-04 0:00:00.0 +00:00:00");
|
snapshot!(format!("{:?}", query.after_enqueued_at), @"Other(2021-12-04 0:00:00.0 +00:00:00)");
|
||||||
snapshot!(format!("{:?}", query.before_enqueued_at.unwrap()), @"2021-12-03 0:00:00.0 +00:00:00");
|
snapshot!(format!("{:?}", query.before_enqueued_at), @"Other(2021-12-03 0:00:00.0 +00:00:00)");
|
||||||
snapshot!(format!("{:?}", query.after_started_at.unwrap()), @"2021-12-04 0:00:00.0 +00:00:00");
|
snapshot!(format!("{:?}", query.after_started_at), @"Other(2021-12-04 0:00:00.0 +00:00:00)");
|
||||||
snapshot!(format!("{:?}", query.before_started_at.unwrap()), @"2021-12-03 0:00:00.0 +00:00:00");
|
snapshot!(format!("{:?}", query.before_started_at), @"Other(2021-12-03 0:00:00.0 +00:00:00)");
|
||||||
snapshot!(format!("{:?}", query.after_finished_at.unwrap()), @"2021-12-04 0:00:00.0 +00:00:00");
|
snapshot!(format!("{:?}", query.after_finished_at), @"Other(2021-12-04 0:00:00.0 +00:00:00)");
|
||||||
snapshot!(format!("{:?}", query.before_finished_at.unwrap()), @"2021-12-03 0:00:00.0 +00:00:00");
|
snapshot!(format!("{:?}", query.before_finished_at), @"Other(2021-12-03 0:00:00.0 +00:00:00)");
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let params =
|
let params =
|
||||||
"afterEnqueuedAt=2021-12-03T23:45:23Z&beforeEnqueuedAt=2021-12-03T23:45:23Z";
|
"afterEnqueuedAt=2021-12-03T23:45:23Z&beforeEnqueuedAt=2021-12-03T23:45:23Z";
|
||||||
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
||||||
snapshot!(format!("{:?}", query.after_enqueued_at.unwrap()), @"2021-12-03 23:45:23.0 +00:00:00");
|
snapshot!(format!("{:?}", query.after_enqueued_at), @"Other(2021-12-03 23:45:23.0 +00:00:00)");
|
||||||
snapshot!(format!("{:?}", query.before_enqueued_at.unwrap()), @"2021-12-03 23:45:23.0 +00:00:00");
|
snapshot!(format!("{:?}", query.before_enqueued_at), @"Other(2021-12-03 23:45:23.0 +00:00:00)");
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let params = "afterEnqueuedAt=1997-11-12T09:55:06-06:20";
|
let params = "afterEnqueuedAt=1997-11-12T09:55:06-06:20";
|
||||||
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
||||||
snapshot!(format!("{:?}", query.after_enqueued_at.unwrap()), @"1997-11-12 9:55:06.0 -06:20:00");
|
snapshot!(format!("{:?}", query.after_enqueued_at), @"Other(1997-11-12 9:55:06.0 -06:20:00)");
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let params = "afterEnqueuedAt=1997-11-12T09:55:06%2B00:00";
|
let params = "afterEnqueuedAt=1997-11-12T09:55:06%2B00:00";
|
||||||
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
||||||
snapshot!(format!("{:?}", query.after_enqueued_at.unwrap()), @"1997-11-12 9:55:06.0 +00:00:00");
|
snapshot!(format!("{:?}", query.after_enqueued_at), @"Other(1997-11-12 9:55:06.0 +00:00:00)");
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let params = "afterEnqueuedAt=1997-11-12T09:55:06.200000300Z";
|
let params = "afterEnqueuedAt=1997-11-12T09:55:06.200000300Z";
|
||||||
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
||||||
snapshot!(format!("{:?}", query.after_enqueued_at.unwrap()), @"1997-11-12 9:55:06.2000003 +00:00:00");
|
snapshot!(format!("{:?}", query.after_enqueued_at), @"Other(1997-11-12 9:55:06.2000003 +00:00:00)");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Stars are allowed in date fields as well
|
||||||
|
let params = "afterEnqueuedAt=*&beforeStartedAt=*&afterFinishedAt=*&beforeFinishedAt=*&afterStartedAt=*&beforeEnqueuedAt=*";
|
||||||
|
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
||||||
|
snapshot!(format!("{:?}", query), @"TaskDeletionOrCancelationQuery { uids: None, canceled_by: None, types: None, statuses: None, index_uids: None, after_enqueued_at: Star, before_enqueued_at: Star, after_started_at: Star, before_started_at: Star, after_finished_at: Star, before_finished_at: Star }");
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let params = "afterFinishedAt=2021";
|
let params = "afterFinishedAt=2021";
|
||||||
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
||||||
snapshot!(format!("{err}"), @"Invalid value in parameter `afterFinishedAt`: `2021` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format.");
|
snapshot!(format!("{err:?}"), @r###"ResponseError { code: 400, message: "Invalid value in parameter `afterFinishedAt`: `2021` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format.", error_code: "invalid_task_after_finished_at", error_type: "invalid_request", error_link: "https://docs.meilisearch.com/errors#invalid-task-after-finished-at" }"###);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let params = "beforeFinishedAt=2021";
|
let params = "beforeFinishedAt=2021";
|
||||||
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
||||||
snapshot!(format!("{err}"), @"Invalid value in parameter `beforeFinishedAt`: `2021` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format.");
|
snapshot!(format!("{err:?}"), @r###"ResponseError { code: 400, message: "Invalid value in parameter `beforeFinishedAt`: `2021` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format.", error_code: "invalid_task_before_finished_at", error_type: "invalid_request", error_link: "https://docs.meilisearch.com/errors#invalid-task-before-finished-at" }"###);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let params = "afterEnqueuedAt=2021-12";
|
let params = "afterEnqueuedAt=2021-12";
|
||||||
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
||||||
snapshot!(format!("{err}"), @"Invalid value in parameter `afterEnqueuedAt`: `2021-12` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format.");
|
snapshot!(format!("{err:?}"), @r###"ResponseError { code: 400, message: "Invalid value in parameter `afterEnqueuedAt`: `2021-12` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format.", error_code: "invalid_task_after_enqueued_at", error_type: "invalid_request", error_link: "https://docs.meilisearch.com/errors#invalid-task-after-enqueued-at" }"###);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let params = "beforeEnqueuedAt=2021-12-03T23";
|
let params = "beforeEnqueuedAt=2021-12-03T23";
|
||||||
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
||||||
snapshot!(format!("{err}"), @"Invalid value in parameter `beforeEnqueuedAt`: `2021-12-03T23` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format.");
|
snapshot!(format!("{err:?}"), @r###"ResponseError { code: 400, message: "Invalid value in parameter `beforeEnqueuedAt`: `2021-12-03T23` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format.", error_code: "invalid_task_before_enqueued_at", error_type: "invalid_request", error_link: "https://docs.meilisearch.com/errors#invalid-task-before-enqueued-at" }"###);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let params = "afterStartedAt=2021-12-03T23:45";
|
let params = "afterStartedAt=2021-12-03T23:45";
|
||||||
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
||||||
snapshot!(format!("{err}"), @"Invalid value in parameter `afterStartedAt`: `2021-12-03T23:45` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format.");
|
snapshot!(format!("{err:?}"), @r###"ResponseError { code: 400, message: "Invalid value in parameter `afterStartedAt`: `2021-12-03T23:45` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format.", error_code: "invalid_task_after_started_at", error_type: "invalid_request", error_link: "https://docs.meilisearch.com/errors#invalid-task-after-started-at" }"###);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let params = "beforeStartedAt=2021-12-03T23:45";
|
let params = "beforeStartedAt=2021-12-03T23:45";
|
||||||
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
||||||
snapshot!(format!("{err}"), @"Invalid value in parameter `beforeStartedAt`: `2021-12-03T23:45` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format.");
|
snapshot!(format!("{err:?}"), @r###"ResponseError { code: 400, message: "Invalid value in parameter `beforeStartedAt`: `2021-12-03T23:45` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format.", error_code: "invalid_task_before_started_at", error_type: "invalid_request", error_link: "https://docs.meilisearch.com/errors#invalid-task-before-started-at" }"###);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -633,22 +591,27 @@ mod tests {
|
|||||||
{
|
{
|
||||||
let params = "uids=78,1,12,73";
|
let params = "uids=78,1,12,73";
|
||||||
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
||||||
snapshot!(format!("{:?}", query.uids.unwrap()), @"[78, 1, 12, 73]");
|
snapshot!(format!("{:?}", query.uids), @"List([78, 1, 12, 73])");
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let params = "uids=1";
|
let params = "uids=1";
|
||||||
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
||||||
snapshot!(format!("{:?}", query.uids.unwrap()), @"[1]");
|
snapshot!(format!("{:?}", query.uids), @"List([1])");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let params = "uids=cat,*,dog";
|
||||||
|
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
||||||
|
snapshot!(format!("{err:?}"), @r###"ResponseError { code: 400, message: "Invalid value in parameter `uids[0]`: could not parse `cat` as a positive integer", error_code: "invalid_task_uids", error_type: "invalid_request", error_link: "https://docs.meilisearch.com/errors#invalid-task-uids" }"###);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let params = "uids=78,hello,world";
|
let params = "uids=78,hello,world";
|
||||||
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
||||||
snapshot!(format!("{err}"), @"Invalid value in parameter `uids`: could not parse `hello` as a positive integer");
|
snapshot!(format!("{err:?}"), @r###"ResponseError { code: 400, message: "Invalid value in parameter `uids[1]`: could not parse `hello` as a positive integer", error_code: "invalid_task_uids", error_type: "invalid_request", error_link: "https://docs.meilisearch.com/errors#invalid-task-uids" }"###);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let params = "uids=cat";
|
let params = "uids=cat";
|
||||||
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
||||||
snapshot!(format!("{err}"), @"Invalid value in parameter `uids`: could not parse `cat` as a positive integer");
|
snapshot!(format!("{err:?}"), @r###"ResponseError { code: 400, message: "Invalid value in parameter `uids`: could not parse `cat` as a positive integer", error_code: "invalid_task_uids", error_type: "invalid_request", error_link: "https://docs.meilisearch.com/errors#invalid-task-uids" }"###);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -657,17 +620,17 @@ mod tests {
|
|||||||
{
|
{
|
||||||
let params = "statuses=succeeded,failed,enqueued,processing,canceled";
|
let params = "statuses=succeeded,failed,enqueued,processing,canceled";
|
||||||
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
||||||
snapshot!(format!("{:?}", query.statuses.unwrap()), @"[Succeeded, Failed, Enqueued, Processing, Canceled]");
|
snapshot!(format!("{:?}", query.statuses), @"List([Succeeded, Failed, Enqueued, Processing, Canceled])");
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let params = "statuses=enqueued";
|
let params = "statuses=enqueued";
|
||||||
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
||||||
snapshot!(format!("{:?}", query.statuses.unwrap()), @"[Enqueued]");
|
snapshot!(format!("{:?}", query.statuses), @"List([Enqueued])");
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let params = "statuses=finished";
|
let params = "statuses=finished";
|
||||||
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
||||||
snapshot!(format!("{err}"), @"Invalid value in parameter `statuses`: `finished` is not a valid task status. Available statuses are `enqueued`, `processing`, `succeeded`, `failed`, `canceled`.");
|
snapshot!(format!("{err:?}"), @r###"ResponseError { code: 400, message: "Invalid value in parameter `statuses`: `finished` is not a valid task status. Available statuses are `enqueued`, `processing`, `succeeded`, `failed`, `canceled`.", error_code: "invalid_task_statuses", error_type: "invalid_request", error_link: "https://docs.meilisearch.com/errors#invalid-task-statuses" }"###);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
@ -675,17 +638,17 @@ mod tests {
|
|||||||
{
|
{
|
||||||
let params = "types=documentAdditionOrUpdate,documentDeletion,settingsUpdate,indexCreation,indexDeletion,indexUpdate,indexSwap,taskCancelation,taskDeletion,dumpCreation,snapshotCreation";
|
let params = "types=documentAdditionOrUpdate,documentDeletion,settingsUpdate,indexCreation,indexDeletion,indexUpdate,indexSwap,taskCancelation,taskDeletion,dumpCreation,snapshotCreation";
|
||||||
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
||||||
snapshot!(format!("{:?}", query.types.unwrap()), @"[DocumentAdditionOrUpdate, DocumentDeletion, SettingsUpdate, IndexCreation, IndexDeletion, IndexUpdate, IndexSwap, TaskCancelation, TaskDeletion, DumpCreation, SnapshotCreation]");
|
snapshot!(format!("{:?}", query.types), @"List([DocumentAdditionOrUpdate, DocumentDeletion, SettingsUpdate, IndexCreation, IndexDeletion, IndexUpdate, IndexSwap, TaskCancelation, TaskDeletion, DumpCreation, SnapshotCreation])");
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let params = "types=settingsUpdate";
|
let params = "types=settingsUpdate";
|
||||||
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
||||||
snapshot!(format!("{:?}", query.types.unwrap()), @"[SettingsUpdate]");
|
snapshot!(format!("{:?}", query.types), @"List([SettingsUpdate])");
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let params = "types=createIndex";
|
let params = "types=createIndex";
|
||||||
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
||||||
snapshot!(format!("{err}"), @"Invalid value in parameter `types`: `createIndex` is not a valid task type. Available types are `documentAdditionOrUpdate`, `documentDeletion`, `settingsUpdate`, `indexCreation`, `indexDeletion`, `indexUpdate`, `indexSwap`, `taskCancelation`, `taskDeletion`, `dumpCreation`, `snapshotCreation`.");
|
snapshot!(format!("{err:?}"), @r###"ResponseError { code: 400, message: "Invalid value in parameter `types`: `createIndex` is not a valid task type. Available types are `documentAdditionOrUpdate`, `documentDeletion`, `settingsUpdate`, `indexCreation`, `indexDeletion`, `indexUpdate`, `indexSwap`, `taskCancelation`, `taskDeletion`, `dumpCreation`, `snapshotCreation`.", error_code: "invalid_task_types", error_type: "invalid_request", error_link: "https://docs.meilisearch.com/errors#invalid-task-types" }"###);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
@ -693,22 +656,22 @@ mod tests {
|
|||||||
{
|
{
|
||||||
let params = "indexUids=toto,tata-78";
|
let params = "indexUids=toto,tata-78";
|
||||||
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
||||||
snapshot!(format!("{:?}", query.index_uids.unwrap()), @r###"[IndexUid("toto"), IndexUid("tata-78")]"###);
|
snapshot!(format!("{:?}", query.index_uids), @r###"List([IndexUid("toto"), IndexUid("tata-78")])"###);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let params = "indexUids=index_a";
|
let params = "indexUids=index_a";
|
||||||
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
||||||
snapshot!(format!("{:?}", query.index_uids.unwrap()), @r###"[IndexUid("index_a")]"###);
|
snapshot!(format!("{:?}", query.index_uids), @r###"List([IndexUid("index_a")])"###);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let params = "indexUids=1,hé";
|
let params = "indexUids=1,hé";
|
||||||
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
||||||
snapshot!(format!("{err}"), @"Invalid value in parameter `indexUids`: `hé` is not a valid index uid. Index uid can be an integer or a string containing only alphanumeric characters, hyphens (-) and underscores (_).");
|
snapshot!(format!("{err:?}"), @r###"ResponseError { code: 400, message: "Invalid value in parameter `indexUids[1]`: `hé` is not a valid index uid. Index uid can be an integer or a string containing only alphanumeric characters, hyphens (-) and underscores (_).", error_code: "invalid_index_uid", error_type: "invalid_request", error_link: "https://docs.meilisearch.com/errors#invalid-index-uid" }"###);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let params = "indexUids=hé";
|
let params = "indexUids=hé";
|
||||||
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
||||||
snapshot!(format!("{err}"), @"Invalid value in parameter `indexUids`: `hé` is not a valid index uid. Index uid can be an integer or a string containing only alphanumeric characters, hyphens (-) and underscores (_).");
|
snapshot!(format!("{err:?}"), @r###"ResponseError { code: 400, message: "Invalid value in parameter `indexUids`: `hé` is not a valid index uid. Index uid can be an integer or a string containing only alphanumeric characters, hyphens (-) and underscores (_).", error_code: "invalid_index_uid", error_type: "invalid_request", error_link: "https://docs.meilisearch.com/errors#invalid-index-uid" }"###);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -717,38 +680,53 @@ mod tests {
|
|||||||
{
|
{
|
||||||
let params = "from=12&limit=15&indexUids=toto,tata-78&statuses=succeeded,enqueued&afterEnqueuedAt=2012-04-23&uids=1,2,3";
|
let params = "from=12&limit=15&indexUids=toto,tata-78&statuses=succeeded,enqueued&afterEnqueuedAt=2012-04-23&uids=1,2,3";
|
||||||
let query = deserr_query_params::<TasksFilterQuery>(params).unwrap();
|
let query = deserr_query_params::<TasksFilterQuery>(params).unwrap();
|
||||||
snapshot!(format!("{:?}", query), @r###"TasksFilterQuery { limit: 15, from: Some(12), uids: Some([1, 2, 3]), canceled_by: None, types: None, statuses: Some([Succeeded, Enqueued]), index_uids: Some([IndexUid("toto"), IndexUid("tata-78")]), after_enqueued_at: Some(2012-04-24 0:00:00.0 +00:00:00), before_enqueued_at: None, after_started_at: None, before_started_at: None, after_finished_at: None, before_finished_at: None }"###);
|
snapshot!(format!("{:?}", query), @r###"TasksFilterQuery { limit: Param(15), from: Some(Param(12)), uids: List([1, 2, 3]), canceled_by: None, types: None, statuses: List([Succeeded, Enqueued]), index_uids: List([IndexUid("toto"), IndexUid("tata-78")]), after_enqueued_at: Other(2012-04-24 0:00:00.0 +00:00:00), before_enqueued_at: None, after_started_at: None, before_started_at: None, after_finished_at: None, before_finished_at: None }"###);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
// Stars should translate to `None` in the query
|
// Stars should translate to `None` in the query
|
||||||
// Verify value of the default limit
|
// Verify value of the default limit
|
||||||
let params = "indexUids=*&statuses=succeeded,*&afterEnqueuedAt=2012-04-23&uids=1,2,3";
|
let params = "indexUids=*&statuses=succeeded,*&afterEnqueuedAt=2012-04-23&uids=1,2,3";
|
||||||
let query = deserr_query_params::<TasksFilterQuery>(params).unwrap();
|
let query = deserr_query_params::<TasksFilterQuery>(params).unwrap();
|
||||||
snapshot!(format!("{:?}", query), @"TasksFilterQuery { limit: 20, from: None, uids: Some([1, 2, 3]), canceled_by: None, types: None, statuses: None, index_uids: None, after_enqueued_at: Some(2012-04-24 0:00:00.0 +00:00:00), before_enqueued_at: None, after_started_at: None, before_started_at: None, after_finished_at: None, before_finished_at: None }");
|
snapshot!(format!("{:?}", query), @"TasksFilterQuery { limit: Param(20), from: None, uids: List([1, 2, 3]), canceled_by: None, types: None, statuses: Star, index_uids: Star, after_enqueued_at: Other(2012-04-24 0:00:00.0 +00:00:00), before_enqueued_at: None, after_started_at: None, before_started_at: None, after_finished_at: None, before_finished_at: None }");
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
// Stars should also translate to `None` in task deletion/cancelation queries
|
// Stars should also translate to `None` in task deletion/cancelation queries
|
||||||
let params = "indexUids=*&statuses=succeeded,*&afterEnqueuedAt=2012-04-23&uids=1,2,3";
|
let params = "indexUids=*&statuses=succeeded,*&afterEnqueuedAt=2012-04-23&uids=1,2,3";
|
||||||
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
||||||
snapshot!(format!("{:?}", query), @"TaskDeletionOrCancelationQuery { uids: Some([1, 2, 3]), canceled_by: None, types: None, statuses: None, index_uids: None, after_enqueued_at: Some(2012-04-24 0:00:00.0 +00:00:00), before_enqueued_at: None, after_started_at: None, before_started_at: None, after_finished_at: None, before_finished_at: None }");
|
snapshot!(format!("{:?}", query), @"TaskDeletionOrCancelationQuery { uids: List([1, 2, 3]), canceled_by: None, types: None, statuses: Star, index_uids: Star, after_enqueued_at: Other(2012-04-24 0:00:00.0 +00:00:00), before_enqueued_at: None, after_started_at: None, before_started_at: None, after_finished_at: None, before_finished_at: None }");
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
// Stars in uids not allowed
|
// Star in from not allowed
|
||||||
let params = "uids=*";
|
let params = "uids=*&from=*";
|
||||||
let err = deserr_query_params::<TasksFilterQuery>(params).unwrap_err();
|
let err = deserr_query_params::<TasksFilterQuery>(params).unwrap_err();
|
||||||
snapshot!(format!("{err}"), @"Invalid value in parameter `uids`: could not parse `*` as a positive integer");
|
snapshot!(format!("{err:?}"), @r###"ResponseError { code: 400, message: "Invalid value in parameter `from`: could not parse `*` as a positive integer", error_code: "invalid_task_from", error_type: "invalid_request", error_link: "https://docs.meilisearch.com/errors#invalid-task-from" }"###);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
// From not allowed in task deletion/cancelation queries
|
// From not allowed in task deletion/cancelation queries
|
||||||
let params = "from=12";
|
let params = "from=12";
|
||||||
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
||||||
snapshot!(format!("{err}"), @"Unknown parameter `from`: expected one of `uids`, `canceledBy`, `types`, `statuses`, `indexUids`, `afterEnqueuedAt`, `beforeEnqueuedAt`, `afterStartedAt`, `beforeStartedAt`, `afterFinishedAt`, `beforeFinishedAt`");
|
snapshot!(format!("{err:?}"), @r###"ResponseError { code: 400, message: "Unknown parameter `from`: expected one of `uids`, `canceledBy`, `types`, `statuses`, `indexUids`, `afterEnqueuedAt`, `beforeEnqueuedAt`, `afterStartedAt`, `beforeStartedAt`, `afterFinishedAt`, `beforeFinishedAt`", error_code: "bad_request", error_type: "invalid_request", error_link: "https://docs.meilisearch.com/errors#bad-request" }"###);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
// Limit not allowed in task deletion/cancelation queries
|
// Limit not allowed in task deletion/cancelation queries
|
||||||
let params = "limit=12";
|
let params = "limit=12";
|
||||||
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
||||||
snapshot!(format!("{err}"), @"Unknown parameter `limit`: expected one of `uids`, `canceledBy`, `types`, `statuses`, `indexUids`, `afterEnqueuedAt`, `beforeEnqueuedAt`, `afterStartedAt`, `beforeStartedAt`, `afterFinishedAt`, `beforeFinishedAt`");
|
snapshot!(format!("{err:?}"), @r###"ResponseError { code: 400, message: "Unknown parameter `limit`: expected one of `uids`, `canceledBy`, `types`, `statuses`, `indexUids`, `afterEnqueuedAt`, `beforeEnqueuedAt`, `afterStartedAt`, `beforeStartedAt`, `afterFinishedAt`, `beforeFinishedAt`", error_code: "bad_request", error_type: "invalid_request", error_link: "https://docs.meilisearch.com/errors#bad-request" }"###);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_task_delete_or_cancel_empty() {
|
||||||
|
{
|
||||||
|
let params = "";
|
||||||
|
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
||||||
|
assert!(query.is_empty());
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let params = "statuses=*";
|
||||||
|
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
||||||
|
assert!(!query.is_empty());
|
||||||
|
snapshot!(format!("{query:?}"), @"TaskDeletionOrCancelationQuery { uids: None, canceled_by: None, types: None, statuses: Star, index_uids: None, after_enqueued_at: None, before_enqueued_at: None, after_started_at: None, before_started_at: None, after_finished_at: None, before_finished_at: None }");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,8 @@ use std::time::Instant;
|
|||||||
|
|
||||||
use deserr::DeserializeFromValue;
|
use deserr::DeserializeFromValue;
|
||||||
use either::Either;
|
use either::Either;
|
||||||
|
use meilisearch_types::deserr::DeserrJsonError;
|
||||||
use meilisearch_types::error::deserr_codes::*;
|
use meilisearch_types::error::deserr_codes::*;
|
||||||
use meilisearch_types::error::DeserrJsonError;
|
|
||||||
use meilisearch_types::settings::DEFAULT_PAGINATION_MAX_TOTAL_HITS;
|
use meilisearch_types::settings::DEFAULT_PAGINATION_MAX_TOTAL_HITS;
|
||||||
use meilisearch_types::{milli, Document};
|
use meilisearch_types::{milli, Document};
|
||||||
use milli::tokenizer::TokenizerBuilder;
|
use milli::tokenizer::TokenizerBuilder;
|
||||||
@ -15,7 +15,7 @@ use milli::{
|
|||||||
SortError, TermsMatchingStrategy, DEFAULT_VALUES_PER_FACET,
|
SortError, TermsMatchingStrategy, DEFAULT_VALUES_PER_FACET,
|
||||||
};
|
};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::Serialize;
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
|
|
||||||
use crate::error::MeilisearchHttpError;
|
use crate::error::MeilisearchHttpError;
|
||||||
@ -74,9 +74,8 @@ impl SearchQuery {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug, Clone, PartialEq, Eq, DeserializeFromValue)]
|
#[derive(Debug, Clone, PartialEq, Eq, DeserializeFromValue)]
|
||||||
#[deserr(rename_all = camelCase)]
|
#[deserr(rename_all = camelCase)]
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub enum MatchingStrategy {
|
pub enum MatchingStrategy {
|
||||||
/// Remove query words from last to first
|
/// Remove query words from last to first
|
||||||
Last,
|
Last,
|
||||||
|
@ -295,7 +295,7 @@ async fn search_bad_show_matches_position() {
|
|||||||
snapshot!(code, @"400 Bad Request");
|
snapshot!(code, @"400 Bad Request");
|
||||||
snapshot!(json_string!(response), @r###"
|
snapshot!(json_string!(response), @r###"
|
||||||
{
|
{
|
||||||
"message": "Invalid value in parameter `showMatchesPosition`: provided string was not `true` or `false`",
|
"message": "Invalid value in parameter `showMatchesPosition`: could not parse `doggo` as a boolean, expected either `true` or `false`",
|
||||||
"code": "invalid_search_show_matches_position",
|
"code": "invalid_search_show_matches_position",
|
||||||
"type": "invalid_request",
|
"type": "invalid_request",
|
||||||
"link": "https://docs.meilisearch.com/errors#invalid-search-show-matches-position"
|
"link": "https://docs.meilisearch.com/errors#invalid-search-show-matches-position"
|
||||||
|
@ -43,7 +43,7 @@ async fn task_bad_uids() {
|
|||||||
snapshot!(code, @"400 Bad Request");
|
snapshot!(code, @"400 Bad Request");
|
||||||
snapshot!(json_string!(response), @r###"
|
snapshot!(json_string!(response), @r###"
|
||||||
{
|
{
|
||||||
"message": "Invalid value in parameter `uids`: could not parse `dogo` as a positive integer",
|
"message": "Invalid value in parameter `uids[1]`: could not parse `dogo` as a positive integer",
|
||||||
"code": "invalid_task_uids",
|
"code": "invalid_task_uids",
|
||||||
"type": "invalid_request",
|
"type": "invalid_request",
|
||||||
"link": "https://docs.meilisearch.com/errors#invalid-task-uids"
|
"link": "https://docs.meilisearch.com/errors#invalid-task-uids"
|
||||||
|
Loading…
Reference in New Issue
Block a user