mirror of
https://github.com/meilisearch/MeiliSearch
synced 2024-11-26 06:44:27 +01:00
Merge #1848
1848: Error format and Definition r=MarinPostma a=ManyTheFish Co-authored-by: many <maxime@meilisearch.com>
This commit is contained in:
commit
cf67964133
4
Cargo.lock
generated
4
Cargo.lock
generated
@ -1782,8 +1782,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "milli"
|
name = "milli"
|
||||||
version = "0.19.0"
|
version = "0.20.0"
|
||||||
source = "git+https://github.com/meilisearch/milli.git?tag=v0.19.0#d7943fe22553b8205b86c32a0f2656d9e42de351"
|
source = "git+https://github.com/meilisearch/milli.git?tag=v0.20.0#5a6d22d4ec51dda0aba94b314e1b5a38af9400a2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bimap",
|
"bimap",
|
||||||
"bincode",
|
"bincode",
|
||||||
|
@ -39,9 +39,9 @@ impl fmt::Display for ErrorType {
|
|||||||
use ErrorType::*;
|
use ErrorType::*;
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
InternalError => write!(f, "internal_error"),
|
InternalError => write!(f, "internal"),
|
||||||
InvalidRequestError => write!(f, "invalid_request_error"),
|
InvalidRequestError => write!(f, "invalid_request"),
|
||||||
AuthenticationError => write!(f, "authentication_error"),
|
AuthenticationError => write!(f, "authentication"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -62,6 +62,7 @@ pub enum Code {
|
|||||||
|
|
||||||
MaxFieldsLimitExceeded,
|
MaxFieldsLimitExceeded,
|
||||||
MissingDocumentId,
|
MissingDocumentId,
|
||||||
|
InvalidDocumentId,
|
||||||
|
|
||||||
Facet,
|
Facet,
|
||||||
Filter,
|
Filter,
|
||||||
@ -76,6 +77,7 @@ pub enum Code {
|
|||||||
InvalidToken,
|
InvalidToken,
|
||||||
MissingAuthorizationHeader,
|
MissingAuthorizationHeader,
|
||||||
NotFound,
|
NotFound,
|
||||||
|
TaskNotFound,
|
||||||
PayloadTooLarge,
|
PayloadTooLarge,
|
||||||
RetrieveDocument,
|
RetrieveDocument,
|
||||||
SearchDocuments,
|
SearchDocuments,
|
||||||
@ -99,7 +101,7 @@ impl Code {
|
|||||||
// index related errors
|
// index related errors
|
||||||
// create index is thrown on internal error while creating an index.
|
// create index is thrown on internal error while creating an index.
|
||||||
CreateIndex => ErrCode::internal("index_creation_failed", StatusCode::BAD_REQUEST),
|
CreateIndex => ErrCode::internal("index_creation_failed", StatusCode::BAD_REQUEST),
|
||||||
IndexAlreadyExists => ErrCode::invalid("index_already_exists", StatusCode::BAD_REQUEST),
|
IndexAlreadyExists => ErrCode::invalid("index_already_exists", StatusCode::CONFLICT),
|
||||||
// thrown when requesting an unexisting index
|
// thrown when requesting an unexisting index
|
||||||
IndexNotFound => ErrCode::invalid("index_not_found", StatusCode::NOT_FOUND),
|
IndexNotFound => ErrCode::invalid("index_not_found", StatusCode::NOT_FOUND),
|
||||||
InvalidIndexUid => ErrCode::invalid("invalid_index_uid", StatusCode::BAD_REQUEST),
|
InvalidIndexUid => ErrCode::invalid("invalid_index_uid", StatusCode::BAD_REQUEST),
|
||||||
@ -113,7 +115,7 @@ impl Code {
|
|||||||
MissingPrimaryKey => ErrCode::invalid("missing_primary_key", StatusCode::BAD_REQUEST),
|
MissingPrimaryKey => ErrCode::invalid("missing_primary_key", StatusCode::BAD_REQUEST),
|
||||||
// error thrown when trying to set an already existing primary key
|
// error thrown when trying to set an already existing primary key
|
||||||
PrimaryKeyAlreadyPresent => {
|
PrimaryKeyAlreadyPresent => {
|
||||||
ErrCode::invalid("primary_key_already_present", StatusCode::BAD_REQUEST)
|
ErrCode::invalid("index_primary_key_already_exists", StatusCode::BAD_REQUEST)
|
||||||
}
|
}
|
||||||
// invalid ranking rule
|
// invalid ranking rule
|
||||||
InvalidRankingRule => ErrCode::invalid("invalid_request", StatusCode::BAD_REQUEST),
|
InvalidRankingRule => ErrCode::invalid("invalid_request", StatusCode::BAD_REQUEST),
|
||||||
@ -123,6 +125,7 @@ impl Code {
|
|||||||
ErrCode::invalid("max_fields_limit_exceeded", StatusCode::BAD_REQUEST)
|
ErrCode::invalid("max_fields_limit_exceeded", StatusCode::BAD_REQUEST)
|
||||||
}
|
}
|
||||||
MissingDocumentId => ErrCode::invalid("missing_document_id", StatusCode::BAD_REQUEST),
|
MissingDocumentId => ErrCode::invalid("missing_document_id", StatusCode::BAD_REQUEST),
|
||||||
|
InvalidDocumentId => ErrCode::invalid("invalid_document_id", StatusCode::BAD_REQUEST),
|
||||||
|
|
||||||
// error related to facets
|
// error related to facets
|
||||||
Facet => ErrCode::invalid("invalid_facet", StatusCode::BAD_REQUEST),
|
Facet => ErrCode::invalid("invalid_facet", StatusCode::BAD_REQUEST),
|
||||||
@ -138,10 +141,11 @@ impl Code {
|
|||||||
InvalidGeoField => {
|
InvalidGeoField => {
|
||||||
ErrCode::authentication("invalid_geo_field", StatusCode::BAD_REQUEST)
|
ErrCode::authentication("invalid_geo_field", StatusCode::BAD_REQUEST)
|
||||||
}
|
}
|
||||||
InvalidToken => ErrCode::authentication("invalid_token", StatusCode::FORBIDDEN),
|
InvalidToken => ErrCode::authentication("invalid_api_key", StatusCode::FORBIDDEN),
|
||||||
MissingAuthorizationHeader => {
|
MissingAuthorizationHeader => {
|
||||||
ErrCode::authentication("missing_authorization_header", StatusCode::UNAUTHORIZED)
|
ErrCode::authentication("missing_authorization_header", StatusCode::UNAUTHORIZED)
|
||||||
}
|
}
|
||||||
|
TaskNotFound => ErrCode::invalid("task_not_found", StatusCode::NOT_FOUND),
|
||||||
NotFound => ErrCode::invalid("not_found", StatusCode::NOT_FOUND),
|
NotFound => ErrCode::invalid("not_found", StatusCode::NOT_FOUND),
|
||||||
PayloadTooLarge => ErrCode::invalid("payload_too_large", StatusCode::PAYLOAD_TOO_LARGE),
|
PayloadTooLarge => ErrCode::invalid("payload_too_large", StatusCode::PAYLOAD_TOO_LARGE),
|
||||||
RetrieveDocument => {
|
RetrieveDocument => {
|
||||||
|
@ -12,11 +12,11 @@ use serde::{Deserialize, Serialize};
|
|||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum MeilisearchHttpError {
|
pub enum MeilisearchHttpError {
|
||||||
#[error("A Content-Type header is missing. Accepted values for the Content-Type header are: {}",
|
#[error("A Content-Type header is missing. Accepted values for the Content-Type header are: {}",
|
||||||
.0.iter().map(|s| format!("\"{}\"", s)).collect::<Vec<_>>().join(", "))]
|
.0.iter().map(|s| format!("`{}`", s)).collect::<Vec<_>>().join(", "))]
|
||||||
MissingContentType(Vec<String>),
|
MissingContentType(Vec<String>),
|
||||||
#[error(
|
#[error(
|
||||||
"The Content-Type \"{0}\" is invalid. Accepted values for the Content-Type header are: {}",
|
"The Content-Type `{0}` is invalid. Accepted values for the Content-Type header are: {}",
|
||||||
.1.iter().map(|s| format!("\"{}\"", s)).collect::<Vec<_>>().join(", ")
|
.1.iter().map(|s| format!("`{}`", s)).collect::<Vec<_>>().join(", ")
|
||||||
)]
|
)]
|
||||||
InvalidContentType(String, Vec<String>),
|
InvalidContentType(String, Vec<String>),
|
||||||
}
|
}
|
||||||
@ -42,8 +42,11 @@ pub struct ResponseError {
|
|||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
code: StatusCode,
|
code: StatusCode,
|
||||||
message: String,
|
message: String,
|
||||||
|
#[serde(rename = "code")]
|
||||||
error_code: String,
|
error_code: String,
|
||||||
|
#[serde(rename = "type")]
|
||||||
error_type: String,
|
error_type: String,
|
||||||
|
#[serde(rename = "link")]
|
||||||
error_link: String,
|
error_link: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ use meilisearch_http::create_app;
|
|||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn strict_json_bad_content_type() {
|
async fn error_json_bad_content_type() {
|
||||||
let routes = [
|
let routes = [
|
||||||
// all the POST routes except the dumps that can be created without any body or content-type
|
// all the POST routes except the dumps that can be created without any body or content-type
|
||||||
// and the search that is not a strict json
|
// and the search that is not a strict json
|
||||||
@ -69,10 +69,10 @@ async fn strict_json_bad_content_type() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
response,
|
response,
|
||||||
json!({
|
json!({
|
||||||
"message": r#"A Content-Type header is missing. Accepted values for the Content-Type header are: "application/json""#,
|
"message": r#"A Content-Type header is missing. Accepted values for the Content-Type header are: `application/json`"#,
|
||||||
"errorCode": "missing_content_type",
|
"code": "missing_content_type",
|
||||||
"errorType": "invalid_request_error",
|
"type": "invalid_request",
|
||||||
"errorLink": "https://docs.meilisearch.com/errors#missing_content_type",
|
"link": "https://docs.meilisearch.com/errors#missing_content_type",
|
||||||
}),
|
}),
|
||||||
"when calling the route `{}` with no content-type",
|
"when calling the route `{}` with no content-type",
|
||||||
route,
|
route,
|
||||||
@ -91,16 +91,16 @@ async fn strict_json_bad_content_type() {
|
|||||||
let response: Value = serde_json::from_slice(&body).unwrap_or_default();
|
let response: Value = serde_json::from_slice(&body).unwrap_or_default();
|
||||||
assert_eq!(status_code, 415);
|
assert_eq!(status_code, 415);
|
||||||
let expected_error_message = format!(
|
let expected_error_message = format!(
|
||||||
r#"The Content-Type "{}" is invalid. Accepted values for the Content-Type header are: "application/json""#,
|
r#"The Content-Type `{}` is invalid. Accepted values for the Content-Type header are: `application/json`"#,
|
||||||
bad_content_type
|
bad_content_type
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
response,
|
response,
|
||||||
json!({
|
json!({
|
||||||
"message": expected_error_message,
|
"message": expected_error_message,
|
||||||
"errorCode": "invalid_content_type",
|
"code": "invalid_content_type",
|
||||||
"errorType": "invalid_request_error",
|
"type": "invalid_request",
|
||||||
"errorLink": "https://docs.meilisearch.com/errors#invalid_content_type",
|
"link": "https://docs.meilisearch.com/errors#invalid_content_type",
|
||||||
}),
|
}),
|
||||||
"when calling the route `{}` with a content-type of `{}`",
|
"when calling the route `{}` with a content-type of `{}`",
|
||||||
route,
|
route,
|
||||||
|
@ -49,53 +49,9 @@ async fn add_documents_test_json_content_types() {
|
|||||||
assert_eq!(response, json!({ "updateId": 1 }));
|
assert_eq!(response, json!({ "updateId": 1 }));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// no content type is still supposed to be accepted as json
|
|
||||||
#[actix_rt::test]
|
|
||||||
async fn add_documents_test_no_content_types() {
|
|
||||||
let document = json!([
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"content": "Montagne des Pyrénées",
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
let server = Server::new().await;
|
|
||||||
let app = test::init_service(create_app!(
|
|
||||||
&server.service.meilisearch,
|
|
||||||
true,
|
|
||||||
&server.service.options
|
|
||||||
))
|
|
||||||
.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, json!({ "updateId": 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, json!({ "updateId": 1 }));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// any other content-type is must be refused
|
/// any other content-type is must be refused
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn add_documents_test_bad_content_types() {
|
async fn error_add_documents_test_bad_content_types() {
|
||||||
let document = json!([
|
let document = json!([
|
||||||
{
|
{
|
||||||
"id": 1,
|
"id": 1,
|
||||||
@ -124,9 +80,15 @@ async fn add_documents_test_bad_content_types() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
response["message"],
|
response["message"],
|
||||||
json!(
|
json!(
|
||||||
r#"The Content-Type "text/plain" is invalid. Accepted values for the Content-Type header are: "application/json", "application/x-ndjson", "text/csv""#
|
r#"The Content-Type `text/plain` is invalid. Accepted values for the Content-Type header are: `application/json`, `application/x-ndjson`, `text/csv`"#
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
assert_eq!(response["code"], "invalid_content_type");
|
||||||
|
assert_eq!(response["type"], "invalid_request");
|
||||||
|
assert_eq!(
|
||||||
|
response["link"],
|
||||||
|
"https://docs.meilisearch.com/errors#invalid_content_type"
|
||||||
|
);
|
||||||
|
|
||||||
// put
|
// put
|
||||||
let req = test::TestRequest::put()
|
let req = test::TestRequest::put()
|
||||||
@ -142,9 +104,415 @@ async fn add_documents_test_bad_content_types() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
response["message"],
|
response["message"],
|
||||||
json!(
|
json!(
|
||||||
r#"The Content-Type "text/plain" is invalid. Accepted values for the Content-Type header are: "application/json", "application/x-ndjson", "text/csv""#
|
r#"The Content-Type `text/plain` is invalid. Accepted values for the Content-Type header are: `application/json`, `application/x-ndjson`, `text/csv`"#
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
assert_eq!(response["code"], "invalid_content_type");
|
||||||
|
assert_eq!(response["type"], "invalid_request");
|
||||||
|
assert_eq!(
|
||||||
|
response["link"],
|
||||||
|
"https://docs.meilisearch.com/errors#invalid_content_type"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// missing content-type must be refused
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn error_add_documents_test_no_content_type() {
|
||||||
|
let document = json!([
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"content": "Leonberg",
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
let server = Server::new().await;
|
||||||
|
let app = test::init_service(create_app!(
|
||||||
|
&server.service.meilisearch,
|
||||||
|
true,
|
||||||
|
&server.service.options
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
// post
|
||||||
|
let req = test::TestRequest::post()
|
||||||
|
.uri("/indexes/dog/documents")
|
||||||
|
.set_payload(document.to_string())
|
||||||
|
.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, 415);
|
||||||
|
assert_eq!(
|
||||||
|
response["message"],
|
||||||
|
json!(
|
||||||
|
r#"A Content-Type header is missing. Accepted values for the Content-Type header are: `application/json`, `application/x-ndjson`, `text/csv`"#
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(response["code"], "missing_content_type");
|
||||||
|
assert_eq!(response["type"], "invalid_request");
|
||||||
|
assert_eq!(
|
||||||
|
response["link"],
|
||||||
|
"https://docs.meilisearch.com/errors#missing_content_type"
|
||||||
|
);
|
||||||
|
|
||||||
|
// put
|
||||||
|
let req = test::TestRequest::put()
|
||||||
|
.uri("/indexes/dog/documents")
|
||||||
|
.set_payload(document.to_string())
|
||||||
|
.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, 415);
|
||||||
|
assert_eq!(
|
||||||
|
response["message"],
|
||||||
|
json!(
|
||||||
|
r#"A Content-Type header is missing. Accepted values for the Content-Type header are: `application/json`, `application/x-ndjson`, `text/csv`"#
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(response["code"], "missing_content_type");
|
||||||
|
assert_eq!(response["type"], "invalid_request");
|
||||||
|
assert_eq!(
|
||||||
|
response["link"],
|
||||||
|
"https://docs.meilisearch.com/errors#missing_content_type"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn error_add_malformed_csv_documents() {
|
||||||
|
let document = "id, content\n1234, hello, world\n12, hello world";
|
||||||
|
|
||||||
|
let server = Server::new().await;
|
||||||
|
let app = test::init_service(create_app!(
|
||||||
|
&server.service.meilisearch,
|
||||||
|
true,
|
||||||
|
&server.service.options
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
// post
|
||||||
|
let req = test::TestRequest::post()
|
||||||
|
.uri("/indexes/dog/documents")
|
||||||
|
.set_payload(document.to_string())
|
||||||
|
.insert_header(("content-type", "text/csv"))
|
||||||
|
.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, 400);
|
||||||
|
assert_eq!(
|
||||||
|
response["message"],
|
||||||
|
json!(
|
||||||
|
r#"The `csv` payload provided is malformed. `CSV error: record 1 (line: 2, byte: 12): found record with 3 fields, but the previous record has 2 fields`."#
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(response["code"], json!("malformed_payload"));
|
||||||
|
assert_eq!(response["type"], json!("invalid_request"));
|
||||||
|
assert_eq!(
|
||||||
|
response["link"],
|
||||||
|
json!("https://docs.meilisearch.com/errors#malformed_payload")
|
||||||
|
);
|
||||||
|
|
||||||
|
// put
|
||||||
|
let req = test::TestRequest::put()
|
||||||
|
.uri("/indexes/dog/documents")
|
||||||
|
.set_payload(document.to_string())
|
||||||
|
.insert_header(("content-type", "text/csv"))
|
||||||
|
.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, 400);
|
||||||
|
assert_eq!(
|
||||||
|
response["message"],
|
||||||
|
json!(
|
||||||
|
r#"The `csv` payload provided is malformed. `CSV error: record 1 (line: 2, byte: 12): found record with 3 fields, but the previous record has 2 fields`."#
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(response["code"], json!("malformed_payload"));
|
||||||
|
assert_eq!(response["type"], json!("invalid_request"));
|
||||||
|
assert_eq!(
|
||||||
|
response["link"],
|
||||||
|
json!("https://docs.meilisearch.com/errors#malformed_payload")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn error_add_malformed_json_documents() {
|
||||||
|
let document = r#"[{"id": 1}, {id: 2}]"#;
|
||||||
|
|
||||||
|
let server = Server::new().await;
|
||||||
|
let app = test::init_service(create_app!(
|
||||||
|
&server.service.meilisearch,
|
||||||
|
true,
|
||||||
|
&server.service.options
|
||||||
|
))
|
||||||
|
.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, 400);
|
||||||
|
assert_eq!(
|
||||||
|
response["message"],
|
||||||
|
json!(
|
||||||
|
r#"The `json` payload provided is malformed. `Couldn't serialize document value: key must be a string at line 1 column 14`."#
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(response["code"], json!("malformed_payload"));
|
||||||
|
assert_eq!(response["type"], json!("invalid_request"));
|
||||||
|
assert_eq!(
|
||||||
|
response["link"],
|
||||||
|
json!("https://docs.meilisearch.com/errors#malformed_payload")
|
||||||
|
);
|
||||||
|
|
||||||
|
// 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, 400);
|
||||||
|
assert_eq!(
|
||||||
|
response["message"],
|
||||||
|
json!(
|
||||||
|
r#"The `json` payload provided is malformed. `Couldn't serialize document value: key must be a string at line 1 column 14`."#
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(response["code"], json!("malformed_payload"));
|
||||||
|
assert_eq!(response["type"], json!("invalid_request"));
|
||||||
|
assert_eq!(
|
||||||
|
response["link"],
|
||||||
|
json!("https://docs.meilisearch.com/errors#malformed_payload")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn error_add_malformed_ndjson_documents() {
|
||||||
|
let document = "{\"id\": 1}\n{id: 2}";
|
||||||
|
|
||||||
|
let server = Server::new().await;
|
||||||
|
let app = test::init_service(create_app!(
|
||||||
|
&server.service.meilisearch,
|
||||||
|
true,
|
||||||
|
&server.service.options
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
// post
|
||||||
|
let req = test::TestRequest::post()
|
||||||
|
.uri("/indexes/dog/documents")
|
||||||
|
.set_payload(document.to_string())
|
||||||
|
.insert_header(("content-type", "application/x-ndjson"))
|
||||||
|
.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, 400);
|
||||||
|
assert_eq!(
|
||||||
|
response["message"],
|
||||||
|
json!(
|
||||||
|
r#"The `ndjson` payload provided is malformed. `Couldn't serialize document value: key must be a string at line 1 column 2`."#
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(response["code"], json!("malformed_payload"));
|
||||||
|
assert_eq!(response["type"], json!("invalid_request"));
|
||||||
|
assert_eq!(
|
||||||
|
response["link"],
|
||||||
|
json!("https://docs.meilisearch.com/errors#malformed_payload")
|
||||||
|
);
|
||||||
|
|
||||||
|
// put
|
||||||
|
let req = test::TestRequest::put()
|
||||||
|
.uri("/indexes/dog/documents")
|
||||||
|
.set_payload(document.to_string())
|
||||||
|
.insert_header(("content-type", "application/x-ndjson"))
|
||||||
|
.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, 400);
|
||||||
|
assert_eq!(
|
||||||
|
response["message"],
|
||||||
|
json!(
|
||||||
|
r#"The `ndjson` payload provided is malformed. `Couldn't serialize document value: key must be a string at line 1 column 2`."#
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(response["code"], json!("malformed_payload"));
|
||||||
|
assert_eq!(response["type"], json!("invalid_request"));
|
||||||
|
assert_eq!(
|
||||||
|
response["link"],
|
||||||
|
json!("https://docs.meilisearch.com/errors#malformed_payload")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn error_add_missing_payload_csv_documents() {
|
||||||
|
let document = "";
|
||||||
|
|
||||||
|
let server = Server::new().await;
|
||||||
|
let app = test::init_service(create_app!(
|
||||||
|
&server.service.meilisearch,
|
||||||
|
true,
|
||||||
|
&server.service.options
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
// post
|
||||||
|
let req = test::TestRequest::post()
|
||||||
|
.uri("/indexes/dog/documents")
|
||||||
|
.set_payload(document.to_string())
|
||||||
|
.insert_header(("content-type", "text/csv"))
|
||||||
|
.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, 400);
|
||||||
|
assert_eq!(response["message"], json!(r#"A csv payload is missing."#));
|
||||||
|
assert_eq!(response["code"], json!("missing_payload"));
|
||||||
|
assert_eq!(response["type"], json!("invalid_request"));
|
||||||
|
assert_eq!(
|
||||||
|
response["link"],
|
||||||
|
json!("https://docs.meilisearch.com/errors#missing_payload")
|
||||||
|
);
|
||||||
|
|
||||||
|
// put
|
||||||
|
let req = test::TestRequest::put()
|
||||||
|
.uri("/indexes/dog/documents")
|
||||||
|
.set_payload(document.to_string())
|
||||||
|
.insert_header(("content-type", "text/csv"))
|
||||||
|
.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, 400);
|
||||||
|
assert_eq!(response["message"], json!(r#"A csv payload is missing."#));
|
||||||
|
assert_eq!(response["code"], json!("missing_payload"));
|
||||||
|
assert_eq!(response["type"], json!("invalid_request"));
|
||||||
|
assert_eq!(
|
||||||
|
response["link"],
|
||||||
|
json!("https://docs.meilisearch.com/errors#missing_payload")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn error_add_missing_payload_json_documents() {
|
||||||
|
let document = "";
|
||||||
|
|
||||||
|
let server = Server::new().await;
|
||||||
|
let app = test::init_service(create_app!(
|
||||||
|
&server.service.meilisearch,
|
||||||
|
true,
|
||||||
|
&server.service.options
|
||||||
|
))
|
||||||
|
.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, 400);
|
||||||
|
assert_eq!(response["message"], json!(r#"A json payload is missing."#));
|
||||||
|
assert_eq!(response["code"], json!("missing_payload"));
|
||||||
|
assert_eq!(response["type"], json!("invalid_request"));
|
||||||
|
assert_eq!(
|
||||||
|
response["link"],
|
||||||
|
json!("https://docs.meilisearch.com/errors#missing_payload")
|
||||||
|
);
|
||||||
|
|
||||||
|
// 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, 400);
|
||||||
|
assert_eq!(response["message"], json!(r#"A json payload is missing."#));
|
||||||
|
assert_eq!(response["code"], json!("missing_payload"));
|
||||||
|
assert_eq!(response["type"], json!("invalid_request"));
|
||||||
|
assert_eq!(
|
||||||
|
response["link"],
|
||||||
|
json!("https://docs.meilisearch.com/errors#missing_payload")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn error_add_missing_payload_ndjson_documents() {
|
||||||
|
let document = "";
|
||||||
|
|
||||||
|
let server = Server::new().await;
|
||||||
|
let app = test::init_service(create_app!(
|
||||||
|
&server.service.meilisearch,
|
||||||
|
true,
|
||||||
|
&server.service.options
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
// post
|
||||||
|
let req = test::TestRequest::post()
|
||||||
|
.uri("/indexes/dog/documents")
|
||||||
|
.set_payload(document.to_string())
|
||||||
|
.insert_header(("content-type", "application/x-ndjson"))
|
||||||
|
.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, 400);
|
||||||
|
assert_eq!(
|
||||||
|
response["message"],
|
||||||
|
json!(r#"A ndjson payload is missing."#)
|
||||||
|
);
|
||||||
|
assert_eq!(response["code"], json!("missing_payload"));
|
||||||
|
assert_eq!(response["type"], json!("invalid_request"));
|
||||||
|
assert_eq!(
|
||||||
|
response["link"],
|
||||||
|
json!("https://docs.meilisearch.com/errors#missing_payload")
|
||||||
|
);
|
||||||
|
|
||||||
|
// put
|
||||||
|
let req = test::TestRequest::put()
|
||||||
|
.uri("/indexes/dog/documents")
|
||||||
|
.set_payload(document.to_string())
|
||||||
|
.insert_header(("content-type", "application/x-ndjson"))
|
||||||
|
.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, 400);
|
||||||
|
assert_eq!(
|
||||||
|
response["message"],
|
||||||
|
json!(r#"A ndjson payload is missing."#)
|
||||||
|
);
|
||||||
|
assert_eq!(response["code"], json!("missing_payload"));
|
||||||
|
assert_eq!(response["type"], json!("invalid_request"));
|
||||||
|
assert_eq!(
|
||||||
|
response["link"],
|
||||||
|
json!("https://docs.meilisearch.com/errors#missing_payload")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
@ -193,19 +561,37 @@ async fn add_documents_no_index_creation() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn document_add_create_index_bad_uid() {
|
async fn error_document_add_create_index_bad_uid() {
|
||||||
let server = Server::new().await;
|
let server = Server::new().await;
|
||||||
let index = server.index("883 fj!");
|
let index = server.index("883 fj!");
|
||||||
let (_response, code) = index.add_documents(json!([]), None).await;
|
let (response, code) = index.add_documents(json!([]), None).await;
|
||||||
|
|
||||||
|
let expected_response = json!({
|
||||||
|
"message": "`883 fj!` is not a valid index uid. Index uid can be an integer or a string containing only alphanumeric characters, hyphens (-) and underscores (_).",
|
||||||
|
"code": "invalid_index_uid",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#invalid_index_uid"
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(response, expected_response);
|
||||||
assert_eq!(code, 400);
|
assert_eq!(code, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn document_update_create_index_bad_uid() {
|
async fn error_document_update_create_index_bad_uid() {
|
||||||
let server = Server::new().await;
|
let server = Server::new().await;
|
||||||
let index = server.index("883 fj!");
|
let index = server.index("883 fj!");
|
||||||
let (response, code) = index.update_documents(json!([]), None).await;
|
let (response, code) = index.update_documents(json!([]), None).await;
|
||||||
assert_eq!(code, 400, "{}", response);
|
|
||||||
|
let expected_response = json!({
|
||||||
|
"message": "`883 fj!` is not a valid index uid. Index uid can be an integer or a string containing only alphanumeric characters, hyphens (-) and underscores (_).",
|
||||||
|
"code": "invalid_index_uid",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#invalid_index_uid"
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(response, expected_response);
|
||||||
|
assert_eq!(code, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
@ -264,60 +650,6 @@ async fn document_update_with_primary_key() {
|
|||||||
assert_eq!(response["primaryKey"], "primary");
|
assert_eq!(response["primaryKey"], "primary");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
|
||||||
async fn add_documents_with_primary_key_and_primary_key_already_exists() {
|
|
||||||
let server = Server::new().await;
|
|
||||||
let index = server.index("test");
|
|
||||||
|
|
||||||
index.create(Some("primary")).await;
|
|
||||||
let documents = json!([
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"content": "foo",
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
let (_response, code) = index.add_documents(documents, Some("id")).await;
|
|
||||||
assert_eq!(code, 202);
|
|
||||||
|
|
||||||
index.wait_update_id(0).await;
|
|
||||||
|
|
||||||
let (response, code) = index.get_update(0).await;
|
|
||||||
assert_eq!(code, 200);
|
|
||||||
assert_eq!(response["status"], "failed");
|
|
||||||
|
|
||||||
let (response, code) = index.get().await;
|
|
||||||
assert_eq!(code, 200);
|
|
||||||
assert_eq!(response["primaryKey"], "primary");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_rt::test]
|
|
||||||
async fn update_documents_with_primary_key_and_primary_key_already_exists() {
|
|
||||||
let server = Server::new().await;
|
|
||||||
let index = server.index("test");
|
|
||||||
|
|
||||||
index.create(Some("primary")).await;
|
|
||||||
let documents = json!([
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"content": "foo",
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
let (_response, code) = index.update_documents(documents, Some("id")).await;
|
|
||||||
assert_eq!(code, 202);
|
|
||||||
|
|
||||||
index.wait_update_id(0).await;
|
|
||||||
let (response, code) = index.get_update(0).await;
|
|
||||||
assert_eq!(code, 200);
|
|
||||||
// Documents without a primary key are not accepted.
|
|
||||||
assert_eq!(response["status"], "failed");
|
|
||||||
|
|
||||||
let (response, code) = index.get().await;
|
|
||||||
assert_eq!(code, 200);
|
|
||||||
assert_eq!(response["primaryKey"], "primary");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn replace_document() {
|
async fn replace_document() {
|
||||||
let server = Server::new().await;
|
let server = Server::new().await;
|
||||||
@ -356,25 +688,21 @@ async fn replace_document() {
|
|||||||
assert_eq!(response.to_string(), r##"{"doc_id":1,"other":"bar"}"##);
|
assert_eq!(response.to_string(), r##"{"doc_id":1,"other":"bar"}"##);
|
||||||
}
|
}
|
||||||
|
|
||||||
// test broken, see issue milli#92
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
#[ignore]
|
async fn error_add_no_documents() {
|
||||||
async fn add_no_documents() {
|
|
||||||
let server = Server::new().await;
|
let server = Server::new().await;
|
||||||
let index = server.index("test");
|
let index = server.index("test");
|
||||||
let (_response, code) = index.add_documents(json!([]), None).await;
|
let (response, code) = index.add_documents(json!([]), None).await;
|
||||||
assert_eq!(code, 200);
|
|
||||||
|
|
||||||
index.wait_update_id(0).await;
|
let expected_response = json!({
|
||||||
let (response, code) = index.get_update(0).await;
|
"message": "The `json` payload must contain at least one document.",
|
||||||
assert_eq!(code, 200);
|
"code": "malformed_payload",
|
||||||
assert_eq!(response["status"], "processed");
|
"type": "invalid_request",
|
||||||
assert_eq!(response["updateId"], 0);
|
"link": "https://docs.meilisearch.com/errors#malformed_payload"
|
||||||
assert_eq!(response["success"]["DocumentsAddition"]["nb_documents"], 0);
|
});
|
||||||
|
|
||||||
let (response, code) = index.get().await;
|
assert_eq!(response, expected_response);
|
||||||
assert_eq!(code, 200);
|
assert_eq!(code, 400);
|
||||||
assert_eq!(response["primaryKey"], Value::Null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
@ -460,7 +788,7 @@ async fn update_larger_dataset() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn add_documents_bad_primary_key() {
|
async fn error_add_documents_bad_document_id() {
|
||||||
let server = Server::new().await;
|
let server = Server::new().await;
|
||||||
let index = server.index("test");
|
let index = server.index("test");
|
||||||
index.create(Some("docid")).await;
|
index.create(Some("docid")).await;
|
||||||
@ -474,11 +802,18 @@ async fn add_documents_bad_primary_key() {
|
|||||||
index.wait_update_id(0).await;
|
index.wait_update_id(0).await;
|
||||||
let (response, code) = index.get_update(0).await;
|
let (response, code) = index.get_update(0).await;
|
||||||
assert_eq!(code, 200);
|
assert_eq!(code, 200);
|
||||||
assert_eq!(response["status"], "failed");
|
assert_eq!(response["status"], json!("failed"));
|
||||||
|
assert_eq!(response["message"], json!("Document identifier `foo & bar` is invalid. A document identifier can be of type integer or string, only composed of alphanumeric characters (a-z A-Z 0-9), hyphens (-) and underscores (_)."));
|
||||||
|
assert_eq!(response["code"], json!("invalid_document_id"));
|
||||||
|
assert_eq!(response["type"], json!("invalid_request"));
|
||||||
|
assert_eq!(
|
||||||
|
response["link"],
|
||||||
|
json!("https://docs.meilisearch.com/errors#invalid_document_id")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn update_documents_bad_primary_key() {
|
async fn error_update_documents_bad_document_id() {
|
||||||
let server = Server::new().await;
|
let server = Server::new().await;
|
||||||
let index = server.index("test");
|
let index = server.index("test");
|
||||||
index.create(Some("docid")).await;
|
index.create(Some("docid")).await;
|
||||||
@ -492,5 +827,160 @@ async fn update_documents_bad_primary_key() {
|
|||||||
index.wait_update_id(0).await;
|
index.wait_update_id(0).await;
|
||||||
let (response, code) = index.get_update(0).await;
|
let (response, code) = index.get_update(0).await;
|
||||||
assert_eq!(code, 200);
|
assert_eq!(code, 200);
|
||||||
assert_eq!(response["status"], "failed");
|
assert_eq!(response["status"], json!("failed"));
|
||||||
|
assert_eq!(response["message"], json!("Document identifier `foo & bar` is invalid. A document identifier can be of type integer or string, only composed of alphanumeric characters (a-z A-Z 0-9), hyphens (-) and underscores (_)."));
|
||||||
|
assert_eq!(response["code"], json!("invalid_document_id"));
|
||||||
|
assert_eq!(response["type"], json!("invalid_request"));
|
||||||
|
assert_eq!(
|
||||||
|
response["link"],
|
||||||
|
json!("https://docs.meilisearch.com/errors#invalid_document_id")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn error_add_documents_missing_document_id() {
|
||||||
|
let server = Server::new().await;
|
||||||
|
let index = server.index("test");
|
||||||
|
index.create(Some("docid")).await;
|
||||||
|
let documents = json!([
|
||||||
|
{
|
||||||
|
"id": "11",
|
||||||
|
"content": "foobar"
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
index.add_documents(documents, None).await;
|
||||||
|
index.wait_update_id(0).await;
|
||||||
|
let (response, code) = index.get_update(0).await;
|
||||||
|
assert_eq!(code, 200);
|
||||||
|
assert_eq!(response["status"], "failed");
|
||||||
|
assert_eq!(
|
||||||
|
response["message"],
|
||||||
|
json!(r#"Document doesn't have a `docid` attribute: `{"id":"11","content":"foobar"}`."#)
|
||||||
|
);
|
||||||
|
assert_eq!(response["code"], json!("missing_document_id"));
|
||||||
|
assert_eq!(response["type"], json!("invalid_request"));
|
||||||
|
assert_eq!(
|
||||||
|
response["link"],
|
||||||
|
json!("https://docs.meilisearch.com/errors#missing_document_id")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn error_update_documents_missing_document_id() {
|
||||||
|
let server = Server::new().await;
|
||||||
|
let index = server.index("test");
|
||||||
|
index.create(Some("docid")).await;
|
||||||
|
let documents = json!([
|
||||||
|
{
|
||||||
|
"id": "11",
|
||||||
|
"content": "foobar"
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
index.update_documents(documents, None).await;
|
||||||
|
index.wait_update_id(0).await;
|
||||||
|
let (response, code) = index.get_update(0).await;
|
||||||
|
assert_eq!(code, 200);
|
||||||
|
assert_eq!(response["status"], "failed");
|
||||||
|
assert_eq!(
|
||||||
|
response["message"],
|
||||||
|
r#"Document doesn't have a `docid` attribute: `{"id":"11","content":"foobar"}`."#
|
||||||
|
);
|
||||||
|
assert_eq!(response["code"], "missing_document_id");
|
||||||
|
assert_eq!(response["type"], "invalid_request");
|
||||||
|
assert_eq!(
|
||||||
|
response["link"],
|
||||||
|
"https://docs.meilisearch.com/errors#missing_document_id"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
#[ignore] // // TODO: Fix in an other PR: this does not provoke any error.
|
||||||
|
async fn error_document_field_limit_reached() {
|
||||||
|
let server = Server::new().await;
|
||||||
|
let index = server.index("test");
|
||||||
|
|
||||||
|
index.create(Some("id")).await;
|
||||||
|
|
||||||
|
let mut big_object = std::collections::HashMap::new();
|
||||||
|
big_object.insert("id".to_owned(), "wow");
|
||||||
|
for i in 0..65535 {
|
||||||
|
let key = i.to_string();
|
||||||
|
big_object.insert(key, "I am a text!");
|
||||||
|
}
|
||||||
|
|
||||||
|
let documents = json!([big_object]);
|
||||||
|
|
||||||
|
let (_response, code) = index.update_documents(documents, Some("id")).await;
|
||||||
|
assert_eq!(code, 202);
|
||||||
|
|
||||||
|
index.wait_update_id(0).await;
|
||||||
|
let (response, code) = index.get_update(0).await;
|
||||||
|
assert_eq!(code, 200);
|
||||||
|
// Documents without a primary key are not accepted.
|
||||||
|
assert_eq!(response["status"], "failed");
|
||||||
|
assert_eq!(
|
||||||
|
response["message"],
|
||||||
|
"A document cannot contain more than 65,535 fields."
|
||||||
|
);
|
||||||
|
assert_eq!(response["code"], "document_fields_limit_reached");
|
||||||
|
assert_eq!(response["type"], "invalid_request");
|
||||||
|
assert_eq!(
|
||||||
|
response["link"],
|
||||||
|
"https://docs.meilisearch.com/errors#document_fields_limit_reached"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
#[ignore] // // TODO: Fix in an other PR: this does not provoke any error.
|
||||||
|
async fn error_add_documents_invalid_geo_field() {
|
||||||
|
let server = Server::new().await;
|
||||||
|
let index = server.index("test");
|
||||||
|
index.create(Some("id")).await;
|
||||||
|
let documents = json!([
|
||||||
|
{
|
||||||
|
"id": "11",
|
||||||
|
"_geo": "foobar"
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
index.add_documents(documents, None).await;
|
||||||
|
index.wait_update_id(0).await;
|
||||||
|
let (response, code) = index.get_update(0).await;
|
||||||
|
assert_eq!(code, 200);
|
||||||
|
assert_eq!(response["status"], "failed");
|
||||||
|
assert_eq!(
|
||||||
|
response["message"],
|
||||||
|
r#"The document with the id: `11` contains an invalid _geo field: :syntaxErrorHelper:REPLACE_ME."#
|
||||||
|
);
|
||||||
|
assert_eq!(response["code"], "invalid_geo_field");
|
||||||
|
assert_eq!(response["type"], "invalid_request");
|
||||||
|
assert_eq!(
|
||||||
|
response["link"],
|
||||||
|
"https://docs.meilisearch.com/errors#invalid_geo_field"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn error_add_documents_payload_size() {
|
||||||
|
let server = Server::new().await;
|
||||||
|
let index = server.index("test");
|
||||||
|
index.create(Some("id")).await;
|
||||||
|
let document = json!(
|
||||||
|
{
|
||||||
|
"id": "11",
|
||||||
|
"content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec metus erat, consequat in blandit venenatis, ultricies eu ipsum. Etiam luctus elit et mollis ultrices. Nam turpis risus, dictum non eros in, eleifend feugiat elit. Morbi non dolor pulvinar, sagittis mi sed, ultricies lorem. Nulla ultricies sem metus. Donec at suscipit quam, sed elementum mi. Suspendisse potenti. Fusce pharetra turpis tortor, sed eleifend odio dapibus ut. Nulla facilisi. Suspendisse elementum, dui eget aliquet dignissim, ex tellus aliquam nisl, at eleifend nisl metus tempus diam. Mauris fermentum sollicitudin efficitur. Donec dignissim est vitae elit finibus faucibus"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
let documents: Vec<_> = (0..16000).into_iter().map(|_| document.clone()).collect();
|
||||||
|
let documents = json!(documents);
|
||||||
|
let (response, code) = index.add_documents(documents, None).await;
|
||||||
|
|
||||||
|
let expected_response = json!({
|
||||||
|
"message": "The provided payload reached the size limit.",
|
||||||
|
"code": "payload_too_large",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#payload_too_large"
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(response, expected_response);
|
||||||
|
assert_eq!(code, 413);
|
||||||
}
|
}
|
||||||
|
@ -83,10 +83,17 @@ async fn clear_all_documents_empty_index() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn delete_batch_unexisting_index() {
|
async fn error_delete_batch_unexisting_index() {
|
||||||
let server = Server::new().await;
|
let server = Server::new().await;
|
||||||
let (response, code) = server.index("test").delete_batch(vec![]).await;
|
let (response, code) = server.index("test").delete_batch(vec![]).await;
|
||||||
assert_eq!(code, 404, "{}", response);
|
let expected_response = json!({
|
||||||
|
"message": "Index `test` not found.",
|
||||||
|
"code": "index_not_found",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#index_not_found"
|
||||||
|
});
|
||||||
|
assert_eq!(code, 404);
|
||||||
|
assert_eq!(response, expected_response);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
|
@ -13,11 +13,20 @@ async fn get_unexisting_index_single_document() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn get_unexisting_document() {
|
async fn error_get_unexisting_document() {
|
||||||
let server = Server::new().await;
|
let server = Server::new().await;
|
||||||
let index = server.index("test");
|
let index = server.index("test");
|
||||||
index.create(None).await;
|
index.create(None).await;
|
||||||
let (_response, code) = index.get_document(1, None).await;
|
let (response, code) = index.get_document(1, None).await;
|
||||||
|
|
||||||
|
let expected_response = json!({
|
||||||
|
"message": "Document `1` not found.",
|
||||||
|
"code": "document_not_found",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#document_not_found"
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(response, expected_response);
|
||||||
assert_eq!(code, 404);
|
assert_eq!(code, 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,12 +56,21 @@ async fn get_document() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn get_unexisting_index_all_documents() {
|
async fn error_get_unexisting_index_all_documents() {
|
||||||
let server = Server::new().await;
|
let server = Server::new().await;
|
||||||
let (_response, code) = server
|
let (response, code) = server
|
||||||
.index("test")
|
.index("test")
|
||||||
.get_all_documents(GetAllDocumentsOptions::default())
|
.get_all_documents(GetAllDocumentsOptions::default())
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
let expected_response = json!({
|
||||||
|
"message": "Index `test` not found.",
|
||||||
|
"code": "index_not_found",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#index_not_found"
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(response, expected_response);
|
||||||
assert_eq!(code, 404);
|
assert_eq!(code, 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,28 +49,6 @@ async fn create_index_with_invalid_primary_key() {
|
|||||||
assert_eq!(response["primaryKey"], Value::Null);
|
assert_eq!(response["primaryKey"], Value::Null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: partial test since we are testing error, amd error is not yet fully implemented in
|
|
||||||
// transplant
|
|
||||||
#[actix_rt::test]
|
|
||||||
async fn create_existing_index() {
|
|
||||||
let server = Server::new().await;
|
|
||||||
let index = server.index("test");
|
|
||||||
let (_, code) = index.create(Some("primary")).await;
|
|
||||||
|
|
||||||
assert_eq!(code, 201);
|
|
||||||
|
|
||||||
let (_response, code) = index.create(Some("primary")).await;
|
|
||||||
assert_eq!(code, 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_rt::test]
|
|
||||||
async fn create_with_invalid_index_uid() {
|
|
||||||
let server = Server::new().await;
|
|
||||||
let index = server.index("test test#!");
|
|
||||||
let (_, code) = index.create(None).await;
|
|
||||||
assert_eq!(code, 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_create_multiple_indexes() {
|
async fn test_create_multiple_indexes() {
|
||||||
let server = Server::new().await;
|
let server = Server::new().await;
|
||||||
@ -88,3 +66,42 @@ async fn test_create_multiple_indexes() {
|
|||||||
assert_eq!(index3.get().await.1, 200);
|
assert_eq!(index3.get().await.1, 200);
|
||||||
assert_eq!(index4.get().await.1, 404);
|
assert_eq!(index4.get().await.1, 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn error_create_existing_index() {
|
||||||
|
let server = Server::new().await;
|
||||||
|
let index = server.index("test");
|
||||||
|
let (_, code) = index.create(Some("primary")).await;
|
||||||
|
|
||||||
|
assert_eq!(code, 201);
|
||||||
|
|
||||||
|
let (response, code) = index.create(Some("primary")).await;
|
||||||
|
|
||||||
|
let expected_response = json!({
|
||||||
|
"message": "Index `test` already exists.",
|
||||||
|
"code": "index_already_exists",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link":"https://docs.meilisearch.com/errors#index_already_exists"
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(response, expected_response);
|
||||||
|
assert_eq!(code, 409);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
#[ignore] // TODO: Fix in an other PR: uid returned `test%20test%23%21` instead of `test test#!`
|
||||||
|
async fn error_create_with_invalid_index_uid() {
|
||||||
|
let server = Server::new().await;
|
||||||
|
let index = server.index("test test#!");
|
||||||
|
let (response, code) = index.create(None).await;
|
||||||
|
|
||||||
|
let expected_response = json!({
|
||||||
|
"message": "`test test#!` is not a valid index uid. Index uid can be an integer or a string containing only alphanumeric characters, hyphens (-) and underscores (_).",
|
||||||
|
"code": "invalid_index_uid",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#invalid_index_uid"
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(response, expected_response);
|
||||||
|
assert_eq!(code, 400);
|
||||||
|
}
|
||||||
|
@ -18,11 +18,19 @@ async fn create_and_delete_index() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn delete_unexisting_index() {
|
async fn error_delete_unexisting_index() {
|
||||||
let server = Server::new().await;
|
let server = Server::new().await;
|
||||||
let index = server.index("test");
|
let index = server.index("test");
|
||||||
let (_response, code) = index.delete().await;
|
let (response, code) = index.delete().await;
|
||||||
|
|
||||||
|
let expected_response = json!({
|
||||||
|
"message": "Index `test` not found.",
|
||||||
|
"code": "index_not_found",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#index_not_found"
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(response, expected_response);
|
||||||
assert_eq!(code, 404);
|
assert_eq!(code, 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use crate::common::Server;
|
use crate::common::Server;
|
||||||
|
use serde_json::json;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
@ -21,15 +22,21 @@ async fn create_and_get_index() {
|
|||||||
assert_eq!(response.as_object().unwrap().len(), 5);
|
assert_eq!(response.as_object().unwrap().len(), 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: partial test since we are testing error, and error is not yet fully implemented in
|
|
||||||
// transplant
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn get_unexisting_index() {
|
async fn error_get_unexisting_index() {
|
||||||
let server = Server::new().await;
|
let server = Server::new().await;
|
||||||
let index = server.index("test");
|
let index = server.index("test");
|
||||||
|
|
||||||
let (_response, code) = index.get().await;
|
let (response, code) = index.get().await;
|
||||||
|
|
||||||
|
let expected_response = json!({
|
||||||
|
"message": "Index `test` not found.",
|
||||||
|
"code": "index_not_found",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#index_not_found"
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(response, expected_response);
|
||||||
assert_eq!(code, 404);
|
assert_eq!(code, 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,3 +46,19 @@ async fn stats() {
|
|||||||
assert_eq!(response["fieldDistribution"]["name"], 1);
|
assert_eq!(response["fieldDistribution"]["name"], 1);
|
||||||
assert_eq!(response["fieldDistribution"]["age"], 1);
|
assert_eq!(response["fieldDistribution"]["age"], 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn error_get_stats_unexisting_index() {
|
||||||
|
let server = Server::new().await;
|
||||||
|
let (response, code) = server.index("test").stats().await;
|
||||||
|
|
||||||
|
let expected_response = json!({
|
||||||
|
"message": "Index `test` not found.",
|
||||||
|
"code": "index_not_found",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#index_not_found"
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(response, expected_response);
|
||||||
|
assert_eq!(code, 404);
|
||||||
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use crate::common::Server;
|
use crate::common::Server;
|
||||||
use chrono::DateTime;
|
use chrono::DateTime;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn update_primary_key() {
|
async fn update_primary_key() {
|
||||||
@ -39,26 +40,48 @@ async fn update_nothing() {
|
|||||||
assert_eq!(response, update);
|
assert_eq!(response, update);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: partial test since we are testing error, amd error is not yet fully implemented in
|
|
||||||
// transplant
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn update_existing_primary_key() {
|
async fn error_update_existing_primary_key() {
|
||||||
let server = Server::new().await;
|
let server = Server::new().await;
|
||||||
let index = server.index("test");
|
let index = server.index("test");
|
||||||
let (_response, code) = index.create(Some("primary")).await;
|
let (_response, code) = index.create(Some("id")).await;
|
||||||
|
|
||||||
assert_eq!(code, 201);
|
assert_eq!(code, 201);
|
||||||
|
|
||||||
let (_update, code) = index.update(Some("primary2")).await;
|
let documents = json!([
|
||||||
|
{
|
||||||
|
"id": "11",
|
||||||
|
"content": "foobar"
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
index.add_documents(documents, None).await;
|
||||||
|
index.wait_update_id(0).await;
|
||||||
|
|
||||||
|
let (response, code) = index.update(Some("primary")).await;
|
||||||
|
|
||||||
|
let expected_response = json!({
|
||||||
|
"message": "Index already has a primary key: `id`.",
|
||||||
|
"code": "index_primary_key_already_exists",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#index_primary_key_already_exists"
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(response, expected_response);
|
||||||
assert_eq!(code, 400);
|
assert_eq!(code, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: partial test since we are testing error, amd error is not yet fully implemented in
|
|
||||||
// transplant
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_unexisting_index() {
|
async fn error_update_unexisting_index() {
|
||||||
let server = Server::new().await;
|
let server = Server::new().await;
|
||||||
let (_response, code) = server.index("test").update(None).await;
|
let (response, code) = server.index("test").update(None).await;
|
||||||
|
|
||||||
|
let expected_response = json!({
|
||||||
|
"message": "Index `test` not found.",
|
||||||
|
"code": "index_not_found",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#index_not_found"
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(response, expected_response);
|
||||||
assert_eq!(code, 404);
|
assert_eq!(code, 404);
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,24 @@
|
|||||||
use crate::common::Server;
|
use crate::common::Server;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
|
use super::DOCUMENTS;
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn search_unexisting_index() {
|
async fn search_unexisting_index() {
|
||||||
let server = Server::new().await;
|
let server = Server::new().await;
|
||||||
let index = server.index("test");
|
let index = server.index("test");
|
||||||
|
|
||||||
|
let expected_response = json!({
|
||||||
|
"message": "Index `test` not found.",
|
||||||
|
"code": "index_not_found",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#index_not_found"
|
||||||
|
});
|
||||||
|
|
||||||
index
|
index
|
||||||
.search(json!({"q": "hello"}), |response, code| {
|
.search(json!({"q": "hello"}), |response, code| {
|
||||||
assert_eq!(code, 404, "{}", response);
|
assert_eq!(code, 404);
|
||||||
assert_eq!(response["errorCode"], "index_not_found");
|
assert_eq!(response, expected_response);
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
@ -22,7 +31,421 @@ async fn search_unexisting_parameter() {
|
|||||||
index
|
index
|
||||||
.search(json!({"marin": "hello"}), |response, code| {
|
.search(json!({"marin": "hello"}), |response, code| {
|
||||||
assert_eq!(code, 400, "{}", response);
|
assert_eq!(code, 400, "{}", response);
|
||||||
assert_eq!(response["errorCode"], "bad_request");
|
assert_eq!(response["code"], "bad_request");
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn filter_invalid_syntax_object() {
|
||||||
|
let server = Server::new().await;
|
||||||
|
let index = server.index("test");
|
||||||
|
|
||||||
|
index
|
||||||
|
.update_settings(json!({"filterableAttributes": ["title"]}))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let documents = DOCUMENTS.clone();
|
||||||
|
index.add_documents(documents, None).await;
|
||||||
|
index.wait_update_id(1).await;
|
||||||
|
|
||||||
|
let expected_response = json!({
|
||||||
|
"message": "Invalid syntax for the filter parameter: ` --> 1:7\n |\n1 | title & Glass\n | ^---\n |\n = expected word`.",
|
||||||
|
"code": "invalid_filter",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#invalid_filter"
|
||||||
|
});
|
||||||
|
index
|
||||||
|
.search(json!({"filter": "title & Glass"}), |response, code| {
|
||||||
|
assert_eq!(response, expected_response);
|
||||||
|
assert_eq!(code, 400);
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn filter_invalid_syntax_array() {
|
||||||
|
let server = Server::new().await;
|
||||||
|
let index = server.index("test");
|
||||||
|
|
||||||
|
index
|
||||||
|
.update_settings(json!({"filterableAttributes": ["title"]}))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let documents = DOCUMENTS.clone();
|
||||||
|
index.add_documents(documents, None).await;
|
||||||
|
index.wait_update_id(1).await;
|
||||||
|
|
||||||
|
let expected_response = json!({
|
||||||
|
"message": "Invalid syntax for the filter parameter: ` --> 1:7\n |\n1 | title & Glass\n | ^---\n |\n = expected word`.",
|
||||||
|
"code": "invalid_filter",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#invalid_filter"
|
||||||
|
});
|
||||||
|
index
|
||||||
|
.search(json!({"filter": [["title & Glass"]]}), |response, code| {
|
||||||
|
assert_eq!(response, expected_response);
|
||||||
|
assert_eq!(code, 400);
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn filter_invalid_syntax_string() {
|
||||||
|
let server = Server::new().await;
|
||||||
|
let index = server.index("test");
|
||||||
|
|
||||||
|
index
|
||||||
|
.update_settings(json!({"filterableAttributes": ["title"]}))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let documents = DOCUMENTS.clone();
|
||||||
|
index.add_documents(documents, None).await;
|
||||||
|
index.wait_update_id(1).await;
|
||||||
|
|
||||||
|
let expected_response = json!({
|
||||||
|
"message": "Invalid syntax for the filter parameter: ` --> 1:15\n |\n1 | title = Glass XOR title = Glass\n | ^---\n |\n = expected EOI, and, or or`.",
|
||||||
|
"code": "invalid_filter",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#invalid_filter"
|
||||||
|
});
|
||||||
|
index
|
||||||
|
.search(
|
||||||
|
json!({"filter": "title = Glass XOR title = Glass"}),
|
||||||
|
|response, code| {
|
||||||
|
assert_eq!(response, expected_response);
|
||||||
|
assert_eq!(code, 400);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn filter_invalid_attribute_array() {
|
||||||
|
let server = Server::new().await;
|
||||||
|
let index = server.index("test");
|
||||||
|
|
||||||
|
index
|
||||||
|
.update_settings(json!({"filterableAttributes": ["title"]}))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let documents = DOCUMENTS.clone();
|
||||||
|
index.add_documents(documents, None).await;
|
||||||
|
index.wait_update_id(1).await;
|
||||||
|
|
||||||
|
let expected_response = json!({
|
||||||
|
"message": "Attribute `many` is not filterable. Available filterable attributes are: `title`.",
|
||||||
|
"code": "invalid_filter",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#invalid_filter"
|
||||||
|
});
|
||||||
|
index
|
||||||
|
.search(json!({"filter": [["many = Glass"]]}), |response, code| {
|
||||||
|
assert_eq!(response, expected_response);
|
||||||
|
assert_eq!(code, 400);
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn filter_invalid_attribute_string() {
|
||||||
|
let server = Server::new().await;
|
||||||
|
let index = server.index("test");
|
||||||
|
|
||||||
|
index
|
||||||
|
.update_settings(json!({"filterableAttributes": ["title"]}))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let documents = DOCUMENTS.clone();
|
||||||
|
index.add_documents(documents, None).await;
|
||||||
|
index.wait_update_id(1).await;
|
||||||
|
|
||||||
|
let expected_response = json!({
|
||||||
|
"message": "Attribute `many` is not filterable. Available filterable attributes are: `title`.",
|
||||||
|
"code": "invalid_filter",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#invalid_filter"
|
||||||
|
});
|
||||||
|
index
|
||||||
|
.search(json!({"filter": "many = Glass"}), |response, code| {
|
||||||
|
assert_eq!(response, expected_response);
|
||||||
|
assert_eq!(code, 400);
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn filter_reserved_geo_attribute_array() {
|
||||||
|
let server = Server::new().await;
|
||||||
|
let index = server.index("test");
|
||||||
|
|
||||||
|
index
|
||||||
|
.update_settings(json!({"filterableAttributes": ["title"]}))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let documents = DOCUMENTS.clone();
|
||||||
|
index.add_documents(documents, None).await;
|
||||||
|
index.wait_update_id(1).await;
|
||||||
|
|
||||||
|
let expected_response = json!({
|
||||||
|
"message": "`_geo` is a reserved keyword and thus can't be used as a filter expression. Use the _geoRadius(latitude, longitude, distance) built-in rule to filter on _geo field coordinates.",
|
||||||
|
"code": "invalid_filter",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#invalid_filter"
|
||||||
|
});
|
||||||
|
index
|
||||||
|
.search(json!({"filter": [["_geo = Glass"]]}), |response, code| {
|
||||||
|
assert_eq!(response, expected_response);
|
||||||
|
assert_eq!(code, 400);
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn filter_reserved_geo_attribute_string() {
|
||||||
|
let server = Server::new().await;
|
||||||
|
let index = server.index("test");
|
||||||
|
|
||||||
|
index
|
||||||
|
.update_settings(json!({"filterableAttributes": ["title"]}))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let documents = DOCUMENTS.clone();
|
||||||
|
index.add_documents(documents, None).await;
|
||||||
|
index.wait_update_id(1).await;
|
||||||
|
|
||||||
|
let expected_response = json!({
|
||||||
|
"message": "`_geo` is a reserved keyword and thus can't be used as a filter expression. Use the _geoRadius(latitude, longitude, distance) built-in rule to filter on _geo field coordinates.",
|
||||||
|
"code": "invalid_filter",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#invalid_filter"
|
||||||
|
});
|
||||||
|
index
|
||||||
|
.search(json!({"filter": "_geo = Glass"}), |response, code| {
|
||||||
|
assert_eq!(response, expected_response);
|
||||||
|
assert_eq!(code, 400);
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn filter_reserved_attribute_array() {
|
||||||
|
let server = Server::new().await;
|
||||||
|
let index = server.index("test");
|
||||||
|
|
||||||
|
index
|
||||||
|
.update_settings(json!({"filterableAttributes": ["title"]}))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let documents = DOCUMENTS.clone();
|
||||||
|
index.add_documents(documents, None).await;
|
||||||
|
index.wait_update_id(1).await;
|
||||||
|
|
||||||
|
let expected_response = json!({
|
||||||
|
"message": "`_geoDistance` is a reserved keyword and thus can't be used as a filter expression.",
|
||||||
|
"code": "invalid_filter",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#invalid_filter"
|
||||||
|
});
|
||||||
|
index
|
||||||
|
.search(
|
||||||
|
json!({"filter": [["_geoDistance = Glass"]]}),
|
||||||
|
|response, code| {
|
||||||
|
assert_eq!(response, expected_response);
|
||||||
|
assert_eq!(code, 400);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn filter_reserved_attribute_string() {
|
||||||
|
let server = Server::new().await;
|
||||||
|
let index = server.index("test");
|
||||||
|
|
||||||
|
index
|
||||||
|
.update_settings(json!({"filterableAttributes": ["title"]}))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let documents = DOCUMENTS.clone();
|
||||||
|
index.add_documents(documents, None).await;
|
||||||
|
index.wait_update_id(1).await;
|
||||||
|
|
||||||
|
let expected_response = json!({
|
||||||
|
"message": "`_geoDistance` is a reserved keyword and thus can't be used as a filter expression.",
|
||||||
|
"code": "invalid_filter",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#invalid_filter"
|
||||||
|
});
|
||||||
|
index
|
||||||
|
.search(
|
||||||
|
json!({"filter": "_geoDistance = Glass"}),
|
||||||
|
|response, code| {
|
||||||
|
assert_eq!(response, expected_response);
|
||||||
|
assert_eq!(code, 400);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn sort_geo_reserved_attribute() {
|
||||||
|
let server = Server::new().await;
|
||||||
|
let index = server.index("test");
|
||||||
|
|
||||||
|
index
|
||||||
|
.update_settings(json!({"sortableAttributes": ["id"]}))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let documents = DOCUMENTS.clone();
|
||||||
|
index.add_documents(documents, None).await;
|
||||||
|
index.wait_update_id(1).await;
|
||||||
|
|
||||||
|
let expected_response = json!({
|
||||||
|
"message": "`_geo` is a reserved keyword and thus can't be used as a sort expression. Use the _geoPoint(latitude, longitude) built-in rule to sort on _geo field coordinates.",
|
||||||
|
"code": "invalid_sort",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#invalid_sort"
|
||||||
|
});
|
||||||
|
index
|
||||||
|
.search(
|
||||||
|
json!({
|
||||||
|
"sort": ["_geo:asc"]
|
||||||
|
}),
|
||||||
|
|response, code| {
|
||||||
|
assert_eq!(response, expected_response);
|
||||||
|
assert_eq!(code, 400);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn sort_reserved_attribute() {
|
||||||
|
let server = Server::new().await;
|
||||||
|
let index = server.index("test");
|
||||||
|
|
||||||
|
index
|
||||||
|
.update_settings(json!({"sortableAttributes": ["id"]}))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let documents = DOCUMENTS.clone();
|
||||||
|
index.add_documents(documents, None).await;
|
||||||
|
index.wait_update_id(1).await;
|
||||||
|
|
||||||
|
let expected_response = json!({
|
||||||
|
"message": "`_geoDistance` is a reserved keyword and thus can't be used as a sort expression.",
|
||||||
|
"code": "invalid_sort",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#invalid_sort"
|
||||||
|
});
|
||||||
|
index
|
||||||
|
.search(
|
||||||
|
json!({
|
||||||
|
"sort": ["_geoDistance:asc"]
|
||||||
|
}),
|
||||||
|
|response, code| {
|
||||||
|
assert_eq!(response, expected_response);
|
||||||
|
assert_eq!(code, 400);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn sort_unsortable_attribute() {
|
||||||
|
let server = Server::new().await;
|
||||||
|
let index = server.index("test");
|
||||||
|
|
||||||
|
index
|
||||||
|
.update_settings(json!({"sortableAttributes": ["id"]}))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let documents = DOCUMENTS.clone();
|
||||||
|
index.add_documents(documents, None).await;
|
||||||
|
index.wait_update_id(1).await;
|
||||||
|
|
||||||
|
let expected_response = json!({
|
||||||
|
"message": "Attribute `title` is not sortable. Available sortable attributes are: `id`.",
|
||||||
|
"code": "invalid_sort",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#invalid_sort"
|
||||||
|
});
|
||||||
|
index
|
||||||
|
.search(
|
||||||
|
json!({
|
||||||
|
"sort": ["title:asc"]
|
||||||
|
}),
|
||||||
|
|response, code| {
|
||||||
|
assert_eq!(response, expected_response);
|
||||||
|
assert_eq!(code, 400);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn sort_invalid_syntax() {
|
||||||
|
let server = Server::new().await;
|
||||||
|
let index = server.index("test");
|
||||||
|
|
||||||
|
index
|
||||||
|
.update_settings(json!({"sortableAttributes": ["id"]}))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let documents = DOCUMENTS.clone();
|
||||||
|
index.add_documents(documents, None).await;
|
||||||
|
index.wait_update_id(1).await;
|
||||||
|
|
||||||
|
let expected_response = json!({
|
||||||
|
"message": "Invalid syntax for the sort parameter: expected expression ending by `:asc` or `:desc`, found `title`.",
|
||||||
|
"code": "invalid_sort",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#invalid_sort"
|
||||||
|
});
|
||||||
|
index
|
||||||
|
.search(
|
||||||
|
json!({
|
||||||
|
"sort": ["title"]
|
||||||
|
}),
|
||||||
|
|response, code| {
|
||||||
|
assert_eq!(response, expected_response);
|
||||||
|
assert_eq!(code, 400);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn sort_unset_ranking_rule() {
|
||||||
|
let server = Server::new().await;
|
||||||
|
let index = server.index("test");
|
||||||
|
|
||||||
|
index
|
||||||
|
.update_settings(
|
||||||
|
json!({"sortableAttributes": ["title"], "rankingRules": ["proximity", "exactness"]}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let documents = DOCUMENTS.clone();
|
||||||
|
index.add_documents(documents, None).await;
|
||||||
|
index.wait_update_id(1).await;
|
||||||
|
|
||||||
|
let expected_response = json!({
|
||||||
|
"message": "The sort ranking rule must be specified in the ranking rules settings to use the sort parameter at search time.",
|
||||||
|
"code": "invalid_sort",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#invalid_sort"
|
||||||
|
});
|
||||||
|
index
|
||||||
|
.search(
|
||||||
|
json!({
|
||||||
|
"sort": ["title:asc"]
|
||||||
|
}),
|
||||||
|
|response, code| {
|
||||||
|
assert_eq!(response, expected_response);
|
||||||
|
assert_eq!(code, 400);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
@ -1,18 +1,37 @@
|
|||||||
use crate::common::Server;
|
use crate::common::Server;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn get_update_unexisting_index() {
|
async fn error_get_update_unexisting_index() {
|
||||||
let server = Server::new().await;
|
let server = Server::new().await;
|
||||||
let (_response, code) = server.index("test").get_update(0).await;
|
let (response, code) = server.index("test").get_update(0).await;
|
||||||
|
|
||||||
|
let expected_response = json!({
|
||||||
|
"message": "Index `test` not found.",
|
||||||
|
"code": "index_not_found",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#index_not_found"
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(response, expected_response);
|
||||||
assert_eq!(code, 404);
|
assert_eq!(code, 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn get_unexisting_update_status() {
|
async fn error_get_unexisting_update_status() {
|
||||||
let server = Server::new().await;
|
let server = Server::new().await;
|
||||||
let index = server.index("test");
|
let index = server.index("test");
|
||||||
index.create(None).await;
|
index.create(None).await;
|
||||||
let (_response, code) = index.get_update(0).await;
|
let (response, code) = index.get_update(0).await;
|
||||||
|
|
||||||
|
let expected_response = json!({
|
||||||
|
"message": "Task `0` not found.",
|
||||||
|
"code": "task_not_found",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#task_not_found"
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(response, expected_response);
|
||||||
assert_eq!(code, 404);
|
assert_eq!(code, 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,9 +55,18 @@ async fn get_update_status() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn list_updates_unexisting_index() {
|
async fn error_list_updates_unexisting_index() {
|
||||||
let server = Server::new().await;
|
let server = Server::new().await;
|
||||||
let (_response, code) = server.index("test").list_updates().await;
|
let (response, code) = server.index("test").list_updates().await;
|
||||||
|
|
||||||
|
let expected_response = json!({
|
||||||
|
"message": "Index `test` not found.",
|
||||||
|
"code": "index_not_found",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#index_not_found"
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(response, expected_response);
|
||||||
assert_eq!(code, 404);
|
assert_eq!(code, 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ lazy_static = "1.4.0"
|
|||||||
log = "0.4.14"
|
log = "0.4.14"
|
||||||
meilisearch-error = { path = "../meilisearch-error" }
|
meilisearch-error = { path = "../meilisearch-error" }
|
||||||
meilisearch-tokenizer = { git = "https://github.com/meilisearch/tokenizer.git", tag = "v0.2.5" }
|
meilisearch-tokenizer = { git = "https://github.com/meilisearch/tokenizer.git", tag = "v0.2.5" }
|
||||||
milli = { git = "https://github.com/meilisearch/milli.git", tag = "v0.19.0" }
|
milli = { git = "https://github.com/meilisearch/milli.git", tag = "v0.20.0" }
|
||||||
mime = "0.3.16"
|
mime = "0.3.16"
|
||||||
num_cpus = "1.13.0"
|
num_cpus = "1.13.0"
|
||||||
once_cell = "1.8.0"
|
once_cell = "1.8.0"
|
||||||
|
@ -25,13 +25,15 @@ impl fmt::Display for PayloadType {
|
|||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum DocumentFormatError {
|
pub enum DocumentFormatError {
|
||||||
#[error("Internal error: {0}")]
|
#[error("Internal error!: {0}")]
|
||||||
Internal(Box<dyn std::error::Error + Send + Sync + 'static>),
|
Internal(Box<dyn std::error::Error + Send + Sync + 'static>),
|
||||||
#[error("{0}. The {1} payload provided is malformed.")]
|
#[error("The `{1}` payload provided is malformed. `{0}`.")]
|
||||||
MalformedPayload(
|
MalformedPayload(
|
||||||
Box<dyn std::error::Error + Send + Sync + 'static>,
|
Box<dyn std::error::Error + Send + Sync + 'static>,
|
||||||
PayloadType,
|
PayloadType,
|
||||||
),
|
),
|
||||||
|
#[error("The `{0}` payload must contain at least one document.")]
|
||||||
|
EmptyPayload(PayloadType),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<(PayloadType, milli::documents::Error)> for DocumentFormatError {
|
impl From<(PayloadType, milli::documents::Error)> for DocumentFormatError {
|
||||||
@ -48,6 +50,7 @@ impl ErrorCode for DocumentFormatError {
|
|||||||
match self {
|
match self {
|
||||||
DocumentFormatError::Internal(_) => Code::Internal,
|
DocumentFormatError::Internal(_) => Code::Internal,
|
||||||
DocumentFormatError::MalformedPayload(_, _) => Code::MalformedPayload,
|
DocumentFormatError::MalformedPayload(_, _) => Code::MalformedPayload,
|
||||||
|
DocumentFormatError::EmptyPayload(_) => Code::MalformedPayload,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -57,10 +60,14 @@ internal_error!(DocumentFormatError: io::Error);
|
|||||||
/// reads csv from input and write an obkv batch to writer.
|
/// reads csv from input and write an obkv batch to writer.
|
||||||
pub fn read_csv(input: impl Read, writer: impl Write + Seek) -> Result<()> {
|
pub fn read_csv(input: impl Read, writer: impl Write + Seek) -> Result<()> {
|
||||||
let writer = BufWriter::new(writer);
|
let writer = BufWriter::new(writer);
|
||||||
DocumentBatchBuilder::from_csv(input, writer)
|
let builder =
|
||||||
.map_err(|e| (PayloadType::Csv, e))?
|
DocumentBatchBuilder::from_csv(input, writer).map_err(|e| (PayloadType::Csv, e))?;
|
||||||
.finish()
|
|
||||||
.map_err(|e| (PayloadType::Csv, e))?;
|
if builder.len() == 0 {
|
||||||
|
return Err(DocumentFormatError::EmptyPayload(PayloadType::Csv));
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.finish().map_err(|e| (PayloadType::Csv, e))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -80,6 +87,10 @@ pub fn read_ndjson(input: impl Read, writer: impl Write + Seek) -> Result<()> {
|
|||||||
buf.clear();
|
buf.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if builder.len() == 0 {
|
||||||
|
return Err(DocumentFormatError::EmptyPayload(PayloadType::Ndjson));
|
||||||
|
}
|
||||||
|
|
||||||
builder.finish().map_err(|e| (PayloadType::Ndjson, e))?;
|
builder.finish().map_err(|e| (PayloadType::Ndjson, e))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -92,6 +103,11 @@ pub fn read_json(input: impl Read, writer: impl Write + Seek) -> Result<()> {
|
|||||||
builder
|
builder
|
||||||
.extend_from_json(input)
|
.extend_from_json(input)
|
||||||
.map_err(|e| (PayloadType::Json, e))?;
|
.map_err(|e| (PayloadType::Json, e))?;
|
||||||
|
|
||||||
|
if builder.len() == 0 {
|
||||||
|
return Err(DocumentFormatError::EmptyPayload(PayloadType::Json));
|
||||||
|
}
|
||||||
|
|
||||||
builder.finish().map_err(|e| (PayloadType::Json, e))?;
|
builder.finish().map_err(|e| (PayloadType::Json, e))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -37,19 +37,17 @@ impl ErrorCode for MilliError<'_> {
|
|||||||
// TODO: wait for spec for new error codes.
|
// TODO: wait for spec for new error codes.
|
||||||
UserError::SerdeJson(_)
|
UserError::SerdeJson(_)
|
||||||
| UserError::MaxDatabaseSizeReached
|
| UserError::MaxDatabaseSizeReached
|
||||||
| UserError::InvalidDocumentId { .. }
|
|
||||||
| UserError::InvalidStoreFile
|
| UserError::InvalidStoreFile
|
||||||
| UserError::NoSpaceLeftOnDevice
|
| UserError::NoSpaceLeftOnDevice
|
||||||
| UserError::DocumentLimitReached => Code::Internal,
|
| UserError::DocumentLimitReached
|
||||||
|
| UserError::UnknownInternalDocumentId { .. } => Code::Internal,
|
||||||
UserError::AttributeLimitReached => Code::MaxFieldsLimitExceeded,
|
UserError::AttributeLimitReached => Code::MaxFieldsLimitExceeded,
|
||||||
UserError::InvalidFilter(_) => Code::Filter,
|
UserError::InvalidFilter(_) => Code::Filter,
|
||||||
UserError::InvalidFilterAttribute(_) => Code::Filter,
|
|
||||||
UserError::MissingDocumentId { .. } => Code::MissingDocumentId,
|
UserError::MissingDocumentId { .. } => Code::MissingDocumentId,
|
||||||
|
UserError::InvalidDocumentId { .. } => Code::InvalidDocumentId,
|
||||||
UserError::MissingPrimaryKey => Code::MissingPrimaryKey,
|
UserError::MissingPrimaryKey => Code::MissingPrimaryKey,
|
||||||
UserError::PrimaryKeyCannotBeChanged => Code::PrimaryKeyAlreadyPresent,
|
UserError::PrimaryKeyCannotBeChanged(_) => Code::PrimaryKeyAlreadyPresent,
|
||||||
UserError::PrimaryKeyCannotBeReset => Code::PrimaryKeyAlreadyPresent,
|
|
||||||
UserError::SortRankingRuleMissing => Code::Sort,
|
UserError::SortRankingRuleMissing => Code::Sort,
|
||||||
UserError::UnknownInternalDocumentId { .. } => Code::DocumentNotFound,
|
|
||||||
UserError::InvalidFacetsDistribution { .. } => Code::BadRequest,
|
UserError::InvalidFacetsDistribution { .. } => Code::BadRequest,
|
||||||
UserError::InvalidSortableAttribute { .. } => Code::Sort,
|
UserError::InvalidSortableAttribute { .. } => Code::Sort,
|
||||||
UserError::CriterionError(_) => Code::InvalidRankingRule,
|
UserError::CriterionError(_) => Code::InvalidRankingRule,
|
||||||
|
@ -11,14 +11,12 @@ pub type Result<T> = std::result::Result<T, IndexError>;
|
|||||||
pub enum IndexError {
|
pub enum IndexError {
|
||||||
#[error("Internal error: {0}")]
|
#[error("Internal error: {0}")]
|
||||||
Internal(Box<dyn Error + Send + Sync + 'static>),
|
Internal(Box<dyn Error + Send + Sync + 'static>),
|
||||||
#[error("Document with id {0} not found.")]
|
#[error("Document `{0}` not found.")]
|
||||||
DocumentNotFound(String),
|
DocumentNotFound(String),
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
Facet(#[from] FacetError),
|
Facet(#[from] FacetError),
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
Milli(#[from] milli::Error),
|
Milli(#[from] milli::Error),
|
||||||
#[error("A primary key is already present. It's impossible to update it")]
|
|
||||||
ExistingPrimaryKey,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal_error!(
|
internal_error!(
|
||||||
@ -35,21 +33,20 @@ impl ErrorCode for IndexError {
|
|||||||
IndexError::DocumentNotFound(_) => Code::DocumentNotFound,
|
IndexError::DocumentNotFound(_) => Code::DocumentNotFound,
|
||||||
IndexError::Facet(e) => e.error_code(),
|
IndexError::Facet(e) => e.error_code(),
|
||||||
IndexError::Milli(e) => MilliError(e).error_code(),
|
IndexError::Milli(e) => MilliError(e).error_code(),
|
||||||
IndexError::ExistingPrimaryKey => Code::PrimaryKeyAlreadyPresent,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum FacetError {
|
pub enum FacetError {
|
||||||
#[error("Invalid facet expression, expected {}, found: {1}", .0.join(", "))]
|
#[error("Invalid syntax for the filter parameter: `expected {}, found: {1}`.", .0.join(", "))]
|
||||||
InvalidExpression(&'static [&'static str], Value),
|
InvalidExpression(&'static [&'static str], Value),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ErrorCode for FacetError {
|
impl ErrorCode for FacetError {
|
||||||
fn error_code(&self) -> Code {
|
fn error_code(&self) -> Code {
|
||||||
match self {
|
match self {
|
||||||
FacetError::InvalidExpression(_, _) => Code::Facet,
|
FacetError::InvalidExpression(_, _) => Code::Filter,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ use uuid::Uuid;
|
|||||||
use crate::index_controller::updates::status::{Failed, Processed, Processing, UpdateResult};
|
use crate::index_controller::updates::status::{Failed, Processed, Processing, UpdateResult};
|
||||||
use crate::Update;
|
use crate::Update;
|
||||||
|
|
||||||
use super::error::{IndexError, Result};
|
use super::error::Result;
|
||||||
use super::index::{Index, IndexMeta};
|
use super::index::{Index, IndexMeta};
|
||||||
|
|
||||||
fn serialize_with_wildcard<S>(
|
fn serialize_with_wildcard<S>(
|
||||||
@ -222,9 +222,6 @@ impl Index {
|
|||||||
match primary_key {
|
match primary_key {
|
||||||
Some(primary_key) => {
|
Some(primary_key) => {
|
||||||
let mut txn = self.write_txn()?;
|
let mut txn = self.write_txn()?;
|
||||||
if self.primary_key(&txn)?.is_some() {
|
|
||||||
return Err(IndexError::ExistingPrimaryKey);
|
|
||||||
}
|
|
||||||
let mut builder = UpdateBuilder::new(0).settings(&mut txn, self);
|
let mut builder = UpdateBuilder::new(0).settings(&mut txn, self);
|
||||||
builder.set_primary_key(primary_key);
|
builder.set_primary_key(primary_key);
|
||||||
builder.execute(|_, _| ())?;
|
builder.execute(|_, _| ())?;
|
||||||
|
@ -9,7 +9,7 @@ pub type Result<T> = std::result::Result<T, DumpActorError>;
|
|||||||
pub enum DumpActorError {
|
pub enum DumpActorError {
|
||||||
#[error("Another dump is already in progress")]
|
#[error("Another dump is already in progress")]
|
||||||
DumpAlreadyRunning,
|
DumpAlreadyRunning,
|
||||||
#[error("Dump `{0}` not found")]
|
#[error("Dump `{0}` not found.")]
|
||||||
DumpDoesNotExist(String),
|
DumpDoesNotExist(String),
|
||||||
#[error("Internal error: {0}")]
|
#[error("Internal error: {0}")]
|
||||||
Internal(Box<dyn std::error::Error + Send + Sync + 'static>),
|
Internal(Box<dyn std::error::Error + Send + Sync + 'static>),
|
||||||
|
@ -3,6 +3,7 @@ use std::fmt;
|
|||||||
use meilisearch_error::{Code, ErrorCode};
|
use meilisearch_error::{Code, ErrorCode};
|
||||||
use tokio::sync::mpsc::error::SendError as MpscSendError;
|
use tokio::sync::mpsc::error::SendError as MpscSendError;
|
||||||
use tokio::sync::oneshot::error::RecvError as OneshotRecvError;
|
use tokio::sync::oneshot::error::RecvError as OneshotRecvError;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{error::MilliError, index::error::IndexError};
|
use crate::{error::MilliError, index::error::IndexError};
|
||||||
|
|
||||||
@ -12,17 +13,19 @@ pub type Result<T> = std::result::Result<T, IndexResolverError>;
|
|||||||
pub enum IndexResolverError {
|
pub enum IndexResolverError {
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
IndexError(#[from] IndexError),
|
IndexError(#[from] IndexError),
|
||||||
#[error("Index already exists")]
|
#[error("Index `{0}` already exists.")]
|
||||||
IndexAlreadyExists,
|
IndexAlreadyExists(String),
|
||||||
#[error("Index {0} not found")]
|
#[error("Index `{0}` not found.")]
|
||||||
UnexistingIndex(String),
|
UnexistingIndex(String),
|
||||||
#[error("A primary key is already present. It's impossible to update it")]
|
#[error("A primary key is already present. It's impossible to update it")]
|
||||||
ExistingPrimaryKey,
|
ExistingPrimaryKey,
|
||||||
#[error("Internal Error: {0}")]
|
#[error("Internal Error: `{0}`")]
|
||||||
Internal(Box<dyn std::error::Error + Send + Sync + 'static>),
|
Internal(Box<dyn std::error::Error + Send + Sync + 'static>),
|
||||||
|
#[error("Internal Error: Index uuid `{0}` is already assigned.")]
|
||||||
|
UuidAlreadyExists(Uuid),
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
Milli(#[from] milli::Error),
|
Milli(#[from] milli::Error),
|
||||||
#[error("Index must have a valid uid; Index uid can be of type integer or string only composed of alphanumeric characters, hyphens (-) and underscores (_).")]
|
#[error("`{0}` is not a valid index uid. Index uid can be an integer or a string containing only alphanumeric characters, hyphens (-) and underscores (_).")]
|
||||||
BadlyFormatted(String),
|
BadlyFormatted(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,10 +56,11 @@ impl ErrorCode for IndexResolverError {
|
|||||||
fn error_code(&self) -> Code {
|
fn error_code(&self) -> Code {
|
||||||
match self {
|
match self {
|
||||||
IndexResolverError::IndexError(e) => e.error_code(),
|
IndexResolverError::IndexError(e) => e.error_code(),
|
||||||
IndexResolverError::IndexAlreadyExists => Code::IndexAlreadyExists,
|
IndexResolverError::IndexAlreadyExists(_) => Code::IndexAlreadyExists,
|
||||||
IndexResolverError::UnexistingIndex(_) => Code::IndexNotFound,
|
IndexResolverError::UnexistingIndex(_) => Code::IndexNotFound,
|
||||||
IndexResolverError::ExistingPrimaryKey => Code::PrimaryKeyAlreadyPresent,
|
IndexResolverError::ExistingPrimaryKey => Code::PrimaryKeyAlreadyPresent,
|
||||||
IndexResolverError::Internal(_) => Code::Internal,
|
IndexResolverError::Internal(_) => Code::Internal,
|
||||||
|
IndexResolverError::UuidAlreadyExists(_) => Code::Internal,
|
||||||
IndexResolverError::Milli(e) => MilliError(e).error_code(),
|
IndexResolverError::Milli(e) => MilliError(e).error_code(),
|
||||||
IndexResolverError::BadlyFormatted(_) => Code::InvalidIndexUid,
|
IndexResolverError::BadlyFormatted(_) => Code::InvalidIndexUid,
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,7 @@ impl IndexStore for MapIndexStore {
|
|||||||
}
|
}
|
||||||
let path = self.path.join(format!("{}", uuid));
|
let path = self.path.join(format!("{}", uuid));
|
||||||
if path.exists() {
|
if path.exists() {
|
||||||
return Err(IndexResolverError::IndexAlreadyExists);
|
return Err(IndexResolverError::UuidAlreadyExists(uuid));
|
||||||
}
|
}
|
||||||
|
|
||||||
let index_size = self.index_size;
|
let index_size = self.index_size;
|
||||||
|
@ -100,7 +100,7 @@ impl HeedUuidStore {
|
|||||||
let mut txn = env.write_txn()?;
|
let mut txn = env.write_txn()?;
|
||||||
|
|
||||||
if db.get(&txn, &name)?.is_some() {
|
if db.get(&txn, &name)?.is_some() {
|
||||||
return Err(IndexResolverError::IndexAlreadyExists);
|
return Err(IndexResolverError::IndexAlreadyExists(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
db.put(&mut txn, &name, uuid.as_bytes())?;
|
db.put(&mut txn, &name, uuid.as_bytes())?;
|
||||||
|
@ -222,10 +222,11 @@ mod test {
|
|||||||
setup();
|
setup();
|
||||||
|
|
||||||
let mut uuid_store = MockUuidStore::new();
|
let mut uuid_store = MockUuidStore::new();
|
||||||
uuid_store
|
uuid_store.expect_snapshot().once().returning(move |_| {
|
||||||
.expect_snapshot()
|
Box::pin(err(IndexResolverError::IndexAlreadyExists(
|
||||||
.once()
|
"test".to_string(),
|
||||||
.returning(move |_| Box::pin(err(IndexResolverError::IndexAlreadyExists)));
|
)))
|
||||||
|
});
|
||||||
|
|
||||||
let mut index_store = MockIndexStore::new();
|
let mut index_store = MockIndexStore::new();
|
||||||
index_store.expect_get().never();
|
index_store.expect_get().never();
|
||||||
@ -264,9 +265,9 @@ mod test {
|
|||||||
let mut indexes = uuids.clone().into_iter().map(|uuid| {
|
let mut indexes = uuids.clone().into_iter().map(|uuid| {
|
||||||
let mocker = Mocker::default();
|
let mocker = Mocker::default();
|
||||||
// index returns random error
|
// index returns random error
|
||||||
mocker
|
mocker.when("snapshot").then(|_: &Path| -> IndexResult<()> {
|
||||||
.when("snapshot")
|
Err(IndexError::DocumentNotFound("1".to_string()))
|
||||||
.then(|_: &Path| -> IndexResult<()> { Err(IndexError::ExistingPrimaryKey) });
|
});
|
||||||
mocker.when("uuid").then(move |_: ()| uuid);
|
mocker.when("uuid").then(move |_: ()| uuid);
|
||||||
Index::faux(mocker)
|
Index::faux(mocker)
|
||||||
});
|
});
|
||||||
|
@ -14,7 +14,7 @@ pub type Result<T> = std::result::Result<T, UpdateLoopError>;
|
|||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
#[allow(clippy::large_enum_variant)]
|
#[allow(clippy::large_enum_variant)]
|
||||||
pub enum UpdateLoopError {
|
pub enum UpdateLoopError {
|
||||||
#[error("Update {0} not found.")]
|
#[error("Task `{0}` not found.")]
|
||||||
UnexistingUpdate(u64),
|
UnexistingUpdate(u64),
|
||||||
#[error("Internal error: {0}")]
|
#[error("Internal error: {0}")]
|
||||||
Internal(Box<dyn Error + Send + Sync + 'static>),
|
Internal(Box<dyn Error + Send + Sync + 'static>),
|
||||||
@ -24,9 +24,8 @@ pub enum UpdateLoopError {
|
|||||||
FatalUpdateStoreError,
|
FatalUpdateStoreError,
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
DocumentFormatError(#[from] DocumentFormatError),
|
DocumentFormatError(#[from] DocumentFormatError),
|
||||||
// TODO: The reference to actix has to go.
|
#[error("The provided payload reached the size limit.")]
|
||||||
#[error("{0}")]
|
PayloadTooLarge,
|
||||||
PayloadError(#[from] actix_web::error::PayloadError),
|
|
||||||
#[error("A {0} payload is missing.")]
|
#[error("A {0} payload is missing.")]
|
||||||
MissingPayload(DocumentAdditionFormat),
|
MissingPayload(DocumentAdditionFormat),
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
@ -48,6 +47,15 @@ impl From<tokio::sync::oneshot::error::RecvError> for UpdateLoopError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<actix_web::error::PayloadError> for UpdateLoopError {
|
||||||
|
fn from(other: actix_web::error::PayloadError) -> Self {
|
||||||
|
match other {
|
||||||
|
actix_web::error::PayloadError::Overflow => Self::PayloadTooLarge,
|
||||||
|
_ => Self::Internal(Box::new(other)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal_error!(
|
internal_error!(
|
||||||
UpdateLoopError: heed::Error,
|
UpdateLoopError: heed::Error,
|
||||||
std::io::Error,
|
std::io::Error,
|
||||||
@ -59,14 +67,11 @@ internal_error!(
|
|||||||
impl ErrorCode for UpdateLoopError {
|
impl ErrorCode for UpdateLoopError {
|
||||||
fn error_code(&self) -> Code {
|
fn error_code(&self) -> Code {
|
||||||
match self {
|
match self {
|
||||||
Self::UnexistingUpdate(_) => Code::NotFound,
|
Self::UnexistingUpdate(_) => Code::TaskNotFound,
|
||||||
Self::Internal(_) => Code::Internal,
|
Self::Internal(_) => Code::Internal,
|
||||||
Self::FatalUpdateStoreError => Code::Internal,
|
Self::FatalUpdateStoreError => Code::Internal,
|
||||||
Self::DocumentFormatError(error) => error.error_code(),
|
Self::DocumentFormatError(error) => error.error_code(),
|
||||||
Self::PayloadError(error) => match error {
|
Self::PayloadTooLarge => Code::PayloadTooLarge,
|
||||||
actix_web::error::PayloadError::Overflow => Code::PayloadTooLarge,
|
|
||||||
_ => Code::Internal,
|
|
||||||
},
|
|
||||||
Self::MissingPayload(_) => Code::MissingPayload,
|
Self::MissingPayload(_) => Code::MissingPayload,
|
||||||
Self::IndexError(e) => e.error_code(),
|
Self::IndexError(e) => e.error_code(),
|
||||||
}
|
}
|
||||||
|
@ -731,7 +731,7 @@ mod test {
|
|||||||
mocker
|
mocker
|
||||||
.when::<Processing, std::result::Result<Processed, Failed>>("handle_update")
|
.when::<Processing, std::result::Result<Processed, Failed>>("handle_update")
|
||||||
.once()
|
.once()
|
||||||
.then(|update| Err(update.fail(IndexError::ExistingPrimaryKey)));
|
.then(|update| Err(update.fail(IndexError::DocumentNotFound("1".to_string()))));
|
||||||
|
|
||||||
Box::pin(ok(Some(Index::faux(mocker))))
|
Box::pin(ok(Some(Index::faux(mocker))))
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user