diff --git a/meilisearch-types/src/index_uid.rs b/meilisearch-types/src/index_uid.rs index 03a31a82f..4bf126794 100644 --- a/meilisearch-types/src/index_uid.rs +++ b/meilisearch-types/src/index_uid.rs @@ -4,13 +4,15 @@ use std::fmt; use std::str::FromStr; use deserr::Deserr; +use utoipa::ToSchema; use crate::error::{Code, ErrorCode}; /// An index uid is composed of only ascii alphanumeric characters, - and _, between 1 and 400 /// bytes long -#[derive(Debug, Clone, PartialEq, Eq, Deserr, PartialOrd, Ord)] +#[derive(Debug, Clone, PartialEq, Eq, Deserr, PartialOrd, Ord, ToSchema)] #[deserr(try_from(String) = IndexUid::try_from -> IndexUidFormatError)] +#[schema(value_type = String, example = "movies")] pub struct IndexUid(String); impl IndexUid { diff --git a/meilisearch/src/routes/indexes/documents.rs b/meilisearch/src/routes/indexes/documents.rs index 85cf33c54..ff0b07c9c 100644 --- a/meilisearch/src/routes/indexes/documents.rs +++ b/meilisearch/src/routes/indexes/documents.rs @@ -29,6 +29,7 @@ use tempfile::tempfile; use tokio::fs::File; use tokio::io::{AsyncSeekExt, AsyncWriteExt, BufWriter}; use tracing::debug; +use utoipa::{IntoParams, OpenApi, ToSchema}; use crate::analytics::{Analytics, DocumentDeletionKind, DocumentFetchKind}; use crate::error::MeilisearchHttpError; @@ -69,6 +70,19 @@ pub struct DocumentParam { document_id: String, } +#[derive(OpenApi)] +#[openapi( + paths(get_documents, replace_documents, update_documents, clear_all_documents, delete_documents_batch), + tags( + ( + name = "Documents", + description = "Documents are objects composed of fields that can store any type of data. Each field contains an attribute and its associated value. Documents are stored inside [indexes](https://www.meilisearch.com/docs/learn/getting_started/indexes).", + external_docs(url = "https://www.meilisearch.com/docs/learn/getting_started/documents"), + ), + ), +)] +pub struct DocumentsApi; + pub fn configure(cfg: &mut web::ServiceConfig) { cfg.service( web::resource("") @@ -170,17 +184,23 @@ pub struct BrowseQueryGet { filter: Option, } -#[derive(Debug, Deserr)] +#[derive(Debug, Deserr, IntoParams, ToSchema)] #[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields)] +#[schema(rename_all = "camelCase")] pub struct BrowseQuery { + #[schema(default, example = 150)] #[deserr(default, error = DeserrJsonError)] offset: usize, + #[schema(default = 20, example = 1)] #[deserr(default = PAGINATION_DEFAULT_LIMIT, error = DeserrJsonError)] limit: usize, + #[schema(example = json!(["title, description"]))] #[deserr(default, error = DeserrJsonError)] fields: Option>, + #[schema(default, example = true)] #[deserr(default, error = DeserrJsonError)] retrieve_vectors: bool, + #[schema(default, example = "popularity > 1000")] #[deserr(default, error = DeserrJsonError)] filter: Option, } @@ -208,6 +228,62 @@ pub async fn documents_by_query_post( documents_by_query(&index_scheduler, index_uid, body) } +/// Get documents +/// +/// Get documents by batches. +#[utoipa::path( + get, + path = "/{indexUid}/documents", + tags = ["Indexes", "Documents"], + 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 + (status = 200, description = "The documents are returned", body = serde_json::Value, content_type = "application/json", example = json!( + { + "results": [ + { + "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 + }, + { + "id": 45881, + "title": "The Bridge of San Luis Rey", + "poster": "https://image.tmdb.org/t/p/w500/4X7quIcdkc24Cveg5XdpfRqxtYA.jpg", + "overview": "The Bridge of San Luis Rey is American author Thornton Wilder's second novel, first published in 1927 to worldwide acclaim. It tells the story of several interrelated people who die in the collapse of an Inca rope-fiber suspension bridge in Peru, and the events that lead up to their being on the bridge.[ A friar who has witnessed the tragic accident then goes about inquiring into the lives of the victims, seeking some sort of cosmic answer to the question of why each had to die. The novel won the Pulitzer Prize in 1928.", + "release_date": 1072915200 + } + ], + "limit": 20, + "offset": 0, + "total": 2 + } + )), + (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 = 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_documents( index_scheduler: GuardedData, Data>, index_uid: web::Path, @@ -276,11 +352,17 @@ fn documents_by_query( Ok(HttpResponse::Ok().json(ret)) } -#[derive(Deserialize, Debug, Deserr)] +#[derive(Deserialize, Debug, Deserr, IntoParams)] #[deserr(error = DeserrQueryParamError, rename_all = camelCase, deny_unknown_fields)] +#[into_params(rename_all = "camelCase")] pub struct UpdateDocumentsQuery { + /// The primary key of the documents. primaryKey is optional. If you want to set the primary key of your index through this route, + /// it only has to be done the first time you add documents to the index. After which it will be ignored if given. + #[param(example = "id")] #[deserr(default, error = DeserrQueryParamError)] pub primary_key: Option, + /// Customize the csv delimiter when importing CSV documents. + #[param(value_type = char, default = ",", example = ";")] #[deserr(default, try_from(char) = from_char_csv_delimiter -> DeserrQueryParamError, error = DeserrQueryParamError)] pub csv_delimiter: Option, } @@ -298,6 +380,51 @@ fn from_char_csv_delimiter( } } +/// Add or replace documents +/// +/// Add a list of documents or replace them if they already exist. +/// +/// If you send an already existing document (same id) the whole existing document will be overwritten by the new document. Fields previously in the document not present in the new document are removed. +/// +/// For a partial update of the document see Add or update documents route. +/// > info +/// > If the provided index does not exist, it will be created. +/// > info +/// > Use the reserved `_geo` object to add geo coordinates to a document. `_geo` is an object made of `lat` and `lng` field. +/// > +/// > When the vectorStore feature is enabled you can use the reserved `_vectors` field in your documents. +/// > It can accept an array of floats, multiple arrays of floats in an outer array or an object. +/// > This object accepts keys corresponding to the different embedders defined your index settings. +#[utoipa::path( + post, + path = "/{indexUid}/documents", + tags = ["Indexes", "Documents"], + security(("Bearer" = ["documents.add", "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 + UpdateDocumentsQuery, + ), + 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 replace_documents( index_scheduler: GuardedData, Data>, index_uid: web::Path, @@ -339,6 +466,49 @@ pub async fn replace_documents( Ok(HttpResponse::Accepted().json(task)) } +/// Add or update documents +/// +/// Add a list of documents or update them if they already exist. +/// If you send an already existing document (same id) the old document will be only partially updated according to the fields of the new document. Thus, any fields not present in the new document are kept and remained unchanged. +/// To completely overwrite a document, see Add or replace documents route. +/// > info +/// > If the provided index does not exist, it will be created. +/// > info +/// > Use the reserved `_geo` object to add geo coordinates to a document. `_geo` is an object made of `lat` and `lng` field. +/// > +/// > When the vectorStore feature is enabled you can use the reserved `_vectors` field in your documents. +/// > It can accept an array of floats, multiple arrays of floats in an outer array or an object. +/// > This object accepts keys corresponding to the different embedders defined your index settings. +#[utoipa::path( + put, + path = "/{indexUid}/documents", + tags = ["Indexes", "Documents"], + security(("Bearer" = ["documents.add", "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 + UpdateDocumentsQuery, + ), + 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 update_documents( index_scheduler: GuardedData, Data>, index_uid: web::Path, @@ -518,6 +688,38 @@ async fn document_addition( Ok(task.into()) } +/// Delete documents +/// +/// Delete a selection of documents based on array of document id's. +#[utoipa::path( + delete, + path = "/{indexUid}/documents", + tags = ["Indexes", "Documents"], + security(("Bearer" = ["documents.delete", "documents.*", "*"])), + params( + ("indexUid", example = "movies", description = "Index Unique Identifier", nullable = false), + ), + // TODO: how to return an array of strings + 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_documents_batch( index_scheduler: GuardedData, Data>, index_uid: web::Path, @@ -665,6 +867,38 @@ pub async fn edit_documents_by_function( Ok(HttpResponse::Accepted().json(task)) } +/// Delete all documents +/// +/// Delete all documents in the specified index. +#[utoipa::path( + delete, + path = "/{indexUid}/documents", + tags = ["Indexes", "Documents"], + security(("Bearer" = ["documents.delete", "documents.*", "*"])), + params( + ("indexUid", example = "movies", description = "Index Unique Identifier", nullable = false), + UpdateDocumentsQuery, + ), + responses( + (status = 200, 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 clear_all_documents( index_scheduler: GuardedData, Data>, index_uid: web::Path, diff --git a/meilisearch/src/routes/indexes/mod.rs b/meilisearch/src/routes/indexes/mod.rs index b7a18ef31..e68e81e47 100644 --- a/meilisearch/src/routes/indexes/mod.rs +++ b/meilisearch/src/routes/indexes/mod.rs @@ -16,7 +16,7 @@ use serde::Serialize; use serde_json::json; use time::OffsetDateTime; use tracing::debug; -use utoipa::ToSchema; +use utoipa::{IntoParams, OpenApi, ToSchema}; use super::{get_task_id, Pagination, SummarizedTaskView, PAGINATION_DEFAULT_LIMIT}; use crate::analytics::Analytics; @@ -32,6 +32,22 @@ pub mod search; pub mod settings; pub mod similar; +#[derive(OpenApi)] +#[openapi( + nest( + (path = "/", api = documents::DocumentsApi), + ), + paths(list_indexes, create_index, get_index, update_index, delete_index, get_index_stats), + tags( + ( + name = "Indexes", + description = "An index is an entity that gathers a set of [documents](https://www.meilisearch.com/docs/learn/getting_started/documents) with its own [settings](https://www.meilisearch.com/docs/reference/api/settings). Learn more about indexes.", + external_docs(url = "https://www.meilisearch.com/docs/reference/api/indexes"), + ), + ), +)] +pub struct IndexesApi; + pub fn configure(cfg: &mut web::ServiceConfig) { cfg.service( web::resource("") @@ -55,14 +71,18 @@ pub fn configure(cfg: &mut web::ServiceConfig) { ); } -#[derive(Debug, Serialize, Clone)] +#[derive(Debug, Serialize, Clone, ToSchema)] #[serde(rename_all = "camelCase")] pub struct IndexView { + /// Unique identifier for the index pub uid: String, + /// An `RFC 3339` format for date/time/duration. #[serde(with = "time::serde::rfc3339")] pub created_at: OffsetDateTime, + /// An `RFC 3339` format for date/time/duration. #[serde(with = "time::serde::rfc3339")] pub updated_at: OffsetDateTime, + /// Custom primaryKey for documents pub primary_key: Option, } @@ -80,20 +100,61 @@ impl IndexView { } } -#[derive(Deserr, Debug, Clone, Copy)] +#[derive(Deserr, Debug, Clone, Copy, IntoParams)] #[deserr(error = DeserrQueryParamError, rename_all = camelCase, deny_unknown_fields)] +#[into_params(rename_all = "camelCase")] pub struct ListIndexes { + /// The number of indexes to skip before starting to retrieve anything + #[param(value_type = Option, default, example = 100)] #[deserr(default, error = DeserrQueryParamError)] pub offset: Param, + /// The number of indexes to retrieve + #[param(value_type = Option, default = 20, example = 1)] #[deserr(default = Param(PAGINATION_DEFAULT_LIMIT), error = DeserrQueryParamError)] pub limit: Param, } + impl ListIndexes { fn as_pagination(self) -> Pagination { Pagination { offset: self.offset.0, limit: self.limit.0 } } } +/// List indexes +/// +/// List all indexes. +#[utoipa::path( + get, + path = "/", + tag = "Indexes", + security(("Bearer" = ["indexes.get", "indexes.*", "*"])), + params(ListIndexes), + responses( + (status = 200, description = "Indexes are returned", body = serde_json::Value, content_type = "application/json", example = json!( + { + "results": [ + { + "uid": "movies", + "primaryKey": "movie_id", + "createdAt": "2019-11-20T09:40:33.711324Z", + "updatedAt": "2019-11-20T09:40:33.711324Z" + } + ], + "limit": 1, + "offset": 0, + "total": 1 + } + )), + (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 list_indexes( index_scheduler: GuardedData, Data>, paginate: AwebQueryParameter, @@ -115,15 +176,49 @@ pub async fn list_indexes( Ok(HttpResponse::Ok().json(ret)) } -#[derive(Deserr, Debug)] +#[derive(Deserr, Debug, ToSchema)] #[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields)] +#[schema(rename_all = "camelCase")] pub struct IndexCreateRequest { + /// The name of the index + #[schema(example = "movies")] #[deserr(error = DeserrJsonError, missing_field_error = DeserrJsonError::missing_index_uid)] uid: IndexUid, + /// The primary key of the index + #[schema(example = "id")] #[deserr(default, error = DeserrJsonError)] primary_key: Option, } +/// Create index +/// +/// Create an index. +#[utoipa::path( + post, + path = "/", + tag = "Indexes", + security(("Bearer" = ["indexes.create", "indexes.*", "*"])), + request_body = IndexCreateRequest, + responses( + (status = 200, description = "Task successfully enqueued", body = SummarizedTaskView, content_type = "application/json", example = json!( + { + "taskUid": 147, + "indexUid": "movies", + "status": "enqueued", + "type": "indexCreation", + "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 create_index( index_scheduler: GuardedData, Data>, body: AwebJson, @@ -174,13 +269,42 @@ fn deny_immutable_fields_index( } } -#[derive(Deserr, Debug)] -#[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields = deny_immutable_fields_index)] -pub struct UpdateIndexRequest { - #[deserr(default, error = DeserrJsonError)] - primary_key: Option, -} - +/// Get index +/// +/// Get information about an index. +#[utoipa::path( + get, + path = "/{indexUid}", + tag = "Indexes", + security(("Bearer" = ["indexes.get", "indexes.*", "*"])), + params(("indexUid", example = "movies", description = "Index Unique Identifier", nullable = false)), + responses( + (status = 200, description = "The index is returned", body = IndexView, content_type = "application/json", example = json!( + { + "uid": "movies", + "primaryKey": "movie_id", + "createdAt": "2019-11-20T09:40:33.711324Z", + "updatedAt": "2019-11-20T09:40:33.711324Z" + } + )), + (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 = 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_index( index_scheduler: GuardedData, Data>, index_uid: web::Path, @@ -195,6 +319,46 @@ pub async fn get_index( Ok(HttpResponse::Ok().json(index_view)) } +#[derive(Deserr, Debug, ToSchema)] +#[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields = deny_immutable_fields_index)] +#[schema(rename_all = "camelCase")] +pub struct UpdateIndexRequest { + /// The new primary key of the index + #[deserr(default, error = DeserrJsonError)] + primary_key: Option, +} + +/// Update index +/// +/// Update the `primaryKey` of an index. +/// Return an error if the index doesn't exists yet or if it contains documents. +#[utoipa::path( + patch, + path = "/{indexUid}", + tag = "Indexes", + security(("Bearer" = ["indexes.update", "indexes.*", "*"])), + params(("indexUid", example = "movies", description = "Index Unique Identifier", nullable = false)), + request_body = UpdateIndexRequest, + responses( + (status = ACCEPTED, description = "Task successfully enqueued", body = SummarizedTaskView, content_type = "application/json", example = json!( + { + "taskUid": 0, + "indexUid": "movies", + "status": "enqueued", + "type": "indexUpdate", + "enqueuedAt": "2021-01-01T09:39:00.000000Z" + } + )), + (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 update_index( index_scheduler: GuardedData, Data>, index_uid: web::Path, @@ -228,6 +392,35 @@ pub async fn update_index( Ok(HttpResponse::Accepted().json(task)) } +/// Delete index +/// +/// Delete an index. +#[utoipa::path( + delete, + path = "/{indexUid}", + tag = "Indexes", + security(("Bearer" = ["indexes.delete", "indexes.*", "*"])), + params(("indexUid", example = "movies", description = "Index Unique Identifier", nullable = false)), + responses( + (status = ACCEPTED, description = "Task successfully enqueued", body = SummarizedTaskView, content_type = "application/json", example = json!( + { + "taskUid": 0, + "indexUid": "movies", + "status": "enqueued", + "type": "indexDeletion", + "enqueuedAt": "2021-01-01T09:39:00.000000Z" + } + )), + (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_index( index_scheduler: GuardedData, Data>, index_uid: web::Path, @@ -270,6 +463,44 @@ impl From for IndexStats { } } +/// Get stats of index +/// +/// Get the stats of an index. +#[utoipa::path( + get, + path = "/{indexUid}/stats", + tags = ["Indexes", "Stats"], + security(("Bearer" = ["stats.get", "stats.*", "*"])), + params(("indexUid", example = "movies", description = "Index Unique Identifier", nullable = false)), + responses( + (status = OK, description = "The stats of the index", body = IndexStats, content_type = "application/json", example = json!( + { + "numberOfDocuments": 10, + "isIndexing": true, + "fieldDistribution": { + "genre": 10, + "author": 9 + } + } + )), + (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 = 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_index_stats( index_scheduler: GuardedData, Data>, index_uid: web::Path, diff --git a/meilisearch/src/routes/mod.rs b/meilisearch/src/routes/mod.rs index cb9d159df..993c3a781 100644 --- a/meilisearch/src/routes/mod.rs +++ b/meilisearch/src/routes/mod.rs @@ -8,30 +8,26 @@ use actix_web::web::Data; use actix_web::{web, HttpRequest, HttpResponse}; use index_scheduler::IndexScheduler; use meilisearch_auth::AuthController; -use meilisearch_types::error::ErrorType; -use meilisearch_types::error::{Code, ResponseError}; +use meilisearch_types::error::{Code, ErrorType, ResponseError}; +use meilisearch_types::index_uid::IndexUid; use meilisearch_types::keys::CreateApiKey; -use meilisearch_types::settings::Checked; -use meilisearch_types::settings::FacetingSettings; -use meilisearch_types::settings::MinWordSizeTyposSetting; -use meilisearch_types::settings::PaginationSettings; -use meilisearch_types::settings::TypoSettings; -use meilisearch_types::settings::{Settings, Unchecked}; -use meilisearch_types::task_view::DetailsView; -use meilisearch_types::task_view::TaskView; +use meilisearch_types::settings::{ + Checked, FacetingSettings, MinWordSizeTyposSetting, PaginationSettings, Settings, TypoSettings, + Unchecked, +}; +use meilisearch_types::task_view::{DetailsView, TaskView}; use meilisearch_types::tasks::{Kind, Status, Task, TaskId}; use serde::{Deserialize, Serialize}; use time::OffsetDateTime; use tracing::debug; -use utoipa::OpenApi; -use utoipa::ToSchema; +use utoipa::{OpenApi, ToSchema}; use utoipa_rapidoc::RapiDoc; -use utoipa_redoc::Redoc; -use utoipa_redoc::Servable; +use utoipa_redoc::{Redoc, Servable}; use utoipa_scalar::{Scalar, Servable as ScalarServable}; use self::api_key::KeyView; -use self::indexes::IndexStats; +use self::indexes::documents::BrowseQuery; +use self::indexes::{IndexCreateRequest, IndexStats, UpdateIndexRequest}; use self::logs::GetLogs; use self::logs::LogMode; use self::logs::UpdateStderrLogs; @@ -56,6 +52,7 @@ pub mod tasks; #[openapi( nest( (path = "/tasks", api = tasks::TaskApi), + (path = "/indexes", api = indexes::IndexesApi), (path = "/snapshots", api = snapshot::SnapshotApi), (path = "/dumps", api = dump::DumpApi), (path = "/keys", api = api_key::ApiKeyApi), @@ -63,8 +60,11 @@ pub mod tasks; (path = "/logs", api = logs::LogsApi), ), paths(get_health, get_version, get_stats), + tags( + (name = "Stats", description = "Stats gives extended information and metrics about indexes and the Meilisearch database."), + ), modifiers(&OpenApiAuth), - components(schemas(KeyView, Action, CreateApiKey, UpdateStderrLogs, LogMode, GetLogs, IndexStats, Stats, HealthStatus, HealthResponse, VersionResponse, Code, ErrorType, AllTasks, TaskView, Status, DetailsView, ResponseError, Settings, Settings, TypoSettings, MinWordSizeTyposSetting, FacetingSettings, PaginationSettings, SummarizedTaskView, Kind)) + 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, Settings, TypoSettings, MinWordSizeTyposSetting, FacetingSettings, PaginationSettings, SummarizedTaskView, Kind)) )] pub struct MeilisearchApi; @@ -77,14 +77,14 @@ pub fn configure(cfg: &mut web::ServiceConfig) { .service(Redoc::with_url("/redoc", openapi)) .service(web::resource("/health").route(web::get().to(get_health))) // done .service(web::scope("/logs").configure(logs::configure)) // done - .service(web::scope("/keys").configure(api_key::configure)) + .service(web::scope("/keys").configure(api_key::configure)) // done .service(web::scope("/dumps").configure(dump::configure)) // done .service(web::scope("/snapshots").configure(snapshot::configure)) // done .service(web::resource("/stats").route(web::get().to(get_stats))) // done .service(web::resource("/version").route(web::get().to(get_version))) // done - .service(web::scope("/indexes").configure(indexes::configure)) - .service(web::scope("/multi-search").configure(multi_search::configure)) - .service(web::scope("/swap-indexes").configure(swap_indexes::configure)) + .service(web::scope("/indexes").configure(indexes::configure)) // WIP + .service(web::scope("/multi-search").configure(multi_search::configure)) // TODO + .service(web::scope("/swap-indexes").configure(swap_indexes::configure)) // TODO .service(web::scope("/metrics").configure(metrics::configure)) // done .service(web::scope("/experimental-features").configure(features::configure)); }