Compare commits

..

12 Commits

Author SHA1 Message Date
Louis Dureuil
15bac3755a Fix analytics 2023-02-14 09:41:34 +01:00
Louis Dureuil
23c320262c Update tests 2023-02-14 08:57:32 +01:00
Louis Dureuil
cf45c4cca5 Use IndexUid type 2023-02-14 08:56:00 +01:00
Louis Dureuil
52b46e0d6c Wrap queries and results in JSON objects 2023-02-14 08:55:27 +01:00
Louis Dureuil
878c9f173a Add basic analytics 2023-02-10 15:59:20 +01:00
Louis Dureuil
837387f2d1 Add multi search tests 2023-02-10 15:59:20 +01:00
Louis Dureuil
10d4b0b4fa Add test server search method for multi search 2023-02-10 15:59:20 +01:00
Louis Dureuil
dbbd2ae91b Add search with an array of indexes 2023-02-10 15:59:20 +01:00
Louis Dureuil
d7d581636d Add note in code so one does not forget next time 2023-02-10 15:50:21 +01:00
Louis Dureuil
1a2d6da24f Change Dockerfile to also pass the VERGEN_GIT_SEMVER_LIGHTWEIGHT when building 2023-02-10 15:48:54 +01:00
Louis Dureuil
1cde517a13 Ignore -dirty flag 2023-02-09 18:54:18 +01:00
Louis Dureuil
e906b2c0be Update contributing.md 2023-02-09 18:54:18 +01:00
9 changed files with 125 additions and 32 deletions

View File

@@ -7,7 +7,8 @@ WORKDIR /meilisearch
ARG COMMIT_SHA
ARG COMMIT_DATE
ENV COMMIT_SHA=${COMMIT_SHA} COMMIT_DATE=${COMMIT_DATE}
ARG GIT_TAG
ENV COMMIT_SHA=${COMMIT_SHA} COMMIT_DATE=${COMMIT_DATE} VERGEN_GIT_SEMVER_LIGHTWEIGHT=${GIT_TAG}
ENV RUSTFLAGS="-C target-feature=-crt-static"
COPY . .

View File

@@ -92,6 +92,7 @@ jobs:
build-args: |
COMMIT_SHA=${{ github.sha }}
COMMIT_DATE=${{ steps.build-metadata.outputs.date }}
GIT_TAG=$(printf "%q" ${{ github.ref_name }})
# /!\ Don't touch this without checking with Cloud team
- name: Send CI information to Cloud team

View File

@@ -121,14 +121,13 @@ The full Meilisearch release process is described in [this guide](https://github
Depending on the developed feature, you might need to provide a prototyped version of Meilisearch to make it easier to test by the users.
The prototype name must follow this convention: `prototype-X-Y` where
- `X` is the feature name formatted in `kebab-case`. It should not end with a number segment.
- `X` is the feature name formatted in `kebab-case`. It should not end with a single number.
- `Y` is the version of the prototype, starting from `0`.
✅ Example: `prototype-auto-resize-0`.
❌ Bad example: `auto-resize-0`: lacks the `prototype` prefix.
❌ Bad example: `prototype-auto-resize`: lacks the version suffix.
❌ Bad example: `prototype-auto-resize-0-0`: feature name ends with a number segment.
✅ Example: `prototype-auto-resize-0`. </br>
❌ Bad example: `auto-resize-0`: lacks the `prototype` prefix. </br>
❌ Bad example: `prototype-auto-resize`: lacks the version suffix. </br>
❌ Bad example: `prototype-auto-resize-0-0`: feature name ends with a single number.
Steps to create a prototype:

View File

@@ -7,7 +7,8 @@ WORKDIR /meilisearch
ARG COMMIT_SHA
ARG COMMIT_DATE
ENV VERGEN_GIT_SHA=${COMMIT_SHA} VERGEN_GIT_COMMIT_TIMESTAMP=${COMMIT_DATE}
ARG GIT_TAG
ENV VERGEN_GIT_SHA=${COMMIT_SHA} VERGEN_GIT_COMMIT_TIMESTAMP=${COMMIT_DATE} VERGEN_GIT_SEMVER_LIGHTWEIGHT=${GIT_TAG}
ENV RUSTFLAGS="-C target-feature=-crt-static"
COPY . .

View File

@@ -1,6 +1,9 @@
use vergen::{vergen, Config, SemverKind};
fn main() {
// Note: any code that needs VERGEN_ environment variables should take care to define them manually in the Dockerfile and pass them
// in the corresponding GitHub workflow (publish_docker.yml).
// This is due to the Dockerfile building the binary outside of the git directory.
let mut config = Config::default();
// allow using non-annotated tags
*config.git_mut().semver_kind_mut() = SemverKind::Lightweight;

View File

@@ -755,7 +755,8 @@ impl MultiSearchAggregator {
let user_agents = extract_user_agents(request).into_iter().collect();
let distinct_indexes: HashSet<_> = query.iter().map(|query| &query.index_uid).collect();
let distinct_indexes: HashSet<_> =
query.iter().map(|query| query.index_uid.as_str()).collect();
Self {
timestamp,

View File

@@ -5,6 +5,7 @@ use log::debug;
use meilisearch_types::deserr::DeserrJsonError;
use meilisearch_types::error::ResponseError;
use meilisearch_types::keys::actions;
use serde::Serialize;
use crate::analytics::{Analytics, MultiSearchAggregator};
use crate::extractors::authentication::policies::ActionPolicy;
@@ -19,13 +20,24 @@ pub fn configure(cfg: &mut web::ServiceConfig) {
cfg.service(web::resource("").route(web::post().to(SeqHandler(search_with_post))));
}
#[derive(Serialize)]
struct SearchResults {
results: Vec<SearchResultWithIndex>,
}
#[derive(Debug, deserr::DeserializeFromValue)]
#[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields)]
pub struct SearchQueries {
queries: Vec<SearchQueryWithIndex>,
}
pub async fn search_with_post(
index_scheduler: GuardedData<ActionPolicy<{ actions::SEARCH }>, Data<IndexScheduler>>,
params: ValidatedJson<Vec<SearchQueryWithIndex>, DeserrJsonError>,
params: ValidatedJson<SearchQueries, DeserrJsonError>,
req: HttpRequest,
analytics: web::Data<dyn Analytics>,
) -> Result<HttpResponse, ResponseError> {
let queries = params.into_inner();
let queries = params.into_inner().queries;
let mut multi_aggregate = MultiSearchAggregator::from_queries(&queries, &req);
@@ -48,7 +60,10 @@ pub async fn search_with_post(
let search_result =
tokio::task::spawn_blocking(move || perform_search(&index, query)).await?;
search_results.push(SearchResultWithIndex { index_uid, result: search_result? });
search_results.push(SearchResultWithIndex {
index_uid: index_uid.into_inner(),
result: search_result?,
});
}
Ok(search_results)
}
@@ -64,5 +79,5 @@ pub async fn search_with_post(
debug!("returns: {:?}", search_results);
Ok(HttpResponse::Ok().json(search_results))
Ok(HttpResponse::Ok().json(SearchResults { results: search_results }))
}

View File

@@ -8,6 +8,7 @@ use either::Either;
use meilisearch_auth::IndexSearchRules;
use meilisearch_types::deserr::DeserrJsonError;
use meilisearch_types::error::deserr_codes::*;
use meilisearch_types::index_uid::IndexUid;
use meilisearch_types::settings::DEFAULT_PAGINATION_MAX_TOTAL_HITS;
use meilisearch_types::{milli, Document};
use milli::tokenizer::TokenizerBuilder;
@@ -82,7 +83,8 @@ impl SearchQuery {
#[derive(Debug, deserr::DeserializeFromValue)]
#[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields)]
pub struct SearchQueryWithIndex {
pub index_uid: String,
#[deserr(error = DeserrJsonError<InvalidIndexUid>, missing_field_error = DeserrJsonError::missing_index_uid)]
pub index_uid: IndexUid,
#[deserr(default, error = DeserrJsonError<InvalidSearchQ>)]
pub q: Option<String>,
#[deserr(default = DEFAULT_SEARCH_OFFSET(), error = DeserrJsonError<InvalidSearchOffset>)]
@@ -120,7 +122,7 @@ pub struct SearchQueryWithIndex {
}
impl SearchQueryWithIndex {
pub fn into_index_query(self) -> (String, SearchQuery) {
pub fn into_index_query(self) -> (IndexUid, SearchQuery) {
let SearchQueryWithIndex {
index_uid,
q,

View File

@@ -8,9 +8,13 @@ use crate::common::Server;
async fn search_empty_list() {
let server = Server::new().await;
let (response, code) = server.search(json!([])).await;
let (response, code) = server.search(json!({"queries": []})).await;
snapshot!(code, @"200 OK");
snapshot!(json_string!(response), @"[]");
snapshot!(json_string!(response), @r###"
{
"results": []
}
"###);
}
#[actix_rt::test]
@@ -21,7 +25,23 @@ async fn search_json_object() {
snapshot!(code, @"400 Bad Request");
snapshot!(json_string!(response), @r###"
{
"message": "Invalid value type: expected an array, but found an object: `{}`",
"message": "Missing field `queries`",
"code": "bad_request",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#bad_request"
}
"###);
}
#[actix_rt::test]
async fn search_json_array() {
let server = Server::new().await;
let (response, code) = server.search(json!([])).await;
snapshot!(code, @"400 Bad Request");
snapshot!(json_string!(response), @r###"
{
"message": "Invalid value type: expected an object, but found an array: `[]`",
"code": "bad_request",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#bad_request"
@@ -39,13 +59,13 @@ async fn simple_search_single_index() {
index.wait_task(0).await;
let (response, code) = server
.search(json!([
.search(json!({"queries": [
{"indexUid" : "test", "q": "glass"},
{"indexUid": "test", "q": "captain"},
]))
]}))
.await;
snapshot!(code, @"200 OK");
insta::assert_json_snapshot!(response, { "[].processingTimeMs" => "[time]" }, @r###"
insta::assert_json_snapshot!(response["results"], { "[].processingTimeMs" => "[time]" }, @r###"
[
{
"indexUid": "test",
@@ -79,6 +99,56 @@ async fn simple_search_single_index() {
"###);
}
#[actix_rt::test]
async fn simple_search_missing_index_uid() {
let server = Server::new().await;
let index = server.index("test");
let documents = DOCUMENTS.clone();
index.add_documents(documents, None).await;
index.wait_task(0).await;
let (response, code) = server
.search(json!({"queries": [
{"q": "glass"},
]}))
.await;
snapshot!(code, @"400 Bad Request");
insta::assert_json_snapshot!(response, @r###"
{
"message": "Missing field `indexUid` inside `.queries[0]`",
"code": "missing_index_uid",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#missing_index_uid"
}
"###);
}
#[actix_rt::test]
async fn simple_search_illegal_index_uid() {
let server = Server::new().await;
let index = server.index("test");
let documents = DOCUMENTS.clone();
index.add_documents(documents, None).await;
index.wait_task(0).await;
let (response, code) = server
.search(json!({"queries": [
{"indexUid": "", "q": "glass"},
]}))
.await;
snapshot!(code, @"400 Bad Request");
insta::assert_json_snapshot!(response, @r###"
{
"message": "Invalid value at `.queries[0].indexUid`: `hé` 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"
}
"###);
}
#[actix_rt::test]
async fn simple_search_two_indexes() {
let server = Server::new().await;
@@ -94,13 +164,13 @@ async fn simple_search_two_indexes() {
index.wait_task(1).await;
let (response, code) = server
.search(json!([
.search(json!({"queries": [
{"indexUid" : "test", "q": "glass"},
{"indexUid": "nested", "q": "pesti"},
]))
]}))
.await;
snapshot!(code, @"200 OK");
insta::assert_json_snapshot!(response, { "[].processingTimeMs" => "[time]" }, @r###"
insta::assert_json_snapshot!(response["results"], { "[].processingTimeMs" => "[time]" }, @r###"
[
{
"indexUid": "test",
@@ -171,10 +241,10 @@ async fn search_one_index_doesnt_exist() {
index.wait_task(0).await;
let (response, code) = server
.search(json!([
.search(json!({"queries": [
{"indexUid" : "test", "q": "glass"},
{"indexUid": "nested", "q": "pesti"},
]))
]}))
.await;
snapshot!(code, @"404 Not Found");
snapshot!(json_string!(response), @r###"
@@ -192,10 +262,10 @@ async fn search_multiple_indexes_dont_exist() {
let server = Server::new().await;
let (response, code) = server
.search(json!([
.search(json!({"queries": [
{"indexUid" : "test", "q": "glass"},
{"indexUid": "nested", "q": "pesti"},
]))
]}))
.await;
snapshot!(code, @"404 Not Found");
snapshot!(json_string!(response), @r###"
@@ -224,10 +294,10 @@ async fn search_one_query_error() {
index.wait_task(1).await;
let (response, code) = server
.search(json!([
.search(json!({"queries": [
{"indexUid" : "test", "q": "glass", "facets": ["title"]},
{"indexUid": "nested", "q": "pesti"},
]))
]}))
.await;
snapshot!(code, @"400 Bad Request");
snapshot!(json_string!(response), @r###"
@@ -256,10 +326,10 @@ async fn search_multiple_query_errors() {
index.wait_task(1).await;
let (response, code) = server
.search(json!([
.search(json!({"queries": [
{"indexUid" : "test", "q": "glass", "facets": ["title"]},
{"indexUid": "nested", "q": "pesti", "facets": ["doggos"]},
]))
]}))
.await;
snapshot!(code, @"400 Bad Request");
snapshot!(json_string!(response), @r###"