diff --git a/Cargo.lock b/Cargo.lock index 647545d8d..d565d4708 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -983,6 +983,9 @@ name = "either" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +dependencies = [ + "serde", +] [[package]] name = "embedded-hal" diff --git a/meilisearch-http/src/routes/indexes/mod.rs b/meilisearch-http/src/routes/indexes/mod.rs index ed6196ebd..9fdd0854c 100644 --- a/meilisearch-http/src/routes/indexes/mod.rs +++ b/meilisearch-http/src/routes/indexes/mod.rs @@ -158,7 +158,14 @@ pub async fn delete_index( pub async fn get_index_stats( meilisearch: GuardedData, MeiliSearch>, path: web::Path, + req: HttpRequest, + analytics: web::Data, ) -> Result { + analytics.publish( + "Stats Seen".to_string(), + json!({ "per_index_uid": true }), + Some(&req), + ); let response = meilisearch.get_index_stats(path.into_inner()).await?; debug!("returns: {:?}", response); diff --git a/meilisearch-http/src/routes/mod.rs b/meilisearch-http/src/routes/mod.rs index 9412dece2..598dae42b 100644 --- a/meilisearch-http/src/routes/mod.rs +++ b/meilisearch-http/src/routes/mod.rs @@ -1,8 +1,9 @@ use actix_web::http::header::{self}; -use actix_web::{web, HttpResponse}; +use actix_web::{web, HttpRequest, HttpResponse}; use log::debug; use serde::{Deserialize, Serialize}; +use serde_json::json; use time::OffsetDateTime; use meilisearch_lib::index::{Settings, Unchecked}; @@ -10,6 +11,7 @@ use meilisearch_lib::MeiliSearch; use meilisearch_types::error::ResponseError; use meilisearch_types::star_or::StarOr; +use crate::analytics::Analytics; use crate::extractors::authentication::{policies::*, GuardedData}; use prometheus::{Encoder, TextEncoder}; @@ -233,7 +235,14 @@ pub async fn running() -> HttpResponse { async fn get_stats( meilisearch: GuardedData, MeiliSearch>, + req: HttpRequest, + analytics: web::Data, ) -> Result { + analytics.publish( + "Stats Seen".to_string(), + json!({ "per_index_uid": false }), + Some(&req), + ); let search_rules = &meilisearch.filters().search_rules; let response = meilisearch.get_all_stats(search_rules).await?; diff --git a/meilisearch-http/src/task.rs b/meilisearch-http/src/task.rs index 08009f7da..fe23720aa 100644 --- a/meilisearch-http/src/task.rs +++ b/meilisearch-http/src/task.rs @@ -4,7 +4,6 @@ use std::str::FromStr; use std::write; use meilisearch_lib::index::{Settings, Unchecked}; -use meilisearch_lib::tasks::batch::BatchId; use meilisearch_lib::tasks::task::{ DocumentDeletion, Task, TaskContent, TaskEvent, TaskId, TaskResult, }; @@ -12,8 +11,6 @@ use meilisearch_types::error::ResponseError; use serde::{Deserialize, Serialize, Serializer}; use time::{Duration, OffsetDateTime}; -use crate::AUTOBATCHING_ENABLED; - #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum TaskType { @@ -230,8 +227,6 @@ pub struct TaskView { started_at: Option, #[serde(serialize_with = "time::serde::rfc3339::option::serialize")] finished_at: Option, - #[serde(skip_serializing_if = "Option::is_none")] - batch_uid: Option, } impl From for TaskView { @@ -380,16 +375,6 @@ impl From for TaskView { let duration = finished_at.zip(started_at).map(|(tf, ts)| (tf - ts)); - let batch_uid = AUTOBATCHING_ENABLED - .load(std::sync::atomic::Ordering::Relaxed) - .then(|| { - events.iter().find_map(|e| match e { - TaskEvent::Batched { batch_id, .. } => Some(*batch_id), - _ => None, - }) - }) - .flatten(); - Self { uid: id, index_uid, @@ -401,7 +386,6 @@ impl From for TaskView { enqueued_at, started_at, finished_at, - batch_uid, } } } diff --git a/meilisearch-http/tests/documents/add_documents.rs b/meilisearch-http/tests/documents/add_documents.rs index b5819ad98..6bbb8c8fc 100644 --- a/meilisearch-http/tests/documents/add_documents.rs +++ b/meilisearch-http/tests/documents/add_documents.rs @@ -52,6 +52,51 @@ async fn add_documents_test_json_content_types() { assert_eq!(response["taskUid"], 1); } +/// Here we try to send a single document instead of an array with a single document inside. +#[actix_rt::test] +async fn add_single_document_test_json_content_types() { + let document = json!({ + "id": 1, + "content": "Bouvier Bernois", + }); + + // this is a what is expected and should work + let server = Server::new().await; + let app = test::init_service(create_app!( + &server.service.meilisearch, + &server.service.auth, + true, + &server.service.options, + analytics::MockAnalytics::new(&server.service.options).0 + )) + .await; + // post + let req = test::TestRequest::post() + .uri("/indexes/dog/documents") + .set_payload(document.to_string()) + .insert_header(("content-type", "application/json")) + .to_request(); + let res = test::call_service(&app, req).await; + let status_code = res.status(); + let body = test::read_body(res).await; + let response: Value = serde_json::from_slice(&body).unwrap_or_default(); + assert_eq!(status_code, 202); + assert_eq!(response["taskUid"], 0); + + // put + let req = test::TestRequest::put() + .uri("/indexes/dog/documents") + .set_payload(document.to_string()) + .insert_header(("content-type", "application/json")) + .to_request(); + let res = test::call_service(&app, req).await; + let status_code = res.status(); + let body = test::read_body(res).await; + let response: Value = serde_json::from_slice(&body).unwrap_or_default(); + assert_eq!(status_code, 202); + assert_eq!(response["taskUid"], 1); +} + /// any other content-type is must be refused #[actix_rt::test] async fn error_add_documents_test_bad_content_types() { @@ -327,7 +372,7 @@ async fn error_add_malformed_json_documents() { assert_eq!( response["message"], json!( - r#"The `json` payload provided is malformed. `Couldn't serialize document value: invalid type: string "0123456789012345678901234567...890123456789012345678901234567890123456789", expected a sequence at line 1 column 102`."# + r#"The `json` payload provided is malformed. `Couldn't serialize document value: data did not match any variant of untagged enum Either`."# ) ); assert_eq!(response["code"], json!("malformed_payload")); @@ -350,7 +395,7 @@ async fn error_add_malformed_json_documents() { assert_eq!(status_code, 400); assert_eq!( response["message"], - json!("The `json` payload provided is malformed. `Couldn't serialize document value: invalid type: string \"0123456789012345678901234567...90123456789012345678901234567890123456789m\", expected a sequence at line 1 column 103`.") + json!("The `json` payload provided is malformed. `Couldn't serialize document value: data did not match any variant of untagged enum Either`.") ); assert_eq!(response["code"], json!("malformed_payload")); assert_eq!(response["type"], json!("invalid_request")); diff --git a/meilisearch-lib/Cargo.toml b/meilisearch-lib/Cargo.toml index d3790a91c..d0ca59289 100644 --- a/meilisearch-lib/Cargo.toml +++ b/meilisearch-lib/Cargo.toml @@ -15,7 +15,7 @@ clap = { version = "3.1.6", features = ["derive", "env"] } crossbeam-channel = "0.5.2" csv = "1.1.6" derivative = "2.2.0" -either = "1.6.1" +either = { version = "1.6.1", features = ["serde"] } flate2 = "1.0.22" fs_extra = "1.2.0" fst = "0.4.7" diff --git a/meilisearch-lib/src/document_formats.rs b/meilisearch-lib/src/document_formats.rs index 72e899845..ebc98f3fb 100644 --- a/meilisearch-lib/src/document_formats.rs +++ b/meilisearch-lib/src/document_formats.rs @@ -2,9 +2,12 @@ use std::borrow::Borrow; use std::fmt::{self, Debug, Display}; use std::io::{self, BufReader, Read, Seek, Write}; +use either::Either; use meilisearch_types::error::{Code, ErrorCode}; use meilisearch_types::internal_error; use milli::documents::{DocumentsBatchBuilder, Error}; +use milli::Object; +use serde::Deserialize; type Result = std::result::Result; @@ -124,11 +127,18 @@ pub fn read_json(input: impl Read, writer: impl Write + Seek) -> Result { let mut builder = DocumentsBatchBuilder::new(writer); let reader = BufReader::new(input); - let objects: Vec<_> = serde_json::from_reader(reader) + #[derive(Deserialize, Debug)] + #[serde(transparent)] + struct ArrayOrSingleObject { + #[serde(with = "either::serde_untagged")] + inner: Either, Object>, + } + + let content: ArrayOrSingleObject = serde_json::from_reader(reader) .map_err(Error::Json) .map_err(|e| (PayloadType::Json, e))?; - for object in objects { + for object in content.inner.map_right(|o| vec![o]).into_inner() { builder .append_json_object(&object) .map_err(Into::into)