mirror of
https://github.com/meilisearch/MeiliSearch
synced 2025-07-03 11:57:07 +02:00
Merge branch 'main' of github.com:meilisearch/meilisearch into CNLHC/change_json_error_message
This commit is contained in:
commit
193c666bf9
62 changed files with 1463 additions and 898 deletions
|
@ -11,82 +11,82 @@ name = "meilisearch"
|
|||
path = "src/main.rs"
|
||||
|
||||
[build-dependencies]
|
||||
static-files = { version = "0.2.1", optional = true }
|
||||
anyhow = { version = "1.0.43", optional = true }
|
||||
cargo_toml = { version = "0.9", optional = true }
|
||||
anyhow = { version = "1.0.56", optional = true }
|
||||
cargo_toml = { version = "0.11.4", optional = true }
|
||||
hex = { version = "0.4.3", optional = true }
|
||||
reqwest = { version = "0.11.4", features = ["blocking", "rustls-tls"], default-features = false, optional = true }
|
||||
sha-1 = { version = "0.9.8", optional = true }
|
||||
tempfile = { version = "3.2.0", optional = true }
|
||||
vergen = { version = "5.1.15", default-features = false, features = ["git"] }
|
||||
reqwest = { version = "0.11.9", features = ["blocking", "rustls-tls"], default-features = false, optional = true }
|
||||
sha-1 = { version = "0.10.0", optional = true }
|
||||
static-files = { version = "0.2.3", optional = true }
|
||||
tempfile = { version = "3.3.0", optional = true }
|
||||
vergen = { version = "7.0.0", default-features = false, features = ["git"] }
|
||||
zip = { version = "0.5.13", optional = true }
|
||||
|
||||
[dependencies]
|
||||
actix-cors = "0.6"
|
||||
actix-web = { version = "4", features = ["rustls"] }
|
||||
actix-cors = "0.6.1"
|
||||
actix-web = { version = "4.0.1", default-features = false, features = ["macros", "compress-brotli", "compress-gzip", "cookies", "rustls"] }
|
||||
actix-web-static-files = { git = "https://github.com/kilork/actix-web-static-files.git", rev = "2d3b6160", optional = true }
|
||||
anyhow = { version = "1.0.43", features = ["backtrace"] }
|
||||
arc-swap = "1.3.2"
|
||||
async-stream = "0.3.2"
|
||||
async-trait = "0.1.51"
|
||||
anyhow = { version = "1.0.56", features = ["backtrace"] }
|
||||
async-stream = "0.3.3"
|
||||
async-trait = "0.1.52"
|
||||
bstr = "0.2.17"
|
||||
byte-unit = { version = "4.0.12", default-features = false, features = ["std", "serde"] }
|
||||
byte-unit = { version = "4.0.14", default-features = false, features = ["std", "serde"] }
|
||||
bytes = "1.1.0"
|
||||
chrono = { version = "0.4.19", features = ["serde"] }
|
||||
crossbeam-channel = "0.5.1"
|
||||
clap = { version = "3.1.6", features = ["derive", "env"] }
|
||||
crossbeam-channel = "0.5.2"
|
||||
either = "1.6.1"
|
||||
env_logger = "0.9.0"
|
||||
flate2 = "1.0.21"
|
||||
flate2 = "1.0.22"
|
||||
fst = "0.4.7"
|
||||
futures = "0.3.17"
|
||||
futures-util = "0.3.17"
|
||||
heed = { git = "https://github.com/Kerollmops/heed", tag = "v0.12.1" }
|
||||
http = "0.2.4"
|
||||
indexmap = { version = "1.7.0", features = ["serde-1"] }
|
||||
itertools = "0.10.1"
|
||||
jsonwebtoken = "7"
|
||||
futures = "0.3.21"
|
||||
futures-util = "0.3.21"
|
||||
http = "0.2.6"
|
||||
indexmap = { version = "1.8.0", features = ["serde-1"] }
|
||||
itertools = "0.10.3"
|
||||
jsonwebtoken = "8.0.1"
|
||||
log = "0.4.14"
|
||||
meilisearch-auth = { path = "../meilisearch-auth" }
|
||||
meilisearch-error = { path = "../meilisearch-error" }
|
||||
meilisearch-lib = { path = "../meilisearch-lib" }
|
||||
mime = "0.3.16"
|
||||
num_cpus = "1.13.0"
|
||||
num_cpus = "1.13.1"
|
||||
obkv = "0.2.0"
|
||||
once_cell = "1.8.0"
|
||||
parking_lot = "0.11.2"
|
||||
pin-project = "1.0.8"
|
||||
once_cell = "1.10.0"
|
||||
parking_lot = "0.12.0"
|
||||
pin-project-lite = "0.2.8"
|
||||
platform-dirs = "0.3.0"
|
||||
rand = "0.8.4"
|
||||
rand = "0.8.5"
|
||||
rayon = "1.5.1"
|
||||
regex = "1.5.4"
|
||||
rustls = "0.20.2"
|
||||
rustls-pemfile = "0.2"
|
||||
segment = { version = "0.1.2", optional = true }
|
||||
serde = { version = "1.0.130", features = ["derive"] }
|
||||
serde_json = { version = "1.0.67", features = ["preserve_order"] }
|
||||
sha2 = "0.9.6"
|
||||
siphasher = "0.3.7"
|
||||
slice-group-by = "0.2.6"
|
||||
static-files = { version = "0.2.1", optional = true }
|
||||
clap = { version = "3.0", features = ["derive", "env"] }
|
||||
sysinfo = "0.20.2"
|
||||
tar = "0.4.37"
|
||||
tempfile = "3.2.0"
|
||||
thiserror = "1.0.28"
|
||||
tokio = { version = "1.11.0", features = ["full"] }
|
||||
tokio-stream = "0.1.7"
|
||||
regex = "1.5.5"
|
||||
rustls = "0.20.4"
|
||||
rustls-pemfile = "0.3.0"
|
||||
segment = { version = "0.2.0", optional = true }
|
||||
serde = { version = "1.0.136", features = ["derive"] }
|
||||
serde_json = { version = "1.0.79", features = ["preserve_order"] }
|
||||
sha2 = "0.10.2"
|
||||
siphasher = "0.3.10"
|
||||
slice-group-by = "0.3.0"
|
||||
static-files = { version = "0.2.3", optional = true }
|
||||
sysinfo = "0.23.5"
|
||||
tar = "0.4.38"
|
||||
tempfile = "3.3.0"
|
||||
thiserror = "1.0.30"
|
||||
time = { version = "0.3.7", features = ["serde-well-known", "formatting", "parsing", "macros"] }
|
||||
tokio = { version = "1.17.0", features = ["full"] }
|
||||
tokio-stream = "0.1.8"
|
||||
uuid = { version = "0.8.2", features = ["serde"] }
|
||||
walkdir = "2.3.2"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "2.2.0"
|
||||
actix-rt = "2.7.0"
|
||||
assert-json-diff = "2.0.1"
|
||||
maplit = "1.0.2"
|
||||
paste = "1.0.5"
|
||||
paste = "1.0.6"
|
||||
serde_url_params = "0.2.1"
|
||||
urlencoding = "2.1.0"
|
||||
|
||||
[features]
|
||||
default = ["analytics", "mini-dashboard"]
|
||||
analytics = ["segment"]
|
||||
mini-dashboard = [
|
||||
"actix-web-static-files",
|
||||
"static-files",
|
||||
|
@ -98,12 +98,10 @@ mini-dashboard = [
|
|||
"tempfile",
|
||||
"zip",
|
||||
]
|
||||
analytics = ["segment"]
|
||||
default = ["analytics", "mini-dashboard"]
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
tikv-jemallocator = "0.4.1"
|
||||
tikv-jemallocator = "0.4.3"
|
||||
|
||||
[package.metadata.mini-dashboard]
|
||||
assets-url = "https://github.com/meilisearch/mini-dashboard/releases/download/v0.1.7/build.zip"
|
||||
sha1 = "e2feedf271917c4b7b88998eff5aaaea1d3925b9"
|
||||
assets-url = "https://github.com/meilisearch/mini-dashboard/releases/download/v0.1.9/build.zip"
|
||||
sha1 = "b1833c3e5dc6b5d9d519ae4834935ae6c8a47024"
|
||||
|
|
|
@ -6,7 +6,6 @@ use std::time::{Duration, Instant};
|
|||
|
||||
use actix_web::http::header::USER_AGENT;
|
||||
use actix_web::HttpRequest;
|
||||
use chrono::{DateTime, Utc};
|
||||
use http::header::CONTENT_TYPE;
|
||||
use meilisearch_auth::SearchRules;
|
||||
use meilisearch_lib::index::{SearchQuery, SearchResult};
|
||||
|
@ -18,6 +17,7 @@ use segment::message::{Identify, Track, User};
|
|||
use segment::{AutoBatcher, Batcher, HttpClient};
|
||||
use serde_json::{json, Value};
|
||||
use sysinfo::{DiskExt, System, SystemExt};
|
||||
use time::OffsetDateTime;
|
||||
use tokio::select;
|
||||
use tokio::sync::mpsc::{self, Receiver, Sender};
|
||||
use uuid::Uuid;
|
||||
|
@ -323,7 +323,7 @@ impl Segment {
|
|||
|
||||
#[derive(Default)]
|
||||
pub struct SearchAggregator {
|
||||
timestamp: Option<DateTime<Utc>>,
|
||||
timestamp: Option<OffsetDateTime>,
|
||||
|
||||
// context
|
||||
user_agents: HashSet<String>,
|
||||
|
@ -360,7 +360,7 @@ pub struct SearchAggregator {
|
|||
impl SearchAggregator {
|
||||
pub fn from_query(query: &SearchQuery, request: &HttpRequest) -> Self {
|
||||
let mut ret = Self::default();
|
||||
ret.timestamp = Some(chrono::offset::Utc::now());
|
||||
ret.timestamp = Some(OffsetDateTime::now_utc());
|
||||
|
||||
ret.total_received = 1;
|
||||
ret.user_agents = extract_user_agents(request).into_iter().collect();
|
||||
|
@ -504,7 +504,7 @@ impl SearchAggregator {
|
|||
|
||||
#[derive(Default)]
|
||||
pub struct DocumentsAggregator {
|
||||
timestamp: Option<DateTime<Utc>>,
|
||||
timestamp: Option<OffsetDateTime>,
|
||||
|
||||
// set to true when at least one request was received
|
||||
updated: bool,
|
||||
|
@ -524,7 +524,7 @@ impl DocumentsAggregator {
|
|||
request: &HttpRequest,
|
||||
) -> Self {
|
||||
let mut ret = Self::default();
|
||||
ret.timestamp = Some(chrono::offset::Utc::now());
|
||||
ret.timestamp = Some(OffsetDateTime::now_utc());
|
||||
|
||||
ret.updated = true;
|
||||
ret.user_agents = extract_user_agents(request).into_iter().collect();
|
||||
|
|
|
@ -5,7 +5,7 @@ pub enum AuthenticationError {
|
|||
#[error("The Authorization header is missing. It must use the bearer authorization method.")]
|
||||
MissingAuthorizationHeader,
|
||||
#[error("The provided API key is invalid.")]
|
||||
InvalidToken(String),
|
||||
InvalidToken,
|
||||
// Triggered on configuration error.
|
||||
#[error("An internal error has occurred. `Irretrievable state`.")]
|
||||
IrretrievableState,
|
||||
|
@ -15,7 +15,7 @@ impl ErrorCode for AuthenticationError {
|
|||
fn error_code(&self) -> Code {
|
||||
match self {
|
||||
AuthenticationError::MissingAuthorizationHeader => Code::MissingAuthorizationHeader,
|
||||
AuthenticationError::InvalidToken(_) => Code::InvalidToken,
|
||||
AuthenticationError::InvalidToken => Code::InvalidToken,
|
||||
AuthenticationError::IrretrievableState => Code::Internal,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,28 +2,83 @@ mod error;
|
|||
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::Deref;
|
||||
use std::pin::Pin;
|
||||
|
||||
use actix_web::FromRequest;
|
||||
use futures::future::err;
|
||||
use futures::future::{ok, Ready};
|
||||
use meilisearch_error::ResponseError;
|
||||
use futures::Future;
|
||||
use meilisearch_error::{Code, ResponseError};
|
||||
|
||||
use error::AuthenticationError;
|
||||
use meilisearch_auth::{AuthController, AuthFilter};
|
||||
|
||||
pub struct GuardedData<T, D> {
|
||||
pub struct GuardedData<P, D> {
|
||||
data: D,
|
||||
filters: AuthFilter,
|
||||
_marker: PhantomData<T>,
|
||||
_marker: PhantomData<P>,
|
||||
}
|
||||
|
||||
impl<T, D> GuardedData<T, D> {
|
||||
impl<P, D> GuardedData<P, D> {
|
||||
pub fn filters(&self) -> &AuthFilter {
|
||||
&self.filters
|
||||
}
|
||||
|
||||
async fn auth_bearer(
|
||||
auth: AuthController,
|
||||
token: String,
|
||||
index: Option<String>,
|
||||
data: Option<D>,
|
||||
) -> Result<Self, ResponseError>
|
||||
where
|
||||
P: Policy + 'static,
|
||||
{
|
||||
match Self::authenticate(auth, token, index).await? {
|
||||
Some(filters) => match data {
|
||||
Some(data) => Ok(Self {
|
||||
data,
|
||||
filters,
|
||||
_marker: PhantomData,
|
||||
}),
|
||||
None => Err(AuthenticationError::IrretrievableState.into()),
|
||||
},
|
||||
None => Err(AuthenticationError::InvalidToken.into()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn auth_token(auth: AuthController, data: Option<D>) -> Result<Self, ResponseError>
|
||||
where
|
||||
P: Policy + 'static,
|
||||
{
|
||||
match Self::authenticate(auth, String::new(), None).await? {
|
||||
Some(filters) => match data {
|
||||
Some(data) => Ok(Self {
|
||||
data,
|
||||
filters,
|
||||
_marker: PhantomData,
|
||||
}),
|
||||
None => Err(AuthenticationError::IrretrievableState.into()),
|
||||
},
|
||||
None => Err(AuthenticationError::MissingAuthorizationHeader.into()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn authenticate(
|
||||
auth: AuthController,
|
||||
token: String,
|
||||
index: Option<String>,
|
||||
) -> Result<Option<AuthFilter>, ResponseError>
|
||||
where
|
||||
P: Policy + 'static,
|
||||
{
|
||||
Ok(tokio::task::spawn_blocking(move || {
|
||||
P::authenticate(auth, token.as_ref(), index.as_deref())
|
||||
})
|
||||
.await
|
||||
.map_err(|e| ResponseError::from_msg(e.to_string(), Code::Internal))?)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, D> Deref for GuardedData<T, D> {
|
||||
impl<P, D> Deref for GuardedData<P, D> {
|
||||
type Target = D;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
|
@ -34,7 +89,7 @@ impl<T, D> Deref for GuardedData<T, D> {
|
|||
impl<P: Policy + 'static, D: 'static + Clone> FromRequest for GuardedData<P, D> {
|
||||
type Error = ResponseError;
|
||||
|
||||
type Future = Ready<Result<Self, Self::Error>>;
|
||||
type Future = Pin<Box<dyn Future<Output = Result<Self, Self::Error>>>>;
|
||||
|
||||
fn from_request(
|
||||
req: &actix_web::HttpRequest,
|
||||
|
@ -51,40 +106,22 @@ impl<P: Policy + 'static, D: 'static + Clone> FromRequest for GuardedData<P, D>
|
|||
// TODO: find a less hardcoded way?
|
||||
let index = req.match_info().get("index_uid");
|
||||
match type_token.next() {
|
||||
Some(token) => match P::authenticate(auth, token, index) {
|
||||
Some(filters) => match req.app_data::<D>().cloned() {
|
||||
Some(data) => ok(Self {
|
||||
data,
|
||||
filters,
|
||||
_marker: PhantomData,
|
||||
}),
|
||||
None => err(AuthenticationError::IrretrievableState.into()),
|
||||
},
|
||||
None => {
|
||||
let token = token.to_string();
|
||||
err(AuthenticationError::InvalidToken(token).into())
|
||||
}
|
||||
},
|
||||
None => {
|
||||
err(AuthenticationError::InvalidToken("unknown".to_string()).into())
|
||||
}
|
||||
Some(token) => Box::pin(Self::auth_bearer(
|
||||
auth,
|
||||
token.to_string(),
|
||||
index.map(String::from),
|
||||
req.app_data::<D>().cloned(),
|
||||
)),
|
||||
None => Box::pin(err(AuthenticationError::InvalidToken.into())),
|
||||
}
|
||||
}
|
||||
_otherwise => err(AuthenticationError::MissingAuthorizationHeader.into()),
|
||||
},
|
||||
None => match P::authenticate(auth, "", None) {
|
||||
Some(filters) => match req.app_data::<D>().cloned() {
|
||||
Some(data) => ok(Self {
|
||||
data,
|
||||
filters,
|
||||
_marker: PhantomData,
|
||||
}),
|
||||
None => err(AuthenticationError::IrretrievableState.into()),
|
||||
},
|
||||
None => err(AuthenticationError::MissingAuthorizationHeader.into()),
|
||||
_otherwise => {
|
||||
Box::pin(err(AuthenticationError::MissingAuthorizationHeader.into()))
|
||||
}
|
||||
},
|
||||
None => Box::pin(Self::auth_token(auth, req.app_data::<D>().cloned())),
|
||||
},
|
||||
None => err(AuthenticationError::IrretrievableState.into()),
|
||||
None => Box::pin(err(AuthenticationError::IrretrievableState.into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -94,21 +131,22 @@ pub trait Policy {
|
|||
}
|
||||
|
||||
pub mod policies {
|
||||
use chrono::Utc;
|
||||
use jsonwebtoken::{dangerous_insecure_decode, decode, Algorithm, DecodingKey, Validation};
|
||||
use once_cell::sync::Lazy;
|
||||
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use crate::extractors::authentication::Policy;
|
||||
use meilisearch_auth::{Action, AuthController, AuthFilter, SearchRules};
|
||||
// reexport actions in policies in order to be used in routes configuration.
|
||||
pub use meilisearch_auth::actions;
|
||||
|
||||
pub static TENANT_TOKEN_VALIDATION: Lazy<Validation> = Lazy::new(|| Validation {
|
||||
validate_exp: false,
|
||||
algorithms: vec![Algorithm::HS256, Algorithm::HS384, Algorithm::HS512],
|
||||
..Default::default()
|
||||
});
|
||||
fn tenant_token_validation() -> Validation {
|
||||
let mut validation = Validation::default();
|
||||
validation.validate_exp = false;
|
||||
validation.required_spec_claims.remove("exp");
|
||||
validation.algorithms = vec![Algorithm::HS256, Algorithm::HS384, Algorithm::HS512];
|
||||
validation
|
||||
}
|
||||
|
||||
pub struct MasterPolicy;
|
||||
|
||||
|
@ -167,12 +205,17 @@ pub mod policies {
|
|||
return None;
|
||||
}
|
||||
|
||||
let mut validation = tenant_token_validation();
|
||||
validation.insecure_disable_signature_validation();
|
||||
let dummy_key = DecodingKey::from_secret(b"secret");
|
||||
let token_data = decode::<Claims>(token, &dummy_key, &validation).ok()?;
|
||||
|
||||
// get token fields without validating it.
|
||||
let Claims {
|
||||
search_rules,
|
||||
exp,
|
||||
api_key_prefix,
|
||||
} = dangerous_insecure_decode::<Claims>(token).ok()?.claims;
|
||||
} = token_data.claims;
|
||||
|
||||
// Check index access if an index restriction is provided.
|
||||
if let Some(index) = index {
|
||||
|
@ -183,7 +226,7 @@ pub mod policies {
|
|||
|
||||
// Check if token is expired.
|
||||
if let Some(exp) = exp {
|
||||
if Utc::now().timestamp() > exp {
|
||||
if OffsetDateTime::now_utc().unix_timestamp() > exp {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
@ -198,7 +241,7 @@ pub mod policies {
|
|||
decode::<Claims>(
|
||||
token,
|
||||
&DecodingKey::from_secret(key.as_bytes()),
|
||||
&TENANT_TOKEN_VALIDATION,
|
||||
&tenant_token_validation(),
|
||||
)
|
||||
.ok()?;
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
pub mod payload;
|
||||
#[macro_use]
|
||||
pub mod authentication;
|
||||
pub mod sequential_extractor;
|
||||
|
|
148
meilisearch-http/src/extractors/sequential_extractor.rs
Normal file
148
meilisearch-http/src/extractors/sequential_extractor.rs
Normal file
|
@ -0,0 +1,148 @@
|
|||
#![allow(non_snake_case)]
|
||||
use std::{future::Future, pin::Pin, task::Poll};
|
||||
|
||||
use actix_web::{dev::Payload, FromRequest, Handler, HttpRequest};
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
/// `SeqHandler` is an actix `Handler` that enforces that extractors errors are returned in the
|
||||
/// same order as they are defined in the wrapped handler. This is needed because, by default, actix
|
||||
/// resolves the extractors concurrently, whereas we always need the authentication extractor to
|
||||
/// throw first.
|
||||
#[derive(Clone)]
|
||||
pub struct SeqHandler<H>(pub H);
|
||||
|
||||
pub struct SeqFromRequest<T>(T);
|
||||
|
||||
/// This macro implements `FromRequest` for arbitrary arity handler, except for one, which is
|
||||
/// useless anyway.
|
||||
macro_rules! gen_seq {
|
||||
($ty:ident; $($T:ident)+) => {
|
||||
pin_project! {
|
||||
pub struct $ty<$($T: FromRequest), +> {
|
||||
$(
|
||||
#[pin]
|
||||
$T: ExtractFuture<$T::Future, $T, $T::Error>,
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
impl<$($T: FromRequest), +> Future for $ty<$($T),+> {
|
||||
type Output = Result<SeqFromRequest<($($T),+)>, actix_web::Error>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
|
||||
let mut this = self.project();
|
||||
|
||||
let mut count_fut = 0;
|
||||
let mut count_finished = 0;
|
||||
|
||||
$(
|
||||
count_fut += 1;
|
||||
match this.$T.as_mut().project() {
|
||||
ExtractProj::Future { fut } => match fut.poll(cx) {
|
||||
Poll::Ready(Ok(output)) => {
|
||||
count_finished += 1;
|
||||
let _ = this
|
||||
.$T
|
||||
.as_mut()
|
||||
.project_replace(ExtractFuture::Done { output });
|
||||
}
|
||||
Poll::Ready(Err(error)) => {
|
||||
count_finished += 1;
|
||||
let _ = this
|
||||
.$T
|
||||
.as_mut()
|
||||
.project_replace(ExtractFuture::Error { error });
|
||||
}
|
||||
Poll::Pending => (),
|
||||
},
|
||||
ExtractProj::Done { .. } => count_finished += 1,
|
||||
ExtractProj::Error { .. } => {
|
||||
// short circuit if all previous are finished and we had an error.
|
||||
if count_finished == count_fut {
|
||||
match this.$T.project_replace(ExtractFuture::Empty) {
|
||||
ExtractReplaceProj::Error { error } => {
|
||||
return Poll::Ready(Err(error.into()))
|
||||
}
|
||||
_ => unreachable!("Invalid future state"),
|
||||
}
|
||||
} else {
|
||||
count_finished += 1;
|
||||
}
|
||||
}
|
||||
ExtractProj::Empty => unreachable!("From request polled after being finished. {}", stringify!($T)),
|
||||
}
|
||||
)+
|
||||
|
||||
if count_fut == count_finished {
|
||||
let result = (
|
||||
$(
|
||||
match this.$T.project_replace(ExtractFuture::Empty) {
|
||||
ExtractReplaceProj::Done { output } => output,
|
||||
ExtractReplaceProj::Error { error } => return Poll::Ready(Err(error.into())),
|
||||
_ => unreachable!("Invalid future state"),
|
||||
},
|
||||
)+
|
||||
);
|
||||
|
||||
Poll::Ready(Ok(SeqFromRequest(result)))
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<$($T: FromRequest,)+> FromRequest for SeqFromRequest<($($T,)+)> {
|
||||
type Error = actix_web::Error;
|
||||
|
||||
type Future = $ty<$($T),+>;
|
||||
|
||||
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
||||
$ty {
|
||||
$(
|
||||
$T: ExtractFuture::Future {
|
||||
fut: $T::from_request(req, payload),
|
||||
},
|
||||
)+
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Han, $($T: FromRequest),+> Handler<SeqFromRequest<($($T),+)>> for SeqHandler<Han>
|
||||
where
|
||||
Han: Handler<($($T),+)>,
|
||||
{
|
||||
type Output = Han::Output;
|
||||
type Future = Han::Future;
|
||||
|
||||
fn call(&self, args: SeqFromRequest<($($T),+)>) -> Self::Future {
|
||||
self.0.call(args.0)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Not working for a single argument, but then, it is not really necessary.
|
||||
// gen_seq! { SeqFromRequestFut1; A }
|
||||
gen_seq! { SeqFromRequestFut2; A B }
|
||||
gen_seq! { SeqFromRequestFut3; A B C }
|
||||
gen_seq! { SeqFromRequestFut4; A B C D }
|
||||
gen_seq! { SeqFromRequestFut5; A B C D E }
|
||||
gen_seq! { SeqFromRequestFut6; A B C D E F }
|
||||
|
||||
pin_project! {
|
||||
#[project = ExtractProj]
|
||||
#[project_replace = ExtractReplaceProj]
|
||||
enum ExtractFuture<Fut, Res, Err> {
|
||||
Future {
|
||||
#[pin]
|
||||
fut: Fut,
|
||||
},
|
||||
Done {
|
||||
output: Res,
|
||||
},
|
||||
Error {
|
||||
error: Err,
|
||||
},
|
||||
Empty,
|
||||
}
|
||||
}
|
|
@ -1,10 +1,11 @@
|
|||
use meilisearch_lib::heed::Env;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
pub trait EnvSizer {
|
||||
fn size(&self) -> u64;
|
||||
}
|
||||
|
||||
impl EnvSizer for heed::Env {
|
||||
impl EnvSizer for Env {
|
||||
fn size(&self) -> u64 {
|
||||
WalkDir::new(self.path())
|
||||
.into_iter()
|
||||
|
|
|
@ -32,7 +32,7 @@ pub fn setup_meilisearch(opt: &Opt) -> anyhow::Result<MeiliSearch> {
|
|||
|
||||
// enable autobatching?
|
||||
let _ = AUTOBATCHING_ENABLED.store(
|
||||
opt.scheduler_options.enable_autobatching,
|
||||
opt.scheduler_options.enable_auto_batching,
|
||||
std::sync::atomic::Ordering::Relaxed,
|
||||
);
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@ pub struct Opt {
|
|||
|
||||
/// Do not send analytics to Meili.
|
||||
#[cfg(all(not(debug_assertions), feature = "analytics"))]
|
||||
#[serde(skip)] // we can't send true
|
||||
#[clap(long, env = "MEILI_NO_ANALYTICS")]
|
||||
pub no_analytics: bool,
|
||||
|
||||
|
@ -148,6 +149,7 @@ pub struct Opt {
|
|||
#[clap(skip)]
|
||||
pub indexer_options: IndexerOpts,
|
||||
|
||||
#[serde(flatten)]
|
||||
#[clap(flatten)]
|
||||
pub scheduler_options: SchedulerConfig,
|
||||
}
|
||||
|
@ -256,3 +258,13 @@ fn load_ocsp(filename: &Option<PathBuf>) -> anyhow::Result<Vec<u8>> {
|
|||
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_valid_opt() {
|
||||
assert!(Opt::try_parse_from(Some("")).is_ok());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +1,29 @@
|
|||
use std::str;
|
||||
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use chrono::SecondsFormat;
|
||||
|
||||
use meilisearch_auth::{Action, AuthController, Key};
|
||||
use meilisearch_auth::{error::AuthControllerError, Action, AuthController, Key};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use crate::extractors::authentication::{policies::*, GuardedData};
|
||||
use meilisearch_error::ResponseError;
|
||||
use crate::extractors::{
|
||||
authentication::{policies::*, GuardedData},
|
||||
sequential_extractor::SeqHandler,
|
||||
};
|
||||
use meilisearch_error::{Code, ResponseError};
|
||||
|
||||
pub fn configure(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(
|
||||
web::resource("")
|
||||
.route(web::post().to(create_api_key))
|
||||
.route(web::get().to(list_api_keys)),
|
||||
.route(web::post().to(SeqHandler(create_api_key)))
|
||||
.route(web::get().to(SeqHandler(list_api_keys))),
|
||||
)
|
||||
.service(
|
||||
web::resource("/{api_key}")
|
||||
.route(web::get().to(get_api_key))
|
||||
.route(web::patch().to(patch_api_key))
|
||||
.route(web::delete().to(delete_api_key)),
|
||||
.route(web::get().to(SeqHandler(get_api_key)))
|
||||
.route(web::patch().to(SeqHandler(patch_api_key)))
|
||||
.route(web::delete().to(SeqHandler(delete_api_key))),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -29,8 +32,13 @@ pub async fn create_api_key(
|
|||
body: web::Json<Value>,
|
||||
_req: HttpRequest,
|
||||
) -> Result<HttpResponse, ResponseError> {
|
||||
let key = auth_controller.create_key(body.into_inner()).await?;
|
||||
let res = KeyView::from_key(key, &auth_controller);
|
||||
let v = body.into_inner();
|
||||
let res = tokio::task::spawn_blocking(move || -> Result<_, AuthControllerError> {
|
||||
let key = auth_controller.create_key(v)?;
|
||||
Ok(KeyView::from_key(key, &auth_controller))
|
||||
})
|
||||
.await
|
||||
.map_err(|e| ResponseError::from_msg(e.to_string(), Code::Internal))??;
|
||||
|
||||
Ok(HttpResponse::Created().json(res))
|
||||
}
|
||||
|
@ -39,11 +47,16 @@ pub async fn list_api_keys(
|
|||
auth_controller: GuardedData<MasterPolicy, AuthController>,
|
||||
_req: HttpRequest,
|
||||
) -> Result<HttpResponse, ResponseError> {
|
||||
let keys = auth_controller.list_keys().await?;
|
||||
let res: Vec<_> = keys
|
||||
.into_iter()
|
||||
.map(|k| KeyView::from_key(k, &auth_controller))
|
||||
.collect();
|
||||
let res = tokio::task::spawn_blocking(move || -> Result<_, AuthControllerError> {
|
||||
let keys = auth_controller.list_keys()?;
|
||||
let res: Vec<_> = keys
|
||||
.into_iter()
|
||||
.map(|k| KeyView::from_key(k, &auth_controller))
|
||||
.collect();
|
||||
Ok(res)
|
||||
})
|
||||
.await
|
||||
.map_err(|e| ResponseError::from_msg(e.to_string(), Code::Internal))??;
|
||||
|
||||
Ok(HttpResponse::Ok().json(KeyListView::from(res)))
|
||||
}
|
||||
|
@ -52,8 +65,13 @@ pub async fn get_api_key(
|
|||
auth_controller: GuardedData<MasterPolicy, AuthController>,
|
||||
path: web::Path<AuthParam>,
|
||||
) -> Result<HttpResponse, ResponseError> {
|
||||
let key = auth_controller.get_key(&path.api_key).await?;
|
||||
let res = KeyView::from_key(key, &auth_controller);
|
||||
let api_key = path.into_inner().api_key;
|
||||
let res = tokio::task::spawn_blocking(move || -> Result<_, AuthControllerError> {
|
||||
let key = auth_controller.get_key(&api_key)?;
|
||||
Ok(KeyView::from_key(key, &auth_controller))
|
||||
})
|
||||
.await
|
||||
.map_err(|e| ResponseError::from_msg(e.to_string(), Code::Internal))??;
|
||||
|
||||
Ok(HttpResponse::Ok().json(res))
|
||||
}
|
||||
|
@ -63,10 +81,14 @@ pub async fn patch_api_key(
|
|||
body: web::Json<Value>,
|
||||
path: web::Path<AuthParam>,
|
||||
) -> Result<HttpResponse, ResponseError> {
|
||||
let key = auth_controller
|
||||
.update_key(&path.api_key, body.into_inner())
|
||||
.await?;
|
||||
let res = KeyView::from_key(key, &auth_controller);
|
||||
let api_key = path.into_inner().api_key;
|
||||
let body = body.into_inner();
|
||||
let res = tokio::task::spawn_blocking(move || -> Result<_, AuthControllerError> {
|
||||
let key = auth_controller.update_key(&api_key, body)?;
|
||||
Ok(KeyView::from_key(key, &auth_controller))
|
||||
})
|
||||
.await
|
||||
.map_err(|e| ResponseError::from_msg(e.to_string(), Code::Internal))??;
|
||||
|
||||
Ok(HttpResponse::Ok().json(res))
|
||||
}
|
||||
|
@ -75,7 +97,10 @@ pub async fn delete_api_key(
|
|||
auth_controller: GuardedData<MasterPolicy, AuthController>,
|
||||
path: web::Path<AuthParam>,
|
||||
) -> Result<HttpResponse, ResponseError> {
|
||||
auth_controller.delete_key(&path.api_key).await?;
|
||||
let api_key = path.into_inner().api_key;
|
||||
tokio::task::spawn_blocking(move || auth_controller.delete_key(&api_key))
|
||||
.await
|
||||
.map_err(|e| ResponseError::from_msg(e.to_string(), Code::Internal))??;
|
||||
|
||||
Ok(HttpResponse::NoContent().finish())
|
||||
}
|
||||
|
@ -92,9 +117,12 @@ struct KeyView {
|
|||
key: String,
|
||||
actions: Vec<Action>,
|
||||
indexes: Vec<String>,
|
||||
expires_at: Option<String>,
|
||||
created_at: String,
|
||||
updated_at: String,
|
||||
#[serde(serialize_with = "time::serde::rfc3339::option::serialize")]
|
||||
expires_at: Option<OffsetDateTime>,
|
||||
#[serde(serialize_with = "time::serde::rfc3339::serialize")]
|
||||
created_at: OffsetDateTime,
|
||||
#[serde(serialize_with = "time::serde::rfc3339::serialize")]
|
||||
updated_at: OffsetDateTime,
|
||||
}
|
||||
|
||||
impl KeyView {
|
||||
|
@ -107,11 +135,9 @@ impl KeyView {
|
|||
key: generated_key,
|
||||
actions: key.actions,
|
||||
indexes: key.indexes,
|
||||
expires_at: key
|
||||
.expires_at
|
||||
.map(|dt| dt.to_rfc3339_opts(SecondsFormat::Secs, true)),
|
||||
created_at: key.created_at.to_rfc3339_opts(SecondsFormat::Secs, true),
|
||||
updated_at: key.updated_at.to_rfc3339_opts(SecondsFormat::Secs, true),
|
||||
expires_at: key.expires_at,
|
||||
created_at: key.created_at,
|
||||
updated_at: key.updated_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,10 +7,13 @@ use serde_json::json;
|
|||
|
||||
use crate::analytics::Analytics;
|
||||
use crate::extractors::authentication::{policies::*, GuardedData};
|
||||
use crate::extractors::sequential_extractor::SeqHandler;
|
||||
|
||||
pub fn configure(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(web::resource("").route(web::post().to(create_dump)))
|
||||
.service(web::resource("/{dump_uid}/status").route(web::get().to(get_dump_status)));
|
||||
cfg.service(web::resource("").route(web::post().to(SeqHandler(create_dump))))
|
||||
.service(
|
||||
web::resource("/{dump_uid}/status").route(web::get().to(SeqHandler(get_dump_status))),
|
||||
);
|
||||
}
|
||||
|
||||
pub async fn create_dump(
|
||||
|
|
|
@ -20,6 +20,7 @@ use crate::analytics::Analytics;
|
|||
use crate::error::MeilisearchHttpError;
|
||||
use crate::extractors::authentication::{policies::*, GuardedData};
|
||||
use crate::extractors::payload::Payload;
|
||||
use crate::extractors::sequential_extractor::SeqHandler;
|
||||
use crate::task::SummarizedTaskView;
|
||||
|
||||
const DEFAULT_RETRIEVE_DOCUMENTS_OFFSET: usize = 0;
|
||||
|
@ -71,17 +72,17 @@ pub struct DocumentParam {
|
|||
pub fn configure(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(
|
||||
web::resource("")
|
||||
.route(web::get().to(get_all_documents))
|
||||
.route(web::post().to(add_documents))
|
||||
.route(web::put().to(update_documents))
|
||||
.route(web::delete().to(clear_all_documents)),
|
||||
.route(web::get().to(SeqHandler(get_all_documents)))
|
||||
.route(web::post().to(SeqHandler(add_documents)))
|
||||
.route(web::put().to(SeqHandler(update_documents)))
|
||||
.route(web::delete().to(SeqHandler(clear_all_documents))),
|
||||
)
|
||||
// this route needs to be before the /documents/{document_id} to match properly
|
||||
.service(web::resource("/delete-batch").route(web::post().to(delete_documents)))
|
||||
.service(web::resource("/delete-batch").route(web::post().to(SeqHandler(delete_documents))))
|
||||
.service(
|
||||
web::resource("/{document_id}")
|
||||
.route(web::get().to(get_document))
|
||||
.route(web::delete().to(delete_document)),
|
||||
.route(web::get().to(SeqHandler(get_document)))
|
||||
.route(web::delete().to(SeqHandler(delete_document))),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use chrono::{DateTime, Utc};
|
||||
use log::debug;
|
||||
use meilisearch_error::ResponseError;
|
||||
use meilisearch_lib::index_controller::Update;
|
||||
use meilisearch_lib::MeiliSearch;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use crate::analytics::Analytics;
|
||||
use crate::extractors::authentication::{policies::*, GuardedData};
|
||||
use crate::extractors::sequential_extractor::SeqHandler;
|
||||
use crate::task::SummarizedTaskView;
|
||||
|
||||
pub mod documents;
|
||||
|
@ -20,17 +21,17 @@ pub fn configure(cfg: &mut web::ServiceConfig) {
|
|||
cfg.service(
|
||||
web::resource("")
|
||||
.route(web::get().to(list_indexes))
|
||||
.route(web::post().to(create_index)),
|
||||
.route(web::post().to(SeqHandler(create_index))),
|
||||
)
|
||||
.service(
|
||||
web::scope("/{index_uid}")
|
||||
.service(
|
||||
web::resource("")
|
||||
.route(web::get().to(get_index))
|
||||
.route(web::put().to(update_index))
|
||||
.route(web::delete().to(delete_index)),
|
||||
.route(web::get().to(SeqHandler(get_index)))
|
||||
.route(web::put().to(SeqHandler(update_index)))
|
||||
.route(web::delete().to(SeqHandler(delete_index))),
|
||||
)
|
||||
.service(web::resource("/stats").route(web::get().to(get_index_stats)))
|
||||
.service(web::resource("/stats").route(web::get().to(SeqHandler(get_index_stats))))
|
||||
.service(web::scope("/documents").configure(documents::configure))
|
||||
.service(web::scope("/search").configure(search::configure))
|
||||
.service(web::scope("/tasks").configure(tasks::configure))
|
||||
|
@ -95,9 +96,12 @@ pub struct UpdateIndexRequest {
|
|||
pub struct UpdateIndexResponse {
|
||||
name: String,
|
||||
uid: String,
|
||||
created_at: DateTime<Utc>,
|
||||
updated_at: DateTime<Utc>,
|
||||
primary_key: Option<String>,
|
||||
#[serde(serialize_with = "time::serde::rfc3339::serialize")]
|
||||
created_at: OffsetDateTime,
|
||||
#[serde(serialize_with = "time::serde::rfc3339::serialize")]
|
||||
updated_at: OffsetDateTime,
|
||||
#[serde(serialize_with = "time::serde::rfc3339::serialize")]
|
||||
primary_key: OffsetDateTime,
|
||||
}
|
||||
|
||||
pub async fn get_index(
|
||||
|
|
|
@ -9,12 +9,13 @@ use serde_json::Value;
|
|||
|
||||
use crate::analytics::{Analytics, SearchAggregator};
|
||||
use crate::extractors::authentication::{policies::*, GuardedData};
|
||||
use crate::extractors::sequential_extractor::SeqHandler;
|
||||
|
||||
pub fn configure(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(
|
||||
web::resource("")
|
||||
.route(web::get().to(search_with_url_query))
|
||||
.route(web::post().to(search_with_post)),
|
||||
.route(web::get().to(SeqHandler(search_with_url_query)))
|
||||
.route(web::post().to(SeqHandler(search_with_post))),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ macro_rules! make_setting_route {
|
|||
|
||||
use crate::analytics::Analytics;
|
||||
use crate::extractors::authentication::{policies::*, GuardedData};
|
||||
use crate::extractors::sequential_extractor::SeqHandler;
|
||||
use crate::task::SummarizedTaskView;
|
||||
use meilisearch_error::ResponseError;
|
||||
|
||||
|
@ -98,9 +99,9 @@ macro_rules! make_setting_route {
|
|||
|
||||
pub fn resources() -> Resource {
|
||||
Resource::new($route)
|
||||
.route(web::get().to(get))
|
||||
.route(web::post().to(update))
|
||||
.route(web::delete().to(delete))
|
||||
.route(web::get().to(SeqHandler(get)))
|
||||
.route(web::post().to(SeqHandler(update)))
|
||||
.route(web::delete().to(SeqHandler(delete)))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -226,11 +227,12 @@ make_setting_route!(
|
|||
macro_rules! generate_configure {
|
||||
($($mod:ident),*) => {
|
||||
pub fn configure(cfg: &mut web::ServiceConfig) {
|
||||
use crate::extractors::sequential_extractor::SeqHandler;
|
||||
cfg.service(
|
||||
web::resource("")
|
||||
.route(web::post().to(update_all))
|
||||
.route(web::get().to(get_all))
|
||||
.route(web::delete().to(delete_all)))
|
||||
.route(web::post().to(SeqHandler(update_all)))
|
||||
.route(web::get().to(SeqHandler(get_all)))
|
||||
.route(web::delete().to(SeqHandler(delete_all))))
|
||||
$(.service($mod::resources()))*;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use chrono::{DateTime, Utc};
|
||||
use log::debug;
|
||||
use meilisearch_error::ResponseError;
|
||||
use meilisearch_lib::MeiliSearch;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use crate::analytics::Analytics;
|
||||
use crate::extractors::authentication::{policies::*, GuardedData};
|
||||
use crate::extractors::sequential_extractor::SeqHandler;
|
||||
use crate::task::{TaskListView, TaskView};
|
||||
|
||||
pub fn configure(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(web::resource("").route(web::get().to(get_all_tasks_status)))
|
||||
.service(web::resource("{task_id}").route(web::get().to(get_task_status)));
|
||||
cfg.service(web::resource("").route(web::get().to(SeqHandler(get_all_tasks_status))))
|
||||
.service(web::resource("{task_id}").route(web::get().to(SeqHandler(get_task_status))));
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
|
@ -20,9 +21,12 @@ pub fn configure(cfg: &mut web::ServiceConfig) {
|
|||
pub struct UpdateIndexResponse {
|
||||
name: String,
|
||||
uid: String,
|
||||
created_at: DateTime<Utc>,
|
||||
updated_at: DateTime<Utc>,
|
||||
primary_key: Option<String>,
|
||||
#[serde(serialize_with = "time::serde::rfc3339::serialize")]
|
||||
created_at: OffsetDateTime,
|
||||
#[serde(serialize_with = "time::serde::rfc3339::serialize")]
|
||||
updated_at: OffsetDateTime,
|
||||
#[serde(serialize_with = "time::serde::rfc3339::serialize")]
|
||||
primary_key: OffsetDateTime,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use actix_web::{web, HttpResponse};
|
||||
use chrono::{DateTime, Utc};
|
||||
use log::debug;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use meilisearch_error::ResponseError;
|
||||
use meilisearch_lib::index::{Settings, Unchecked};
|
||||
|
@ -54,8 +54,10 @@ pub struct ProcessedUpdateResult {
|
|||
#[serde(rename = "type")]
|
||||
pub update_type: UpdateType,
|
||||
pub duration: f64, // in seconds
|
||||
pub enqueued_at: DateTime<Utc>,
|
||||
pub processed_at: DateTime<Utc>,
|
||||
#[serde(with = "time::serde::rfc3339")]
|
||||
pub enqueued_at: OffsetDateTime,
|
||||
#[serde(with = "time::serde::rfc3339")]
|
||||
pub processed_at: OffsetDateTime,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
@ -66,8 +68,10 @@ pub struct FailedUpdateResult {
|
|||
pub update_type: UpdateType,
|
||||
pub error: ResponseError,
|
||||
pub duration: f64, // in seconds
|
||||
pub enqueued_at: DateTime<Utc>,
|
||||
pub processed_at: DateTime<Utc>,
|
||||
#[serde(with = "time::serde::rfc3339")]
|
||||
pub enqueued_at: OffsetDateTime,
|
||||
#[serde(with = "time::serde::rfc3339")]
|
||||
pub processed_at: OffsetDateTime,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
@ -76,9 +80,13 @@ pub struct EnqueuedUpdateResult {
|
|||
pub update_id: u64,
|
||||
#[serde(rename = "type")]
|
||||
pub update_type: UpdateType,
|
||||
pub enqueued_at: DateTime<Utc>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub started_processing_at: Option<DateTime<Utc>>,
|
||||
#[serde(with = "time::serde::rfc3339")]
|
||||
pub enqueued_at: OffsetDateTime,
|
||||
#[serde(
|
||||
skip_serializing_if = "Option::is_none",
|
||||
with = "time::serde::rfc3339::option"
|
||||
)]
|
||||
pub started_processing_at: Option<OffsetDateTime>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
|
|
@ -7,11 +7,12 @@ use serde_json::json;
|
|||
|
||||
use crate::analytics::Analytics;
|
||||
use crate::extractors::authentication::{policies::*, GuardedData};
|
||||
use crate::extractors::sequential_extractor::SeqHandler;
|
||||
use crate::task::{TaskListView, TaskView};
|
||||
|
||||
pub fn configure(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(web::resource("").route(web::get().to(get_tasks)))
|
||||
.service(web::resource("/{task_id}").route(web::get().to(get_task)));
|
||||
cfg.service(web::resource("").route(web::get().to(SeqHandler(get_tasks))))
|
||||
.service(web::resource("/{task_id}").route(web::get().to(SeqHandler(get_task))));
|
||||
}
|
||||
|
||||
async fn get_tasks(
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
use chrono::{DateTime, Duration, Utc};
|
||||
use std::fmt::Write;
|
||||
use std::write;
|
||||
|
||||
use meilisearch_error::ResponseError;
|
||||
use meilisearch_lib::index::{Settings, Unchecked};
|
||||
use meilisearch_lib::milli::update::IndexDocumentsMethod;
|
||||
|
@ -7,6 +9,7 @@ use meilisearch_lib::tasks::task::{
|
|||
DocumentDeletion, Task, TaskContent, TaskEvent, TaskId, TaskResult,
|
||||
};
|
||||
use serde::{Serialize, Serializer};
|
||||
use time::{Duration, OffsetDateTime};
|
||||
|
||||
use crate::AUTOBATCHING_ENABLED;
|
||||
|
||||
|
@ -79,14 +82,52 @@ enum TaskDetails {
|
|||
ClearAll { deleted_documents: Option<u64> },
|
||||
}
|
||||
|
||||
/// Serialize a `time::Duration` as a best effort ISO 8601 while waiting for
|
||||
/// https://github.com/time-rs/time/issues/378.
|
||||
/// This code is a port of the old code of time that was removed in 0.2.
|
||||
fn serialize_duration<S: Serializer>(
|
||||
duration: &Option<Duration>,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error> {
|
||||
match duration {
|
||||
Some(duration) => {
|
||||
let duration_str = duration.to_string();
|
||||
serializer.serialize_str(&duration_str)
|
||||
// technically speaking, negative duration is not valid ISO 8601
|
||||
if duration.is_negative() {
|
||||
return serializer.serialize_none();
|
||||
}
|
||||
|
||||
const SECS_PER_DAY: i64 = Duration::DAY.whole_seconds();
|
||||
let secs = duration.whole_seconds();
|
||||
let days = secs / SECS_PER_DAY;
|
||||
let secs = secs - days * SECS_PER_DAY;
|
||||
let hasdate = days != 0;
|
||||
let nanos = duration.subsec_nanoseconds();
|
||||
let hastime = (secs != 0 || nanos != 0) || !hasdate;
|
||||
|
||||
// all the following unwrap can't fail
|
||||
let mut res = String::new();
|
||||
write!(&mut res, "P").unwrap();
|
||||
|
||||
if hasdate {
|
||||
write!(&mut res, "{}D", days).unwrap();
|
||||
}
|
||||
|
||||
const NANOS_PER_MILLI: i32 = Duration::MILLISECOND.subsec_nanoseconds();
|
||||
const NANOS_PER_MICRO: i32 = Duration::MICROSECOND.subsec_nanoseconds();
|
||||
|
||||
if hastime {
|
||||
if nanos == 0 {
|
||||
write!(&mut res, "T{}S", secs).unwrap();
|
||||
} else if nanos % NANOS_PER_MILLI == 0 {
|
||||
write!(&mut res, "T{}.{:03}S", secs, nanos / NANOS_PER_MILLI).unwrap();
|
||||
} else if nanos % NANOS_PER_MICRO == 0 {
|
||||
write!(&mut res, "T{}.{:06}S", secs, nanos / NANOS_PER_MICRO).unwrap();
|
||||
} else {
|
||||
write!(&mut res, "T{}.{:09}S", secs, nanos).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
serializer.serialize_str(&res)
|
||||
}
|
||||
None => serializer.serialize_none(),
|
||||
}
|
||||
|
@ -106,9 +147,12 @@ pub struct TaskView {
|
|||
error: Option<ResponseError>,
|
||||
#[serde(serialize_with = "serialize_duration")]
|
||||
duration: Option<Duration>,
|
||||
enqueued_at: DateTime<Utc>,
|
||||
started_at: Option<DateTime<Utc>>,
|
||||
finished_at: Option<DateTime<Utc>>,
|
||||
#[serde(serialize_with = "time::serde::rfc3339::serialize")]
|
||||
enqueued_at: OffsetDateTime,
|
||||
#[serde(serialize_with = "time::serde::rfc3339::option::serialize")]
|
||||
started_at: Option<OffsetDateTime>,
|
||||
#[serde(serialize_with = "time::serde::rfc3339::option::serialize")]
|
||||
finished_at: Option<OffsetDateTime>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
batch_uid: Option<Option<BatchId>>,
|
||||
}
|
||||
|
@ -302,7 +346,8 @@ pub struct SummarizedTaskView {
|
|||
status: TaskStatus,
|
||||
#[serde(rename = "type")]
|
||||
task_type: TaskType,
|
||||
enqueued_at: DateTime<Utc>,
|
||||
#[serde(serialize_with = "time::serde::rfc3339::serialize")]
|
||||
enqueued_at: OffsetDateTime,
|
||||
}
|
||||
|
||||
impl From<Task> for SummarizedTaskView {
|
||||
|
|
|
@ -257,7 +257,7 @@ async fn error_add_api_key_missing_parameter() {
|
|||
"message": "`indexes` field is mandatory.",
|
||||
"code": "missing_parameter",
|
||||
"type": "invalid_request",
|
||||
"link":"https://docs.meilisearch.com/errors#missing_parameter"
|
||||
"link": "https://docs.meilisearch.com/errors#missing_parameter"
|
||||
});
|
||||
|
||||
assert_eq!(response, expected_response);
|
||||
|
@ -275,7 +275,7 @@ async fn error_add_api_key_missing_parameter() {
|
|||
"message": "`actions` field is mandatory.",
|
||||
"code": "missing_parameter",
|
||||
"type": "invalid_request",
|
||||
"link":"https://docs.meilisearch.com/errors#missing_parameter"
|
||||
"link": "https://docs.meilisearch.com/errors#missing_parameter"
|
||||
});
|
||||
|
||||
assert_eq!(response, expected_response);
|
||||
|
@ -293,7 +293,7 @@ async fn error_add_api_key_missing_parameter() {
|
|||
"message": "`expiresAt` field is mandatory.",
|
||||
"code": "missing_parameter",
|
||||
"type": "invalid_request",
|
||||
"link":"https://docs.meilisearch.com/errors#missing_parameter"
|
||||
"link": "https://docs.meilisearch.com/errors#missing_parameter"
|
||||
});
|
||||
|
||||
assert_eq!(response, expected_response);
|
||||
|
@ -316,7 +316,7 @@ async fn error_add_api_key_invalid_parameters_description() {
|
|||
let (response, code) = server.add_api_key(content).await;
|
||||
|
||||
let expected_response = json!({
|
||||
"message": r#"description field value `{"name":"products"}` is invalid. It should be a string or specified as a null value."#,
|
||||
"message": r#"`description` field value `{"name":"products"}` is invalid. It should be a string or specified as a null value."#,
|
||||
"code": "invalid_api_key_description",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid_api_key_description"
|
||||
|
@ -342,7 +342,7 @@ async fn error_add_api_key_invalid_parameters_indexes() {
|
|||
let (response, code) = server.add_api_key(content).await;
|
||||
|
||||
let expected_response = json!({
|
||||
"message": r#"indexes field value `{"name":"products"}` is invalid. It should be an array of string representing index names."#,
|
||||
"message": r#"`indexes` field value `{"name":"products"}` is invalid. It should be an array of string representing index names."#,
|
||||
"code": "invalid_api_key_indexes",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid_api_key_indexes"
|
||||
|
@ -366,7 +366,7 @@ async fn error_add_api_key_invalid_parameters_actions() {
|
|||
let (response, code) = server.add_api_key(content).await;
|
||||
|
||||
let expected_response = json!({
|
||||
"message": r#"actions field value `{"name":"products"}` is invalid. It should be an array of string representing action names."#,
|
||||
"message": r#"`actions` field value `{"name":"products"}` is invalid. It should be an array of string representing action names."#,
|
||||
"code": "invalid_api_key_actions",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid_api_key_actions"
|
||||
|
@ -386,7 +386,7 @@ async fn error_add_api_key_invalid_parameters_actions() {
|
|||
let (response, code) = server.add_api_key(content).await;
|
||||
|
||||
let expected_response = json!({
|
||||
"message": r#"actions field value `["doc.add"]` is invalid. It should be an array of string representing action names."#,
|
||||
"message": r#"`actions` field value `["doc.add"]` is invalid. It should be an array of string representing action names."#,
|
||||
"code": "invalid_api_key_actions",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid_api_key_actions"
|
||||
|
@ -412,7 +412,7 @@ async fn error_add_api_key_invalid_parameters_expires_at() {
|
|||
let (response, code) = server.add_api_key(content).await;
|
||||
|
||||
let expected_response = json!({
|
||||
"message": r#"expiresAt field value `{"name":"products"}` is invalid. It should be in ISO-8601 format to represents a date or datetime in the future or specified as a null value. e.g. 'YYYY-MM-DD' or 'YYYY-MM-DDTHH:MM:SS'."#,
|
||||
"message": r#"`expiresAt` field value `{"name":"products"}` is invalid. 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'."#,
|
||||
"code": "invalid_api_key_expires_at",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid_api_key_expires_at"
|
||||
|
@ -438,7 +438,7 @@ async fn error_add_api_key_invalid_parameters_expires_at_in_the_past() {
|
|||
let (response, code) = server.add_api_key(content).await;
|
||||
|
||||
let expected_response = json!({
|
||||
"message": r#"expiresAt field value `"2010-11-13T00:00:00Z"` is invalid. It should be in ISO-8601 format to represents a date or datetime in the future or specified as a null value. e.g. 'YYYY-MM-DD' or 'YYYY-MM-DDTHH:MM:SS'."#,
|
||||
"message": r#"`expiresAt` field value `"2010-11-13T00:00:00Z"` is invalid. 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'."#,
|
||||
"code": "invalid_api_key_expires_at",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid_api_key_expires_at"
|
||||
|
@ -1213,7 +1213,7 @@ async fn error_patch_api_key_indexes_invalid_parameters() {
|
|||
let (response, code) = server.patch_api_key(&key, content).await;
|
||||
|
||||
let expected_response = json!({
|
||||
"message": "description field value `13` is invalid. It should be a string or specified as a null value.",
|
||||
"message": "`description` field value `13` is invalid. It should be a string or specified as a null value.",
|
||||
"code": "invalid_api_key_description",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid_api_key_description"
|
||||
|
@ -1230,7 +1230,7 @@ async fn error_patch_api_key_indexes_invalid_parameters() {
|
|||
let (response, code) = server.patch_api_key(&key, content).await;
|
||||
|
||||
let expected_response = json!({
|
||||
"message": "indexes field value `13` is invalid. It should be an array of string representing index names.",
|
||||
"message": "`indexes` field value `13` is invalid. It should be an array of string representing index names.",
|
||||
"code": "invalid_api_key_indexes",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid_api_key_indexes"
|
||||
|
@ -1246,7 +1246,7 @@ async fn error_patch_api_key_indexes_invalid_parameters() {
|
|||
let (response, code) = server.patch_api_key(&key, content).await;
|
||||
|
||||
let expected_response = json!({
|
||||
"message": "actions field value `13` is invalid. It should be an array of string representing action names.",
|
||||
"message": "`actions` field value `13` is invalid. It should be an array of string representing action names.",
|
||||
"code": "invalid_api_key_actions",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid_api_key_actions"
|
||||
|
@ -1262,7 +1262,7 @@ async fn error_patch_api_key_indexes_invalid_parameters() {
|
|||
let (response, code) = server.patch_api_key(&key, content).await;
|
||||
|
||||
let expected_response = json!({
|
||||
"message": "expiresAt field value `13` is invalid. It should be in ISO-8601 format to represents a date or datetime in the future or specified as a null value. e.g. 'YYYY-MM-DD' or 'YYYY-MM-DDTHH:MM:SS'.",
|
||||
"message": "`expiresAt` field value `13` is invalid. 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'.",
|
||||
"code": "invalid_api_key_expires_at",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid_api_key_expires_at"
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use crate::common::Server;
|
||||
use chrono::{Duration, Utc};
|
||||
use ::time::format_description::well_known::Rfc3339;
|
||||
use maplit::{hashmap, hashset};
|
||||
use once_cell::sync::Lazy;
|
||||
use serde_json::{json, Value};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use time::{Duration, OffsetDateTime};
|
||||
|
||||
pub static AUTHORIZATIONS: Lazy<HashMap<(&'static str, &'static str), HashSet<&'static str>>> =
|
||||
Lazy::new(|| {
|
||||
|
@ -76,7 +77,7 @@ async fn error_access_expired_key() {
|
|||
let content = json!({
|
||||
"indexes": ["products"],
|
||||
"actions": ALL_ACTIONS.clone(),
|
||||
"expiresAt": (Utc::now() + Duration::seconds(1)),
|
||||
"expiresAt": (OffsetDateTime::now_utc() + Duration::seconds(1)).format(&Rfc3339).unwrap(),
|
||||
});
|
||||
|
||||
let (response, code) = server.add_api_key(content).await;
|
||||
|
@ -106,7 +107,7 @@ async fn error_access_unauthorized_index() {
|
|||
let content = json!({
|
||||
"indexes": ["sales"],
|
||||
"actions": ALL_ACTIONS.clone(),
|
||||
"expiresAt": Utc::now() + Duration::hours(1),
|
||||
"expiresAt": (OffsetDateTime::now_utc() + Duration::hours(1)).format(&Rfc3339).unwrap(),
|
||||
});
|
||||
|
||||
let (response, code) = server.add_api_key(content).await;
|
||||
|
@ -137,7 +138,7 @@ async fn error_access_unauthorized_action() {
|
|||
let content = json!({
|
||||
"indexes": ["products"],
|
||||
"actions": [],
|
||||
"expiresAt": Utc::now() + Duration::hours(1),
|
||||
"expiresAt": (OffsetDateTime::now_utc() + Duration::hours(1)).format(&Rfc3339).unwrap(),
|
||||
});
|
||||
|
||||
let (response, code) = server.add_api_key(content).await;
|
||||
|
@ -174,7 +175,7 @@ async fn access_authorized_restricted_index() {
|
|||
let content = json!({
|
||||
"indexes": ["products"],
|
||||
"actions": [],
|
||||
"expiresAt": Utc::now() + Duration::hours(1),
|
||||
"expiresAt": (OffsetDateTime::now_utc() + Duration::hours(1)).format(&Rfc3339).unwrap(),
|
||||
});
|
||||
|
||||
let (response, code) = server.add_api_key(content).await;
|
||||
|
@ -213,7 +214,7 @@ async fn access_authorized_no_index_restriction() {
|
|||
let content = json!({
|
||||
"indexes": ["*"],
|
||||
"actions": [],
|
||||
"expiresAt": Utc::now() + Duration::hours(1),
|
||||
"expiresAt": (OffsetDateTime::now_utc() + Duration::hours(1)).format(&Rfc3339).unwrap(),
|
||||
});
|
||||
|
||||
let (response, code) = server.add_api_key(content).await;
|
||||
|
@ -263,7 +264,7 @@ async fn access_authorized_stats_restricted_index() {
|
|||
let content = json!({
|
||||
"indexes": ["products"],
|
||||
"actions": ["stats.get"],
|
||||
"expiresAt": Utc::now() + Duration::hours(1),
|
||||
"expiresAt": (OffsetDateTime::now_utc() + Duration::hours(1)).format(&Rfc3339).unwrap(),
|
||||
});
|
||||
let (response, code) = server.add_api_key(content).await;
|
||||
assert_eq!(code, 201);
|
||||
|
@ -303,7 +304,7 @@ async fn access_authorized_stats_no_index_restriction() {
|
|||
let content = json!({
|
||||
"indexes": ["*"],
|
||||
"actions": ["stats.get"],
|
||||
"expiresAt": Utc::now() + Duration::hours(1),
|
||||
"expiresAt": (OffsetDateTime::now_utc() + Duration::hours(1)).format(&Rfc3339).unwrap(),
|
||||
});
|
||||
let (response, code) = server.add_api_key(content).await;
|
||||
assert_eq!(code, 201);
|
||||
|
@ -343,7 +344,7 @@ async fn list_authorized_indexes_restricted_index() {
|
|||
let content = json!({
|
||||
"indexes": ["products"],
|
||||
"actions": ["indexes.get"],
|
||||
"expiresAt": Utc::now() + Duration::hours(1),
|
||||
"expiresAt": (OffsetDateTime::now_utc() + Duration::hours(1)).format(&Rfc3339).unwrap(),
|
||||
});
|
||||
let (response, code) = server.add_api_key(content).await;
|
||||
assert_eq!(code, 201);
|
||||
|
@ -384,7 +385,7 @@ async fn list_authorized_indexes_no_index_restriction() {
|
|||
let content = json!({
|
||||
"indexes": ["*"],
|
||||
"actions": ["indexes.get"],
|
||||
"expiresAt": Utc::now() + Duration::hours(1),
|
||||
"expiresAt": (OffsetDateTime::now_utc() + Duration::hours(1)).format(&Rfc3339).unwrap(),
|
||||
});
|
||||
let (response, code) = server.add_api_key(content).await;
|
||||
assert_eq!(code, 201);
|
||||
|
@ -424,7 +425,7 @@ async fn list_authorized_tasks_restricted_index() {
|
|||
let content = json!({
|
||||
"indexes": ["products"],
|
||||
"actions": ["tasks.get"],
|
||||
"expiresAt": Utc::now() + Duration::hours(1),
|
||||
"expiresAt": (OffsetDateTime::now_utc() + Duration::hours(1)).format(&Rfc3339).unwrap(),
|
||||
});
|
||||
let (response, code) = server.add_api_key(content).await;
|
||||
assert_eq!(code, 201);
|
||||
|
@ -464,7 +465,7 @@ async fn list_authorized_tasks_no_index_restriction() {
|
|||
let content = json!({
|
||||
"indexes": ["*"],
|
||||
"actions": ["tasks.get"],
|
||||
"expiresAt": Utc::now() + Duration::hours(1),
|
||||
"expiresAt": (OffsetDateTime::now_utc() + Duration::hours(1)).format(&Rfc3339).unwrap(),
|
||||
});
|
||||
let (response, code) = server.add_api_key(content).await;
|
||||
assert_eq!(code, 201);
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use crate::common::Server;
|
||||
use chrono::{Duration, Utc};
|
||||
use ::time::format_description::well_known::Rfc3339;
|
||||
use maplit::hashmap;
|
||||
use once_cell::sync::Lazy;
|
||||
use serde_json::{json, Value};
|
||||
use std::collections::HashMap;
|
||||
use time::{Duration, OffsetDateTime};
|
||||
|
||||
use super::authorization::{ALL_ACTIONS, AUTHORIZATIONS};
|
||||
|
||||
|
@ -63,22 +64,22 @@ static ACCEPTED_KEYS: Lazy<Vec<Value>> = Lazy::new(|| {
|
|||
json!({
|
||||
"indexes": ["*"],
|
||||
"actions": ["*"],
|
||||
"expiresAt": Utc::now() + Duration::days(1)
|
||||
"expiresAt": (OffsetDateTime::now_utc() + Duration::days(1)).format(&Rfc3339).unwrap()
|
||||
}),
|
||||
json!({
|
||||
"indexes": ["*"],
|
||||
"actions": ["search"],
|
||||
"expiresAt": Utc::now() + Duration::days(1)
|
||||
"expiresAt": (OffsetDateTime::now_utc() + Duration::days(1)).format(&Rfc3339).unwrap()
|
||||
}),
|
||||
json!({
|
||||
"indexes": ["sales"],
|
||||
"actions": ["*"],
|
||||
"expiresAt": Utc::now() + Duration::days(1)
|
||||
"expiresAt": (OffsetDateTime::now_utc() + Duration::days(1)).format(&Rfc3339).unwrap()
|
||||
}),
|
||||
json!({
|
||||
"indexes": ["sales"],
|
||||
"actions": ["search"],
|
||||
"expiresAt": Utc::now() + Duration::days(1)
|
||||
"expiresAt": (OffsetDateTime::now_utc() + Duration::days(1)).format(&Rfc3339).unwrap()
|
||||
}),
|
||||
]
|
||||
});
|
||||
|
@ -89,23 +90,23 @@ static REFUSED_KEYS: Lazy<Vec<Value>> = Lazy::new(|| {
|
|||
json!({
|
||||
"indexes": ["*"],
|
||||
"actions": ALL_ACTIONS.iter().cloned().filter(|a| *a != "search" && *a != "*").collect::<Vec<_>>(),
|
||||
"expiresAt": Utc::now() + Duration::days(1)
|
||||
"expiresAt": (OffsetDateTime::now_utc() + Duration::days(1)).format(&Rfc3339).unwrap()
|
||||
}),
|
||||
json!({
|
||||
"indexes": ["sales"],
|
||||
"actions": ALL_ACTIONS.iter().cloned().filter(|a| *a != "search" && *a != "*").collect::<Vec<_>>(),
|
||||
"expiresAt": Utc::now() + Duration::days(1)
|
||||
"expiresAt": (OffsetDateTime::now_utc() + Duration::days(1)).format(&Rfc3339).unwrap()
|
||||
}),
|
||||
// bad index
|
||||
json!({
|
||||
"indexes": ["products"],
|
||||
"actions": ["*"],
|
||||
"expiresAt": Utc::now() + Duration::days(1)
|
||||
"expiresAt": (OffsetDateTime::now_utc() + Duration::days(1)).format(&Rfc3339).unwrap()
|
||||
}),
|
||||
json!({
|
||||
"indexes": ["products"],
|
||||
"actions": ["search"],
|
||||
"expiresAt": Utc::now() + Duration::days(1)
|
||||
"expiresAt": (OffsetDateTime::now_utc() + Duration::days(1)).format(&Rfc3339).unwrap()
|
||||
}),
|
||||
]
|
||||
});
|
||||
|
@ -204,19 +205,19 @@ async fn search_authorized_simple_token() {
|
|||
let tenant_tokens = vec![
|
||||
hashmap! {
|
||||
"searchRules" => json!({"*": {}}),
|
||||
"exp" => json!((Utc::now() + Duration::hours(1)).timestamp())
|
||||
"exp" => json!((OffsetDateTime::now_utc() + Duration::hours(1)).unix_timestamp())
|
||||
},
|
||||
hashmap! {
|
||||
"searchRules" => json!(["*"]),
|
||||
"exp" => json!((Utc::now() + Duration::hours(1)).timestamp())
|
||||
"exp" => json!((OffsetDateTime::now_utc() + Duration::hours(1)).unix_timestamp())
|
||||
},
|
||||
hashmap! {
|
||||
"searchRules" => json!({"sales": {}}),
|
||||
"exp" => json!((Utc::now() + Duration::hours(1)).timestamp())
|
||||
"exp" => json!((OffsetDateTime::now_utc() + Duration::hours(1)).unix_timestamp())
|
||||
},
|
||||
hashmap! {
|
||||
"searchRules" => json!(["sales"]),
|
||||
"exp" => json!((Utc::now() + Duration::hours(1)).timestamp())
|
||||
"exp" => json!((OffsetDateTime::now_utc() + Duration::hours(1)).unix_timestamp())
|
||||
},
|
||||
hashmap! {
|
||||
"searchRules" => json!({"*": {}}),
|
||||
|
@ -253,19 +254,19 @@ async fn search_authorized_filter_token() {
|
|||
let tenant_tokens = vec![
|
||||
hashmap! {
|
||||
"searchRules" => json!({"*": {"filter": "color = blue"}}),
|
||||
"exp" => json!((Utc::now() + Duration::hours(1)).timestamp())
|
||||
"exp" => json!((OffsetDateTime::now_utc() + Duration::hours(1)).unix_timestamp())
|
||||
},
|
||||
hashmap! {
|
||||
"searchRules" => json!({"sales": {"filter": "color = blue"}}),
|
||||
"exp" => json!((Utc::now() + Duration::hours(1)).timestamp())
|
||||
"exp" => json!((OffsetDateTime::now_utc() + Duration::hours(1)).unix_timestamp())
|
||||
},
|
||||
hashmap! {
|
||||
"searchRules" => json!({"*": {"filter": ["color = blue"]}}),
|
||||
"exp" => json!((Utc::now() + Duration::hours(1)).timestamp())
|
||||
"exp" => json!((OffsetDateTime::now_utc() + Duration::hours(1)).unix_timestamp())
|
||||
},
|
||||
hashmap! {
|
||||
"searchRules" => json!({"sales": {"filter": ["color = blue"]}}),
|
||||
"exp" => json!((Utc::now() + Duration::hours(1)).timestamp())
|
||||
"exp" => json!((OffsetDateTime::now_utc() + Duration::hours(1)).unix_timestamp())
|
||||
},
|
||||
// filter on sales should override filters on *
|
||||
hashmap! {
|
||||
|
@ -273,28 +274,28 @@ async fn search_authorized_filter_token() {
|
|||
"*": {"filter": "color = green"},
|
||||
"sales": {"filter": "color = blue"}
|
||||
}),
|
||||
"exp" => json!((Utc::now() + Duration::hours(1)).timestamp())
|
||||
"exp" => json!((OffsetDateTime::now_utc() + Duration::hours(1)).unix_timestamp())
|
||||
},
|
||||
hashmap! {
|
||||
"searchRules" => json!({
|
||||
"*": {},
|
||||
"sales": {"filter": "color = blue"}
|
||||
}),
|
||||
"exp" => json!((Utc::now() + Duration::hours(1)).timestamp())
|
||||
"exp" => json!((OffsetDateTime::now_utc() + Duration::hours(1)).unix_timestamp())
|
||||
},
|
||||
hashmap! {
|
||||
"searchRules" => json!({
|
||||
"*": {"filter": "color = green"},
|
||||
"sales": {"filter": ["color = blue"]}
|
||||
}),
|
||||
"exp" => json!((Utc::now() + Duration::hours(1)).timestamp())
|
||||
"exp" => json!((OffsetDateTime::now_utc() + Duration::hours(1)).unix_timestamp())
|
||||
},
|
||||
hashmap! {
|
||||
"searchRules" => json!({
|
||||
"*": {},
|
||||
"sales": {"filter": ["color = blue"]}
|
||||
}),
|
||||
"exp" => json!((Utc::now() + Duration::hours(1)).timestamp())
|
||||
"exp" => json!((OffsetDateTime::now_utc() + Duration::hours(1)).unix_timestamp())
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -307,19 +308,19 @@ async fn filter_search_authorized_filter_token() {
|
|||
let tenant_tokens = vec![
|
||||
hashmap! {
|
||||
"searchRules" => json!({"*": {"filter": "color = blue"}}),
|
||||
"exp" => json!((Utc::now() + Duration::hours(1)).timestamp())
|
||||
"exp" => json!((OffsetDateTime::now_utc() + Duration::hours(1)).unix_timestamp())
|
||||
},
|
||||
hashmap! {
|
||||
"searchRules" => json!({"sales": {"filter": "color = blue"}}),
|
||||
"exp" => json!((Utc::now() + Duration::hours(1)).timestamp())
|
||||
"exp" => json!((OffsetDateTime::now_utc() + Duration::hours(1)).unix_timestamp())
|
||||
},
|
||||
hashmap! {
|
||||
"searchRules" => json!({"*": {"filter": ["color = blue"]}}),
|
||||
"exp" => json!((Utc::now() + Duration::hours(1)).timestamp())
|
||||
"exp" => json!((OffsetDateTime::now_utc() + Duration::hours(1)).unix_timestamp())
|
||||
},
|
||||
hashmap! {
|
||||
"searchRules" => json!({"sales": {"filter": ["color = blue"]}}),
|
||||
"exp" => json!((Utc::now() + Duration::hours(1)).timestamp())
|
||||
"exp" => json!((OffsetDateTime::now_utc() + Duration::hours(1)).unix_timestamp())
|
||||
},
|
||||
// filter on sales should override filters on *
|
||||
hashmap! {
|
||||
|
@ -327,28 +328,28 @@ async fn filter_search_authorized_filter_token() {
|
|||
"*": {"filter": "color = green"},
|
||||
"sales": {"filter": "color = blue"}
|
||||
}),
|
||||
"exp" => json!((Utc::now() + Duration::hours(1)).timestamp())
|
||||
"exp" => json!((OffsetDateTime::now_utc() + Duration::hours(1)).unix_timestamp())
|
||||
},
|
||||
hashmap! {
|
||||
"searchRules" => json!({
|
||||
"*": {},
|
||||
"sales": {"filter": "color = blue"}
|
||||
}),
|
||||
"exp" => json!((Utc::now() + Duration::hours(1)).timestamp())
|
||||
"exp" => json!((OffsetDateTime::now_utc() + Duration::hours(1)).unix_timestamp())
|
||||
},
|
||||
hashmap! {
|
||||
"searchRules" => json!({
|
||||
"*": {"filter": "color = green"},
|
||||
"sales": {"filter": ["color = blue"]}
|
||||
}),
|
||||
"exp" => json!((Utc::now() + Duration::hours(1)).timestamp())
|
||||
"exp" => json!((OffsetDateTime::now_utc() + Duration::hours(1)).unix_timestamp())
|
||||
},
|
||||
hashmap! {
|
||||
"searchRules" => json!({
|
||||
"*": {},
|
||||
"sales": {"filter": ["color = blue"]}
|
||||
}),
|
||||
"exp" => json!((Utc::now() + Duration::hours(1)).timestamp())
|
||||
"exp" => json!((OffsetDateTime::now_utc() + Duration::hours(1)).unix_timestamp())
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -361,27 +362,27 @@ async fn error_search_token_forbidden_parent_key() {
|
|||
let tenant_tokens = vec![
|
||||
hashmap! {
|
||||
"searchRules" => json!({"*": {}}),
|
||||
"exp" => json!((Utc::now() + Duration::hours(1)).timestamp())
|
||||
"exp" => json!((OffsetDateTime::now_utc() + Duration::hours(1)).unix_timestamp())
|
||||
},
|
||||
hashmap! {
|
||||
"searchRules" => json!({"*": Value::Null}),
|
||||
"exp" => json!((Utc::now() + Duration::hours(1)).timestamp())
|
||||
"exp" => json!((OffsetDateTime::now_utc() + Duration::hours(1)).unix_timestamp())
|
||||
},
|
||||
hashmap! {
|
||||
"searchRules" => json!(["*"]),
|
||||
"exp" => json!((Utc::now() + Duration::hours(1)).timestamp())
|
||||
"exp" => json!((OffsetDateTime::now_utc() + Duration::hours(1)).unix_timestamp())
|
||||
},
|
||||
hashmap! {
|
||||
"searchRules" => json!({"sales": {}}),
|
||||
"exp" => json!((Utc::now() + Duration::hours(1)).timestamp())
|
||||
"exp" => json!((OffsetDateTime::now_utc() + Duration::hours(1)).unix_timestamp())
|
||||
},
|
||||
hashmap! {
|
||||
"searchRules" => json!({"sales": Value::Null}),
|
||||
"exp" => json!((Utc::now() + Duration::hours(1)).timestamp())
|
||||
"exp" => json!((OffsetDateTime::now_utc() + Duration::hours(1)).unix_timestamp())
|
||||
},
|
||||
hashmap! {
|
||||
"searchRules" => json!(["sales"]),
|
||||
"exp" => json!((Utc::now() + Duration::hours(1)).timestamp())
|
||||
"exp" => json!((OffsetDateTime::now_utc() + Duration::hours(1)).unix_timestamp())
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -395,11 +396,11 @@ async fn error_search_forbidden_token() {
|
|||
// bad index
|
||||
hashmap! {
|
||||
"searchRules" => json!({"products": {}}),
|
||||
"exp" => json!((Utc::now() + Duration::hours(1)).timestamp())
|
||||
"exp" => json!((OffsetDateTime::now_utc() + Duration::hours(1)).unix_timestamp())
|
||||
},
|
||||
hashmap! {
|
||||
"searchRules" => json!(["products"]),
|
||||
"exp" => json!((Utc::now() + Duration::hours(1)).timestamp())
|
||||
"exp" => json!((OffsetDateTime::now_utc() + Duration::hours(1)).unix_timestamp())
|
||||
},
|
||||
hashmap! {
|
||||
"searchRules" => json!({"products": {}}),
|
||||
|
@ -416,27 +417,27 @@ async fn error_search_forbidden_token() {
|
|||
// expired token
|
||||
hashmap! {
|
||||
"searchRules" => json!({"*": {}}),
|
||||
"exp" => json!((Utc::now() - Duration::hours(1)).timestamp())
|
||||
"exp" => json!((OffsetDateTime::now_utc() - Duration::hours(1)).unix_timestamp())
|
||||
},
|
||||
hashmap! {
|
||||
"searchRules" => json!({"*": Value::Null}),
|
||||
"exp" => json!((Utc::now() - Duration::hours(1)).timestamp())
|
||||
"exp" => json!((OffsetDateTime::now_utc() - Duration::hours(1)).unix_timestamp())
|
||||
},
|
||||
hashmap! {
|
||||
"searchRules" => json!(["*"]),
|
||||
"exp" => json!((Utc::now() - Duration::hours(1)).timestamp())
|
||||
"exp" => json!((OffsetDateTime::now_utc() - Duration::hours(1)).unix_timestamp())
|
||||
},
|
||||
hashmap! {
|
||||
"searchRules" => json!({"sales": {}}),
|
||||
"exp" => json!((Utc::now() - Duration::hours(1)).timestamp())
|
||||
"exp" => json!((OffsetDateTime::now_utc() - Duration::hours(1)).unix_timestamp())
|
||||
},
|
||||
hashmap! {
|
||||
"searchRules" => json!({"sales": Value::Null}),
|
||||
"exp" => json!((Utc::now() - Duration::hours(1)).timestamp())
|
||||
"exp" => json!((OffsetDateTime::now_utc() - Duration::hours(1)).unix_timestamp())
|
||||
},
|
||||
hashmap! {
|
||||
"searchRules" => json!(["sales"]),
|
||||
"exp" => json!((Utc::now() - Duration::hours(1)).timestamp())
|
||||
"exp" => json!((OffsetDateTime::now_utc() - Duration::hours(1)).unix_timestamp())
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -452,7 +453,7 @@ async fn error_access_forbidden_routes() {
|
|||
let content = json!({
|
||||
"indexes": ["*"],
|
||||
"actions": ["*"],
|
||||
"expiresAt": (Utc::now() + Duration::hours(1)),
|
||||
"expiresAt": (OffsetDateTime::now_utc() + Duration::hours(1)).format(&Rfc3339).unwrap(),
|
||||
});
|
||||
|
||||
let (response, code) = server.add_api_key(content).await;
|
||||
|
@ -463,7 +464,7 @@ async fn error_access_forbidden_routes() {
|
|||
|
||||
let tenant_token = hashmap! {
|
||||
"searchRules" => json!(["*"]),
|
||||
"exp" => json!((Utc::now() + Duration::hours(1)).timestamp())
|
||||
"exp" => json!((OffsetDateTime::now_utc() + Duration::hours(1)).unix_timestamp())
|
||||
};
|
||||
let web_token = generate_tenant_token(&key, tenant_token);
|
||||
server.use_api_key(&web_token);
|
||||
|
@ -487,7 +488,7 @@ async fn error_access_expired_parent_key() {
|
|||
let content = json!({
|
||||
"indexes": ["*"],
|
||||
"actions": ["*"],
|
||||
"expiresAt": (Utc::now() + Duration::seconds(1)),
|
||||
"expiresAt": (OffsetDateTime::now_utc() + Duration::seconds(1)).format(&Rfc3339).unwrap(),
|
||||
});
|
||||
|
||||
let (response, code) = server.add_api_key(content).await;
|
||||
|
@ -498,7 +499,7 @@ async fn error_access_expired_parent_key() {
|
|||
|
||||
let tenant_token = hashmap! {
|
||||
"searchRules" => json!(["*"]),
|
||||
"exp" => json!((Utc::now() + Duration::hours(1)).timestamp())
|
||||
"exp" => json!((OffsetDateTime::now_utc() + Duration::hours(1)).unix_timestamp())
|
||||
};
|
||||
let web_token = generate_tenant_token(&key, tenant_token);
|
||||
server.use_api_key(&web_token);
|
||||
|
@ -529,7 +530,7 @@ async fn error_access_modified_token() {
|
|||
let content = json!({
|
||||
"indexes": ["*"],
|
||||
"actions": ["*"],
|
||||
"expiresAt": (Utc::now() + Duration::hours(1)),
|
||||
"expiresAt": (OffsetDateTime::now_utc() + Duration::hours(1)).format(&Rfc3339).unwrap(),
|
||||
});
|
||||
|
||||
let (response, code) = server.add_api_key(content).await;
|
||||
|
@ -540,7 +541,7 @@ async fn error_access_modified_token() {
|
|||
|
||||
let tenant_token = hashmap! {
|
||||
"searchRules" => json!(["products"]),
|
||||
"exp" => json!((Utc::now() + Duration::hours(1)).timestamp())
|
||||
"exp" => json!((OffsetDateTime::now_utc() + Duration::hours(1)).unix_timestamp())
|
||||
};
|
||||
let web_token = generate_tenant_token(&key, tenant_token);
|
||||
server.use_api_key(&web_token);
|
||||
|
@ -554,7 +555,7 @@ async fn error_access_modified_token() {
|
|||
|
||||
let tenant_token = hashmap! {
|
||||
"searchRules" => json!(["*"]),
|
||||
"exp" => json!((Utc::now() + Duration::hours(1)).timestamp())
|
||||
"exp" => json!((OffsetDateTime::now_utc() + Duration::hours(1)).unix_timestamp())
|
||||
};
|
||||
|
||||
let alt = generate_tenant_token(&key, tenant_token);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use crate::common::{GetAllDocumentsOptions, Server};
|
||||
use actix_web::test;
|
||||
use chrono::DateTime;
|
||||
use meilisearch_http::{analytics, create_app};
|
||||
use serde_json::{json, Value};
|
||||
use time::{format_description::well_known::Rfc3339, OffsetDateTime};
|
||||
|
||||
/// This is the basic usage of our API and every other tests uses the content-type application/json
|
||||
#[actix_rt::test]
|
||||
|
@ -568,9 +568,9 @@ async fn add_documents_no_index_creation() {
|
|||
assert_eq!(response["details"]["indexedDocuments"], 1);
|
||||
|
||||
let processed_at =
|
||||
DateTime::parse_from_rfc3339(response["finishedAt"].as_str().unwrap()).unwrap();
|
||||
OffsetDateTime::parse(response["finishedAt"].as_str().unwrap(), &Rfc3339).unwrap();
|
||||
let enqueued_at =
|
||||
DateTime::parse_from_rfc3339(response["enqueuedAt"].as_str().unwrap()).unwrap();
|
||||
OffsetDateTime::parse(response["enqueuedAt"].as_str().unwrap(), &Rfc3339).unwrap();
|
||||
assert!(processed_at > enqueued_at);
|
||||
|
||||
// index was created, and primary key was infered.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::common::Server;
|
||||
use chrono::DateTime;
|
||||
use serde_json::json;
|
||||
use time::{format_description::well_known::Rfc3339, OffsetDateTime};
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn update_primary_key() {
|
||||
|
@ -25,8 +25,10 @@ async fn update_primary_key() {
|
|||
assert!(response.get("createdAt").is_some());
|
||||
assert!(response.get("updatedAt").is_some());
|
||||
|
||||
let created_at = DateTime::parse_from_rfc3339(response["createdAt"].as_str().unwrap()).unwrap();
|
||||
let updated_at = DateTime::parse_from_rfc3339(response["updatedAt"].as_str().unwrap()).unwrap();
|
||||
let created_at =
|
||||
OffsetDateTime::parse(response["createdAt"].as_str().unwrap(), &Rfc3339).unwrap();
|
||||
let updated_at =
|
||||
OffsetDateTime::parse(response["updatedAt"].as_str().unwrap(), &Rfc3339).unwrap();
|
||||
assert!(created_at < updated_at);
|
||||
|
||||
assert_eq!(response["primaryKey"], "primary");
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use serde_json::json;
|
||||
use time::{format_description::well_known::Rfc3339, OffsetDateTime};
|
||||
|
||||
use crate::common::Server;
|
||||
|
||||
|
@ -57,11 +58,15 @@ async fn stats() {
|
|||
|
||||
index.wait_task(1).await;
|
||||
|
||||
let timestamp = OffsetDateTime::now_utc();
|
||||
let (response, code) = server.stats().await;
|
||||
|
||||
assert_eq!(code, 200);
|
||||
assert!(response["databaseSize"].as_u64().unwrap() > 0);
|
||||
assert!(response.get("lastUpdate").is_some());
|
||||
let last_update =
|
||||
OffsetDateTime::parse(response["lastUpdate"].as_str().unwrap(), &Rfc3339).unwrap();
|
||||
assert!(last_update - timestamp < time::Duration::SECOND);
|
||||
|
||||
assert_eq!(response["indexes"]["test"]["numberOfDocuments"], 2);
|
||||
assert!(response["indexes"]["test"]["isIndexing"] == false);
|
||||
assert_eq!(response["indexes"]["test"]["fieldDistribution"]["id"], 2);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use crate::common::Server;
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde_json::json;
|
||||
use time::format_description::well_known::Rfc3339;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn error_get_task_unexisting_index() {
|
||||
|
@ -98,7 +99,8 @@ macro_rules! assert_valid_summarized_task {
|
|||
assert_eq!($response["status"], "enqueued");
|
||||
assert_eq!($response["type"], $task_type);
|
||||
let date = $response["enqueuedAt"].as_str().expect("missing date");
|
||||
date.parse::<DateTime<Utc>>().unwrap();
|
||||
|
||||
OffsetDateTime::parse(date, &Rfc3339).unwrap();
|
||||
}};
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue