implement all the /indexes/documents route

This commit is contained in:
Tamo 2024-12-19 15:54:10 +01:00
parent 13afdaf393
commit 78f6f22a80
No known key found for this signature in database
GPG Key ID: 20CD8020AFA88D69
5 changed files with 213 additions and 13 deletions

View File

@ -6,6 +6,7 @@ use std::str::FromStr;
use deserr::{DeserializeError, Deserr, MergeWithError, ValueKind};
use serde::de::Visitor;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use utoipa::{IntoParams, PartialSchema, ToSchema};
use crate::deserr::query_params::FromQueryParameter;
@ -229,7 +230,7 @@ pub enum OptionStarOrList<T> {
List(Vec<T>),
}
impl<T> OptionStarOrList<T> {
impl<T: PartialSchema> OptionStarOrList<T> {
pub fn is_some(&self) -> bool {
match self {
Self::None => false,

View File

@ -74,7 +74,7 @@ async fn get_batches(
let next = if results.len() == limit as usize { results.pop().map(|t| t.uid) } else { None };
let from = results.first().map(|t| t.uid);
let tasks = AllBatches { results, limit: limit.saturating_sub(1) as u32, total, from, next };
let tasks = AllBatches { results, limit: limit.saturating_sub(1), total, from, next };
Ok(HttpResponse::Ok().json(tasks))
}

View File

@ -74,7 +74,7 @@ pub struct DocumentParam {
#[derive(OpenApi)]
#[openapi(
paths(get_documents, replace_documents, update_documents, clear_all_documents, delete_documents_batch),
paths(get_document, get_documents, delete_document, replace_documents, update_documents, clear_all_documents, delete_documents_batch, delete_documents_by_filter, edit_documents_by_function, documents_by_query_post),
tags(
(
name = "Documents",
@ -107,12 +107,14 @@ pub fn configure(cfg: &mut web::ServiceConfig) {
);
}
#[derive(Debug, Deserr)]
#[derive(Debug, Deserr, IntoParams)]
#[deserr(error = DeserrQueryParamError, rename_all = camelCase, deny_unknown_fields)]
pub struct GetDocument {
#[deserr(default, error = DeserrQueryParamError<InvalidDocumentFields>)]
#[param(value_type = Option<Vec<String>>)]
fields: OptionStarOrList<String>,
#[deserr(default, error = DeserrQueryParamError<InvalidDocumentRetrieveVectors>)]
#[param(value_type = Option<bool>)]
retrieve_vectors: Param<bool>,
}
@ -188,6 +190,56 @@ impl<Method: AggregateMethod> Aggregate for DocumentsFetchAggregator<Method> {
}
}
/// Get one document
///
/// Get one document from its primary key.
#[utoipa::path(
get,
path = "/{indexUid}/documents/{documentId}",
tags = ["Indexes", "Documents"],
security(("Bearer" = ["documents.get", "documents.*", "*"])),
params(
("indexUid" = String, Path, example = "movies", description = "Index Unique Identifier", nullable = false),
("documentId" = String, Path, example = "85087", description = "The document identifier", nullable = false),
GetDocument,
),
responses(
(status = 200, description = "The documents are returned", body = serde_json::Value, content_type = "application/json", example = json!(
{
"id": 25684,
"title": "American Ninja 5",
"poster": "https://image.tmdb.org/t/p/w1280/iuAQVI4mvjI83wnirpD8GVNRVuY.jpg",
"overview": "When a scientists daughter is kidnapped, American Ninja, attempts to find her, but this time he teams up with a youngster he has trained in the ways of the ninja.",
"release_date": 725846400
}
)),
(status = 404, description = "Index not found", body = ResponseError, content_type = "application/json", example = json!(
{
"message": "Index `movies` not found.",
"code": "index_not_found",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#index_not_found"
}
)),
(status = 404, description = "Document not found", body = ResponseError, content_type = "application/json", example = json!(
{
"message": "Document `a` not found.",
"code": "document_not_found",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#document_not_found"
}
)),
(status = 401, description = "The authorization header is missing", body = ResponseError, content_type = "application/json", example = json!(
{
"message": "The Authorization header is missing. It must use the bearer authorization method.",
"code": "missing_authorization_header",
"type": "auth",
"link": "https://docs.meilisearch.com/errors#missing_authorization_header"
}
)),
)
)]
pub async fn get_document(
index_scheduler: GuardedData<ActionPolicy<{ actions::DOCUMENTS_GET }>, Data<IndexScheduler>>,
document_param: web::Path<DocumentParam>,
@ -251,6 +303,39 @@ impl Aggregate for DocumentsDeletionAggregator {
}
}
/// Delete a document
///
/// Delete a single document by id.
#[utoipa::path(
delete,
path = "/{indexUid}/documents/{documentsId}",
tags = ["Indexes", "Documents"],
security(("Bearer" = ["documents.delete", "documents.*", "*"])),
params(
("indexUid" = String, Path, example = "movies", description = "Index Unique Identifier", nullable = false),
("documentsId" = String, Path, example = "movies", description = "Document Identifier", nullable = false),
),
responses(
(status = 200, description = "Task successfully enqueued", body = SummarizedTaskView, content_type = "application/json", example = json!(
{
"taskUid": 147,
"indexUid": null,
"status": "enqueued",
"type": "documentAdditionOrUpdate",
"enqueuedAt": "2024-08-08T17:05:55.791772Z"
}
)),
(status = 401, description = "The authorization header is missing", body = ResponseError, content_type = "application/json", example = json!(
{
"message": "The Authorization header is missing. It must use the bearer authorization method.",
"code": "missing_authorization_header",
"type": "auth",
"link": "https://docs.meilisearch.com/errors#missing_authorization_header"
}
)),
)
)]
pub async fn delete_document(
index_scheduler: GuardedData<ActionPolicy<{ actions::DOCUMENTS_DELETE }>, Data<IndexScheduler>>,
path: web::Path<DocumentParam>,
@ -321,6 +406,58 @@ pub struct BrowseQuery {
filter: Option<Value>,
}
/// Get documents with POST
///
/// Get a set of documents.
#[utoipa::path(
post,
path = "/{indexUid}/documents/fetch",
tags = ["Indexes", "Documents"],
security(("Bearer" = ["documents.delete", "documents.*", "*"])),
params(
("indexUid", example = "movies", description = "Index Unique Identifier", nullable = false),
BrowseQuery,
),
responses(
(status = 200, description = "Task successfully enqueued", body = PaginationView<serde_json::Value>, content_type = "application/json", example = json!(
{
"results":[
{
"title":"The Travels of Ibn Battuta",
"genres":[
"Travel",
"Adventure"
],
"language":"English",
"rating":4.5
},
{
"title":"Pride and Prejudice",
"genres":[
"Classics",
"Fiction",
"Romance",
"Literature"
],
"language":"English",
"rating":4
},
],
"offset":0,
"limit":2,
"total":5
}
)),
(status = 401, description = "The authorization header is missing", body = ResponseError, content_type = "application/json", example = json!(
{
"message": "The Authorization header is missing. It must use the bearer authorization method.",
"code": "missing_authorization_header",
"type": "auth",
"link": "https://docs.meilisearch.com/errors#missing_authorization_header"
}
)),
)
)]
pub async fn documents_by_query_post(
index_scheduler: GuardedData<ActionPolicy<{ actions::DOCUMENTS_GET }>, Data<IndexScheduler>>,
index_uid: web::Path<String>,
@ -356,12 +493,10 @@ pub async fn documents_by_query_post(
security(("Bearer" = ["documents.get", "documents.*", "*"])),
params(
("indexUid", example = "movies", description = "Index Unique Identifier", nullable = false),
// Here we can use the post version of the browse query since it contains the exact same parameter
BrowseQuery
),
responses(
// body = PaginationView<Document>
(status = 200, description = "The documents are returned", body = serde_json::Value, content_type = "application/json", example = json!(
(status = 200, description = "The documents are returned", body = PaginationView<serde_json::Value>, content_type = "application/json", example = json!(
{
"results": [
{
@ -922,8 +1057,8 @@ async fn copy_body_to_file(
security(("Bearer" = ["documents.delete", "documents.*", "*"])),
params(
("indexUid", example = "movies", description = "Index Unique Identifier", nullable = false),
// TODO: how to task an array of strings in parameter
),
// TODO: how to return an array of strings
responses(
(status = 200, description = "Task successfully enqueued", body = SummarizedTaskView, content_type = "application/json", example = json!(
{
@ -983,13 +1118,45 @@ pub async fn delete_documents_batch(
Ok(HttpResponse::Accepted().json(task))
}
#[derive(Debug, Deserr)]
#[derive(Debug, Deserr, IntoParams)]
#[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields)]
pub struct DocumentDeletionByFilter {
#[deserr(error = DeserrJsonError<InvalidDocumentFilter>, missing_field_error = DeserrJsonError::missing_document_filter)]
filter: Value,
}
/// Delete documents by filter
///
/// Delete a set of documents based on a filter.
#[utoipa::path(
post,
path = "/{indexUid}/documents/delete",
tags = ["Indexes", "Documents"],
security(("Bearer" = ["documents.delete", "documents.*", "*"])),
params(
("indexUid", example = "movies", description = "Index Unique Identifier", nullable = false),
DocumentDeletionByFilter,
),
responses(
(status = 202, description = "Task successfully enqueued", body = SummarizedTaskView, content_type = "application/json", example = json!(
{
"taskUid": 147,
"indexUid": null,
"status": "enqueued",
"type": "documentDeletion",
"enqueuedAt": "2024-08-08T17:05:55.791772Z"
}
)),
(status = 401, description = "The authorization header is missing", body = ResponseError, content_type = "application/json", example = json!(
{
"message": "The Authorization header is missing. It must use the bearer authorization method.",
"code": "missing_authorization_header",
"type": "auth",
"link": "https://docs.meilisearch.com/errors#missing_authorization_header"
}
)),
)
)]
pub async fn delete_documents_by_filter(
index_scheduler: GuardedData<ActionPolicy<{ actions::DOCUMENTS_DELETE }>, Data<IndexScheduler>>,
index_uid: web::Path<String>,
@ -1030,7 +1197,7 @@ pub async fn delete_documents_by_filter(
Ok(HttpResponse::Accepted().json(task))
}
#[derive(Debug, Deserr)]
#[derive(Debug, Deserr, IntoParams)]
#[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields)]
pub struct DocumentEditionByFunction {
#[deserr(default, error = DeserrJsonError<InvalidDocumentFilter>)]
@ -1069,6 +1236,38 @@ impl Aggregate for EditDocumentsByFunctionAggregator {
}
}
/// Edit documents by function.
///
/// Use a [RHAI function](https://rhai.rs/book/engine/hello-world.html) to edit one or more documents directly in Meilisearch.
#[utoipa::path(
post,
path = "/{indexUid}/documents/edit",
tags = ["Indexes", "Documents"],
security(("Bearer" = ["documents.*", "*"])),
params(
("indexUid", example = "movies", description = "Index Unique Identifier", nullable = false),
DocumentEditionByFunction,
),
responses(
(status = 202, description = "Task successfully enqueued", body = SummarizedTaskView, content_type = "application/json", example = json!(
{
"taskUid": 147,
"indexUid": null,
"status": "enqueued",
"type": "documentDeletion",
"enqueuedAt": "2024-08-08T17:05:55.791772Z"
}
)),
(status = 401, description = "The authorization header is missing", body = ResponseError, content_type = "application/json", example = json!(
{
"message": "The Authorization header is missing. It must use the bearer authorization method.",
"code": "missing_authorization_header",
"type": "auth",
"link": "https://docs.meilisearch.com/errors#missing_authorization_header"
}
)),
)
)]
pub async fn edit_documents_by_function(
index_scheduler: GuardedData<ActionPolicy<{ actions::DOCUMENTS_ALL }>, Data<IndexScheduler>>,
index_uid: web::Path<String>,

View File

@ -66,7 +66,7 @@ pub mod tasks;
(name = "Stats", description = "Stats gives extended information and metrics about indexes and the Meilisearch database."),
),
modifiers(&OpenApiAuth),
components(schemas(BrowseQuery, UpdateIndexRequest, IndexUid, IndexCreateRequest, KeyView, Action, CreateApiKey, UpdateStderrLogs, LogMode, GetLogs, IndexStats, Stats, HealthStatus, HealthResponse, VersionResponse, Code, ErrorType, AllTasks, TaskView, Status, DetailsView, ResponseError, Settings<Unchecked>, Settings<Checked>, TypoSettings, MinWordSizeTyposSetting, FacetingSettings, PaginationSettings, SummarizedTaskView, Kind))
components(schemas(PaginationView<serde_json::Value>, BrowseQuery, UpdateIndexRequest, IndexUid, IndexCreateRequest, KeyView, Action, CreateApiKey, UpdateStderrLogs, LogMode, GetLogs, IndexStats, Stats, HealthStatus, HealthResponse, VersionResponse, Code, ErrorType, AllTasks, TaskView, Status, DetailsView, ResponseError, Settings<Unchecked>, Settings<Checked>, TypoSettings, MinWordSizeTyposSetting, FacetingSettings, PaginationSettings, SummarizedTaskView, Kind))
)]
pub struct MeilisearchApi;
@ -177,7 +177,7 @@ pub struct Pagination {
pub limit: usize,
}
#[derive(Debug, Clone, Serialize)]
#[derive(Debug, Clone, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct PaginationView<T> {
pub results: Vec<T>,

View File

@ -67,7 +67,7 @@ pub struct TasksFilterQuery {
/// Permits to filter tasks by their batch uid. By default, when the `batchUids` query parameter is not set, all task uids are returned. It's possible to specify several batch uids by separating them with the `,` character.
#[deserr(default, error = DeserrQueryParamError<InvalidBatchUids>)]
#[param(required = false, value_type = Option<BatchId>, example = 12421)]
#[param(required = false, value_type = Option<u32>, example = 12421)]
pub batch_uids: OptionStarOrList<BatchId>,
/// Permits to filter tasks by their uid. By default, when the uids query parameter is not set, all task uids are returned. It's possible to specify several uids by separating them with the `,` character.