meilisearch/meilisearch-types/src/tasks.rs

529 lines
19 KiB
Rust
Raw Normal View History

2022-10-17 22:30:18 +08:00
use std::collections::HashSet;
2022-10-21 00:00:07 +08:00
use std::fmt::{Display, Write};
2022-10-18 17:02:46 +08:00
use std::str::FromStr;
use enum_iterator::Sequence;
use milli::update::IndexDocumentsMethod;
use roaring::RoaringBitmap;
use serde::{Deserialize, Serialize, Serializer};
use time::{Duration, OffsetDateTime};
2022-10-12 09:21:25 +08:00
use uuid::Uuid;
2022-10-21 00:00:07 +08:00
use crate::error::{Code, ResponseError};
use crate::keys::Key;
use crate::settings::{Settings, Unchecked};
use crate::InstanceUid;
pub type TaskId = u32;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
2022-10-12 09:21:25 +08:00
pub struct Task {
pub uid: TaskId,
#[serde(with = "time::serde::rfc3339")]
pub enqueued_at: OffsetDateTime,
2022-10-12 09:21:25 +08:00
#[serde(with = "time::serde::rfc3339::option")]
pub started_at: Option<OffsetDateTime>,
2022-10-12 09:21:25 +08:00
#[serde(with = "time::serde::rfc3339::option")]
pub finished_at: Option<OffsetDateTime>,
2022-10-12 09:21:25 +08:00
pub error: Option<ResponseError>,
pub canceled_by: Option<TaskId>,
2022-10-12 09:21:25 +08:00
pub details: Option<Details>,
pub status: Status,
pub kind: KindWithContent,
}
impl Task {
pub fn index_uid(&self) -> Option<&str> {
use KindWithContent::*;
match &self.kind {
DumpExport { .. }
| Snapshot
| TaskCancelation { .. }
| TaskDeletion { .. }
2022-10-12 09:21:25 +08:00
| IndexSwap { .. } => None,
DocumentAdditionOrUpdate { index_uid, .. }
2022-10-12 09:21:25 +08:00
| DocumentDeletion { index_uid, .. }
| DocumentClear { index_uid }
| SettingsUpdate { index_uid, .. }
2022-10-12 09:21:25 +08:00
| IndexCreation { index_uid, .. }
| IndexUpdate { index_uid, .. }
| IndexDeletion { index_uid } => Some(index_uid),
}
}
/// Return the list of indexes updated by this tasks.
pub fn indexes(&self) -> Option<Vec<&str>> {
2022-10-17 22:30:18 +08:00
self.kind.indexes()
2022-10-12 09:21:25 +08:00
}
2022-10-13 21:02:59 +08:00
/// Return the content-uuid if there is one
pub fn content_uuid(&self) -> Option<&Uuid> {
match self.kind {
KindWithContent::DocumentAdditionOrUpdate { ref content_file, .. } => {
Some(content_file)
}
2022-10-13 21:02:59 +08:00
KindWithContent::DocumentDeletion { .. }
| KindWithContent::DocumentClear { .. }
| KindWithContent::SettingsUpdate { .. }
2022-10-13 21:02:59 +08:00
| KindWithContent::IndexDeletion { .. }
| KindWithContent::IndexCreation { .. }
| KindWithContent::IndexUpdate { .. }
| KindWithContent::IndexSwap { .. }
| KindWithContent::TaskCancelation { .. }
2022-10-17 21:11:35 +08:00
| KindWithContent::TaskDeletion { .. }
2022-10-13 21:02:59 +08:00
| KindWithContent::DumpExport { .. }
| KindWithContent::Snapshot => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
2022-10-12 09:21:25 +08:00
pub enum KindWithContent {
DocumentAdditionOrUpdate {
2022-10-12 09:21:25 +08:00
index_uid: String,
primary_key: Option<String>,
method: IndexDocumentsMethod,
content_file: Uuid,
documents_count: u64,
allow_index_creation: bool,
},
DocumentDeletion {
index_uid: String,
documents_ids: Vec<String>,
},
DocumentClear {
index_uid: String,
},
SettingsUpdate {
2022-10-12 09:21:25 +08:00
index_uid: String,
new_settings: Settings<Unchecked>,
is_deletion: bool,
allow_index_creation: bool,
},
IndexDeletion {
index_uid: String,
},
IndexCreation {
index_uid: String,
primary_key: Option<String>,
},
IndexUpdate {
index_uid: String,
primary_key: Option<String>,
},
IndexSwap {
2022-10-17 22:30:18 +08:00
swaps: Vec<(String, String)>,
2022-10-12 09:21:25 +08:00
},
TaskCancelation {
query: String,
tasks: RoaringBitmap,
2022-10-12 09:21:25 +08:00
},
TaskDeletion {
2022-10-12 09:21:25 +08:00
query: String,
tasks: RoaringBitmap,
2022-10-12 09:21:25 +08:00
},
DumpExport {
2022-10-13 21:02:59 +08:00
dump_uid: String,
keys: Vec<Key>,
instance_uid: Option<InstanceUid>,
2022-10-12 09:21:25 +08:00
},
Snapshot,
}
2022-10-12 09:21:25 +08:00
impl KindWithContent {
pub fn as_kind(&self) -> Kind {
match self {
KindWithContent::DocumentAdditionOrUpdate { .. } => Kind::DocumentAdditionOrUpdate,
2022-10-12 09:21:25 +08:00
KindWithContent::DocumentDeletion { .. } => Kind::DocumentDeletion,
KindWithContent::DocumentClear { .. } => Kind::DocumentDeletion,
KindWithContent::SettingsUpdate { .. } => Kind::SettingsUpdate,
2022-10-12 09:21:25 +08:00
KindWithContent::IndexCreation { .. } => Kind::IndexCreation,
KindWithContent::IndexDeletion { .. } => Kind::IndexDeletion,
KindWithContent::IndexUpdate { .. } => Kind::IndexUpdate,
KindWithContent::IndexSwap { .. } => Kind::IndexSwap,
KindWithContent::TaskCancelation { .. } => Kind::TaskCancelation,
KindWithContent::TaskDeletion { .. } => Kind::TaskDeletion,
2022-10-12 09:21:25 +08:00
KindWithContent::DumpExport { .. } => Kind::DumpExport,
KindWithContent::Snapshot => Kind::Snapshot,
}
}
2022-10-12 09:21:25 +08:00
pub fn indexes(&self) -> Option<Vec<&str>> {
use KindWithContent::*;
match self {
DumpExport { .. } | Snapshot | TaskCancelation { .. } | TaskDeletion { .. } => None,
DocumentAdditionOrUpdate { index_uid, .. }
2022-10-12 09:21:25 +08:00
| DocumentDeletion { index_uid, .. }
| DocumentClear { index_uid }
| SettingsUpdate { index_uid, .. }
2022-10-12 09:21:25 +08:00
| IndexCreation { index_uid, .. }
| IndexUpdate { index_uid, .. }
| IndexDeletion { index_uid } => Some(vec![index_uid]),
2022-10-17 22:30:18 +08:00
IndexSwap { swaps } => {
let mut indexes = HashSet::<&str>::default();
for (lhs, rhs) in swaps {
indexes.insert(lhs.as_str());
indexes.insert(rhs.as_str());
}
Some(indexes.into_iter().collect())
}
2022-10-12 09:21:25 +08:00
}
}
/// Returns the default `Details` that correspond to this `KindWithContent`,
/// `None` if it cannot be generated.
pub fn default_details(&self) -> Option<Details> {
match self {
KindWithContent::DocumentAdditionOrUpdate { documents_count, .. } => {
Some(Details::DocumentAdditionOrUpdate {
2022-10-21 00:00:07 +08:00
received_documents: *documents_count,
indexed_documents: None,
})
}
KindWithContent::DocumentDeletion { index_uid: _, documents_ids } => {
Some(Details::DocumentDeletion {
received_document_ids: documents_ids.len(),
deleted_documents: None,
})
}
KindWithContent::DocumentClear { .. } => {
Some(Details::ClearAll { deleted_documents: None })
}
KindWithContent::SettingsUpdate { new_settings, .. } => {
Some(Details::SettingsUpdate { settings: new_settings.clone() })
2022-10-21 00:00:07 +08:00
}
2022-10-12 09:21:25 +08:00
KindWithContent::IndexDeletion { .. } => None,
KindWithContent::IndexCreation { primary_key, .. }
2022-10-21 00:00:07 +08:00
| KindWithContent::IndexUpdate { primary_key, .. } => {
Some(Details::IndexInfo { primary_key: primary_key.clone() })
}
KindWithContent::IndexSwap { swaps } => {
Some(Details::IndexSwap { swaps: swaps.clone() })
}
KindWithContent::TaskCancelation { query, tasks } => Some(Details::TaskCancelation {
matched_tasks: tasks.len(),
canceled_tasks: None,
original_query: query.clone(),
}),
KindWithContent::TaskDeletion { query, tasks } => Some(Details::TaskDeletion {
2022-10-18 01:24:06 +08:00
matched_tasks: tasks.len(),
2022-10-12 09:21:25 +08:00
deleted_tasks: None,
original_query: query.clone(),
2022-10-19 22:44:42 +08:00
}),
KindWithContent::DumpExport { .. } => None,
KindWithContent::Snapshot => None,
}
}
pub fn default_finished_details(&self) -> Option<Details> {
match self {
KindWithContent::DocumentAdditionOrUpdate { documents_count, .. } => {
Some(Details::DocumentAdditionOrUpdate {
2022-10-21 00:00:07 +08:00
received_documents: *documents_count,
indexed_documents: Some(0),
})
}
KindWithContent::DocumentDeletion { index_uid: _, documents_ids } => {
Some(Details::DocumentDeletion {
received_document_ids: documents_ids.len(),
deleted_documents: Some(0),
})
}
KindWithContent::DocumentClear { .. } => {
Some(Details::ClearAll { deleted_documents: None })
}
KindWithContent::SettingsUpdate { new_settings, .. } => {
Some(Details::SettingsUpdate { settings: new_settings.clone() })
2022-10-21 00:00:07 +08:00
}
2022-10-19 22:44:42 +08:00
KindWithContent::IndexDeletion { .. } => None,
KindWithContent::IndexCreation { primary_key, .. }
2022-10-21 00:00:07 +08:00
| KindWithContent::IndexUpdate { primary_key, .. } => {
Some(Details::IndexInfo { primary_key: primary_key.clone() })
}
2022-10-19 22:44:42 +08:00
KindWithContent::IndexSwap { .. } => {
todo!()
}
KindWithContent::TaskCancelation { query, tasks } => Some(Details::TaskCancelation {
matched_tasks: tasks.len(),
canceled_tasks: Some(0),
original_query: query.clone(),
}),
KindWithContent::TaskDeletion { query, tasks } => Some(Details::TaskDeletion {
matched_tasks: tasks.len(),
deleted_tasks: Some(0),
original_query: query.clone(),
2022-10-12 09:21:25 +08:00
}),
KindWithContent::DumpExport { .. } => None,
KindWithContent::Snapshot => None,
}
}
}
2022-10-13 21:02:59 +08:00
impl From<&KindWithContent> for Option<Details> {
fn from(kind: &KindWithContent) -> Self {
match kind {
KindWithContent::DocumentAdditionOrUpdate { documents_count, .. } => {
Some(Details::DocumentAdditionOrUpdate {
2022-10-21 00:00:07 +08:00
received_documents: *documents_count,
indexed_documents: None,
})
}
2022-10-13 21:02:59 +08:00
KindWithContent::DocumentDeletion { .. } => None,
KindWithContent::DocumentClear { .. } => None,
KindWithContent::SettingsUpdate { new_settings, .. } => {
Some(Details::SettingsUpdate { settings: new_settings.clone() })
2022-10-21 00:00:07 +08:00
}
2022-10-13 21:02:59 +08:00
KindWithContent::IndexDeletion { .. } => None,
2022-10-21 00:00:07 +08:00
KindWithContent::IndexCreation { primary_key, .. } => {
Some(Details::IndexInfo { primary_key: primary_key.clone() })
}
KindWithContent::IndexUpdate { primary_key, .. } => {
Some(Details::IndexInfo { primary_key: primary_key.clone() })
}
2022-10-13 21:02:59 +08:00
KindWithContent::IndexSwap { .. } => None,
KindWithContent::TaskCancelation { query, tasks } => Some(Details::TaskCancelation {
matched_tasks: tasks.len(),
canceled_tasks: None,
original_query: query.clone(),
}),
2022-10-18 01:24:06 +08:00
KindWithContent::TaskDeletion { query, tasks } => Some(Details::TaskDeletion {
matched_tasks: tasks.len(),
deleted_tasks: None,
original_query: query.clone(),
}),
2022-10-21 00:00:07 +08:00
KindWithContent::DumpExport { dump_uid, .. } => {
Some(Details::Dump { dump_uid: dump_uid.clone() })
}
2022-10-13 21:02:59 +08:00
KindWithContent::Snapshot => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Sequence)]
#[serde(rename_all = "camelCase")]
pub enum Status {
Enqueued,
Processing,
Succeeded,
Failed,
2022-10-17 20:02:14 +08:00
Canceled,
}
impl Display for Status {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Status::Enqueued => write!(f, "enqueued"),
Status::Processing => write!(f, "processing"),
Status::Succeeded => write!(f, "succeeded"),
Status::Failed => write!(f, "failed"),
2022-10-17 20:02:14 +08:00
Status::Canceled => write!(f, "canceled"),
}
}
}
impl FromStr for Status {
type Err = ResponseError;
fn from_str(status: &str) -> Result<Self, Self::Err> {
if status.eq_ignore_ascii_case("enqueued") {
Ok(Status::Enqueued)
} else if status.eq_ignore_ascii_case("processing") {
Ok(Status::Processing)
} else if status.eq_ignore_ascii_case("succeeded") {
Ok(Status::Succeeded)
} else if status.eq_ignore_ascii_case("failed") {
Ok(Status::Failed)
2022-10-17 20:02:14 +08:00
} else if status.eq_ignore_ascii_case("canceled") {
Ok(Status::Canceled)
} else {
Err(ResponseError::from_msg(
format!(
"`{}` is not a status. Available status are {}.",
status,
enum_iterator::all::<Status>()
.map(|s| format!("`{s}`"))
.collect::<Vec<String>>()
.join(", ")
),
Code::BadRequest,
))
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Sequence)]
#[serde(rename_all = "camelCase")]
pub enum Kind {
DocumentAdditionOrUpdate,
DocumentDeletion,
SettingsUpdate,
IndexCreation,
IndexDeletion,
IndexUpdate,
IndexSwap,
TaskCancelation,
TaskDeletion,
DumpExport,
Snapshot,
}
impl FromStr for Kind {
type Err = ResponseError;
fn from_str(kind: &str) -> Result<Self, Self::Err> {
if kind.eq_ignore_ascii_case("indexCreation") {
Ok(Kind::IndexCreation)
} else if kind.eq_ignore_ascii_case("indexUpdate") {
Ok(Kind::IndexUpdate)
} else if kind.eq_ignore_ascii_case("indexDeletion") {
Ok(Kind::IndexDeletion)
} else if kind.eq_ignore_ascii_case("documentAdditionOrUpdate") {
Ok(Kind::DocumentAdditionOrUpdate)
} else if kind.eq_ignore_ascii_case("documentDeletion") {
Ok(Kind::DocumentDeletion)
} else if kind.eq_ignore_ascii_case("settingsUpdate") {
Ok(Kind::SettingsUpdate)
} else if kind.eq_ignore_ascii_case("taskCancelation") {
Ok(Kind::TaskCancelation)
} else if kind.eq_ignore_ascii_case("taskDeletion") {
Ok(Kind::TaskDeletion)
} else if kind.eq_ignore_ascii_case("dumpCreation") {
Ok(Kind::DumpExport)
} else {
Err(ResponseError::from_msg(
format!(
"`{}` is not a type. Available types are {}.",
kind,
enum_iterator::all::<Kind>()
.map(|k| format!(
"`{}`",
// by default serde is going to insert `"` around the value.
serde_json::to_string(&k).unwrap().trim_matches('"')
))
.collect::<Vec<String>>()
.join(", ")
),
Code::BadRequest,
))
}
}
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
#[allow(clippy::large_enum_variant)]
pub enum Details {
DocumentAdditionOrUpdate {
received_documents: u64,
2022-10-13 21:02:59 +08:00
indexed_documents: Option<u64>,
},
SettingsUpdate {
settings: Settings<Unchecked>,
},
IndexInfo {
primary_key: Option<String>,
},
DocumentDeletion {
received_document_ids: usize,
// TODO why is this optional?
deleted_documents: Option<u64>,
},
ClearAll {
deleted_documents: Option<u64>,
},
TaskCancelation {
matched_tasks: u64,
canceled_tasks: Option<u64>,
original_query: String,
},
TaskDeletion {
2022-10-18 01:24:06 +08:00
matched_tasks: u64,
deleted_tasks: Option<u64>,
original_query: String,
},
Dump {
dump_uid: String,
},
2022-10-17 22:30:18 +08:00
// TODO: Lo: Revisit this variant once we have decided on what the POST payload of swapping indexes should be
IndexSwap {
swaps: Vec<(String, String)>,
},
}
/// Serialize a `time::Duration` as a best effort ISO 8601 while waiting for
/// https://github.com/time-rs/time/issues/378.
/// This code is a port of the old code of time that was removed in 0.2.
2022-10-12 09:21:25 +08:00
pub fn serialize_duration<S: Serializer>(
duration: &Option<Duration>,
serializer: S,
) -> Result<S::Ok, S::Error> {
match duration {
Some(duration) => {
// technically speaking, negative duration is not valid ISO 8601
if duration.is_negative() {
return serializer.serialize_none();
}
const SECS_PER_DAY: i64 = Duration::DAY.whole_seconds();
let secs = duration.whole_seconds();
let days = secs / SECS_PER_DAY;
let secs = secs - days * SECS_PER_DAY;
let hasdate = days != 0;
let nanos = duration.subsec_nanoseconds();
let hastime = (secs != 0 || nanos != 0) || !hasdate;
// all the following unwrap can't fail
let mut res = String::new();
write!(&mut res, "P").unwrap();
if hasdate {
write!(&mut res, "{}D", days).unwrap();
}
const NANOS_PER_MILLI: i32 = Duration::MILLISECOND.subsec_nanoseconds();
const NANOS_PER_MICRO: i32 = Duration::MICROSECOND.subsec_nanoseconds();
if hastime {
if nanos == 0 {
write!(&mut res, "T{}S", secs).unwrap();
} else if nanos % NANOS_PER_MILLI == 0 {
write!(&mut res, "T{}.{:03}S", secs, nanos / NANOS_PER_MILLI).unwrap();
} else if nanos % NANOS_PER_MICRO == 0 {
write!(&mut res, "T{}.{:06}S", secs, nanos / NANOS_PER_MICRO).unwrap();
} else {
write!(&mut res, "T{}.{:09}S", secs, nanos).unwrap();
}
}
serializer.serialize_str(&res)
}
None => serializer.serialize_none(),
}
}
2022-10-12 09:21:25 +08:00
#[cfg(test)]
mod tests {
use super::Details;
2022-10-21 00:00:07 +08:00
use crate::heed::types::SerdeJson;
use crate::heed::{BytesDecode, BytesEncode};
2022-10-12 09:21:25 +08:00
#[test]
fn bad_deser() {
let details = Details::TaskDeletion {
2022-10-12 09:21:25 +08:00
matched_tasks: 1,
deleted_tasks: None,
original_query: "hello".to_owned(),
};
let serialised = SerdeJson::<Details>::bytes_encode(&details).unwrap();
let deserialised = SerdeJson::<Details>::bytes_decode(&serialised).unwrap();
2022-10-22 22:23:19 +08:00
meili_snap::snapshot!(format!("{:?}", details), @r###"TaskDeletion { matched_tasks: 1, deleted_tasks: None, original_query: "hello" }"###);
meili_snap::snapshot!(format!("{:?}", deserialised), @r###"TaskDeletion { matched_tasks: 1, deleted_tasks: None, original_query: "hello" }"###);
2022-10-12 09:21:25 +08:00
}
}