Implements the get and delete tasks route

This commit is contained in:
Tamo 2024-08-08 19:14:19 +02:00
parent e78da35287
commit 4859a36cb6
11 changed files with 271 additions and 23 deletions

39
Cargo.lock generated
View File

@ -3455,6 +3455,8 @@ dependencies = [
"tracing-trace",
"url",
"urlencoding",
"utoipa",
"utoipa-scalar",
"uuid",
"wiremock",
"yaup",
@ -3507,6 +3509,7 @@ dependencies = [
"thiserror",
"time",
"tokio",
"utoipa",
"uuid",
]
@ -5841,6 +5844,42 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "utoipa"
version = "5.0.0-alpha.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c082de846a4d434a9dcfe3358dbe4a0aa5d4f826c3af29cdbd97404e1ffe71f4"
dependencies = [
"indexmap",
"serde",
"serde_json",
"utoipa-gen",
]
[[package]]
name = "utoipa-gen"
version = "5.0.0-alpha.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84a71c23e17df16027cc552b5b249a2a5e6a1ea36ab37363a1ac29b69ab36035"
dependencies = [
"proc-macro2",
"quote",
"regex",
"syn 2.0.60",
]
[[package]]
name = "utoipa-scalar"
version = "0.2.0-alpha.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c851bc855ea797c92d61de0cd4d36195c01657bc92e42a02fd1373897d6bfe7"
dependencies = [
"actix-web",
"serde",
"serde_json",
"utoipa",
]
[[package]]
name = "uuid"
version = "1.10.0"

View File

@ -37,6 +37,7 @@ time = { version = "0.3.36", features = [
"macros",
] }
tokio = "1.38"
utoipa = { version = "5.0.0-alpha.1" }
uuid = { version = "1.10.0", features = ["serde", "v4"] }
[dev-dependencies]

View File

@ -16,6 +16,7 @@ use std::ops::Deref;
use std::str::FromStr;
use deserr::{DeserializeError, Deserr, MergeWithError, ValueKind};
use utoipa::ToSchema;
use super::{DeserrParseBoolError, DeserrParseIntError};
use crate::index_uid::IndexUid;
@ -29,6 +30,12 @@ use crate::tasks::{Kind, Status};
#[derive(Default, Debug, Clone, Copy)]
pub struct Param<T>(pub T);
impl<'a, T: ToSchema<'a>> ToSchema<'a> for Param<T> {
fn schema() -> (&'a str, utoipa::openapi::RefOr<utoipa::openapi::schema::Schema>) {
T::schema()
}
}
impl<T> Deref for Param<T> {
type Target = T;

View File

@ -7,9 +7,11 @@ use aweb::rt::task::JoinError;
use convert_case::Casing;
use milli::heed::{Error as HeedError, MdbError};
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, ToSchema)]
#[serde(rename_all = "camelCase")]
#[schema(rename_all = "camelCase")]
pub struct ResponseError {
#[serde(skip)]
pub code: StatusCode,

View File

@ -13,6 +13,7 @@ use milli::proximity::ProximityPrecision;
use milli::update::Setting;
use milli::{Criterion, CriterionError, Index, DEFAULT_VALUES_PER_FACET};
use serde::{Deserialize, Serialize, Serializer};
use utoipa::ToSchema;
use crate::deserr::DeserrJsonError;
use crate::error::deserr_codes::*;
@ -69,54 +70,63 @@ fn validate_min_word_size_for_typo_setting<E: DeserializeError>(
Ok(s)
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, Deserr)]
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, Deserr, ToSchema)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
#[deserr(deny_unknown_fields, rename_all = camelCase, validate = validate_min_word_size_for_typo_setting -> DeserrJsonError<InvalidSettingsTypoTolerance>)]
pub struct MinWordSizeTyposSetting {
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
#[deserr(default)]
#[schema(value_type = Option<u8>, example = json!(5))]
pub one_typo: Setting<u8>,
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
#[deserr(default)]
#[schema(value_type = Option<u8>, example = json!(9))]
pub two_typos: Setting<u8>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, Deserr)]
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, Deserr, ToSchema)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
#[deserr(deny_unknown_fields, rename_all = camelCase, where_predicate = __Deserr_E: deserr::MergeWithError<DeserrJsonError<InvalidSettingsTypoTolerance>>)]
pub struct TypoSettings {
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
#[deserr(default)]
#[schema(value_type = Option<bool>, example = json!(true))]
pub enabled: Setting<bool>,
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
#[deserr(default, error = DeserrJsonError<InvalidSettingsTypoTolerance>)]
#[schema(value_type = Option<MinWordSizeTyposSetting>, example = json!({ "oneTypo": 5, "twoTypo": 9 }))]
pub min_word_size_for_typos: Setting<MinWordSizeTyposSetting>,
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
#[deserr(default)]
#[schema(value_type = Option<BTreeSet<String>>, example = json!(["iPhone", "phone"]))]
pub disable_on_words: Setting<BTreeSet<String>>,
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
#[deserr(default)]
#[schema(value_type = Option<BTreeSet<String>>, example = json!(["uuid", "url"]))]
pub disable_on_attributes: Setting<BTreeSet<String>>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, Deserr)]
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, Deserr, ToSchema)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
#[deserr(rename_all = camelCase, deny_unknown_fields)]
pub struct FacetingSettings {
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
#[deserr(default)]
#[schema(value_type = Option<usize>, example = json!(10))]
pub max_values_per_facet: Setting<usize>,
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
#[deserr(default)]
#[schema(value_type = Option<BTreeMap<String, FacetValuesSort>>, example = json!({ "genre": FacetValuesSort::Count }))]
pub sort_facet_values_by: Setting<BTreeMap<String, FacetValuesSort>>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, Deserr)]
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, Deserr, ToSchema)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
#[deserr(rename_all = camelCase, deny_unknown_fields)]
pub struct PaginationSettings {
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
#[deserr(default)]
#[schema(value_type = Option<usize>, example = json!(250))]
pub max_total_hits: Setting<usize>,
}
@ -137,67 +147,100 @@ impl MergeWithError<milli::CriterionError> for DeserrJsonError<InvalidSettingsRa
/// Holds all the settings for an index. `T` can either be `Checked` if they represents settings
/// whose validity is guaranteed, or `Unchecked` if they need to be validated. In the later case, a
/// call to `check` will return a `Settings<Checked>` from a `Settings<Unchecked>`.
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, Deserr)]
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, Deserr, ToSchema)]
#[serde(
deny_unknown_fields,
rename_all = "camelCase",
bound(serialize = "T: Serialize", deserialize = "T: Deserialize<'static>")
)]
#[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields)]
#[schema(rename_all = "camelCase")]
pub struct Settings<T> {
/// Fields displayed in the returned documents.
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
#[deserr(default, error = DeserrJsonError<InvalidSettingsDisplayedAttributes>)]
#[schema(value_type = Option<Vec<String>>, example = json!(["id", "title", "description", "url"]))]
pub displayed_attributes: WildcardSetting,
/// Fields in which to search for matching query words sorted by order of importance.
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
#[deserr(default, error = DeserrJsonError<InvalidSettingsSearchableAttributes>)]
#[schema(value_type = Option<Vec<String>>, example = json!(["title", "description"]))]
pub searchable_attributes: WildcardSetting,
/// Attributes to use for faceting and filtering. See [Filtering and Faceted Search](https://www.meilisearch.com/docs/learn/filtering_and_sorting/search_with_facet_filters).
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
#[deserr(default, error = DeserrJsonError<InvalidSettingsFilterableAttributes>)]
#[schema(value_type = Option<Vec<String>>, example = json!(["release_date", "genre"]))]
pub filterable_attributes: Setting<BTreeSet<String>>,
/// Attributes to use when sorting search results.
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
#[deserr(default, error = DeserrJsonError<InvalidSettingsSortableAttributes>)]
#[schema(value_type = Option<Vec<String>>, example = json!(["release_date"]))]
pub sortable_attributes: Setting<BTreeSet<String>>,
/// List of ranking rules sorted by order of importance. The order is customizable.
/// [A list of ordered built-in ranking rules](https://www.meilisearch.com/docs/learn/relevancy/relevancy).
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
#[deserr(default, error = DeserrJsonError<InvalidSettingsRankingRules>)]
#[schema(value_type = Option<Vec<String>>, example = json!([RankingRuleView::Words, RankingRuleView::Typo, RankingRuleView::Proximity, RankingRuleView::Attribute, RankingRuleView::Exactness]))]
pub ranking_rules: Setting<Vec<RankingRuleView>>,
/// List of words ignored when present in search queries.
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
#[deserr(default, error = DeserrJsonError<InvalidSettingsStopWords>)]
#[schema(value_type = Option<Vec<String>>, example = json!(["the", "a", "them", "their"]))]
pub stop_words: Setting<BTreeSet<String>>,
/// List of characters not delimiting where one term begins and ends.
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
#[deserr(default, error = DeserrJsonError<InvalidSettingsNonSeparatorTokens>)]
#[schema(value_type = Option<Vec<String>>, example = json!([" ", "\n"]))]
pub non_separator_tokens: Setting<BTreeSet<String>>,
/// List of characters delimiting where one term begins and ends.
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
#[deserr(default, error = DeserrJsonError<InvalidSettingsSeparatorTokens>)]
#[schema(value_type = Option<Vec<String>>, example = json!(["S"]))]
pub separator_tokens: Setting<BTreeSet<String>>,
/// List of strings Meilisearch should parse as a single term.
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
#[deserr(default, error = DeserrJsonError<InvalidSettingsDictionary>)]
#[schema(value_type = Option<Vec<String>>, example = json!(["iPhone pro"]))]
pub dictionary: Setting<BTreeSet<String>>,
/// List of associated words treated similarly. A word associated to an array of word as synonyms.
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
#[deserr(default, error = DeserrJsonError<InvalidSettingsSynonyms>)]
#[schema(value_type = Option<BTreeMap<String, Vec<String>>>, example = json!({ "he": ["she", "they", "them"], "phone": ["iPhone", "android"]}))]
pub synonyms: Setting<BTreeMap<String, Vec<String>>>,
/// Search returns documents with distinct (different) values of the given field.
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
#[deserr(default, error = DeserrJsonError<InvalidSettingsDistinctAttribute>)]
#[schema(value_type = Option<String>, example = json!("sku"))]
pub distinct_attribute: Setting<String>,
/// Precision level when calculating the proximity ranking rule.
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
#[deserr(default, error = DeserrJsonError<InvalidSettingsProximityPrecision>)]
#[schema(value_type = Option<String>, example = json!(ProximityPrecisionView::ByAttribute))]
pub proximity_precision: Setting<ProximityPrecisionView>,
/// Customize typo tolerance feature.
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
#[deserr(default, error = DeserrJsonError<InvalidSettingsTypoTolerance>)]
#[schema(value_type = Option<TypoSettings>, example = json!({ "enabled": true, "disableOnAttributes": ["title"]}))]
pub typo_tolerance: Setting<TypoSettings>,
/// Faceting settings.
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
#[deserr(default, error = DeserrJsonError<InvalidSettingsFaceting>)]
#[schema(value_type = Option<FacetingSettings>, example = json!({ "maxValuesPerFacet": 10, "sortFacetValuesBy": { "genre": FacetValuesSort::Count }}))]
pub faceting: Setting<FacetingSettings>,
/// Pagination settings.
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
#[deserr(default, error = DeserrJsonError<InvalidSettingsPagination>)]
#[schema(value_type = Option<PaginationSettings>, example = json!({ "maxValuesPerFacet": 10, "sortFacetValuesBy": { "genre": FacetValuesSort::Count }}))]
pub pagination: Setting<PaginationSettings>,
/// Embedder required for performing meaning-based search queries.
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
#[deserr(default, error = DeserrJsonError<InvalidSettingsEmbedders>)]
pub embedders: Setting<BTreeMap<String, Setting<milli::vector::settings::EmbeddingSettings>>>,
/// Maximum duration of a search query.
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
#[deserr(default, error = DeserrJsonError<InvalidSettingsSearchCutoffMs>)]
#[schema(value_type = Option<u64>, example = json!(50))]
pub search_cutoff_ms: Setting<u64>,
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
#[deserr(default, error = DeserrJsonError<InvalidSettingsLocalizedAttributes>)]
@ -865,7 +908,7 @@ impl From<ProximityPrecisionView> for ProximityPrecision {
}
}
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq)]
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq, ToSchema)]
pub struct WildcardSetting(Setting<Vec<String>>);
impl From<Setting<Vec<String>>> for WildcardSetting {

View File

@ -1,30 +1,47 @@
use milli::Object;
use serde::Serialize;
use time::{Duration, OffsetDateTime};
use utoipa::ToSchema;
use crate::error::ResponseError;
use crate::settings::{Settings, Unchecked};
use crate::tasks::{serialize_duration, Details, IndexSwap, Kind, Status, Task, TaskId};
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
#[schema(rename_all = "camelCase")]
pub struct TaskView {
/// The unique sequential identifier of the task.
#[schema(value_type = u32, example = 4312)]
pub uid: TaskId,
/// The unique identifier of the index where this task is operated.
#[schema(example = json!("movies"))]
#[serde(default)]
pub index_uid: Option<String>,
pub status: Status,
/// The type of the task.
#[serde(rename = "type")]
pub kind: Kind,
/// The uid of the task that performed the taskCancelation if the task has been canceled.
#[schema(value_type = Option<u32>, example = json!(4326))]
pub canceled_by: Option<TaskId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub details: Option<DetailsView>,
pub error: Option<ResponseError>,
/// Total elasped time the engine was in processing state expressed as a `ISO-8601` duration format.
#[schema(value_type = Option<String>, example = json!(null))]
#[serde(serialize_with = "serialize_duration", default)]
pub duration: Option<Duration>,
/// An `RFC 3339` format for date/time/duration.
#[schema(value_type = String, example = json!("2024-08-08_14:12:09.393Z"))]
#[serde(with = "time::serde::rfc3339")]
pub enqueued_at: OffsetDateTime,
/// An `RFC 3339` format for date/time/duration.
#[schema(value_type = String, example = json!("2024-08-08_14:12:09.393Z"))]
#[serde(with = "time::serde::rfc3339::option", default)]
pub started_at: Option<OffsetDateTime>,
/// An `RFC 3339` format for date/time/duration.
#[schema(value_type = String, example = json!("2024-08-08_14:12:09.393Z"))]
#[serde(with = "time::serde::rfc3339::option", default)]
pub finished_at: Option<OffsetDateTime>,
}
@ -47,37 +64,52 @@ impl TaskView {
}
}
#[derive(Default, Debug, PartialEq, Eq, Clone, Serialize)]
/// Details information of the task payload.
#[derive(Default, Debug, PartialEq, Eq, Clone, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
#[schema(rename_all = "camelCase")]
pub struct DetailsView {
/// Number of documents received for documentAdditionOrUpdate task.
#[serde(skip_serializing_if = "Option::is_none")]
pub received_documents: Option<u64>,
/// Number of documents finally indexed for documentAdditionOrUpdate task or a documentAdditionOrUpdate batch of tasks.
#[serde(skip_serializing_if = "Option::is_none")]
pub indexed_documents: Option<Option<u64>>,
/// Number of documents edited for editDocumentByFunction task.
#[serde(skip_serializing_if = "Option::is_none")]
pub edited_documents: Option<Option<u64>>,
/// Value for the primaryKey field encountered if any for indexCreation or indexUpdate task.
#[serde(skip_serializing_if = "Option::is_none")]
pub primary_key: Option<Option<String>>,
/// Number of provided document ids for the documentDeletion task.
#[serde(skip_serializing_if = "Option::is_none")]
pub provided_ids: Option<usize>,
/// Number of documents finally deleted for documentDeletion and indexDeletion tasks.
#[serde(skip_serializing_if = "Option::is_none")]
pub deleted_documents: Option<Option<u64>>,
/// Number of tasks that match the request for taskCancelation or taskDeletion tasks.
#[serde(skip_serializing_if = "Option::is_none")]
pub matched_tasks: Option<u64>,
/// Number of tasks canceled for taskCancelation.
#[serde(skip_serializing_if = "Option::is_none")]
pub canceled_tasks: Option<Option<u64>>,
/// Number of tasks deleted for taskDeletion.
#[serde(skip_serializing_if = "Option::is_none")]
pub deleted_tasks: Option<Option<u64>>,
/// Original filter query for taskCancelation or taskDeletion tasks.
#[serde(skip_serializing_if = "Option::is_none")]
pub original_filter: Option<Option<String>>,
/// Identifier generated for the dump for dumpCreation task.
#[serde(skip_serializing_if = "Option::is_none")]
pub dump_uid: Option<Option<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub context: Option<Option<Object>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub function: Option<String>,
/// [Learn more about the settings in this guide](https://www.meilisearch.com/docs/reference/api/settings).
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(flatten)]
// #[serde(flatten)]
#[schema(value_type = Option<Settings<Unchecked>>)]
pub settings: Option<Box<Settings<Unchecked>>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub swaps: Option<Vec<IndexSwap>>,

View File

@ -9,6 +9,7 @@ use milli::Object;
use roaring::RoaringBitmap;
use serde::{Deserialize, Serialize, Serializer};
use time::{Duration, OffsetDateTime};
use utoipa::{IntoParams, ToSchema};
use uuid::Uuid;
use crate::error::ResponseError;
@ -361,7 +362,9 @@ impl From<&KindWithContent> for Option<Details> {
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Sequence)]
/// The status of a task.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Sequence, ToSchema)]
#[schema(example = json!(Status::Processing))]
#[serde(rename_all = "camelCase")]
pub enum Status {
Enqueued,
@ -420,8 +423,10 @@ impl fmt::Display for ParseTaskStatusError {
}
impl std::error::Error for ParseTaskStatusError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Sequence)]
/// The type of the task.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Sequence, ToSchema)]
#[serde(rename_all = "camelCase")]
#[schema(rename_all = "camelCase", example = json!(Kind::DocumentAdditionOrUpdate))]
pub enum Kind {
DocumentAdditionOrUpdate,
DocumentEdition,

View File

@ -104,6 +104,8 @@ tracing-trace = { version = "0.1.0", path = "../tracing-trace" }
tracing-actix-web = "0.7.11"
build-info = { version = "1.7.0", path = "../build-info" }
roaring = "0.10.2"
utoipa = { version = "5.0.0-alpha.1", features = ["actix_extras", "non_strict_integers"] }
utoipa-scalar = { version = "0.2.0-alpha.0", features = ["actix-web"] }
[dev-dependencies]
actix-rt = "2.10.0"

View File

@ -24,6 +24,7 @@ use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
use tracing::level_filters::LevelFilter;
use tracing_subscriber::layer::SubscriberExt as _;
use tracing_subscriber::Layer;
use utoipa::OpenApi;
#[global_allocator]
static ALLOC: MiMalloc = MiMalloc;

View File

@ -1,20 +1,22 @@
use std::collections::BTreeMap;
use crate::extractors::authentication::policies::*;
use crate::extractors::authentication::GuardedData;
use crate::search_queue::SearchQueue;
use crate::Opt;
use actix_web::web::Data;
use actix_web::{web, HttpRequest, HttpResponse};
use index_scheduler::IndexScheduler;
use meilisearch_auth::AuthController;
use meilisearch_types::deserr::query_params::Param;
use meilisearch_types::error::{Code, ResponseError};
use meilisearch_types::settings::{Settings, Unchecked};
use meilisearch_types::tasks::{Kind, Status, Task, TaskId};
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
use tracing::debug;
use crate::extractors::authentication::policies::*;
use crate::extractors::authentication::GuardedData;
use crate::search_queue::SearchQueue;
use crate::Opt;
use utoipa::OpenApi;
use utoipa_scalar::{Scalar, Servable as ScalarServable};
const PAGINATION_DEFAULT_LIMIT: usize = 20;
@ -29,8 +31,19 @@ mod snapshot;
mod swap_indexes;
pub mod tasks;
#[derive(OpenApi)]
#[openapi(
nest((path = "/tasks", api = tasks::TaskApi) ),
// paths(get_todos, create_todo, delete_todo, get_todo_by_id, update_todo, search_todos),
// components(schemas(TaskId))
)]
pub struct MeilisearchApi;
pub fn configure(cfg: &mut web::ServiceConfig) {
let openapi = MeilisearchApi::openapi();
cfg.service(web::scope("/tasks").configure(tasks::configure))
.service(Scalar::with_url("/scalar", openapi.clone()))
.service(web::resource("/health").route(web::get().to(get_health)))
.service(web::scope("/logs").configure(logs::configure))
.service(web::scope("/keys").configure(api_key::configure))

View File

@ -8,8 +8,12 @@ use meilisearch_types::deserr::DeserrQueryParamError;
use meilisearch_types::error::deserr_codes::*;
use meilisearch_types::error::{InvalidTaskDateError, ResponseError};
use meilisearch_types::index_uid::IndexUid;
use meilisearch_types::settings::{
Checked, FacetingSettings, MinWordSizeTyposSetting, PaginationSettings, Settings, TypoSettings,
Unchecked,
};
use meilisearch_types::star_or::{OptionStarOr, OptionStarOrList};
use meilisearch_types::task_view::TaskView;
use meilisearch_types::task_view::{DetailsView, TaskView};
use meilisearch_types::tasks::{Kind, KindWithContent, Status};
use serde::Serialize;
use serde_json::json;
@ -17,6 +21,7 @@ use time::format_description::well_known::Rfc3339;
use time::macros::format_description;
use time::{Date, Duration, OffsetDateTime, Time};
use tokio::task;
use utoipa::{IntoParams, OpenApi, ToSchema};
use super::{get_task_id, is_dry_run, SummarizedTaskView};
use crate::analytics::Analytics;
@ -27,6 +32,16 @@ use crate::Opt;
const DEFAULT_LIMIT: u32 = 20;
#[derive(OpenApi)]
#[openapi(
paths(get_tasks, delete_tasks),
info(
title = "The tasks route gives information about the progress of the [asynchronous operations](https://docs.meilisearch.com/learn/advanced/asynchronous_operations.html)."
),
components(schemas(AllTasks, TaskView, Status, DetailsView, ResponseError, Settings<Unchecked>, Settings<Checked>, TypoSettings, MinWordSizeTyposSetting, FacetingSettings, PaginationSettings))
)]
pub struct TaskApi;
pub fn configure(cfg: &mut web::ServiceConfig) {
cfg.service(
web::resource("")
@ -36,36 +51,54 @@ pub fn configure(cfg: &mut web::ServiceConfig) {
.service(web::resource("/cancel").route(web::post().to(SeqHandler(cancel_tasks))))
.service(web::resource("/{task_id}").route(web::get().to(SeqHandler(get_task))));
}
#[derive(Debug, Deserr)]
#[derive(Debug, Deserr, IntoParams)]
#[deserr(error = DeserrQueryParamError, rename_all = camelCase, deny_unknown_fields)]
#[into_params(rename_all = "camelCase", parameter_in = Query)]
pub struct TasksFilterQuery {
/// Maximum number of results to return.
#[deserr(default = Param(DEFAULT_LIMIT), error = DeserrQueryParamError<InvalidTaskLimit>)]
#[param(required = false, value_type = u32, example = 12, default = json!(DEFAULT_LIMIT))]
pub limit: Param<u32>,
/// Fetch the next set of results from the given uid.
#[deserr(default, error = DeserrQueryParamError<InvalidTaskFrom>)]
#[param(required = false, value_type = Option<u32>, example = 12421)]
pub from: Option<Param<TaskId>>,
/// 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.
#[deserr(default, error = DeserrQueryParamError<InvalidTaskUids>)]
#[param(required = false, value_type = Option<Vec<u32>>, example = json!([231, 423, 598]))]
pub uids: OptionStarOrList<u32>,
#[deserr(default, error = DeserrQueryParamError<InvalidTaskCanceledBy>)]
#[param(required = false, value_type = Option<Vec<u32>>, example = json!([374]))]
pub canceled_by: OptionStarOrList<u32>,
#[deserr(default, error = DeserrQueryParamError<InvalidTaskTypes>)]
#[param(required = false, value_type = Option<Vec<Kind>>, example = json!([Kind::DocumentDeletion]))]
pub types: OptionStarOrList<Kind>,
#[deserr(default, error = DeserrQueryParamError<InvalidTaskStatuses>)]
#[param(required = false, value_type = Option<Vec<Status>>, example = json!([Status::Succeeded, Status::Failed, Status::Canceled]))]
pub statuses: OptionStarOrList<Status>,
#[deserr(default, error = DeserrQueryParamError<InvalidIndexUid>)]
#[param(required = false, value_type = Option<Vec<String>>, example = json!(["movies", "theater"]))]
pub index_uids: OptionStarOrList<IndexUid>,
#[deserr(default, error = DeserrQueryParamError<InvalidTaskAfterEnqueuedAt>, try_from(OptionStarOr<String>) = deserialize_date_after -> InvalidTaskDateError)]
#[param(required = false, value_type = Option<String>, example = json!("2024-08-08_16:37:09.971Z"))]
pub after_enqueued_at: OptionStarOr<OffsetDateTime>,
#[deserr(default, error = DeserrQueryParamError<InvalidTaskBeforeEnqueuedAt>, try_from(OptionStarOr<String>) = deserialize_date_before -> InvalidTaskDateError)]
#[param(required = false, value_type = Option<String>, example = json!("2024-08-08_16:37:09.971Z"))]
pub before_enqueued_at: OptionStarOr<OffsetDateTime>,
#[deserr(default, error = DeserrQueryParamError<InvalidTaskAfterStartedAt>, try_from(OptionStarOr<String>) = deserialize_date_after -> InvalidTaskDateError)]
#[param(required = false, value_type = Option<String>, example = json!("2024-08-08_16:37:09.971Z"))]
pub after_started_at: OptionStarOr<OffsetDateTime>,
#[deserr(default, error = DeserrQueryParamError<InvalidTaskBeforeStartedAt>, try_from(OptionStarOr<String>) = deserialize_date_before -> InvalidTaskDateError)]
#[param(required = false, value_type = Option<String>, example = json!("2024-08-08_16:37:09.971Z"))]
pub before_started_at: OptionStarOr<OffsetDateTime>,
#[deserr(default, error = DeserrQueryParamError<InvalidTaskAfterFinishedAt>, try_from(OptionStarOr<String>) = deserialize_date_after -> InvalidTaskDateError)]
#[param(required = false, value_type = Option<String>, example = json!("2024-08-08_16:37:09.971Z"))]
pub after_finished_at: OptionStarOr<OffsetDateTime>,
#[deserr(default, error = DeserrQueryParamError<InvalidTaskBeforeFinishedAt>, try_from(OptionStarOr<String>) = deserialize_date_before -> InvalidTaskDateError)]
#[param(required = false, value_type = Option<String>, example = json!("2024-08-08_16:37:09.971Z"))]
pub before_finished_at: OptionStarOr<OffsetDateTime>,
}
@ -110,31 +143,43 @@ impl TaskDeletionOrCancelationQuery {
}
}
#[derive(Debug, Deserr)]
#[derive(Debug, Deserr, IntoParams)]
#[deserr(error = DeserrQueryParamError, rename_all = camelCase, deny_unknown_fields)]
#[into_params(rename_all = "camelCase", parameter_in = Query)]
pub struct TaskDeletionOrCancelationQuery {
#[deserr(default, error = DeserrQueryParamError<InvalidTaskUids>)]
#[param(required = false, value_type = Option<Vec<u32>>, example = json!([231, 423, 598]))]
pub uids: OptionStarOrList<u32>,
#[deserr(default, error = DeserrQueryParamError<InvalidTaskCanceledBy>)]
#[param(required = false, value_type = Option<Vec<u32>>, example = json!([374]))]
pub canceled_by: OptionStarOrList<u32>,
#[deserr(default, error = DeserrQueryParamError<InvalidTaskTypes>)]
#[param(required = false, value_type = Option<Vec<Kind>>, example = json!([Kind::DocumentDeletion]))]
pub types: OptionStarOrList<Kind>,
#[deserr(default, error = DeserrQueryParamError<InvalidTaskStatuses>)]
#[param(required = false, value_type = Option<Vec<Status>>, example = json!([Status::Succeeded, Status::Failed, Status::Canceled]))]
pub statuses: OptionStarOrList<Status>,
#[deserr(default, error = DeserrQueryParamError<InvalidIndexUid>)]
#[param(required = false, value_type = Option<Vec<String>>, example = json!(["movies", "theater"]))]
pub index_uids: OptionStarOrList<IndexUid>,
#[deserr(default, error = DeserrQueryParamError<InvalidTaskAfterEnqueuedAt>, try_from(OptionStarOr<String>) = deserialize_date_after -> InvalidTaskDateError)]
#[param(required = false, value_type = Option<String>, example = json!("2024-08-08_16:37:09.971Z"))]
pub after_enqueued_at: OptionStarOr<OffsetDateTime>,
#[deserr(default, error = DeserrQueryParamError<InvalidTaskBeforeEnqueuedAt>, try_from(OptionStarOr<String>) = deserialize_date_before -> InvalidTaskDateError)]
#[param(required = false, value_type = Option<String>, example = json!("2024-08-08_16:37:09.971Z"))]
pub before_enqueued_at: OptionStarOr<OffsetDateTime>,
#[deserr(default, error = DeserrQueryParamError<InvalidTaskAfterStartedAt>, try_from(OptionStarOr<String>) = deserialize_date_after -> InvalidTaskDateError)]
#[param(required = false, value_type = Option<String>, example = json!("2024-08-08_16:37:09.971Z"))]
pub after_started_at: OptionStarOr<OffsetDateTime>,
#[deserr(default, error = DeserrQueryParamError<InvalidTaskBeforeStartedAt>, try_from(OptionStarOr<String>) = deserialize_date_before -> InvalidTaskDateError)]
#[param(required = false, value_type = Option<String>, example = json!("2024-08-08_16:37:09.971Z"))]
pub before_started_at: OptionStarOr<OffsetDateTime>,
#[deserr(default, error = DeserrQueryParamError<InvalidTaskAfterFinishedAt>, try_from(OptionStarOr<String>) = deserialize_date_after -> InvalidTaskDateError)]
#[param(required = false, value_type = Option<String>, example = json!("2024-08-08_16:37:09.971Z"))]
pub after_finished_at: OptionStarOr<OffsetDateTime>,
#[deserr(default, error = DeserrQueryParamError<InvalidTaskBeforeFinishedAt>, try_from(OptionStarOr<String>) = deserialize_date_before -> InvalidTaskDateError)]
#[param(required = false, value_type = Option<String>, example = json!("2024-08-08_16:37:09.971Z"))]
pub before_finished_at: OptionStarOr<OffsetDateTime>,
}
@ -209,6 +254,23 @@ async fn cancel_tasks(
Ok(HttpResponse::Ok().json(task))
}
/// Delete tasks.
///
/// Delete [tasks](https://docs.meilisearch.com/learn/advanced/asynchronous_operations.html) on filter
#[utoipa::path(
delete,
path = "",
params(TaskDeletionOrCancelationQuery),
responses(
(status = 200, description = "Task successfully enqueued", body = SummarizedTaskView, content_type = "application/json", example = json!({
"taskUid": 147,
"indexUid": null,
"status": "enqueued",
"type": "taskDeletion",
"enqueuedAt": "2024-08-08T17:05:55.791772Z"
}))
)
)]
async fn delete_tasks(
index_scheduler: GuardedData<ActionPolicy<{ actions::TASKS_DELETE }>, Data<IndexScheduler>>,
params: AwebQueryParameter<TaskDeletionOrCancelationQuery, DeserrQueryParamError>,
@ -258,15 +320,56 @@ async fn delete_tasks(
Ok(HttpResponse::Ok().json(task))
}
#[derive(Debug, Serialize)]
#[derive(Debug, Serialize, ToSchema)]
pub struct AllTasks {
results: Vec<TaskView>,
/// Total number of browsable results using offset/limit parameters for the given resource.
total: u64,
/// Limit given for the query. If limit is not provided as a query parameter, this parameter displays the default limit value.
limit: u32,
/// The first task uid returned.
from: Option<u32>,
/// Represents the value to send in from to fetch the next slice of the results. The first item for the next slice starts at this exact number. When the returned value is null, it means that all the data have been browsed in the given order.
next: Option<u32>,
}
/// Get all tasks
///
/// Get all [tasks](https://docs.meilisearch.com/learn/advanced/asynchronous_operations.html)
#[utoipa::path(
get,
path = "",
params(TasksFilterQuery),
responses(
(status = 200, description = "Get all tasks", body = AllTasks, content_type = "application/json", example = json!({
"results": [
{
"uid": 144,
"indexUid": "mieli",
"status": "succeeded",
"type": "settingsUpdate",
"canceledBy": null,
"details": {
"settings": {
"filterableAttributes": [
"play_count"
]
}
},
"error": null,
"duration": "PT0.009330S",
"enqueuedAt": "2024-08-08T09:01:13.348471Z",
"startedAt": "2024-08-08T09:01:13.349442Z",
"finishedAt": "2024-08-08T09:01:13.358772Z"
}
],
"total": 1,
"limit": 1,
"from": 144,
"next": null
}))
)
)]
async fn get_tasks(
index_scheduler: GuardedData<ActionPolicy<{ actions::TASKS_GET }>, Data<IndexScheduler>>,
params: AwebQueryParameter<TasksFilterQuery, DeserrQueryParamError>,