From a8128678a4949b734627668028eb2913d51a9456 Mon Sep 17 00:00:00 2001 From: Tamo Date: Thu, 6 Oct 2022 15:49:30 +0200 Subject: [PATCH] implement the dump v4 import --- dump/src/reader/compat/v4.rs | 145 -- dump/src/reader/v4/keys.rs | 77 + dump/src/reader/v4/meta.rs | 142 ++ dump/src/reader/v4/mod.rs | 277 +++- dump/src/reader/v4/settings.rs | 266 ++++ ...mp__reader__v4__test__read_dump_v4-10.snap | 1252 +++++++++++++++++ ...mp__reader__v4__test__read_dump_v4-12.snap | 57 + ...mp__reader__v4__test__read_dump_v4-13.snap | 533 +++++++ ...ump__reader__v4__test__read_dump_v4-3.snap | 384 +++++ ...ump__reader__v4__test__read_dump_v4-4.snap | 50 + ...ump__reader__v4__test__read_dump_v4-6.snap | 71 + ...ump__reader__v4__test__read_dump_v4-7.snap | 308 ++++ ...ump__reader__v4__test__read_dump_v4-9.snap | 63 + dump/src/reader/v4/tasks.rs | 454 ++++++ dump/src/reader/v5/mod.rs | 1 - dump/src/writer.rs | 1 - dump/tests/assets/v4.dump | Bin 0 -> 64636 bytes 17 files changed, 3933 insertions(+), 148 deletions(-) delete mode 100644 dump/src/reader/compat/v4.rs create mode 100644 dump/src/reader/v4/keys.rs create mode 100644 dump/src/reader/v4/meta.rs create mode 100644 dump/src/reader/v4/settings.rs create mode 100644 dump/src/reader/v4/snapshots/dump__reader__v4__test__read_dump_v4-10.snap create mode 100644 dump/src/reader/v4/snapshots/dump__reader__v4__test__read_dump_v4-12.snap create mode 100644 dump/src/reader/v4/snapshots/dump__reader__v4__test__read_dump_v4-13.snap create mode 100644 dump/src/reader/v4/snapshots/dump__reader__v4__test__read_dump_v4-3.snap create mode 100644 dump/src/reader/v4/snapshots/dump__reader__v4__test__read_dump_v4-4.snap create mode 100644 dump/src/reader/v4/snapshots/dump__reader__v4__test__read_dump_v4-6.snap create mode 100644 dump/src/reader/v4/snapshots/dump__reader__v4__test__read_dump_v4-7.snap create mode 100644 dump/src/reader/v4/snapshots/dump__reader__v4__test__read_dump_v4-9.snap create mode 100644 dump/src/reader/v4/tasks.rs create mode 100644 dump/tests/assets/v4.dump diff --git a/dump/src/reader/compat/v4.rs b/dump/src/reader/compat/v4.rs deleted file mode 100644 index c412e7f17..000000000 --- a/dump/src/reader/compat/v4.rs +++ /dev/null @@ -1,145 +0,0 @@ -use meilisearch_types::error::ResponseError; -use meilisearch_types::index_uid::IndexUid; -use milli::update::IndexDocumentsMethod; -use serde::{Deserialize, Serialize}; -use time::OffsetDateTime; -use uuid::Uuid; - -use crate::index::{Settings, Unchecked}; -use crate::tasks::batch::BatchId; -use crate::tasks::task::{ - DocumentDeletion, TaskContent as NewTaskContent, TaskEvent as NewTaskEvent, TaskId, TaskResult, -}; - -#[derive(Debug, Serialize, Deserialize)] -pub struct Task { - pub id: TaskId, - pub index_uid: IndexUid, - pub content: TaskContent, - pub events: Vec, -} - -impl From for crate::tasks::task::Task { - fn from(other: Task) -> Self { - Self { - id: other.id, - content: NewTaskContent::from((other.index_uid, other.content)), - events: other.events.into_iter().map(Into::into).collect(), - } - } -} - -#[derive(Debug, Serialize, Deserialize)] -pub enum TaskEvent { - Created(#[serde(with = "time::serde::rfc3339")] OffsetDateTime), - Batched { - #[serde(with = "time::serde::rfc3339")] - timestamp: OffsetDateTime, - batch_id: BatchId, - }, - Processing(#[serde(with = "time::serde::rfc3339")] OffsetDateTime), - Succeded { - result: TaskResult, - #[serde(with = "time::serde::rfc3339")] - timestamp: OffsetDateTime, - }, - Failed { - error: ResponseError, - #[serde(with = "time::serde::rfc3339")] - timestamp: OffsetDateTime, - }, -} - -impl From for NewTaskEvent { - fn from(other: TaskEvent) -> Self { - match other { - TaskEvent::Created(x) => NewTaskEvent::Created(x), - TaskEvent::Batched { - timestamp, - batch_id, - } => NewTaskEvent::Batched { - timestamp, - batch_id, - }, - TaskEvent::Processing(x) => NewTaskEvent::Processing(x), - TaskEvent::Succeded { result, timestamp } => { - NewTaskEvent::Succeeded { result, timestamp } - } - TaskEvent::Failed { error, timestamp } => NewTaskEvent::Failed { error, timestamp }, - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] -#[allow(clippy::large_enum_variant)] -pub enum TaskContent { - DocumentAddition { - content_uuid: Uuid, - merge_strategy: IndexDocumentsMethod, - primary_key: Option, - documents_count: usize, - allow_index_creation: bool, - }, - DocumentDeletion(DocumentDeletion), - SettingsUpdate { - settings: Settings, - /// Indicates whether the task was a deletion - is_deletion: bool, - allow_index_creation: bool, - }, - IndexDeletion, - IndexCreation { - primary_key: Option, - }, - IndexUpdate { - primary_key: Option, - }, - Dump { - uid: String, - }, -} - -impl From<(IndexUid, TaskContent)> for NewTaskContent { - fn from((index_uid, content): (IndexUid, TaskContent)) -> Self { - match content { - TaskContent::DocumentAddition { - content_uuid, - merge_strategy, - primary_key, - documents_count, - allow_index_creation, - } => NewTaskContent::DocumentAddition { - index_uid, - content_uuid, - merge_strategy, - primary_key, - documents_count, - allow_index_creation, - }, - TaskContent::DocumentDeletion(deletion) => NewTaskContent::DocumentDeletion { - index_uid, - deletion, - }, - TaskContent::SettingsUpdate { - settings, - is_deletion, - allow_index_creation, - } => NewTaskContent::SettingsUpdate { - index_uid, - settings, - is_deletion, - allow_index_creation, - }, - TaskContent::IndexDeletion => NewTaskContent::IndexDeletion { index_uid }, - TaskContent::IndexCreation { primary_key } => NewTaskContent::IndexCreation { - index_uid, - primary_key, - }, - TaskContent::IndexUpdate { primary_key } => NewTaskContent::IndexUpdate { - index_uid, - primary_key, - }, - TaskContent::Dump { uid } => NewTaskContent::Dump { uid }, - } - } -} diff --git a/dump/src/reader/v4/keys.rs b/dump/src/reader/v4/keys.rs new file mode 100644 index 000000000..26e5cad7d --- /dev/null +++ b/dump/src/reader/v4/keys.rs @@ -0,0 +1,77 @@ +use serde::Deserialize; +use time::OffsetDateTime; + +pub const KEY_ID_LENGTH: usize = 8; +pub type KeyId = [u8; KEY_ID_LENGTH]; + +#[derive(Debug, Deserialize)] +#[cfg_attr(test, derive(serde::Serialize))] +pub struct Key { + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + pub id: KeyId, + pub actions: Vec, + pub indexes: Vec, + #[serde(with = "time::serde::rfc3339::option")] + pub expires_at: Option, + #[serde(with = "time::serde::rfc3339")] + pub created_at: OffsetDateTime, + #[serde(with = "time::serde::rfc3339")] + pub updated_at: OffsetDateTime, +} + +#[derive(Copy, Clone, Deserialize, Debug, Eq, PartialEq)] +#[cfg_attr(test, derive(serde::Serialize))] +#[repr(u8)] +pub enum Action { + #[serde(rename = "*")] + All = 0, + #[serde(rename = "search")] + Search = actions::SEARCH, + #[serde(rename = "documents.add")] + DocumentsAdd = actions::DOCUMENTS_ADD, + #[serde(rename = "documents.get")] + DocumentsGet = actions::DOCUMENTS_GET, + #[serde(rename = "documents.delete")] + DocumentsDelete = actions::DOCUMENTS_DELETE, + #[serde(rename = "indexes.create")] + IndexesAdd = actions::INDEXES_CREATE, + #[serde(rename = "indexes.get")] + IndexesGet = actions::INDEXES_GET, + #[serde(rename = "indexes.update")] + IndexesUpdate = actions::INDEXES_UPDATE, + #[serde(rename = "indexes.delete")] + IndexesDelete = actions::INDEXES_DELETE, + #[serde(rename = "tasks.get")] + TasksGet = actions::TASKS_GET, + #[serde(rename = "settings.get")] + SettingsGet = actions::SETTINGS_GET, + #[serde(rename = "settings.update")] + SettingsUpdate = actions::SETTINGS_UPDATE, + #[serde(rename = "stats.get")] + StatsGet = actions::STATS_GET, + #[serde(rename = "dumps.create")] + DumpsCreate = actions::DUMPS_CREATE, + #[serde(rename = "dumps.get")] + DumpsGet = actions::DUMPS_GET, + #[serde(rename = "version")] + Version = actions::VERSION, +} + +pub mod actions { + pub const SEARCH: u8 = 1; + pub const DOCUMENTS_ADD: u8 = 2; + pub const DOCUMENTS_GET: u8 = 3; + pub const DOCUMENTS_DELETE: u8 = 4; + pub const INDEXES_CREATE: u8 = 5; + pub const INDEXES_GET: u8 = 6; + pub const INDEXES_UPDATE: u8 = 7; + pub const INDEXES_DELETE: u8 = 8; + pub const TASKS_GET: u8 = 9; + pub const SETTINGS_GET: u8 = 10; + pub const SETTINGS_UPDATE: u8 = 11; + pub const STATS_GET: u8 = 12; + pub const DUMPS_CREATE: u8 = 13; + pub const DUMPS_GET: u8 = 14; + pub const VERSION: u8 = 15; +} diff --git a/dump/src/reader/v4/meta.rs b/dump/src/reader/v4/meta.rs new file mode 100644 index 000000000..a92ca5e61 --- /dev/null +++ b/dump/src/reader/v4/meta.rs @@ -0,0 +1,142 @@ +use std::{ + fmt::{self, Display, Formatter}, + marker::PhantomData, + str::FromStr, +}; + +use serde::{de::Visitor, Deserialize, Deserializer}; +use uuid::Uuid; + +use super::settings::{Settings, Unchecked}; + +#[derive(Deserialize, Debug)] +#[cfg_attr(test, derive(serde::Serialize))] +pub struct IndexUuid { + pub uid: String, + pub index_meta: IndexMeta, +} + +#[derive(Deserialize, Debug)] +#[cfg_attr(test, derive(serde::Serialize))] +pub struct IndexMeta { + pub uuid: Uuid, + pub creation_task_id: usize, +} + +// There is one in each indexes under `meta.json`. +#[derive(Deserialize)] +#[cfg_attr(test, derive(serde::Serialize))] +pub struct DumpMeta { + pub settings: Settings, + pub primary_key: Option, +} + +#[derive(Deserialize, Debug, Clone, PartialEq, Eq)] +#[cfg_attr(test, derive(serde::Serialize))] +pub struct IndexUid(pub String); + +impl TryFrom for IndexUid { + type Error = IndexUidFormatError; + + fn try_from(uid: String) -> Result { + if !uid + .chars() + .all(|x| x.is_ascii_alphanumeric() || x == '-' || x == '_') + || uid.is_empty() + || uid.len() > 400 + { + Err(IndexUidFormatError { invalid_uid: uid }) + } else { + Ok(IndexUid(uid)) + } + } +} + +impl FromStr for IndexUid { + type Err = IndexUidFormatError; + + fn from_str(uid: &str) -> Result { + uid.to_string().try_into() + } +} + +impl From for String { + fn from(uid: IndexUid) -> Self { + uid.into_inner() + } +} + +#[derive(Debug)] +pub struct IndexUidFormatError { + pub invalid_uid: String, +} + +impl Display for IndexUidFormatError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!( + f, + "invalid index uid `{}`, the uid must be an integer \ + or a string containing only alphanumeric characters \ + a-z A-Z 0-9, hyphens - and underscores _.", + self.invalid_uid, + ) + } +} + +impl std::error::Error for IndexUidFormatError {} + +/// A type that tries to match either a star (*) or +/// any other thing that implements `FromStr`. +#[derive(Debug)] +#[cfg_attr(test, derive(serde::Serialize))] +pub enum StarOr { + Star, + Other(T), +} + +impl<'de, T, E> Deserialize<'de> for StarOr +where + T: FromStr, + E: Display, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + /// Serde can't differentiate between `StarOr::Star` and `StarOr::Other` without a tag. + /// Simply using `#[serde(untagged)]` + `#[serde(rename="*")]` will lead to attempting to + /// deserialize everything as a `StarOr::Other`, including "*". + /// [`#[serde(other)]`](https://serde.rs/variant-attrs.html#other) might have helped but is + /// not supported on untagged enums. + struct StarOrVisitor(PhantomData); + + impl<'de, T, FE> Visitor<'de> for StarOrVisitor + where + T: FromStr, + FE: Display, + { + type Value = StarOr; + + fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { + formatter.write_str("a string") + } + + fn visit_str(self, v: &str) -> Result + where + SE: serde::de::Error, + { + match v { + "*" => Ok(StarOr::Star), + v => { + let other = FromStr::from_str(v).map_err(|e: T::Err| { + SE::custom(format!("Invalid `other` value: {}", e)) + })?; + Ok(StarOr::Other(other)) + } + } + } + } + + deserializer.deserialize_str(StarOrVisitor(PhantomData)) + } +} diff --git a/dump/src/reader/v4/mod.rs b/dump/src/reader/v4/mod.rs index 82ccf67ed..95cb916c4 100644 --- a/dump/src/reader/v4/mod.rs +++ b/dump/src/reader/v4/mod.rs @@ -1 +1,276 @@ -// hello +use std::{ + fs::{self, File}, + io::{BufRead, BufReader}, + path::Path, +}; + +use serde::{Deserialize, Serialize}; +use tempfile::TempDir; +use time::OffsetDateTime; +use uuid::Uuid; + +mod keys; +mod meta; +mod settings; +mod tasks; + +use crate::{IndexMetadata, Result, Version}; + +use self::{ + keys::Key, + meta::{DumpMeta, IndexUuid}, + settings::{Checked, Settings}, + tasks::Task, +}; + +use super::{/* compat::v4_to_v5::CompatV4ToV5, */ DumpReader, IndexReader}; + +pub type Document = serde_json::Map; +pub type UpdateFile = File; + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct Metadata { + db_version: String, + index_db_size: usize, + update_db_size: usize, + #[serde(with = "time::serde::rfc3339")] + dump_date: OffsetDateTime, +} + +pub struct V4Reader { + dump: TempDir, + metadata: Metadata, + tasks: BufReader, + keys: BufReader, + index_uuid: Vec, +} + +impl V4Reader { + pub fn open(dump: TempDir) -> Result { + let meta_file = fs::read(dump.path().join("metadata.json"))?; + let metadata = serde_json::from_reader(&*meta_file)?; + let index_uuid = File::open(dump.path().join("index_uuids/data.jsonl"))?; + let index_uuid = BufReader::new(index_uuid); + let index_uuid = index_uuid + .lines() + .map(|line| -> Result<_> { Ok(serde_json::from_str(&line?)?) }) + .collect::>>()?; + + Ok(V4Reader { + metadata, + tasks: BufReader::new( + File::open(dump.path().join("updates").join("data.jsonl")).unwrap(), + ), + keys: BufReader::new(File::open(dump.path().join("keys"))?), + index_uuid, + dump, + }) + } + + // pub fn to_v5(self) -> CompatV4ToV5 { + // CompatV4ToV5::new(self) + // } + + pub fn version(&self) -> Version { + Version::V4 + } + + pub fn date(&self) -> Option { + Some(self.metadata.dump_date) + } + + pub fn instance_uid(&self) -> Result> { + let uuid = fs::read_to_string(self.dump.path().join("instance-uid"))?; + Ok(Some(Uuid::parse_str(&uuid)?)) + } + + pub fn indexes(&self) -> Result> + '_> { + Ok(self.index_uuid.iter().map(|index| -> Result<_> { + Ok(V4IndexReader::new( + index.uid.clone(), + &self + .dump + .path() + .join("indexes") + .join(index.index_meta.uuid.to_string()), + )?) + })) + } + + pub fn tasks(&mut self) -> impl Iterator)>> + '_ { + (&mut self.tasks).lines().map(|line| -> Result<_> { + let task: Task = serde_json::from_str(&line?)?; + if !task.is_finished() { + if let Some(uuid) = task.get_content_uuid() { + let update_file_path = self + .dump + .path() + .join("updates") + .join("updates_files") + .join(uuid.to_string()); + Ok((task, Some(File::open(update_file_path).unwrap()))) + } else { + Ok((task, None)) + } + } else { + Ok((task, None)) + } + }) + } + + pub fn keys(&mut self) -> impl Iterator> + '_ { + (&mut self.keys) + .lines() + .map(|line| -> Result<_> { Ok(serde_json::from_str(&line?)?) }) + } +} + +pub struct V4IndexReader { + metadata: IndexMetadata, + settings: Settings, + + documents: BufReader, +} + +impl V4IndexReader { + pub fn new(name: String, path: &Path) -> Result { + let meta = File::open(path.join("meta.json"))?; + let meta: DumpMeta = serde_json::from_reader(meta)?; + + let metadata = IndexMetadata { + uid: name, + primary_key: meta.primary_key, + // FIXME: Iterate over the whole task queue to find the creation and last update date. + created_at: OffsetDateTime::now_utc(), + updated_at: OffsetDateTime::now_utc(), + }; + + let ret = V4IndexReader { + metadata, + settings: meta.settings.check(), + documents: BufReader::new(File::open(path.join("documents.jsonl"))?), + }; + + Ok(ret) + } + + pub fn metadata(&self) -> &IndexMetadata { + &self.metadata + } + + pub fn documents(&mut self) -> Result> + '_> { + Ok((&mut self.documents) + .lines() + .map(|line| -> Result<_> { Ok(serde_json::from_str(&line?)?) })) + } + + pub fn settings(&mut self) -> Result> { + Ok(self.settings.clone()) + } +} + +#[cfg(test)] +pub(crate) mod test { + use std::{fs::File, io::BufReader}; + + use flate2::bufread::GzDecoder; + use tempfile::TempDir; + + use super::*; + + #[test] + fn read_dump_v4() { + let dump = File::open("tests/assets/v4.dump").unwrap(); + let dir = TempDir::new().unwrap(); + let mut dump = BufReader::new(dump); + let gz = GzDecoder::new(&mut dump); + let mut archive = tar::Archive::new(gz); + archive.unpack(dir.path()).unwrap(); + + let mut dump = V4Reader::open(dir).unwrap(); + + // top level infos + insta::assert_display_snapshot!(dump.date().unwrap(), @"2022-10-06 12:53:49.131989609 +00:00:00"); + insta::assert_display_snapshot!(dump.instance_uid().unwrap().unwrap(), @"9e15e977-f2ae-4761-943f-1eaf75fd736d"); + + // tasks + let tasks = dump.tasks().collect::>>().unwrap(); + let (tasks, update_files): (Vec<_>, Vec<_>) = tasks.into_iter().unzip(); + insta::assert_json_snapshot!(tasks); + assert_eq!(update_files.len(), 10); + assert!(update_files[0].is_some()); // the enqueued document addition + assert!(update_files[1..].iter().all(|u| u.is_none())); // everything already processed + + // keys + let keys = dump.keys().collect::>>().unwrap(); + insta::assert_json_snapshot!(keys); + + // indexes + let mut indexes = dump.indexes().unwrap().collect::>>().unwrap(); + // the index are not ordered in any way by default + indexes.sort_by_key(|index| index.metadata().uid.to_string()); + + let mut products = indexes.pop().unwrap(); + let mut movies = indexes.pop().unwrap(); + let mut spells = indexes.pop().unwrap(); + assert!(indexes.is_empty()); + + // products + insta::assert_json_snapshot!(products.metadata(), { ".createdAt" => "[now]", ".updatedAt" => "[now]" }, @r###" + { + "uid": "products", + "primaryKey": "sku", + "createdAt": "[now]", + "updatedAt": "[now]" + } + "###); + + insta::assert_debug_snapshot!(products.settings()); + let documents = products + .documents() + .unwrap() + .collect::>>() + .unwrap(); + assert_eq!(documents.len(), 10); + insta::assert_json_snapshot!(documents); + + // movies + insta::assert_json_snapshot!(movies.metadata(), { ".createdAt" => "[now]", ".updatedAt" => "[now]" }, @r###" + { + "uid": "movies", + "primaryKey": "id", + "createdAt": "[now]", + "updatedAt": "[now]" + } + "###); + + insta::assert_debug_snapshot!(movies.settings()); + let documents = movies + .documents() + .unwrap() + .collect::>>() + .unwrap(); + assert_eq!(documents.len(), 110); + insta::assert_debug_snapshot!(documents); + + // spells + insta::assert_json_snapshot!(spells.metadata(), { ".createdAt" => "[now]", ".updatedAt" => "[now]" }, @r###" + { + "uid": "dnd_spells", + "primaryKey": "index", + "createdAt": "[now]", + "updatedAt": "[now]" + } + "###); + + insta::assert_debug_snapshot!(spells.settings()); + let documents = spells + .documents() + .unwrap() + .collect::>>() + .unwrap(); + assert_eq!(documents.len(), 10); + insta::assert_json_snapshot!(documents); + } +} diff --git a/dump/src/reader/v4/settings.rs b/dump/src/reader/v4/settings.rs new file mode 100644 index 000000000..099473329 --- /dev/null +++ b/dump/src/reader/v4/settings.rs @@ -0,0 +1,266 @@ +use std::{ + collections::{BTreeMap, BTreeSet}, + marker::PhantomData, + num::NonZeroUsize, +}; + +use serde::{Deserialize, Deserializer}; + +#[cfg(test)] +fn serialize_with_wildcard( + field: &Setting>, + s: S, +) -> std::result::Result +where + S: serde::Serializer, +{ + use serde::Serialize; + + let wildcard = vec!["*".to_string()]; + match field { + Setting::Set(value) => Some(value), + Setting::Reset => Some(&wildcard), + Setting::NotSet => None, + } + .serialize(s) +} + +#[derive(Clone, Default, Debug, PartialEq)] +#[cfg_attr(test, derive(serde::Serialize))] +pub struct Checked; + +#[derive(Clone, Default, Debug, Deserialize, PartialEq)] +#[cfg_attr(test, derive(serde::Serialize))] +pub struct Unchecked; + +#[derive(Debug, Clone, Default, Deserialize, PartialEq)] +#[cfg_attr(test, derive(serde::Serialize))] +#[serde(deny_unknown_fields)] +#[serde(rename_all = "camelCase")] +pub struct MinWordSizeTyposSetting { + #[serde(default, skip_serializing_if = "Setting::is_not_set")] + pub one_typo: Setting, + #[serde(default, skip_serializing_if = "Setting::is_not_set")] + pub two_typos: Setting, +} + +#[derive(Debug, Clone, Default, Deserialize, PartialEq)] +#[cfg_attr(test, derive(serde::Serialize))] +#[serde(deny_unknown_fields)] +#[serde(rename_all = "camelCase")] +pub struct TypoSettings { + #[serde(default, skip_serializing_if = "Setting::is_not_set")] + pub enabled: Setting, + #[serde(default, skip_serializing_if = "Setting::is_not_set")] + pub min_word_size_for_typos: Setting, + #[serde(default, skip_serializing_if = "Setting::is_not_set")] + pub disable_on_words: Setting>, + #[serde(default, skip_serializing_if = "Setting::is_not_set")] + pub disable_on_attributes: Setting>, +} +/// 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` from a `Settings`. +#[derive(Debug, Clone, Default, Deserialize, PartialEq)] +#[cfg_attr(test, derive(serde::Serialize))] +#[serde(deny_unknown_fields)] +#[serde(rename_all = "camelCase")] +#[serde(bound( + serialize = "T: serde::Serialize", + deserialize = "T: Deserialize<'static>" +))] +pub struct Settings { + #[serde( + default, + serialize_with = "serialize_with_wildcard", + skip_serializing_if = "Setting::is_not_set" + )] + pub displayed_attributes: Setting>, + + #[serde( + default, + serialize_with = "serialize_with_wildcard", + skip_serializing_if = "Setting::is_not_set" + )] + pub searchable_attributes: Setting>, + + #[serde(default, skip_serializing_if = "Setting::is_not_set")] + pub filterable_attributes: Setting>, + #[serde(default, skip_serializing_if = "Setting::is_not_set")] + pub sortable_attributes: Setting>, + #[serde(default, skip_serializing_if = "Setting::is_not_set")] + pub ranking_rules: Setting>, + #[serde(default, skip_serializing_if = "Setting::is_not_set")] + pub stop_words: Setting>, + #[serde(default, skip_serializing_if = "Setting::is_not_set")] + pub synonyms: Setting>>, + #[serde(default, skip_serializing_if = "Setting::is_not_set")] + pub distinct_attribute: Setting, + #[serde(default, skip_serializing_if = "Setting::is_not_set")] + pub typo_tolerance: Setting, + + #[serde(skip)] + pub _kind: PhantomData, +} + +impl Settings { + pub fn cleared() -> Settings { + Settings { + displayed_attributes: Setting::Reset, + searchable_attributes: Setting::Reset, + filterable_attributes: Setting::Reset, + sortable_attributes: Setting::Reset, + ranking_rules: Setting::Reset, + stop_words: Setting::Reset, + synonyms: Setting::Reset, + distinct_attribute: Setting::Reset, + typo_tolerance: Setting::Reset, + _kind: PhantomData, + } + } + + pub fn into_unchecked(self) -> Settings { + let Self { + displayed_attributes, + searchable_attributes, + filterable_attributes, + sortable_attributes, + ranking_rules, + stop_words, + synonyms, + distinct_attribute, + typo_tolerance, + .. + } = self; + + Settings { + displayed_attributes, + searchable_attributes, + filterable_attributes, + sortable_attributes, + ranking_rules, + stop_words, + synonyms, + distinct_attribute, + typo_tolerance, + _kind: PhantomData, + } + } +} + +impl Settings { + pub fn check(self) -> Settings { + let displayed_attributes = match self.displayed_attributes { + Setting::Set(fields) => { + if fields.iter().any(|f| f == "*") { + Setting::Reset + } else { + Setting::Set(fields) + } + } + otherwise => otherwise, + }; + + let searchable_attributes = match self.searchable_attributes { + Setting::Set(fields) => { + if fields.iter().any(|f| f == "*") { + Setting::Reset + } else { + Setting::Set(fields) + } + } + otherwise => otherwise, + }; + + Settings { + displayed_attributes, + searchable_attributes, + filterable_attributes: self.filterable_attributes, + sortable_attributes: self.sortable_attributes, + ranking_rules: self.ranking_rules, + stop_words: self.stop_words, + synonyms: self.synonyms, + distinct_attribute: self.distinct_attribute, + typo_tolerance: self.typo_tolerance, + _kind: PhantomData, + } + } +} + +#[derive(Debug, Clone, Deserialize)] +#[cfg_attr(test, derive(serde::Serialize))] +#[serde(deny_unknown_fields)] +#[serde(rename_all = "camelCase")] +pub struct Facets { + pub level_group_size: Option, + pub min_level_size: Option, +} + +#[derive(Debug, Clone, PartialEq, Copy)] +pub enum Setting { + Set(T), + Reset, + NotSet, +} + +impl Default for Setting { + fn default() -> Self { + Self::NotSet + } +} + +impl Setting { + pub fn set(self) -> Option { + match self { + Self::Set(value) => Some(value), + _ => None, + } + } + + pub const fn as_ref(&self) -> Setting<&T> { + match *self { + Self::Set(ref value) => Setting::Set(value), + Self::Reset => Setting::Reset, + Self::NotSet => Setting::NotSet, + } + } + + pub const fn is_not_set(&self) -> bool { + matches!(self, Self::NotSet) + } + + /// If `Self` is `Reset`, then map self to `Set` with the provided `val`. + pub fn or_reset(self, val: T) -> Self { + match self { + Self::Reset => Self::Set(val), + otherwise => otherwise, + } + } +} + +#[cfg(test)] +impl serde::Serialize for Setting { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + match self { + Self::Set(value) => Some(value), + // Usually not_set isn't serialized by setting skip_serializing_if field attribute + Self::NotSet | Self::Reset => None, + } + .serialize(serializer) + } +} + +impl<'de, T: Deserialize<'de>> Deserialize<'de> for Setting { + fn deserialize(deserializer: D) -> std::result::Result + where + D: Deserializer<'de>, + { + Deserialize::deserialize(deserializer).map(|x| match x { + Some(x) => Self::Set(x), + None => Self::Reset, // Reset is forced by sending null value + }) + } +} diff --git a/dump/src/reader/v4/snapshots/dump__reader__v4__test__read_dump_v4-10.snap b/dump/src/reader/v4/snapshots/dump__reader__v4__test__read_dump_v4-10.snap new file mode 100644 index 000000000..7786a115d --- /dev/null +++ b/dump/src/reader/v4/snapshots/dump__reader__v4__test__read_dump_v4-10.snap @@ -0,0 +1,1252 @@ +--- +source: dump/src/reader/v4/mod.rs +expression: documents +--- +[ + { + "id": String("287947"), + "title": String("Shazam!"), + "poster": String("https://image.tmdb.org/t/p/w500/xnopI5Xtky18MPhK40cZAGAOVeV.jpg"), + "overview": String("A boy is given the ability to become an adult superhero in times of need with a single magic word."), + "release_date": Number(1553299200), + "genres": Array [ + String("Action"), + String("Comedy"), + String("Fantasy"), + ], + }, + { + "id": String("299537"), + "title": String("Captain Marvel"), + "poster": String("https://image.tmdb.org/t/p/w500/AtsgWhDnHTq68L0lLsUrCnM7TjG.jpg"), + "overview": String("The story follows Carol Danvers as she becomes one of the universe’s most powerful heroes when Earth is caught in the middle of a galactic war between two alien races. Set in the 1990s, Captain Marvel is an all-new adventure from a previously unseen period in the history of the Marvel Cinematic Universe."), + "release_date": Number(1551830400), + "genres": Array [ + String("Action"), + String("Adventure"), + String("Science Fiction"), + ], + }, + { + "id": String("522681"), + "title": String("Escape Room"), + "poster": String("https://image.tmdb.org/t/p/w500/8Ls1tZ6qjGzfGHjBB7ihOnf7f0b.jpg"), + "overview": String("Six strangers find themselves in circumstances beyond their control, and must use their wits to survive."), + "release_date": Number(1546473600), + "genres": Array [ + String("Thriller"), + String("Action"), + String("Horror"), + String("Science Fiction"), + ], + }, + { + "id": String("166428"), + "title": String("How to Train Your Dragon: The Hidden World"), + "poster": String("https://image.tmdb.org/t/p/w500/xvx4Yhf0DVH8G4LzNISpMfFBDy2.jpg"), + "overview": String("As Hiccup fulfills his dream of creating a peaceful dragon utopia, Toothless’ discovery of an untamed, elusive mate draws the Night Fury away. When danger mounts at home and Hiccup’s reign as village chief is tested, both dragon and rider must make impossible decisions to save their kind."), + "release_date": Number(1546473600), + "genres": Array [ + String("Animation"), + String("Family"), + String("Adventure"), + ], + }, + { + "id": String("450465"), + "title": String("Glass"), + "poster": String("https://image.tmdb.org/t/p/w500/svIDTNUoajS8dLEo7EosxvyAsgJ.jpg"), + "overview": String("In a series of escalating encounters, security guard David Dunn uses his supernatural abilities to track Kevin Wendell Crumb, a disturbed man who has twenty-four personalities. Meanwhile, the shadowy presence of Elijah Price emerges as an orchestrator who holds secrets critical to both men."), + "release_date": Number(1547596800), + "genres": Array [ + String("Documentary"), + ], + }, + { + "id": String("495925"), + "title": String("Doraemon the Movie: Nobita's Treasure Island"), + "poster": String("https://image.tmdb.org/t/p/w500/xiLRClQmKSVAbiu6rgCRzNQjcSX.jpg"), + "overview": String("The story is based on Robert Louis Stevenson's Treasure Island novel."), + "release_date": Number(1520035200), + "genres": Array [ + String("Animation"), + ], + }, + { + "id": String("329996"), + "title": String("Dumbo"), + "poster": String("https://image.tmdb.org/t/p/w500/deTOAcMWuHTjOUPQphwcPFFfTQz.jpg"), + "overview": String("A young elephant, whose oversized ears enable him to fly, helps save a struggling circus, but when the circus plans a new venture, Dumbo and his friends discover dark secrets beneath its shiny veneer."), + "release_date": Number(1553644800), + "genres": Array [ + String("Adventure"), + String("Family"), + String("Fantasy"), + ], + }, + { + "id": String("299536"), + "title": String("Avengers: Infinity War"), + "poster": String("https://image.tmdb.org/t/p/w500/7WsyChQLEftFiDOVTGkv3hFpyyt.jpg"), + "overview": String("As the Avengers and their allies have continued to protect the world from threats too large for any one hero to handle, a new danger has emerged from the cosmic shadows: Thanos. A despot of intergalactic infamy, his goal is to collect all six Infinity Stones, artifacts of unimaginable power, and use them to inflict his twisted will on all of reality. Everything the Avengers have fought for has led up to this moment - the fate of Earth and existence itself has never been more uncertain."), + "release_date": Number(1524618000), + "genres": Array [ + String("Adventure"), + String("Action"), + String("Science Fiction"), + ], + }, + { + "id": String("458723"), + "title": String("Us"), + "poster": String("https://image.tmdb.org/t/p/w500/ux2dU1jQ2ACIMShzB3yP93Udpzc.jpg"), + "overview": String("Husband and wife Gabe and Adelaide Wilson take their kids to their beach house expecting to unplug and unwind with friends. But as night descends, their serenity turns to tension and chaos when some shocking visitors arrive uninvited."), + "release_date": Number(1552521600), + "genres": Array [ + String("Documentary"), + String("Family"), + ], + }, + { + "id": String("424783"), + "title": String("Bumblebee"), + "poster": String("https://image.tmdb.org/t/p/w500/fw02ONlDhrYjTSZV8XO6hhU3ds3.jpg"), + "overview": String("On the run in the year 1987, Bumblebee finds refuge in a junkyard in a small Californian beach town. Charlie, on the cusp of turning 18 and trying to find her place in the world, discovers Bumblebee, battle-scarred and broken. When Charlie revives him, she quickly learns this is no ordinary yellow VW bug."), + "release_date": Number(1544832000), + "genres": Array [ + String("Action"), + String("Adventure"), + String("Science Fiction"), + ], + }, + { + "id": String("920"), + "title": String("Cars"), + "poster": String("https://image.tmdb.org/t/p/w500/qa6HCwP4Z15l3hpsASz3auugEW6.jpg"), + "overview": String("Lightning McQueen, a hotshot rookie race car driven to succeed, discovers that life is about the journey, not the finish line, when he finds himself unexpectedly detoured in the sleepy Route 66 town of Radiator Springs. On route across the country to the big Piston Cup Championship in California to compete against two seasoned pros, McQueen gets to know the town's offbeat characters."), + "release_date": Number(1149728400), + "genres": Array [ + String("Animation"), + String("Adventure"), + String("Comedy"), + String("Family"), + ], + }, + { + "id": String("299534"), + "title": String("Avengers: Endgame"), + "poster": String("https://image.tmdb.org/t/p/w500/ulzhLuWrPK07P1YkdWQLZnQh1JL.jpg"), + "overview": String("After the devastating events of Avengers: Infinity War, the universe is in ruins due to the efforts of the Mad Titan, Thanos. With the help of remaining allies, the Avengers must assemble once more in order to undo Thanos' actions and restore order to the universe once and for all, no matter what consequences may be in store."), + "release_date": Number(1556067600), + "genres": Array [ + String("Adventure"), + String("Science Fiction"), + String("Action"), + ], + }, + { + "id": String("324857"), + "title": String("Spider-Man: Into the Spider-Verse"), + "poster": String("https://image.tmdb.org/t/p/w500/iiZZdoQBEYBv6id8su7ImL0oCbD.jpg"), + "overview": String("Miles Morales is juggling his life between being a high school student and being a spider-man. When Wilson 'Kingpin' Fisk uses a super collider, others from across the Spider-Verse are transported to this dimension."), + "release_date": Number(1544140800), + "genres": Array [ + String("Action"), + String("Adventure"), + String("Animation"), + String("Science Fiction"), + String("Comedy"), + ], + }, + { + "id": String("157433"), + "title": String("Pet Sematary"), + "poster": String("https://image.tmdb.org/t/p/w500/7SPhr7Qj39vbnfF9O2qHRYaKHAL.jpg"), + "overview": String("Louis Creed, his wife Rachel and their two children Gage and Ellie move to a rural home where they are welcomed and enlightened about the eerie 'Pet Sematary' located nearby. After the tragedy of their cat being killed by a truck, Louis resorts to burying it in the mysterious pet cemetery, which is definitely not as it seems, as it proves to the Creeds that sometimes dead is better."), + "release_date": Number(1554339600), + "genres": Array [ + String("Thriller"), + String("Horror"), + ], + }, + { + "id": String("456740"), + "title": String("Hellboy"), + "poster": String("https://image.tmdb.org/t/p/w500/bk8LyaMqUtaQ9hUShuvFznQYQKR.jpg"), + "overview": String("Hellboy comes to England, where he must defeat Nimue, Merlin's consort and the Blood Queen. But their battle will bring about the end of the world, a fate he desperately tries to turn away."), + "release_date": Number(1554944400), + "genres": Array [ + String("Fantasy"), + String("Action"), + ], + }, + { + "id": String("537915"), + "title": String("After"), + "poster": String("https://image.tmdb.org/t/p/w500/u3B2YKUjWABcxXZ6Nm9h10hLUbh.jpg"), + "overview": String("A young woman falls for a guy with a dark secret and the two embark on a rocky relationship."), + "release_date": Number(1554944400), + "genres": Array [ + String("Mystery"), + String("Drama"), + ], + }, + { + "id": String("485811"), + "title": String("Redcon-1"), + "poster": String("https://image.tmdb.org/t/p/w500/vVPrWngVJ2cfYAncBedQty69Dlf.jpg"), + "overview": String("After a zombie apocalypse spreads from a London prison, the UK is brought to its knees. The spread of the virus is temporarily contained but, without a cure, it’s only a matter of time before it breaks its boundaries and the biggest problem of all… any zombies with combat skills are now enhanced. With the South East of England quarantined from the rest of the world using fortified borders, intelligence finds that the scientist responsible for the outbreak is alive and well in London. With his recovery being the only hope of a cure, a squad of eight Special Forces soldiers is sent on a suicide mission to the city, now ruled by the undead, with a single task: get him out alive within 72 hours by any means necessary. What emerges is an unlikely pairing on a course to save humanity against ever-rising odds."), + "release_date": Number(1538096400), + "genres": Array [ + String("Action"), + String("Horror"), + ], + }, + { + "id": String("471507"), + "title": String("Destroyer"), + "poster": String("https://image.tmdb.org/t/p/w500/sHw9gTdo43nJL82py0oaROkXXNr.jpg"), + "overview": String("Erin Bell is an LAPD detective who, as a young cop, was placed undercover with a gang in the California desert with tragic results. When the leader of that gang re-emerges many years later, she must work her way back through the remaining members and into her own history with them to finally reckon with the demons that destroyed her past."), + "release_date": Number(1545696000), + "genres": Array [ + String("Horror"), + String("Thriller"), + ], + }, + { + "id": String("400650"), + "title": String("Mary Poppins Returns"), + "poster": String("https://image.tmdb.org/t/p/w500/uTVGku4LibMGyKgQvjBtv3OYfAX.jpg"), + "overview": String("In Depression-era London, a now-grown Jane and Michael Banks, along with Michael's three children, are visited by the enigmatic Mary Poppins following a personal loss. Through her unique magical skills, and with the aid of her friend Jack, she helps the family rediscover the joy and wonder missing in their lives."), + "release_date": Number(1544659200), + "genres": Array [ + String("Documentary"), + ], + }, + { + "id": String("297802"), + "title": String("Aquaman"), + "poster": String("https://image.tmdb.org/t/p/w500/5Kg76ldv7VxeX9YlcQXiowHgdX6.jpg"), + "overview": String("Once home to the most advanced civilization on Earth, Atlantis is now an underwater kingdom ruled by the power-hungry King Orm. With a vast army at his disposal, Orm plans to conquer the remaining oceanic people and then the surface world. Standing in his way is Arthur Curry, Orm's half-human, half-Atlantean brother and true heir to the throne."), + "release_date": Number(1544140800), + "genres": Array [ + String("Action"), + String("Adventure"), + String("TV Movie"), + ], + }, + { + "id": String("512196"), + "title": String("Happy Death Day 2U"), + "poster": String("https://image.tmdb.org/t/p/w500/4tdnePOkOOzwuGPEOAHp8UA4vqx.jpg"), + "overview": String("Collegian Tree Gelbman wakes up in horror to learn that she's stuck in a parallel universe. Her boyfriend Carter is now with someone else, and her friends and fellow students seem to be completely different versions of themselves. When Tree discovers that Carter's roommate has been altering time, she finds herself once again the target of a masked killer. When the psychopath starts to go after her inner circle, Tree soon realizes that she must die over and over again to save everyone."), + "release_date": Number(1550016000), + "genres": Array [ + String("Comedy"), + String("Horror"), + String("Science Fiction"), + ], + }, + { + "id": String("390634"), + "title": String("Fate/stay night: Heaven’s Feel II. lost butterfly"), + "poster": String("https://image.tmdb.org/t/p/w500/nInpnGCjhzVhsASIUAmgM1QIhYM.jpg"), + "overview": String("Theatrical-release adaptation of the visual novel 'Fate/stay night', following the third and final route. (Part 2 of a trilogy.)"), + "release_date": Number(1547251200), + "genres": Array [ + String("Animation"), + String("Action"), + String("Fantasy"), + String("Drama"), + ], + }, + { + "id": String("500682"), + "title": String("The Highwaymen"), + "poster": String("https://image.tmdb.org/t/p/w500/4bRYg4l12yDuJvAfqvUOPnBrxno.jpg"), + "overview": String("In 1934, Frank Hamer and Manny Gault, two former Texas Rangers, are commissioned to put an end to the wave of vicious crimes perpetrated by Bonnie Parker and Clyde Barrow, a notorious duo of infamous robbers and cold-blooded killers who nevertheless are worshiped by the public."), + "release_date": Number(1552608000), + "genres": Array [ + String("Music"), + ], + }, + { + "id": String("454294"), + "title": String("The Kid Who Would Be King"), + "poster": String("https://image.tmdb.org/t/p/w500/kBuvLX6zynQP0sjyqbXV4jNaZ4E.jpg"), + "overview": String("Old-school magic meets the modern world when young Alex stumbles upon the mythical sword Excalibur. He soon unites his friends and enemies, and they become knights who join forces with the legendary wizard Merlin. Together, they must save mankind from the wicked enchantress Morgana and her army of supernatural warriors."), + "release_date": Number(1547596800), + "genres": Array [ + String("Action"), + String("Adventure"), + String("Fantasy"), + String("Family"), + ], + }, + { + "id": String("543103"), + "title": String("Kamen Rider Heisei Generations FOREVER"), + "poster": String("https://image.tmdb.org/t/p/w500/kHMuyjlvNIwhCaDFiRwnl45wF7z.jpg"), + "overview": String("In the world of Sougo Tokiwa and Sento Kiryu, their 'companions' are losing their memories one after the other as they're replaced by other people. The Super Time Jacker, Tid , appears before them. He orders his powerful underlings, Another Double and Another Den-O, to pursue a young boy called Shingo. While fighting to protect Shingo, Sougo meets Ataru, a young man who loves Riders, but Ataru says that Kamen Riders aren't real. What is the meaning of those words? While the mystery deepens, the true enemy that Sougo and Sento must defeat appears in the Kuriogatake mountain..."), + "release_date": Number(1545436800), + "genres": Array [ + String("Action"), + String("Adventure"), + String("Science Fiction"), + ], + }, + { + "id": String("404368"), + "title": String("Ralph Breaks the Internet"), + "poster": String("https://image.tmdb.org/t/p/w500/qEnH5meR381iMpmCumAIMswcQw2.jpg"), + "overview": String("Video game bad guy Ralph and fellow misfit Vanellope von Schweetz must risk it all by traveling to the World Wide Web in search of a replacement part to save Vanellope's video game, 'Sugar Rush.' In way over their heads, Ralph and Vanellope rely on the citizens of the internet -- the netizens -- to help navigate their way, including an entrepreneur named Yesss, who is the head algorithm and the heart and soul of trend-making site BuzzzTube."), + "release_date": Number(1542672000), + "genres": Array [ + String("Family"), + String("Animation"), + String("Comedy"), + String("Adventure"), + ], + }, + { + "id": String("338952"), + "title": String("Fantastic Beasts: The Crimes of Grindelwald"), + "poster": String("https://image.tmdb.org/t/p/w500/fMMrl8fD9gRCFJvsx0SuFwkEOop.jpg"), + "overview": String("Gellert Grindelwald has escaped imprisonment and has begun gathering followers to his cause—elevating wizards above all non-magical beings. The only one capable of putting a stop to him is the wizard he once called his closest friend, Albus Dumbledore. However, Dumbledore will need to seek help from the wizard who had thwarted Grindelwald once before, his former student Newt Scamander, who agrees to help, unaware of the dangers that lie ahead. Lines are drawn as love and loyalty are tested, even among the truest friends and family, in an increasingly divided wizarding world."), + "release_date": Number(1542153600), + "genres": Array [ + String("Adventure"), + String("Fantasy"), + String("Family"), + ], + }, + { + "id": String("399579"), + "title": String("Alita: Battle Angel"), + "poster": String("https://image.tmdb.org/t/p/w500/xRWht48C2V8XNfzvPehyClOvDni.jpg"), + "overview": String("When Alita awakens with no memory of who she is in a future world she does not recognize, she is taken in by Ido, a compassionate doctor who realizes that somewhere in this abandoned cyborg shell is the heart and soul of a young woman with an extraordinary past."), + "release_date": Number(1548892800), + "genres": Array [ + String("Action"), + String("Science Fiction"), + ], + }, + { + "id": String("450001"), + "title": String("Master Z: Ip Man Legacy"), + "poster": String("https://image.tmdb.org/t/p/w500/6VxEvOF7QDovsG6ro9OVyjH07LF.jpg"), + "overview": String("After being defeated by Ip Man, Cheung Tin Chi is attempting to keep a low profile. While going about his business, he gets into a fight with a foreigner by the name of Davidson, who is a big boss behind the bar district. Tin Chi fights hard with Wing Chun and earns respect."), + "release_date": Number(1545264000), + "genres": Array [ + String("Action"), + ], + }, + { + "id": String("504172"), + "title": String("The Mule"), + "poster": String("https://image.tmdb.org/t/p/w500/klazQbxk3yfuZ8JcfO9jdKOZQJ7.jpg"), + "overview": String("Earl Stone, a man in his 80s who is broke, alone, and facing foreclosure of his business when he is offered a job that simply requires him to drive. Easy enough, but, unbeknownst to Earl, he’s just signed on as a drug courier for a Mexican cartel. He does so well that his cargo increases exponentially, and Earl hit the radar of hard-charging DEA agent Colin Bates."), + "release_date": Number(1544745600), + "genres": Array [ + String("Crime"), + String("Comedy"), + ], + }, + { + "id": String("527729"), + "title": String("Asterix: The Secret of the Magic Potion"), + "poster": String("https://image.tmdb.org/t/p/w500/wmMq5ypRNJbWpdhC9aPjpdx1MMp.jpg"), + "overview": String("Following a fall during mistletoe picking, Druid Getafix decides that it is time to secure the future of the village. Accompanied by Asterix and Obelix, he undertakes to travel the Gallic world in search of a talented young druid to transmit the Secret of the Magic Potion."), + "release_date": Number(1543968000), + "genres": Array [ + String("Animation"), + String("Family"), + String("Comedy"), + String("Adventure"), + ], + }, + { + "id": String("118340"), + "title": String("Guardians of the Galaxy"), + "poster": String("https://image.tmdb.org/t/p/w500/r7vmZjiyZw9rpJMQJdXpjgiCOk9.jpg"), + "overview": String("Light years from Earth, 26 years after being abducted, Peter Quill finds himself the prime target of a manhunt after discovering an orb wanted by Ronan the Accuser."), + "release_date": Number(1406682000), + "genres": Array [], + }, + { + "id": String("411728"), + "title": String("The Professor and the Madman"), + "poster": String("https://image.tmdb.org/t/p/w500/gtGCDLhfjW96qVarwctnuTpGOtD.jpg"), + "overview": String("Professor James Murray begins work compiling words for the first edition of the Oxford English Dictionary in the mid 19th century and receives over 10,000 entries from a patient at Broadmoor Criminal Lunatic Asylum , Dr William Minor."), + "release_date": Number(1551916800), + "genres": Array [ + String("Drama"), + String("History"), + String("Mystery"), + String("Thriller"), + ], + }, + { + "id": String("527641"), + "title": String("Five Feet Apart"), + "poster": String("https://image.tmdb.org/t/p/w500/kreTuJBkUjVWePRfhHZuYfhNE1T.jpg"), + "overview": String("Seventeen-year-old Stella spends most of her time in the hospital as a cystic fibrosis patient. Her life is full of routines, boundaries and self-control -- all of which get put to the test when she meets Will, an impossibly charming teen who has the same illness."), + "release_date": Number(1552608000), + "genres": Array [ + String("Romance"), + String("Drama"), + ], + }, + { + "id": String("576071"), + "title": String("Unplanned"), + "poster": String("https://image.tmdb.org/t/p/w500/tvCtAz8z5tF49a7q9RRHvxiTjzv.jpg"), + "overview": String("As one of the youngest Planned Parenthood clinic directors in the nation, Abby Johnson was involved in upwards of 22,000 abortions and counseled countless women on their reproductive choices. Her passion surrounding a woman's right to choose led her to become a spokesperson for Planned Parenthood, fighting to enact legislation for the cause she so deeply believed in. Until the day she saw something that changed everything."), + "release_date": Number(1553126400), + "genres": Array [ + String("Drama"), + ], + }, + { + "id": String("283995"), + "title": String("Guardians of the Galaxy Vol. 2"), + "poster": String("https://image.tmdb.org/t/p/w500/y4MBh0EjBlMuOzv9axM4qJlmhzz.jpg"), + "overview": String("The Guardians must fight to keep their newfound family together as they unravel the mysteries of Peter Quill's true parentage."), + "release_date": Number(1492563600), + "genres": Array [ + String("Action"), + String("Adventure"), + String("Comedy"), + String("Science Fiction"), + ], + }, + { + "id": String("464504"), + "title": String("A Madea Family Funeral"), + "poster": String("https://image.tmdb.org/t/p/w500/sFvOTUlZrIxCLdmz1fC16wK0lme.jpg"), + "overview": String("A joyous family reunion becomes a hilarious nightmare as Madea and the crew travel to backwoods Georgia, where they find themselves unexpectedly planning a funeral that might unveil unpleasant family secrets."), + "release_date": Number(1551398400), + "genres": Array [ + String("Comedy"), + ], + }, + { + "id": String("428078"), + "title": String("Mortal Engines"), + "poster": String("https://image.tmdb.org/t/p/w500/gLhYg9NIvIPKVRTtvzCWnp1qJWG.jpg"), + "overview": String("Many thousands of years in the future, Earth’s cities roam the globe on huge wheels, devouring each other in a struggle for ever diminishing resources. On one of these massive traction cities, the old London, Tom Natsworthy has an unexpected encounter with a mysterious young woman from the wastelands who will change the course of his life forever."), + "release_date": Number(1543276800), + "genres": Array [ + String("Adventure"), + String("Fantasy"), + ], + }, + { + "id": String("460539"), + "title": String("Kuppathu Raja"), + "poster": String("https://image.tmdb.org/t/p/w500/wzLde7keWQqWA0CJYVz0X5RVKjd.jpg"), + "overview": String("Kuppathu Raja is an upcoming Tamil comedy drama film directed by Baba Bhaskar. The film features G. V. Prakash Kumar and Parthiban in the lead roles."), + "release_date": Number(1554426000), + "genres": Array [ + String("Drama"), + ], + }, + { + "id": String("24428"), + "title": String("The Avengers"), + "poster": String("https://image.tmdb.org/t/p/w500/RYMX2wcKCBAr24UyPD7xwmjaTn.jpg"), + "overview": String("When an unexpected enemy emerges and threatens global safety and security, Nick Fury, director of the international peacekeeping agency known as S.H.I.E.L.D., finds himself in need of a team to pull the world back from the brink of disaster. Spanning the globe, a daring recruitment effort begins!"), + "release_date": Number(1335315600), + "genres": Array [ + String("Documentary"), + ], + }, + { + "id": String("120"), + "title": String("The Lord of the Rings: The Fellowship of the Ring"), + "poster": String("https://image.tmdb.org/t/p/w500/6oom5QYQ2yQTMJIbnvbkBL9cHo6.jpg"), + "overview": String("Young hobbit Frodo Baggins, after inheriting a mysterious ring from his uncle Bilbo, must leave his home in order to keep it from falling into the hands of its evil creator. Along the way, a fellowship is formed to protect the ringbearer and make sure that the ring arrives at its final destination: Mt. Doom, the only place where it can be destroyed."), + "release_date": Number(1008633600), + "genres": Array [ + String("Adventure"), + String("Fantasy"), + String("Action"), + ], + }, + { + "id": String("671"), + "title": String("Harry Potter and the Philosopher's Stone"), + "poster": String("https://image.tmdb.org/t/p/w500/wuMc08IPKEatf9rnMNXvIDxqP4W.jpg"), + "overview": String("Harry Potter has lived under the stairs at his aunt and uncle's house his whole life. But on his 11th birthday, he learns he's a powerful wizard -- with a place waiting for him at the Hogwarts School of Witchcraft and Wizardry. As he learns to harness his newfound powers with the help of the school's kindly headmaster, Harry uncovers the truth about his parents' deaths -- and about the villain who's to blame."), + "release_date": Number(1005868800), + "genres": Array [ + String("Adventure"), + String("Fantasy"), + String("Family"), + ], + }, + { + "id": String("500904"), + "title": String("A Vigilante"), + "poster": String("https://image.tmdb.org/t/p/w500/x5MSMGVagNINIWyZaxdjLarTDM3.jpg"), + "overview": String("A vigilante helps victims escape their domestic abusers."), + "release_date": Number(1553817600), + "genres": Array [ + String("Thriller"), + String("Drama"), + ], + }, + { + "id": String("284053"), + "title": String("Thor: Ragnarok"), + "poster": String("https://image.tmdb.org/t/p/w500/rzRwTcFvttcN1ZpX2xv4j3tSdJu.jpg"), + "overview": String("Thor is imprisoned on the other side of the universe and finds himself in a race against time to get back to Asgard to stop Ragnarok, the destruction of his home-world and the end of Asgardian civilization, at the hands of an all-powerful new threat, the ruthless Hela."), + "release_date": Number(1508893200), + "genres": Array [ + String("Action"), + String("Adventure"), + String("Comedy"), + String("Fantasy"), + ], + }, + { + "id": String("424694"), + "title": String("Bohemian Rhapsody"), + "poster": String("https://image.tmdb.org/t/p/w500/lHu1wtNaczFPGFDTrjCSzeLPTKN.jpg"), + "overview": String("Singer Freddie Mercury, guitarist Brian May, drummer Roger Taylor and bass guitarist John Deacon take the music world by storm when they form the rock 'n' roll band Queen in 1970. Hit songs become instant classics. When Mercury's increasingly wild lifestyle starts to spiral out of control, Queen soon faces its greatest challenge yet – finding a way to keep the band together amid the success and excess."), + "release_date": Number(1540342800), + "genres": Array [ + String("Music"), + String("Documentary"), + ], + }, + { + "id": String("508763"), + "title": String("A Dog's Way Home"), + "poster": String("https://image.tmdb.org/t/p/w500/pZn87R7gtmMCGGO8KeaAfZDhXLg.jpg"), + "overview": String("A Dog’s Way Home chronicles the heartwarming adventure of Bella, a dog who embarks on an epic 400-mile journey home after she is separated from her beloved human."), + "release_date": Number(1547078400), + "genres": Array [ + String("Drama"), + String("Family"), + String("Adventure"), + ], + }, + { + "id": String("284054"), + "title": String("Black Panther"), + "poster": String("https://image.tmdb.org/t/p/w500/uxzzxijgPIY7slzFvMotPv8wjKA.jpg"), + "overview": String("King T'Challa returns home from America to the reclusive, technologically advanced African nation of Wakanda to serve as his country's new leader. However, T'Challa soon finds that he is challenged for the throne by factions within his own country as well as without. Using powers reserved to Wakandan kings, T'Challa assumes the Black Panther mantel to join with girlfriend Nakia, the queen-mother, his princess-kid sister, members of the Dora Milaje (the Wakandan 'special forces') and an American secret agent, to prevent Wakanda from being dragged into a world war."), + "release_date": Number(1518480000), + "genres": Array [ + String("Family"), + String("Drama"), + ], + }, + { + "id": String("335983"), + "title": String("Venom"), + "poster": String("https://image.tmdb.org/t/p/w500/2uNW4WbgBXL25BAbXGLnLqX71Sw.jpg"), + "overview": String("Investigative journalist Eddie Brock attempts a comeback following a scandal, but accidentally becomes the host of Venom, a violent, super powerful alien symbiote. Soon, he must rely on his newfound powers to protect the world from a shadowy organization looking for a symbiote of their own."), + "release_date": Number(1538096400), + "genres": Array [ + String("Thriller"), + ], + }, + { + "id": String("440472"), + "title": String("The Upside"), + "poster": String("https://image.tmdb.org/t/p/w500/hPZ2caow1PCND6qnerfgn6RTXdm.jpg"), + "overview": String("Phillip is a wealthy quadriplegic who needs a caretaker to help him with his day-to-day routine in his New York penthouse. He decides to hire Dell, a struggling parolee who's trying to reconnect with his ex and his young son. Despite coming from two different worlds, an unlikely friendship starts to blossom."), + "release_date": Number(1547078400), + "genres": Array [ + String("Drama"), + ], + }, + { + "id": String("363088"), + "title": String("Ant-Man and the Wasp"), + "poster": String("https://image.tmdb.org/t/p/w500/eivQmS3wqzqnQWILHLc4FsEfcXP.jpg"), + "overview": String("Just when his time under house arrest is about to end, Scott Lang once again puts his freedom at risk to help Hope van Dyne and Dr. Hank Pym dive into the quantum realm and try to accomplish, against time and any chance of success, a very dangerous rescue mission."), + "release_date": Number(1530666000), + "genres": Array [ + String("Action"), + String("Adventure"), + String("Science Fiction"), + String("Comedy"), + ], + }, + { + "id": String("351286"), + "title": String("Jurassic World: Fallen Kingdom"), + "poster": String("https://image.tmdb.org/t/p/w500/c9XxwwhPHdaImA2f1WEfEsbhaFB.jpg"), + "overview": String("Three years after the demise of Jurassic World, a volcanic eruption threatens the remaining dinosaurs on the isla Nublar, so Claire Dearing, the former park manager, recruits Owen Grady to help prevent the extinction of the dinosaurs once again."), + "release_date": Number(1528246800), + "genres": Array [ + String("Action"), + String("Adventure"), + String("Science Fiction"), + ], + }, + { + "id": String("441384"), + "title": String("The Beach Bum"), + "poster": String("https://image.tmdb.org/t/p/w500/iXMxdC7T0t3dxislnUNybcvJmAH.jpg"), + "overview": String("An irreverent comedy about the misadventures of Moondog, a rebellious stoner and lovable rogue who lives large."), + "release_date": Number(1553126400), + "genres": Array [ + String("Comedy"), + ], + }, + { + "id": String("480530"), + "title": String("Creed II"), + "poster": String("https://image.tmdb.org/t/p/w500/v3QyboWRoA4O9RbcsqH8tJMe8EB.jpg"), + "overview": String("Between personal obligations and training for his next big fight against an opponent with ties to his family's past, Adonis Creed is up against the challenge of his life."), + "release_date": Number(1542758400), + "genres": Array [ + String("Drama"), + ], + }, + { + "id": String("399361"), + "title": String("Triple Frontier"), + "poster": String("https://image.tmdb.org/t/p/w500/aBw8zYuAljVM1FeK5bZKITPH8ZD.jpg"), + "overview": String("Struggling to make ends meet, former special ops soldiers reunite for a high-stakes heist: stealing $75 million from a South American drug lord."), + "release_date": Number(1551830400), + "genres": Array [ + String("Action"), + String("Thriller"), + String("Crime"), + String("Adventure"), + ], + }, + { + "id": String("122917"), + "title": String("The Hobbit: The Battle of the Five Armies"), + "poster": String("https://image.tmdb.org/t/p/w500/xT98tLqatZPQApyRmlPL12LtiWp.jpg"), + "overview": String("Immediately after the events of The Desolation of Smaug, Bilbo and the dwarves try to defend Erebor's mountain of treasure from others who claim it: the men of the ruined Laketown and the elves of Mirkwood. Meanwhile an army of Orcs led by Azog the Defiler is marching on Erebor, fueled by the rise of the dark lord Sauron. Dwarves, elves and men must unite, and the hope for Middle-Earth falls into Bilbo's hands."), + "release_date": Number(1418169600), + "genres": Array [ + String("Action"), + String("Adventure"), + String("Fantasy"), + ], + }, + { + "id": String("400157"), + "title": String("Wonder Park"), + "poster": String("https://image.tmdb.org/t/p/w500/8KomINZhIuJeB4oB7k7tkq8tmE.jpg"), + "overview": String("The story of a magnificent amusement park where the imagination of a wildly creative girl named June comes alive."), + "release_date": Number(1552521600), + "genres": Array [ + String("Comedy"), + String("Animation"), + String("Adventure"), + String("Family"), + String("Fantasy"), + ], + }, + { + "id": String("566555"), + "title": String("Detective Conan: The Fist of Blue Sapphire"), + "poster": String("https://image.tmdb.org/t/p/w500/jUfCBwhSTE02jTN9REJbHm2lRL8.jpg"), + "overview": String("23rd Detective Conan Movie."), + "release_date": Number(1555030800), + "genres": Array [ + String("Animation"), + String("Action"), + String("Drama"), + String("Mystery"), + String("Comedy"), + ], + }, + { + "id": String("438650"), + "title": String("Cold Pursuit"), + "poster": String("https://image.tmdb.org/t/p/w500/hXgmWPd1SuujRZ4QnKLzrj79PAw.jpg"), + "overview": String("Nels Coxman's quiet life comes crashing down when his beloved son dies under mysterious circumstances. His search for the truth soon becomes a quest for revenge as he seeks coldblooded justice against a drug lord and his inner circle."), + "release_date": Number(1549497600), + "genres": Array [ + String("Action"), + ], + }, + { + "id": String("181808"), + "title": String("Star Wars: The Last Jedi"), + "poster": String("https://image.tmdb.org/t/p/w500/kOVEVeg59E0wsnXmF9nrh6OmWII.jpg"), + "overview": String("Rey develops her newly discovered abilities with the guidance of Luke Skywalker, who is unsettled by the strength of her powers. Meanwhile, the Resistance prepares to do battle with the First Order."), + "release_date": Number(1513123200), + "genres": Array [ + String("Documentary"), + ], + }, + { + "id": String("383498"), + "title": String("Deadpool 2"), + "poster": String("https://image.tmdb.org/t/p/w500/to0spRl1CMDvyUbOnbb4fTk3VAd.jpg"), + "overview": String("Wisecracking mercenary Deadpool battles the evil and powerful Cable and other bad guys to save a boy's life."), + "release_date": Number(1526346000), + "genres": Array [ + String("Action"), + String("Comedy"), + String("Adventure"), + ], + }, + { + "id": String("157336"), + "title": String("Interstellar"), + "poster": String("https://image.tmdb.org/t/p/w500/gEU2QniE6E77NI6lCU6MxlNBvIx.jpg"), + "overview": String("Interstellar chronicles the adventures of a group of explorers who make use of a newly discovered wormhole to surpass the limitations on human space travel and conquer the vast distances involved in an interstellar voyage."), + "release_date": Number(1415145600), + "genres": Array [ + String("Adventure"), + String("Drama"), + String("Science Fiction"), + ], + }, + { + "id": String("449985"), + "title": String("Triple Threat"), + "poster": String("https://image.tmdb.org/t/p/w500/cSpM3QxmoSLp4O1WAMQpUDcaB7R.jpg"), + "overview": String("A crime syndicate places a hit on a billionaire's daughter, making her the target of an elite assassin squad. A small band of down-and-out mercenaries protects her, fighting tooth and nail to stop the assassins from reaching their target."), + "release_date": Number(1552953600), + "genres": Array [ + String("Action"), + String("Thriller"), + ], + }, + { + "id": String("99861"), + "title": String("Avengers: Age of Ultron"), + "poster": String("https://image.tmdb.org/t/p/w500/4ssDuvEDkSArWEdyBl2X5EHvYKU.jpg"), + "overview": String("When Tony Stark tries to jumpstart a dormant peacekeeping program, things go awry and Earth’s Mightiest Heroes are put to the ultimate test as the fate of the planet hangs in the balance. As the villainous Ultron emerges, it is up to The Avengers to stop him from enacting his terrible plans, and soon uneasy alliances and unexpected action pave the way for an epic and unique global adventure."), + "release_date": Number(1429664400), + "genres": Array [ + String("Action"), + String("Adventure"), + String("Science Fiction"), + ], + }, + { + "id": String("271110"), + "title": String("Captain America: Civil War"), + "poster": String("https://image.tmdb.org/t/p/w500/rAGiXaUfPzY7CDEyNKUofk3Kw2e.jpg"), + "overview": String("Following the events of Age of Ultron, the collective governments of the world pass an act designed to regulate all superhuman activity. This polarizes opinion amongst the Avengers, causing two factions to side with Iron Man or Captain America, which causes an epic battle between former allies."), + "release_date": Number(1461718800), + "genres": Array [ + String("Comedy"), + String("Documentary"), + ], + }, + { + "id": String("529216"), + "title": String("Mirage"), + "poster": String("https://image.tmdb.org/t/p/w500/oLO9y7GoyAVUVoAWD6jCgY7GQfs.jpg"), + "overview": String("Two storms separated by 25 years. A woman murdered. A daughter missed. Only 72 hours to discover the truth."), + "release_date": Number(1543536000), + "genres": Array [ + String("Horror"), + ], + }, + { + "id": String("22"), + "title": String("Pirates of the Caribbean: The Curse of the Black Pearl"), + "poster": String("https://image.tmdb.org/t/p/w500/z8onk7LV9Mmw6zKz4hT6pzzvmvl.jpg"), + "overview": String("Jack Sparrow, a freewheeling 18th-century pirate, quarrels with a rival pirate bent on pillaging Port Royal. When the governor's daughter is kidnapped, Sparrow decides to help the girl's love save her."), + "release_date": Number(1057712400), + "genres": Array [ + String("Adventure"), + String("Fantasy"), + String("Action"), + ], + }, + { + "id": String("490132"), + "title": String("Green Book"), + "poster": String("https://image.tmdb.org/t/p/w500/7BsvSuDQuoqhWmU2fL7W2GOcZHU.jpg"), + "overview": String("Tony Lip, a bouncer in 1962, is hired to drive pianist Don Shirley on a tour through the Deep South in the days when African Americans, forced to find alternate accommodations and services due to segregation laws below the Mason-Dixon Line, relied on a guide called The Negro Motorist Green Book."), + "release_date": Number(1542326400), + "genres": Array [ + String("Drama"), + String("Comedy"), + ], + }, + { + "id": String("351044"), + "title": String("Welcome to Marwen"), + "poster": String("https://image.tmdb.org/t/p/w500/dOULsxYQFsOR0cEBBB20xnjJkPD.jpg"), + "overview": String("When a devastating attack shatters Mark Hogancamp and wipes away all memories, no one expected recovery. Putting together pieces from his old and new life, Mark meticulously creates a wondrous town named Marwen where he can heal and be heroic. As he builds an astonishing art installation — a testament to the most powerful women he knows — through his fantasy world, he draws strength to triumph in the real one."), + "release_date": Number(1545350400), + "genres": Array [ + String("Drama"), + String("Comedy"), + String("Fantasy"), + ], + }, + { + "id": String("76338"), + "title": String("Thor: The Dark World"), + "poster": String("https://image.tmdb.org/t/p/w500/wp6OxE4poJ4G7c0U2ZIXasTSMR7.jpg"), + "overview": String("Thor fights to restore order across the cosmos… but an ancient race led by the vengeful Malekith returns to plunge the universe back into darkness. Faced with an enemy that even Odin and Asgard cannot withstand, Thor must embark on his most perilous and personal journey yet, one that will reunite him with Jane Foster and force him to sacrifice everything to save us all."), + "release_date": Number(1383004800), + "genres": Array [ + String("Action"), + String("Adventure"), + String("Fantasy"), + ], + }, + { + "id": String("460321"), + "title": String("Close"), + "poster": String("https://image.tmdb.org/t/p/w500/4kjUGqPIv6kpxJUvjmeQX7nQpKd.jpg"), + "overview": String("A counter-terrorism expert takes a job protecting a young heiress. After an attempted kidnapping puts both of their lives in danger, they must flee."), + "release_date": Number(1547769600), + "genres": Array [ + String("Crime"), + String("Drama"), + ], + }, + { + "id": String("327331"), + "title": String("The Dirt"), + "poster": String("https://image.tmdb.org/t/p/w500/xGY5rr8441ib0lT9mtHZn7e8Aay.jpg"), + "overview": String("The story of Mötley Crüe and their rise from the Sunset Strip club scene of the early 1980s to superstardom."), + "release_date": Number(1553212800), + "genres": Array [], + }, + { + "id": String("412157"), + "title": String("Steel Country"), + "poster": String("https://image.tmdb.org/t/p/w500/7QqFn9UzuSnh1uOPeSfYL1MFjkB.jpg"), + "overview": String("When a young boy turns up dead in a sleepy Pennsylvania town, a local sanitation truck driver, Donald, plays detective, embarking on a precarious and obsessive investigation to prove the boy was murdered."), + "release_date": Number(1555030800), + "genres": Array [], + }, + { + "id": String("122"), + "title": String("The Lord of the Rings: The Return of the King"), + "poster": String("https://image.tmdb.org/t/p/w500/rCzpDGLbOoPwLjy3OAm5NUPOTrC.jpg"), + "overview": String("Aragorn is revealed as the heir to the ancient kings as he, Gandalf and the other members of the broken fellowship struggle to save Gondor from Sauron's forces. Meanwhile, Frodo and Sam bring the ring closer to the heart of Mordor, the dark lord's realm."), + "release_date": Number(1070236800), + "genres": Array [ + String("Adventure"), + String("Fantasy"), + String("Action"), + ], + }, + { + "id": String("348"), + "title": String("Alien"), + "poster": String("https://image.tmdb.org/t/p/w500/vfrQk5IPloGg1v9Rzbh2Eg3VGyM.jpg"), + "overview": String("During its return to the earth, commercial spaceship Nostromo intercepts a distress signal from a distant planet. When a three-member team of the crew discovers a chamber containing thousands of eggs on the planet, a creature inside one of the eggs attacks an explorer. The entire crew is unaware of the impending nightmare set to descend upon them when the alien parasite planted inside its unfortunate host is birthed."), + "release_date": Number(296442000), + "genres": Array [ + String("Drama"), + ], + }, + { + "id": String("140607"), + "title": String("Star Wars: The Force Awakens"), + "poster": String("https://image.tmdb.org/t/p/w500/wqnLdwVXoBjKibFRR5U3y0aDUhs.jpg"), + "overview": String("Thirty years after defeating the Galactic Empire, Han Solo and his allies face a new threat from the evil Kylo Ren and his army of Stormtroopers."), + "release_date": Number(1450137600), + "genres": Array [ + String("Documentary"), + ], + }, + { + "id": String("293660"), + "title": String("Deadpool"), + "poster": String("https://image.tmdb.org/t/p/w500/yGSxMiF0cYuAiyuve5DA6bnWEOI.jpg"), + "overview": String("Deadpool tells the origin story of former Special Forces operative turned mercenary Wade Wilson, who after being subjected to a rogue experiment that leaves him with accelerated healing powers, adopts the alter ego Deadpool. Armed with his new abilities and a dark, twisted sense of humor, Deadpool hunts down the man who nearly destroyed his life."), + "release_date": Number(1454976000), + "genres": Array [ + String("Action"), + String("Adventure"), + String("Comedy"), + ], + }, + { + "id": String("332562"), + "title": String("A Star Is Born"), + "poster": String("https://image.tmdb.org/t/p/w500/wrFpXMNBRj2PBiN4Z5kix51XaIZ.jpg"), + "overview": String("Seasoned musician Jackson Maine discovers — and falls in love with — struggling artist Ally. She has just about given up on her dream to make it big as a singer — until Jack coaxes her into the spotlight. But even as Ally's career takes off, the personal side of their relationship is breaking down, as Jack fights an ongoing battle with his own internal demons."), + "release_date": Number(1538528400), + "genres": Array [ + String("Documentary"), + String("Music"), + ], + }, + { + "id": String("426563"), + "title": String("Holmes & Watson"), + "poster": String("https://image.tmdb.org/t/p/w500/orEUlKndjV1rEcWqXbbjegjfv97.jpg"), + "overview": String("Detective Sherlock Holmes and Dr. John Watson join forces to investigate a murder at Buckingham Palace. They soon learn that they have only four days to solve the case, or the queen will become the next victim."), + "release_date": Number(1545696000), + "genres": Array [ + String("Mystery"), + String("Adventure"), + String("Comedy"), + String("Crime"), + ], + }, + { + "id": String("429197"), + "title": String("Vice"), + "poster": String("https://image.tmdb.org/t/p/w500/1gCab6rNv1r6V64cwsU4oEr649Y.jpg"), + "overview": String("George W. Bush picks Dick Cheney, the CEO of Halliburton Co., to be his Republican running mate in the 2000 presidential election. No stranger to politics, Cheney's impressive résumé includes stints as White House chief of staff, House Minority Whip and defense secretary. When Bush wins by a narrow margin, Cheney begins to use his newfound power to help reshape the country and the world."), + "release_date": Number(1545696000), + "genres": Array [ + String("Action"), + String("Thriller"), + ], + }, + { + "id": String("335984"), + "title": String("Blade Runner 2049"), + "poster": String("https://image.tmdb.org/t/p/w500/gajva2L0rPYkEWjzgFlBXCAVBE5.jpg"), + "overview": String("Thirty years after the events of the first film, a new blade runner, LAPD Officer K, unearths a long-buried secret that has the potential to plunge what's left of society into chaos. K's discovery leads him on a quest to find Rick Deckard, a former LAPD blade runner who has been missing for 30 years."), + "release_date": Number(1507078800), + "genres": Array [ + String("Documentary"), + ], + }, + { + "id": String("339380"), + "title": String("On the Basis of Sex"), + "poster": String("https://image.tmdb.org/t/p/w500/izY9Le3QWtu7xkHq7bjJnuE5yGI.jpg"), + "overview": String("Young lawyer Ruth Bader Ginsburg teams with her husband Marty to bring a groundbreaking case before the U.S. Court of Appeals and overturn a century of sex discrimination."), + "release_date": Number(1545696000), + "genres": Array [ + String("Drama"), + String("History"), + ], + }, + { + "id": String("562"), + "title": String("Die Hard"), + "poster": String("https://image.tmdb.org/t/p/w500/1fq1IpnuVqkD5BMuaXAUW0eVB21.jpg"), + "overview": String("NYPD cop John McClane's plan to reconcile with his estranged wife is thrown for a serious loop when, minutes after he arrives at her office, the entire building is overtaken by a group of terrorists. With little help from the LAPD, wisecracking McClane sets out to single-handedly rescue the hostages and bring the bad guys down."), + "release_date": Number(584931600), + "genres": Array [ + String("Action"), + ], + }, + { + "id": String("375588"), + "title": String("Robin Hood"), + "poster": String("https://image.tmdb.org/t/p/w500/AiRfixFcfTkNbn2A73qVJPlpkUo.jpg"), + "overview": String("A war-hardened Crusader and his Moorish commander mount an audacious revolt against the corrupt English crown."), + "release_date": Number(1542672000), + "genres": Array [ + String("Family"), + String("Animation"), + ], + }, + { + "id": String("381288"), + "title": String("Split"), + "poster": String("https://image.tmdb.org/t/p/w500/bqb9WsmZmDIKxqYmBJ9lj7J6hzn.jpg"), + "overview": String("Though Kevin has evidenced 23 personalities to his trusted psychiatrist, Dr. Fletcher, there remains one still submerged who is set to materialize and dominate all the others. Compelled to abduct three teenage girls led by the willful, observant Casey, Kevin reaches a war for survival among all of those contained within him — as well as everyone around him — as the walls between his compartments shatter apart."), + "release_date": Number(1484784000), + "genres": Array [ + String("Science Fiction"), + String("Drama"), + ], + }, + { + "id": String("10191"), + "title": String("How to Train Your Dragon"), + "poster": String("https://image.tmdb.org/t/p/w500/ygGmAO60t8GyqUo9xYeYxSZAR3b.jpg"), + "overview": String("As the son of a Viking leader on the cusp of manhood, shy Hiccup Horrendous Haddock III faces a rite of passage: he must kill a dragon to prove his warrior mettle. But after downing a feared dragon, he realizes that he no longer wants to destroy it, and instead befriends the beast – which he names Toothless – much to the chagrin of his warrior father"), + "release_date": Number(1268179200), + "genres": Array [ + String("Fantasy"), + String("Adventure"), + String("Animation"), + String("Family"), + ], + }, + { + "id": String("315635"), + "title": String("Spider-Man: Homecoming"), + "poster": String("https://image.tmdb.org/t/p/w500/c24sv2weTHPsmDa7jEMN0m2P3RT.jpg"), + "overview": String("Following the events of Captain America: Civil War, Peter Parker, with the help of his mentor Tony Stark, tries to balance his life as an ordinary high school student in Queens, New York City, with fighting crime as his superhero alter ego Spider-Man as a new threat, the Vulture, emerges."), + "release_date": Number(1499216400), + "genres": Array [ + String("Action"), + String("Adventure"), + String("Science Fiction"), + String("Drama"), + ], + }, + { + "id": String("603"), + "title": String("The Matrix"), + "poster": String("https://image.tmdb.org/t/p/w500/f89U3ADr1oiB1s9GkdPOEpXUk5H.jpg"), + "overview": String("Set in the 22nd century, The Matrix tells the story of a computer hacker who joins a group of underground insurgents fighting the vast and powerful computers who now rule the earth."), + "release_date": Number(922755600), + "genres": Array [ + String("Documentary"), + String("Science Fiction"), + ], + }, + { + "id": String("586347"), + "title": String("The Hard Way"), + "poster": String("https://image.tmdb.org/t/p/w500/kwtLphVv3ZbIblc79YNYbZuzbzb.jpg"), + "overview": String("After learning of his brother's death during a mission in Romania, a former soldier joins two allies to hunt down a mysterious enemy and take his revenge."), + "release_date": Number(1553040000), + "genres": Array [ + String("Drama"), + String("Thriller"), + ], + }, + { + "id": String("141052"), + "title": String("Justice League"), + "poster": String("https://image.tmdb.org/t/p/w500/eifGNCSDuxJeS1loAXil5bIGgvC.jpg"), + "overview": String("Fuelled by his restored faith in humanity and inspired by Superman's selfless act, Bruce Wayne and Diana Prince assemble a team of metahumans consisting of Barry Allen, Arthur Curry and Victor Stone to face the catastrophic threat of Steppenwolf and the Parademons who are on the hunt for three Mother Boxes on Earth."), + "release_date": Number(1510704000), + "genres": Array [ + String("Animation"), + ], + }, + { + "id": String("680"), + "title": String("Pulp Fiction"), + "poster": String("https://image.tmdb.org/t/p/w500/plnlrtBUULT0rh3Xsjmpubiso3L.jpg"), + "overview": String("A burger-loving hit man, his philosophical partner, a drug-addled gangster's moll and a washed-up boxer converge in this sprawling, comedic crime caper. Their adventures unfurl in three stories that ingeniously trip back and forth in time."), + "release_date": Number(779158800), + "genres": Array [], + }, + { + "id": String("337167"), + "title": String("Fifty Shades Freed"), + "poster": String("https://image.tmdb.org/t/p/w500/9ZedQHPQVveaIYmDSTazhT3y273.jpg"), + "overview": String("Believing they have left behind shadowy figures from their past, newlyweds Christian and Ana fully embrace an inextricable connection and shared life of luxury. But just as she steps into her role as Mrs. Grey and he relaxes into an unfamiliar stability, new threats could jeopardize their happy ending before it even begins."), + "release_date": Number(1516147200), + "genres": Array [ + String("Drama"), + String("Romance"), + ], + }, + { + "id": String("102899"), + "title": String("Ant-Man"), + "poster": String("https://image.tmdb.org/t/p/w500/rQRnQfUl3kfp78nCWq8Ks04vnq1.jpg"), + "overview": String("Armed with the astonishing ability to shrink in scale but increase in strength, master thief Scott Lang must embrace his inner-hero and help his mentor, Doctor Hank Pym, protect the secret behind his spectacular Ant-Man suit from a new generation of towering threats. Against seemingly insurmountable obstacles, Pym and Lang must plan and pull off a heist that will save the world."), + "release_date": Number(1436835600), + "genres": Array [ + String("Documentary"), + ], + }, + { + "id": String("11"), + "title": String("Star Wars"), + "poster": String("https://image.tmdb.org/t/p/w500/6FfCtAuVAW8XJjZ7eWeLibRLWTw.jpg"), + "overview": String("Princess Leia is captured and held hostage by the evil Imperial forces in their effort to take over the galactic Empire. Venturesome Luke Skywalker and dashing captain Han Solo team together with the loveable robot duo R2-D2 and C-3PO to rescue the beautiful princess and restore peace and justice in the Empire."), + "release_date": Number(233370000), + "genres": Array [ + String("Action"), + ], + }, + { + "id": String("807"), + "title": String("Se7en"), + "poster": String("https://image.tmdb.org/t/p/w500/6yoghtyTpznpBik8EngEmJskVUO.jpg"), + "overview": String("Two homicide detectives are on a desperate hunt for a serial killer whose crimes are based on the 'seven deadly sins' in this dark and haunting film that takes viewers from the tortured remains of one victim to the next. The seasoned Det. Sommerset researches each sin in an effort to get inside the killer's mind, while his novice partner, Mills, scoffs at his efforts to unravel the case."), + "release_date": Number(811731600), + "genres": Array [ + String("Crime"), + String("Mystery"), + String("Thriller"), + ], + }, + { + "id": String("27205"), + "title": String("Inception"), + "poster": String("https://image.tmdb.org/t/p/w500/9gk7adHYeDvHkCSEqAvQNLV5Uge.jpg"), + "overview": String("Cobb, a skilled thief who commits corporate espionage by infiltrating the subconscious of his targets is offered a chance to regain his old life as payment for a task considered to be impossible: 'inception', the implantation of another person's idea into a target's subconscious."), + "release_date": Number(1279155600), + "genres": Array [ + String("Action"), + String("Science Fiction"), + String("Adventure"), + ], + }, + { + "id": String("767"), + "title": String("Harry Potter and the Half-Blood Prince"), + "poster": String("https://image.tmdb.org/t/p/w500/z7uo9zmQdQwU5ZJHFpv2Upl30i1.jpg"), + "overview": String("As Harry begins his sixth year at Hogwarts, he discovers an old book marked as 'Property of the Half-Blood Prince', and begins to learn more about Lord Voldemort's dark past."), + "release_date": Number(1246928400), + "genres": Array [ + String("Adventure"), + String("Fantasy"), + ], + }, + { + "id": String("1726"), + "title": String("Iron Man"), + "poster": String("https://image.tmdb.org/t/p/w500/78lPtwv72eTNqFW9COBYI0dWDJa.jpg"), + "overview": String("After being held captive in an Afghan cave, billionaire engineer Tony Stark creates a unique weaponized suit of armor to fight evil."), + "release_date": Number(1209517200), + "genres": Array [ + String("Drama"), + ], + }, + { + "id": String("87101"), + "title": String("Terminator Genisys"), + "poster": String("https://image.tmdb.org/t/p/w500/oZRVDpNtmHk8M1VYy1aeOWUXgbC.jpg"), + "overview": String("The year is 2029. John Connor, leader of the resistance continues the war against the machines. At the Los Angeles offensive, John's fears of the unknown future begin to emerge when TECOM spies reveal a new plot by SkyNet that will attack him from both fronts; past and future, and will ultimately change warfare forever."), + "release_date": Number(1435021200), + "genres": Array [ + String("Science Fiction"), + String("Action"), + String("Thriller"), + String("Adventure"), + ], + }, + { + "id": String("438799"), + "title": String("Overlord"), + "poster": String("https://image.tmdb.org/t/p/w500/l76Rgp32z2UxjULApxGXAPpYdAP.jpg"), + "overview": String("France, June 1944. On the eve of D-Day, some American paratroopers fall behind enemy lines after their aircraft crashes while on a mission to destroy a radio tower in a small village near the beaches of Normandy. After reaching their target, the surviving paratroopers realise that, in addition to fighting the Nazi troops that patrol the village, they also must fight against something else."), + "release_date": Number(1541030400), + "genres": Array [ + String("Horror"), + String("War"), + String("Science Fiction"), + ], + }, + { + "id": String("260513"), + "title": String("Incredibles 2"), + "poster": String("https://image.tmdb.org/t/p/w500/9lFKBtaVIhP7E2Pk0IY1CwTKTMZ.jpg"), + "overview": String("Elastigirl springs into action to save the day, while Mr. Incredible faces his greatest challenge yet – taking care of the problems of his three children."), + "release_date": Number(1528938000), + "genres": Array [ + String("Action"), + String("Adventure"), + String("Animation"), + String("Family"), + ], + }, + { + "id": String("672"), + "title": String("Harry Potter and the Chamber of Secrets"), + "poster": String("https://image.tmdb.org/t/p/w500/sdEOH0992YZ0QSxgXNIGLq1ToUi.jpg"), + "overview": String("Ignoring threats to his life, Harry returns to Hogwarts to investigate – aided by Ron and Hermione – a mysterious series of attacks."), + "release_date": Number(1037145600), + "genres": Array [ + String("Adventure"), + String("Fantasy"), + ], + }, + { + "id": String("487297"), + "title": String("What Men Want"), + "poster": String("https://image.tmdb.org/t/p/w500/30IiwvIRqPGjUV0bxJkZfnSiCL.jpg"), + "overview": String("Magically able to hear what men are thinking, a sports agent uses her newfound ability to turn the tables on her overbearing male colleagues."), + "release_date": Number(1549584000), + "genres": Array [ + String("Drama"), + String("Romance"), + ], + }, + { + "id": String("399402"), + "title": String("Hunter Killer"), + "poster": String("https://image.tmdb.org/t/p/w500/a0j18XNVhP4RcW3wXwsqT0kVoQm.jpg"), + "overview": String("Captain Glass of the USS “Arkansas” discovers that a coup d'état is taking place in Russia, so he and his crew join an elite group working on the ground to prevent a war."), + "release_date": Number(1539910800), + "genres": Array [ + String("Action"), + String("Thriller"), + ], + }, + { + "id": String("466282"), + "title": String("To All the Boys I've Loved Before"), + "poster": String("https://image.tmdb.org/t/p/w500/hKHZhUbIyUAjcSrqJThFGYIR6kI.jpg"), + "overview": String("Lara Jean's love life goes from imaginary to out of control when her secret letters to every boy she's ever fallen for are mysteriously mailed out."), + "release_date": Number(1534381200), + "genres": Array [ + String("Comedy"), + String("Romance"), + ], + }, + { + "id": String("209112"), + "title": String("Batman v Superman: Dawn of Justice"), + "poster": String("https://image.tmdb.org/t/p/w500/5UsK3grJvtQrtzEgqNlDljJW96w.jpg"), + "overview": String("Fearing the actions of a god-like Super Hero left unchecked, Gotham City’s own formidable, forceful vigilante takes on Metropolis’s most revered, modern-day savior, while the world wrestles with what sort of hero it really needs. And with Batman and Superman at war with one another, a new threat quickly arises, putting mankind in greater danger than it’s ever known before."), + "release_date": Number(1458691200), + "genres": Array [ + String("Action"), + String("Adventure"), + String("Fantasy"), + ], + }, + { + "id": String("360920"), + "title": String("The Grinch"), + "poster": String("https://image.tmdb.org/t/p/w500/stAu0oF6dYDhV5ssbmFUYkQPtCP.jpg"), + "overview": String("The Grinch hatches a scheme to ruin Christmas when the residents of Whoville plan their annual holiday celebration."), + "release_date": Number(1541635200), + "genres": Array [ + String("Animation"), + String("Family"), + String("Music"), + ], + }, + { + "id": String("10195"), + "title": String("Thor"), + "poster": String("https://image.tmdb.org/t/p/w500/prSfAi1xGrhLQNxVSUFh61xQ4Qy.jpg"), + "overview": String("Against his father Odin's will, The Mighty Thor - a powerful but arrogant warrior god - recklessly reignites an ancient war. Thor is cast down to Earth and forced to live among humans as punishment. Once here, Thor learns what it takes to be a true hero when the most dangerous villain of his world sends the darkest forces of Asgard to invade Earth."), + "release_date": Number(1303347600), + "genres": Array [ + String("Adventure"), + String("Fantasy"), + String("Action"), + ], + }, + { + "id": String("514439"), + "title": String("Breakthrough"), + "poster": String("https://image.tmdb.org/t/p/w500/t58dx7JIgchr9If5uxn3NmHaHoS.jpg"), + "overview": String("When he was 14, Smith drowned in Lake St. Louis and was dead for nearly an hour. According to reports at the time, CPR was performed 27 minutes to no avail. Then the youth's mother, Joyce Smith, entered the room, praying loudly. Suddenly, there was a pulse, and Smith came around."), + "release_date": Number(1554944400), + "genres": Array [ + String("War"), + ], + }, + { + "id": String("278"), + "title": String("The Shawshank Redemption"), + "poster": String("https://image.tmdb.org/t/p/w500/q6y0Go1tsGEsmtFryDOJo3dEmqu.jpg"), + "overview": String("Framed in the 1940s for the double murder of his wife and her lover, upstanding banker Andy Dufresne begins a new life at the Shawshank prison, where he puts his accounting skills to work for an amoral warden. During his long stretch in prison, Dufresne comes to be admired by the other inmates -- including an older prisoner named Red -- for his integrity and unquenchable sense of hope."), + "release_date": Number(780282000), + "genres": Array [ + String("Drama"), + String("Crime"), + ], + }, + { + "id": String("297762"), + "title": String("Wonder Woman"), + "poster": String("https://image.tmdb.org/t/p/w500/gfJGlDaHuWimErCr5Ql0I8x9QSy.jpg"), + "overview": String("An Amazon princess comes to the world of Man in the grips of the First World War to confront the forces of evil and bring an end to human conflict."), + "release_date": Number(1496106000), + "genres": Array [ + String("Action"), + String("Adventure"), + String("Fantasy"), + String("TV Movie"), + ], + }, +] diff --git a/dump/src/reader/v4/snapshots/dump__reader__v4__test__read_dump_v4-12.snap b/dump/src/reader/v4/snapshots/dump__reader__v4__test__read_dump_v4-12.snap new file mode 100644 index 000000000..1fe72cff5 --- /dev/null +++ b/dump/src/reader/v4/snapshots/dump__reader__v4__test__read_dump_v4-12.snap @@ -0,0 +1,57 @@ +--- +source: dump/src/reader/v4/mod.rs +expression: spells.settings() +--- +Ok( + Settings { + displayed_attributes: Reset, + searchable_attributes: Reset, + filterable_attributes: Set( + {}, + ), + sortable_attributes: Set( + {}, + ), + ranking_rules: Set( + [ + "words", + "typo", + "proximity", + "attribute", + "sort", + "exactness", + ], + ), + stop_words: Set( + {}, + ), + synonyms: Set( + {}, + ), + distinct_attribute: Reset, + typo_tolerance: Set( + TypoSettings { + enabled: Set( + true, + ), + min_word_size_for_typos: Set( + MinWordSizeTyposSetting { + one_typo: Set( + 5, + ), + two_typos: Set( + 9, + ), + }, + ), + disable_on_words: Set( + {}, + ), + disable_on_attributes: Set( + {}, + ), + }, + ), + _kind: PhantomData, + }, +) diff --git a/dump/src/reader/v4/snapshots/dump__reader__v4__test__read_dump_v4-13.snap b/dump/src/reader/v4/snapshots/dump__reader__v4__test__read_dump_v4-13.snap new file mode 100644 index 000000000..26d101c4b --- /dev/null +++ b/dump/src/reader/v4/snapshots/dump__reader__v4__test__read_dump_v4-13.snap @@ -0,0 +1,533 @@ +--- +source: dump/src/reader/v4/mod.rs +expression: documents +--- +[ + { + "index": "acid-arrow", + "name": "Acid Arrow", + "desc": [ + "A shimmering green arrow streaks toward a target within range and bursts in a spray of acid. Make a ranged spell attack against the target. On a hit, the target takes 4d4 acid damage immediately and 2d4 acid damage at the end of its next turn. On a miss, the arrow splashes the target with acid for half as much of the initial damage and no damage at the end of its next turn." + ], + "higher_level": [ + "When you cast this spell using a spell slot of 3rd level or higher, the damage (both initial and later) increases by 1d4 for each slot level above 2nd." + ], + "range": "90 feet", + "components": [ + "V", + "S", + "M" + ], + "material": "Powdered rhubarb leaf and an adder's stomach.", + "ritual": false, + "duration": "Instantaneous", + "concentration": false, + "casting_time": "1 action", + "level": 2, + "attack_type": "ranged", + "damage": { + "damage_type": { + "index": "acid", + "name": "Acid", + "url": "/api/damage-types/acid" + }, + "damage_at_slot_level": { + "2": "4d4", + "3": "5d4", + "4": "6d4", + "5": "7d4", + "6": "8d4", + "7": "9d4", + "8": "10d4", + "9": "11d4" + } + }, + "school": { + "index": "evocation", + "name": "Evocation", + "url": "/api/magic-schools/evocation" + }, + "classes": [ + { + "index": "wizard", + "name": "Wizard", + "url": "/api/classes/wizard" + } + ], + "subclasses": [ + { + "index": "lore", + "name": "Lore", + "url": "/api/subclasses/lore" + }, + { + "index": "land", + "name": "Land", + "url": "/api/subclasses/land" + } + ], + "url": "/api/spells/acid-arrow" + }, + { + "index": "acid-splash", + "name": "Acid Splash", + "desc": [ + "You hurl a bubble of acid. Choose one creature within range, or choose two creatures within range that are within 5 feet of each other. A target must succeed on a dexterity saving throw or take 1d6 acid damage.", + "This spell's damage increases by 1d6 when you reach 5th level (2d6), 11th level (3d6), and 17th level (4d6)." + ], + "range": "60 feet", + "components": [ + "V", + "S" + ], + "ritual": false, + "duration": "Instantaneous", + "concentration": false, + "casting_time": "1 action", + "level": 0, + "damage": { + "damage_type": { + "index": "acid", + "name": "Acid", + "url": "/api/damage-types/acid" + }, + "damage_at_character_level": { + "1": "1d6", + "5": "2d6", + "11": "3d6", + "17": "4d6" + } + }, + "school": { + "index": "conjuration", + "name": "Conjuration", + "url": "/api/magic-schools/conjuration" + }, + "classes": [ + { + "index": "sorcerer", + "name": "Sorcerer", + "url": "/api/classes/sorcerer" + }, + { + "index": "wizard", + "name": "Wizard", + "url": "/api/classes/wizard" + } + ], + "subclasses": [ + { + "index": "lore", + "name": "Lore", + "url": "/api/subclasses/lore" + } + ], + "url": "/api/spells/acid-splash", + "dc": { + "dc_type": { + "index": "dex", + "name": "DEX", + "url": "/api/ability-scores/dex" + }, + "dc_success": "none" + } + }, + { + "index": "aid", + "name": "Aid", + "desc": [ + "Your spell bolsters your allies with toughness and resolve. Choose up to three creatures within range. Each target's hit point maximum and current hit points increase by 5 for the duration." + ], + "higher_level": [ + "When you cast this spell using a spell slot of 3rd level or higher, a target's hit points increase by an additional 5 for each slot level above 2nd." + ], + "range": "30 feet", + "components": [ + "V", + "S", + "M" + ], + "material": "A tiny strip of white cloth.", + "ritual": false, + "duration": "8 hours", + "concentration": false, + "casting_time": "1 action", + "level": 2, + "school": { + "index": "abjuration", + "name": "Abjuration", + "url": "/api/magic-schools/abjuration" + }, + "classes": [ + { + "index": "cleric", + "name": "Cleric", + "url": "/api/classes/cleric" + }, + { + "index": "paladin", + "name": "Paladin", + "url": "/api/classes/paladin" + } + ], + "subclasses": [ + { + "index": "lore", + "name": "Lore", + "url": "/api/subclasses/lore" + } + ], + "url": "/api/spells/aid", + "heal_at_slot_level": { + "2": "5", + "3": "10", + "4": "15", + "5": "20", + "6": "25", + "7": "30", + "8": "35", + "9": "40" + } + }, + { + "index": "alarm", + "name": "Alarm", + "desc": [ + "You set an alarm against unwanted intrusion. Choose a door, a window, or an area within range that is no larger than a 20-foot cube. Until the spell ends, an alarm alerts you whenever a Tiny or larger creature touches or enters the warded area. When you cast the spell, you can designate creatures that won't set off the alarm. You also choose whether the alarm is mental or audible.", + "A mental alarm alerts you with a ping in your mind if you are within 1 mile of the warded area. This ping awakens you if you are sleeping.", + "An audible alarm produces the sound of a hand bell for 10 seconds within 60 feet." + ], + "range": "30 feet", + "components": [ + "V", + "S", + "M" + ], + "material": "A tiny bell and a piece of fine silver wire.", + "ritual": true, + "duration": "8 hours", + "concentration": false, + "casting_time": "1 minute", + "level": 1, + "school": { + "index": "abjuration", + "name": "Abjuration", + "url": "/api/magic-schools/abjuration" + }, + "classes": [ + { + "index": "ranger", + "name": "Ranger", + "url": "/api/classes/ranger" + }, + { + "index": "wizard", + "name": "Wizard", + "url": "/api/classes/wizard" + } + ], + "subclasses": [ + { + "index": "lore", + "name": "Lore", + "url": "/api/subclasses/lore" + } + ], + "url": "/api/spells/alarm", + "area_of_effect": { + "type": "cube", + "size": 20 + } + }, + { + "index": "alter-self", + "name": "Alter Self", + "desc": [ + "You assume a different form. When you cast the spell, choose one of the following options, the effects of which last for the duration of the spell. While the spell lasts, you can end one option as an action to gain the benefits of a different one.", + "***Aquatic Adaptation.*** You adapt your body to an aquatic environment, sprouting gills and growing webbing between your fingers. You can breathe underwater and gain a swimming speed equal to your walking speed.", + "***Change Appearance.*** You transform your appearance. You decide what you look like, including your height, weight, facial features, sound of your voice, hair length, coloration, and distinguishing characteristics, if any. You can make yourself appear as a member of another race, though none of your statistics change. You also can't appear as a creature of a different size than you, and your basic shape stays the same; if you're bipedal, you can't use this spell to become quadrupedal, for instance. At any time for the duration of the spell, you can use your action to change your appearance in this way again.", + "***Natural Weapons.*** You grow claws, fangs, spines, horns, or a different natural weapon of your choice. Your unarmed strikes deal 1d6 bludgeoning, piercing, or slashing damage, as appropriate to the natural weapon you chose, and you are proficient with your unarmed strikes. Finally, the natural weapon is magic and you have a +1 bonus to the attack and damage rolls you make using it." + ], + "range": "Self", + "components": [ + "V", + "S" + ], + "ritual": false, + "duration": "Up to 1 hour", + "concentration": true, + "casting_time": "1 action", + "level": 2, + "school": { + "index": "transmutation", + "name": "Transmutation", + "url": "/api/magic-schools/transmutation" + }, + "classes": [ + { + "index": "sorcerer", + "name": "Sorcerer", + "url": "/api/classes/sorcerer" + }, + { + "index": "wizard", + "name": "Wizard", + "url": "/api/classes/wizard" + } + ], + "subclasses": [ + { + "index": "lore", + "name": "Lore", + "url": "/api/subclasses/lore" + } + ], + "url": "/api/spells/alter-self" + }, + { + "index": "animal-friendship", + "name": "Animal Friendship", + "desc": [ + "This spell lets you convince a beast that you mean it no harm. Choose a beast that you can see within range. It must see and hear you. If the beast's Intelligence is 4 or higher, the spell fails. Otherwise, the beast must succeed on a wisdom saving throw or be charmed by you for the spell's duration. If you or one of your companions harms the target, the spells ends." + ], + "range": "30 feet", + "components": [ + "V", + "S", + "M" + ], + "material": "A morsel of food.", + "ritual": false, + "duration": "24 hours", + "concentration": false, + "casting_time": "1 action", + "level": 1, + "school": { + "index": "enchantment", + "name": "Enchantment", + "url": "/api/magic-schools/enchantment" + }, + "classes": [ + { + "index": "bard", + "name": "Bard", + "url": "/api/classes/bard" + }, + { + "index": "cleric", + "name": "Cleric", + "url": "/api/classes/cleric" + }, + { + "index": "druid", + "name": "Druid", + "url": "/api/classes/druid" + }, + { + "index": "ranger", + "name": "Ranger", + "url": "/api/classes/ranger" + } + ], + "subclasses": [], + "url": "/api/spells/animal-friendship", + "dc": { + "dc_type": { + "index": "wis", + "name": "WIS", + "url": "/api/ability-scores/wis" + }, + "dc_success": "none" + } + }, + { + "index": "animal-messenger", + "name": "Animal Messenger", + "desc": [ + "By means of this spell, you use an animal to deliver a message. Choose a Tiny beast you can see within range, such as a squirrel, a blue jay, or a bat. You specify a location, which you must have visited, and a recipient who matches a general description, such as \"a man or woman dressed in the uniform of the town guard\" or \"a red-haired dwarf wearing a pointed hat.\" You also speak a message of up to twenty-five words. The target beast travels for the duration of the spell toward the specified location, covering about 50 miles per 24 hours for a flying messenger, or 25 miles for other animals.", + "When the messenger arrives, it delivers your message to the creature that you described, replicating the sound of your voice. The messenger speaks only to a creature matching the description you gave. If the messenger doesn't reach its destination before the spell ends, the message is lost, and the beast makes its way back to where you cast this spell." + ], + "higher_level": [ + "If you cast this spell using a spell slot of 3nd level or higher, the duration of the spell increases by 48 hours for each slot level above 2nd." + ], + "range": "30 feet", + "components": [ + "V", + "S", + "M" + ], + "material": "A morsel of food.", + "ritual": true, + "duration": "24 hours", + "concentration": false, + "casting_time": "1 action", + "level": 2, + "school": { + "index": "enchantment", + "name": "Enchantment", + "url": "/api/magic-schools/enchantment" + }, + "classes": [ + { + "index": "bard", + "name": "Bard", + "url": "/api/classes/bard" + }, + { + "index": "druid", + "name": "Druid", + "url": "/api/classes/druid" + }, + { + "index": "ranger", + "name": "Ranger", + "url": "/api/classes/ranger" + } + ], + "subclasses": [ + { + "index": "lore", + "name": "Lore", + "url": "/api/subclasses/lore" + } + ], + "url": "/api/spells/animal-messenger" + }, + { + "index": "animal-shapes", + "name": "Animal Shapes", + "desc": [ + "Your magic turns others into beasts. Choose any number of willing creatures that you can see within range. You transform each target into the form of a Large or smaller beast with a challenge rating of 4 or lower. On subsequent turns, you can use your action to transform affected creatures into new forms.", + "The transformation lasts for the duration for each target, or until the target drops to 0 hit points or dies. You can choose a different form for each target. A target's game statistics are replaced by the statistics of the chosen beast, though the target retains its alignment and Intelligence, Wisdom, and Charisma scores. The target assumes the hit points of its new form, and when it reverts to its normal form, it returns to the number of hit points it had before it transformed. If it reverts as a result of dropping to 0 hit points, any excess damage carries over to its normal form. As long as the excess damage doesn't reduce the creature's normal form to 0 hit points, it isn't knocked unconscious. The creature is limited in the actions it can perform by the nature of its new form, and it can't speak or cast spells.", + "The target's gear melds into the new form. The target can't activate, wield, or otherwise benefit from any of its equipment." + ], + "range": "30 feet", + "components": [ + "V", + "S" + ], + "ritual": false, + "duration": "Up to 24 hours", + "concentration": true, + "casting_time": "1 action", + "level": 8, + "school": { + "index": "transmutation", + "name": "Transmutation", + "url": "/api/magic-schools/transmutation" + }, + "classes": [ + { + "index": "druid", + "name": "Druid", + "url": "/api/classes/druid" + } + ], + "subclasses": [], + "url": "/api/spells/animal-shapes" + }, + { + "index": "animate-dead", + "name": "Animate Dead", + "desc": [ + "This spell creates an undead servant. Choose a pile of bones or a corpse of a Medium or Small humanoid within range. Your spell imbues the target with a foul mimicry of life, raising it as an undead creature. The target becomes a skeleton if you chose bones or a zombie if you chose a corpse (the DM has the creature's game statistics).", + "On each of your turns, you can use a bonus action to mentally command any creature you made with this spell if the creature is within 60 feet of you (if you control multiple creatures, you can command any or all of them at the same time, issuing the same command to each one). You decide what action the creature will take and where it will move during its next turn, or you can issue a general command, such as to guard a particular chamber or corridor. If you issue no commands, the creature only defends itself against hostile creatures. Once given an order, the creature continues to follow it until its task is complete.", + "The creature is under your control for 24 hours, after which it stops obeying any command you've given it. To maintain control of the creature for another 24 hours, you must cast this spell on the creature again before the current 24-hour period ends. This use of the spell reasserts your control over up to four creatures you have animated with this spell, rather than animating a new one." + ], + "higher_level": [ + "When you cast this spell using a spell slot of 4th level or higher, you animate or reassert control over two additional undead creatures for each slot level above 3rd. Each of the creatures must come from a different corpse or pile of bones." + ], + "range": "10 feet", + "components": [ + "V", + "S", + "M" + ], + "material": "A drop of blood, a piece of flesh, and a pinch of bone dust.", + "ritual": false, + "duration": "Instantaneous", + "concentration": false, + "casting_time": "1 minute", + "level": 3, + "school": { + "index": "necromancy", + "name": "Necromancy", + "url": "/api/magic-schools/necromancy" + }, + "classes": [ + { + "index": "cleric", + "name": "Cleric", + "url": "/api/classes/cleric" + }, + { + "index": "wizard", + "name": "Wizard", + "url": "/api/classes/wizard" + } + ], + "subclasses": [ + { + "index": "lore", + "name": "Lore", + "url": "/api/subclasses/lore" + } + ], + "url": "/api/spells/animate-dead" + }, + { + "index": "animate-objects", + "name": "Animate Objects", + "desc": [ + "Objects come to life at your command. Choose up to ten nonmagical objects within range that are not being worn or carried. Medium targets count as two objects, Large targets count as four objects, Huge targets count as eight objects. You can't animate any object larger than Huge. Each target animates and becomes a creature under your control until the spell ends or until reduced to 0 hit points.", + "As a bonus action, you can mentally command any creature you made with this spell if the creature is within 500 feet of you (if you control multiple creatures, you can command any or all of them at the same time, issuing the same command to each one). You decide what action the creature will take and where it will move during its next turn, or you can issue a general command, such as to guard a particular chamber or corridor. If you issue no commands, the creature only defends itself against hostile creatures. Once given an order, the creature continues to follow it until its task is complete.", + "##### Animated Object Statistics", + "| Size | HP | AC | Attack | Str | Dex |", + "|---|---|---|---|---|---|", + "| Tiny | 20 | 18 | +8 to hit, 1d4 + 4 damage | 4 | 18 |", + "| Small | 25 | 16 | +6 to hit, 1d8 + 2 damage | 6 | 14 |", + "| Medium | 40 | 13 | +5 to hit, 2d6 + 1 damage | 10 | 12 |", + "| Large | 50 | 10 | +6 to hit, 2d10 + 2 damage | 14 | 10 |", + "| Huge | 80 | 10 | +8 to hit, 2d12 + 4 damage | 18 | 6 |", + "An animated object is a construct with AC, hit points, attacks, Strength, and Dexterity determined by its size. Its Constitution is 10 and its Intelligence and Wisdom are 3, and its Charisma is 1. Its speed is 30 feet; if the object lacks legs or other appendages it can use for locomotion, it instead has a flying speed of 30 feet and can hover. If the object is securely attached to a surface or a larger object, such as a chain bolted to a wall, its speed is 0. It has blindsight with a radius of 30 feet and is blind beyond that distance. When the animated object drops to 0 hit points, it reverts to its original object form, and any remaining damage carries over to its original object form.", + "If you command an object to attack, it can make a single melee attack against a creature within 5 feet of it. It makes a slam attack with an attack bonus and bludgeoning damage determined by its size. The DM might rule that a specific object inflicts slashing or piercing damage based on its form." + ], + "higher_level": [ + "If you cast this spell using a spell slot of 6th level or higher, you can animate two additional objects for each slot level above 5th." + ], + "range": "120 feet", + "components": [ + "V", + "S" + ], + "ritual": false, + "duration": "Up to 1 minute", + "concentration": true, + "casting_time": "1 action", + "level": 5, + "school": { + "index": "transmutation", + "name": "Transmutation", + "url": "/api/magic-schools/transmutation" + }, + "classes": [ + { + "index": "bard", + "name": "Bard", + "url": "/api/classes/bard" + }, + { + "index": "sorcerer", + "name": "Sorcerer", + "url": "/api/classes/sorcerer" + }, + { + "index": "wizard", + "name": "Wizard", + "url": "/api/classes/wizard" + } + ], + "subclasses": [], + "url": "/api/spells/animate-objects" + } +] diff --git a/dump/src/reader/v4/snapshots/dump__reader__v4__test__read_dump_v4-3.snap b/dump/src/reader/v4/snapshots/dump__reader__v4__test__read_dump_v4-3.snap new file mode 100644 index 000000000..8cd4a1110 --- /dev/null +++ b/dump/src/reader/v4/snapshots/dump__reader__v4__test__read_dump_v4-3.snap @@ -0,0 +1,384 @@ +--- +source: dump/src/reader/v4/mod.rs +expression: tasks +--- +[ + { + "id": 9, + "index_uid": "movies_2", + "content": { + "DocumentAddition": { + "content_uuid": "3b12a971-bca2-4716-9889-36ffb715ae1d", + "merge_strategy": "ReplaceDocuments", + "primary_key": null, + "documents_count": 200, + "allow_index_creation": true + } + }, + "events": [ + { + "Created": "2022-10-06T12:53:49.125132233Z" + } + ] + }, + { + "id": 8, + "index_uid": "movies", + "content": { + "DocumentAddition": { + "content_uuid": "cae3205a-6016-471b-81de-081a195f098c", + "merge_strategy": "ReplaceDocuments", + "primary_key": null, + "documents_count": 100, + "allow_index_creation": true + } + }, + "events": [ + { + "Created": "2022-10-06T12:53:49.114226973Z" + }, + { + "Batched": { + "timestamp": "2022-10-06T12:53:49.125918825Z", + "batch_id": 8 + } + }, + { + "Processing": "2022-10-06T12:53:49.125930546Z" + }, + { + "Succeded": { + "result": { + "DocumentAddition": { + "indexed_documents": 100 + } + }, + "timestamp": "2022-10-06T12:53:49.785862546Z" + } + } + ] + }, + { + "id": 7, + "index_uid": "dnd_spells", + "content": { + "DocumentAddition": { + "content_uuid": "7ba1eaa0-d2fb-4852-8d00-f35ed166728f", + "merge_strategy": "ReplaceDocuments", + "primary_key": "index", + "documents_count": 10, + "allow_index_creation": true + } + }, + "events": [ + { + "Created": "2022-10-06T12:53:41.070732179Z" + }, + { + "Batched": { + "timestamp": "2022-10-06T12:53:41.085563291Z", + "batch_id": 7 + } + }, + { + "Processing": "2022-10-06T12:53:41.085563961Z" + }, + { + "Succeded": { + "result": { + "DocumentAddition": { + "indexed_documents": 10 + } + }, + "timestamp": "2022-10-06T12:53:41.116036186Z" + } + } + ] + }, + { + "id": 6, + "index_uid": "dnd_spells", + "content": { + "DocumentAddition": { + "content_uuid": "f2fb7d6e-11b6-45d9-aa7a-9495a567a275", + "merge_strategy": "ReplaceDocuments", + "primary_key": null, + "documents_count": 10, + "allow_index_creation": true + } + }, + "events": [ + { + "Created": "2022-10-06T12:53:40.831649057Z" + }, + { + "Batched": { + "timestamp": "2022-10-06T12:53:40.834515892Z", + "batch_id": 6 + } + }, + { + "Processing": "2022-10-06T12:53:40.834516572Z" + }, + { + "Failed": { + "error": { + "message": "The primary key inference process failed because the engine did not find any fields containing `id` substring in their name. If your document identifier does not contain any `id` substring, you can set the primary key of the index.", + "code": "primary_key_inference_failed", + "type": "invalid_request", + "link": "https://docs.meilisearch.com/errors#primary_key_inference_failed" + }, + "timestamp": "2022-10-06T12:53:40.905384918Z" + } + } + ] + }, + { + "id": 5, + "index_uid": "products", + "content": { + "DocumentAddition": { + "content_uuid": "f269fe46-36fe-4fe7-8c4e-2054f1b23594", + "merge_strategy": "ReplaceDocuments", + "primary_key": "sku", + "documents_count": 10, + "allow_index_creation": true + } + }, + "events": [ + { + "Created": "2022-10-06T12:53:40.576727649Z" + }, + { + "Batched": { + "timestamp": "2022-10-06T12:53:40.587595408Z", + "batch_id": 5 + } + }, + { + "Processing": "2022-10-06T12:53:40.587596158Z" + }, + { + "Succeded": { + "result": { + "DocumentAddition": { + "indexed_documents": 10 + } + }, + "timestamp": "2022-10-06T12:53:40.603035979Z" + } + } + ] + }, + { + "id": 4, + "index_uid": "products", + "content": { + "DocumentAddition": { + "content_uuid": "7d1ea292-cdb6-4f47-8b25-c2ddde89035c", + "merge_strategy": "ReplaceDocuments", + "primary_key": null, + "documents_count": 10, + "allow_index_creation": true + } + }, + "events": [ + { + "Created": "2022-10-06T12:53:39.979427178Z" + }, + { + "Batched": { + "timestamp": "2022-10-06T12:53:39.986159313Z", + "batch_id": 4 + } + }, + { + "Processing": "2022-10-06T12:53:39.986160113Z" + }, + { + "Failed": { + "error": { + "message": "The primary key inference process failed because the engine did not find any fields containing `id` substring in their name. If your document identifier does not contain any `id` substring, you can set the primary key of the index.", + "code": "primary_key_inference_failed", + "type": "invalid_request", + "link": "https://docs.meilisearch.com/errors#primary_key_inference_failed" + }, + "timestamp": "2022-10-06T12:53:39.98921592Z" + } + } + ] + }, + { + "id": 3, + "index_uid": "products", + "content": { + "SettingsUpdate": { + "settings": { + "synonyms": { + "android": [ + "phone", + "smartphone" + ], + "iphone": [ + "phone", + "smartphone" + ], + "phone": [ + "smartphone", + "iphone", + "android" + ] + } + }, + "is_deletion": false, + "allow_index_creation": true + } + }, + "events": [ + { + "Created": "2022-10-06T12:53:39.360187055Z" + }, + { + "Batched": { + "timestamp": "2022-10-06T12:53:39.371250258Z", + "batch_id": 3 + } + }, + { + "Processing": "2022-10-06T12:53:39.371250918Z" + }, + { + "Processing": "2022-10-06T12:53:39.373988491Z" + }, + { + "Succeded": { + "result": "Other", + "timestamp": "2022-10-06T12:53:39.449840865Z" + } + } + ] + }, + { + "id": 2, + "index_uid": "movies", + "content": { + "SettingsUpdate": { + "settings": { + "rankingRules": [ + "words", + "typo", + "proximity", + "attribute", + "sort", + "exactness", + "release_date:asc" + ] + }, + "is_deletion": false, + "allow_index_creation": true + } + }, + "events": [ + { + "Created": "2022-10-06T12:53:39.143829637Z" + }, + { + "Batched": { + "timestamp": "2022-10-06T12:53:39.154803808Z", + "batch_id": 2 + } + }, + { + "Processing": "2022-10-06T12:53:39.154804558Z" + }, + { + "Processing": "2022-10-06T12:53:39.157501241Z" + }, + { + "Succeded": { + "result": "Other", + "timestamp": "2022-10-06T12:53:39.160263154Z" + } + } + ] + }, + { + "id": 1, + "index_uid": "movies", + "content": { + "SettingsUpdate": { + "settings": { + "filterableAttributes": [ + "genres", + "id" + ], + "sortableAttributes": [ + "release_date" + ] + }, + "is_deletion": false, + "allow_index_creation": true + } + }, + "events": [ + { + "Created": "2022-10-06T12:53:38.922837679Z" + }, + { + "Batched": { + "timestamp": "2022-10-06T12:53:38.937712641Z", + "batch_id": 1 + } + }, + { + "Processing": "2022-10-06T12:53:38.937713141Z" + }, + { + "Processing": "2022-10-06T12:53:38.940482335Z" + }, + { + "Succeded": { + "result": "Other", + "timestamp": "2022-10-06T12:53:38.953566059Z" + } + } + ] + }, + { + "id": 0, + "index_uid": "movies", + "content": { + "DocumentAddition": { + "content_uuid": "cee1eef7-fadd-4970-93dc-25518655175f", + "merge_strategy": "ReplaceDocuments", + "primary_key": null, + "documents_count": 10, + "allow_index_creation": true + } + }, + "events": [ + { + "Created": "2022-10-06T12:53:38.710611568Z" + }, + { + "Batched": { + "timestamp": "2022-10-06T12:53:38.717455314Z", + "batch_id": 0 + } + }, + { + "Processing": "2022-10-06T12:53:38.717456194Z" + }, + { + "Succeded": { + "result": { + "DocumentAddition": { + "indexed_documents": 10 + } + }, + "timestamp": "2022-10-06T12:53:38.811687295Z" + } + } + ] + } +] diff --git a/dump/src/reader/v4/snapshots/dump__reader__v4__test__read_dump_v4-4.snap b/dump/src/reader/v4/snapshots/dump__reader__v4__test__read_dump_v4-4.snap new file mode 100644 index 000000000..dcb7e998d --- /dev/null +++ b/dump/src/reader/v4/snapshots/dump__reader__v4__test__read_dump_v4-4.snap @@ -0,0 +1,50 @@ +--- +source: dump/src/reader/v4/mod.rs +expression: keys +--- +[ + { + "description": "Default Search API Key (Use it to search from the frontend)", + "id": [ + 110, + 113, + 57, + 52, + 113, + 97, + 71, + 106 + ], + "actions": [ + "search" + ], + "indexes": [ + "*" + ], + "expires_at": null, + "created_at": "2022-10-06T12:53:33.424274047Z", + "updated_at": "2022-10-06T12:53:33.424274047Z" + }, + { + "description": "Default Admin API Key (Use it for all other operations. Caution! Do not use it on a public frontend)", + "id": [ + 105, + 121, + 109, + 83, + 109, + 111, + 53, + 83 + ], + "actions": [ + "*" + ], + "indexes": [ + "*" + ], + "expires_at": null, + "created_at": "2022-10-06T12:53:33.417707446Z", + "updated_at": "2022-10-06T12:53:33.417707446Z" + } +] diff --git a/dump/src/reader/v4/snapshots/dump__reader__v4__test__read_dump_v4-6.snap b/dump/src/reader/v4/snapshots/dump__reader__v4__test__read_dump_v4-6.snap new file mode 100644 index 000000000..38d2792e2 --- /dev/null +++ b/dump/src/reader/v4/snapshots/dump__reader__v4__test__read_dump_v4-6.snap @@ -0,0 +1,71 @@ +--- +source: dump/src/reader/v4/mod.rs +expression: products.settings() +--- +Ok( + Settings { + displayed_attributes: Reset, + searchable_attributes: Reset, + filterable_attributes: Set( + {}, + ), + sortable_attributes: Set( + {}, + ), + ranking_rules: Set( + [ + "words", + "typo", + "proximity", + "attribute", + "sort", + "exactness", + ], + ), + stop_words: Set( + {}, + ), + synonyms: Set( + { + "android": [ + "phone", + "smartphone", + ], + "iphone": [ + "phone", + "smartphone", + ], + "phone": [ + "android", + "iphone", + "smartphone", + ], + }, + ), + distinct_attribute: Reset, + typo_tolerance: Set( + TypoSettings { + enabled: Set( + true, + ), + min_word_size_for_typos: Set( + MinWordSizeTyposSetting { + one_typo: Set( + 5, + ), + two_typos: Set( + 9, + ), + }, + ), + disable_on_words: Set( + {}, + ), + disable_on_attributes: Set( + {}, + ), + }, + ), + _kind: PhantomData, + }, +) diff --git a/dump/src/reader/v4/snapshots/dump__reader__v4__test__read_dump_v4-7.snap b/dump/src/reader/v4/snapshots/dump__reader__v4__test__read_dump_v4-7.snap new file mode 100644 index 000000000..975d07f8f --- /dev/null +++ b/dump/src/reader/v4/snapshots/dump__reader__v4__test__read_dump_v4-7.snap @@ -0,0 +1,308 @@ +--- +source: dump/src/reader/v4/mod.rs +expression: documents +--- +[ + { + "sku": 43900, + "name": "Duracell - AAA Batteries (4-Pack)", + "type": "HardGood", + "price": 5.49, + "upc": "041333424019", + "category": [ + { + "id": "pcmcat312300050015", + "name": "Connected Home & Housewares" + }, + { + "id": "pcmcat248700050021", + "name": "Housewares" + }, + { + "id": "pcmcat303600050001", + "name": "Household Batteries" + }, + { + "id": "abcat0208002", + "name": "Alkaline Batteries" + } + ], + "shipping": 5.49, + "description": "Compatible with select electronic devices; AAA size; DURALOCK Power Preserve technology; 4-pack", + "manufacturer": "Duracell", + "model": "MN2400B4Z", + "url": "http://www.bestbuy.com/site/duracell-aaa-batteries-4-pack/43900.p?id=1051384074145&skuId=43900&cmp=RMXCC", + "image": "http://img.bbystatic.com/BestBuy_US/images/products/4390/43900_sa.jpg" + }, + { + "sku": 48530, + "name": "Duracell - AA 1.5V CopperTop Batteries (4-Pack)", + "type": "HardGood", + "price": 5.49, + "upc": "041333415017", + "category": [ + { + "id": "pcmcat312300050015", + "name": "Connected Home & Housewares" + }, + { + "id": "pcmcat248700050021", + "name": "Housewares" + }, + { + "id": "pcmcat303600050001", + "name": "Household Batteries" + }, + { + "id": "abcat0208002", + "name": "Alkaline Batteries" + } + ], + "shipping": 5.49, + "description": "Long-lasting energy; DURALOCK Power Preserve technology; for toys, clocks, radios, games, remotes, PDAs and more", + "manufacturer": "Duracell", + "model": "MN1500B4Z", + "url": "http://www.bestbuy.com/site/duracell-aa-1-5v-coppertop-batteries-4-pack/48530.p?id=1099385268988&skuId=48530&cmp=RMXCC", + "image": "http://img.bbystatic.com/BestBuy_US/images/products/4853/48530_sa.jpg" + }, + { + "sku": 127687, + "name": "Duracell - AA Batteries (8-Pack)", + "type": "HardGood", + "price": 7.49, + "upc": "041333825014", + "category": [ + { + "id": "pcmcat312300050015", + "name": "Connected Home & Housewares" + }, + { + "id": "pcmcat248700050021", + "name": "Housewares" + }, + { + "id": "pcmcat303600050001", + "name": "Household Batteries" + }, + { + "id": "abcat0208002", + "name": "Alkaline Batteries" + } + ], + "shipping": 5.49, + "description": "Compatible with select electronic devices; AA size; DURALOCK Power Preserve technology; 8-pack", + "manufacturer": "Duracell", + "model": "MN1500B8Z", + "url": "http://www.bestbuy.com/site/duracell-aa-batteries-8-pack/127687.p?id=1051384045676&skuId=127687&cmp=RMXCC", + "image": "http://img.bbystatic.com/BestBuy_US/images/products/1276/127687_sa.jpg" + }, + { + "sku": 150115, + "name": "Energizer - MAX Batteries AA (4-Pack)", + "type": "HardGood", + "price": 4.99, + "upc": "039800011329", + "category": [ + { + "id": "pcmcat312300050015", + "name": "Connected Home & Housewares" + }, + { + "id": "pcmcat248700050021", + "name": "Housewares" + }, + { + "id": "pcmcat303600050001", + "name": "Household Batteries" + }, + { + "id": "abcat0208002", + "name": "Alkaline Batteries" + } + ], + "shipping": 5.49, + "description": "4-pack AA alkaline batteries; battery tester included", + "manufacturer": "Energizer", + "model": "E91BP-4", + "url": "http://www.bestbuy.com/site/energizer-max-batteries-aa-4-pack/150115.p?id=1051384046217&skuId=150115&cmp=RMXCC", + "image": "http://img.bbystatic.com/BestBuy_US/images/products/1501/150115_sa.jpg" + }, + { + "sku": 185230, + "name": "Duracell - C Batteries (4-Pack)", + "type": "HardGood", + "price": 8.99, + "upc": "041333440019", + "category": [ + { + "id": "pcmcat312300050015", + "name": "Connected Home & Housewares" + }, + { + "id": "pcmcat248700050021", + "name": "Housewares" + }, + { + "id": "pcmcat303600050001", + "name": "Household Batteries" + }, + { + "id": "abcat0208002", + "name": "Alkaline Batteries" + } + ], + "shipping": 5.49, + "description": "Compatible with select electronic devices; C size; DURALOCK Power Preserve technology; 4-pack", + "manufacturer": "Duracell", + "model": "MN1400R4Z", + "url": "http://www.bestbuy.com/site/duracell-c-batteries-4-pack/185230.p?id=1051384046486&skuId=185230&cmp=RMXCC", + "image": "http://img.bbystatic.com/BestBuy_US/images/products/1852/185230_sa.jpg" + }, + { + "sku": 185267, + "name": "Duracell - D Batteries (4-Pack)", + "type": "HardGood", + "price": 9.99, + "upc": "041333430010", + "category": [ + { + "id": "pcmcat312300050015", + "name": "Connected Home & Housewares" + }, + { + "id": "pcmcat248700050021", + "name": "Housewares" + }, + { + "id": "pcmcat303600050001", + "name": "Household Batteries" + }, + { + "id": "abcat0208002", + "name": "Alkaline Batteries" + } + ], + "shipping": 5.99, + "description": "Compatible with select electronic devices; D size; DURALOCK Power Preserve technology; 4-pack", + "manufacturer": "Duracell", + "model": "MN1300R4Z", + "url": "http://www.bestbuy.com/site/duracell-d-batteries-4-pack/185267.p?id=1051384046551&skuId=185267&cmp=RMXCC", + "image": "http://img.bbystatic.com/BestBuy_US/images/products/1852/185267_sa.jpg" + }, + { + "sku": 312290, + "name": "Duracell - 9V Batteries (2-Pack)", + "type": "HardGood", + "price": 7.99, + "upc": "041333216010", + "category": [ + { + "id": "pcmcat312300050015", + "name": "Connected Home & Housewares" + }, + { + "id": "pcmcat248700050021", + "name": "Housewares" + }, + { + "id": "pcmcat303600050001", + "name": "Household Batteries" + }, + { + "id": "abcat0208002", + "name": "Alkaline Batteries" + } + ], + "shipping": 5.49, + "description": "Compatible with select electronic devices; alkaline chemistry; 9V size; DURALOCK Power Preserve technology; 2-pack", + "manufacturer": "Duracell", + "model": "MN1604B2Z", + "url": "http://www.bestbuy.com/site/duracell-9v-batteries-2-pack/312290.p?id=1051384050321&skuId=312290&cmp=RMXCC", + "image": "http://img.bbystatic.com/BestBuy_US/images/products/3122/312290_sa.jpg" + }, + { + "sku": 324884, + "name": "Directed Electronics - Viper Audio Glass Break Sensor", + "type": "HardGood", + "price": 39.99, + "upc": "093207005060", + "category": [ + { + "id": "pcmcat113100050015", + "name": "Carfi Instore Only" + } + ], + "shipping": 0, + "description": "From our expanded online assortment; compatible with Directed Electronics alarm systems; microphone and microprocessor detect and analyze intrusions; detects quiet glass breaks", + "manufacturer": "Directed Electronics", + "model": "506T", + "url": "http://www.bestbuy.com/site/directed-electronics-viper-audio-glass-break-sensor/324884.p?id=1112808077651&skuId=324884&cmp=RMXCC", + "image": "http://img.bbystatic.com/BestBuy_US/images/products/3248/324884_rc.jpg" + }, + { + "sku": 333179, + "name": "Energizer - N Cell E90 Batteries (2-Pack)", + "type": "HardGood", + "price": 5.99, + "upc": "039800013200", + "category": [ + { + "id": "pcmcat312300050015", + "name": "Connected Home & Housewares" + }, + { + "id": "pcmcat248700050021", + "name": "Housewares" + }, + { + "id": "pcmcat303600050001", + "name": "Household Batteries" + }, + { + "id": "abcat0208006", + "name": "Specialty Batteries" + } + ], + "shipping": 5.49, + "description": "Alkaline batteries; 1.5V", + "manufacturer": "Energizer", + "model": "E90BP-2", + "url": "http://www.bestbuy.com/site/energizer-n-cell-e90-batteries-2-pack/333179.p?id=1185268509951&skuId=333179&cmp=RMXCC", + "image": "http://img.bbystatic.com/BestBuy_US/images/products/3331/333179_sa.jpg" + }, + { + "sku": 346575, + "name": "Metra - Radio Installation Dash Kit for Most 1989-2000 Ford, Lincoln & Mercury Vehicles - Black", + "type": "HardGood", + "price": 16.99, + "upc": "086429002757", + "category": [ + { + "id": "abcat0300000", + "name": "Car Electronics & GPS" + }, + { + "id": "pcmcat165900050023", + "name": "Car Installation Parts & Accessories" + }, + { + "id": "pcmcat331600050007", + "name": "Car Audio Installation Parts" + }, + { + "id": "pcmcat165900050031", + "name": "Deck Installation Parts" + }, + { + "id": "pcmcat165900050033", + "name": "Dash Installation Kits" + } + ], + "shipping": 0, + "description": "From our expanded online assortment; compatible with most 1989-2000 Ford, Lincoln and Mercury vehicles; snap-in TurboKit offers fast installation; spacer/trim ring; rear support bracket", + "manufacturer": "Metra", + "model": "99-5512", + "url": "http://www.bestbuy.com/site/metra-radio-installation-dash-kit-for-most-1989-2000-ford-lincoln-mercury-vehicles-black/346575.p?id=1218118704590&skuId=346575&cmp=RMXCC", + "image": "http://img.bbystatic.com/BestBuy_US/images/products/3465/346575_rc.jpg" + } +] diff --git a/dump/src/reader/v4/snapshots/dump__reader__v4__test__read_dump_v4-9.snap b/dump/src/reader/v4/snapshots/dump__reader__v4__test__read_dump_v4-9.snap new file mode 100644 index 000000000..558dcbef2 --- /dev/null +++ b/dump/src/reader/v4/snapshots/dump__reader__v4__test__read_dump_v4-9.snap @@ -0,0 +1,63 @@ +--- +source: dump/src/reader/v4/mod.rs +expression: movies.settings() +--- +Ok( + Settings { + displayed_attributes: Reset, + searchable_attributes: Reset, + filterable_attributes: Set( + { + "genres", + "id", + }, + ), + sortable_attributes: Set( + { + "release_date", + }, + ), + ranking_rules: Set( + [ + "words", + "typo", + "proximity", + "attribute", + "sort", + "exactness", + "release_date:asc", + ], + ), + stop_words: Set( + {}, + ), + synonyms: Set( + {}, + ), + distinct_attribute: Reset, + typo_tolerance: Set( + TypoSettings { + enabled: Set( + true, + ), + min_word_size_for_typos: Set( + MinWordSizeTyposSetting { + one_typo: Set( + 5, + ), + two_typos: Set( + 9, + ), + }, + ), + disable_on_words: Set( + {}, + ), + disable_on_attributes: Set( + {}, + ), + }, + ), + _kind: PhantomData, + }, +) diff --git a/dump/src/reader/v4/tasks.rs b/dump/src/reader/v4/tasks.rs new file mode 100644 index 000000000..c6f31c9a8 --- /dev/null +++ b/dump/src/reader/v4/tasks.rs @@ -0,0 +1,454 @@ +use std::fmt::Write; + +use serde::{Deserialize, Serializer}; +use time::{Duration, OffsetDateTime}; +use uuid::Uuid; + +use super::{ + meta::IndexUid, + settings::{Settings, Unchecked}, +}; + +pub type TaskId = u32; +pub type BatchId = u32; + +#[derive(Clone, Debug, Deserialize, PartialEq)] +#[cfg_attr(test, derive(serde::Serialize))] +pub struct Task { + pub id: TaskId, + pub index_uid: IndexUid, + pub content: TaskContent, + pub events: Vec, +} + +#[derive(Clone, Debug, Deserialize, PartialEq)] +#[cfg_attr(test, derive(serde::Serialize))] +#[allow(clippy::large_enum_variant)] +pub enum TaskContent { + DocumentAddition { + content_uuid: Uuid, + merge_strategy: IndexDocumentsMethod, + primary_key: Option, + documents_count: usize, + allow_index_creation: bool, + }, + DocumentDeletion(DocumentDeletion), + SettingsUpdate { + settings: Settings, + /// Indicates whether the task was a deletion + is_deletion: bool, + allow_index_creation: bool, + }, + IndexDeletion, + IndexCreation { + primary_key: Option, + }, + IndexUpdate { + primary_key: Option, + }, +} + +#[derive(Debug)] +#[cfg_attr(test, derive(serde::Serialize))] +#[cfg_attr(test, serde(untagged))] +#[allow(clippy::large_enum_variant)] +enum TaskDetails { + #[cfg_attr(test, serde(rename_all = "camelCase"))] + DocumentAddition { + received_documents: usize, + indexed_documents: Option, + }, + #[cfg_attr(test, serde(rename_all = "camelCase"))] + Settings { + #[cfg_attr(test, serde(flatten))] + settings: Settings, + }, + #[cfg_attr(test, serde(rename_all = "camelCase"))] + IndexInfo { primary_key: Option }, + #[cfg_attr(test, serde(rename_all = "camelCase"))] + DocumentDeletion { + received_document_ids: usize, + deleted_documents: Option, + }, + #[cfg_attr(test, serde(rename_all = "camelCase"))] + ClearAll { deleted_documents: Option }, +} + +#[derive(Debug)] +#[cfg_attr(test, derive(serde::Serialize))] +#[cfg_attr(test, serde(rename_all = "camelCase"))] +enum TaskStatus { + Enqueued, + Processing, + Succeeded, + Failed, +} + +#[derive(Debug)] +#[cfg_attr(test, derive(serde::Serialize))] +#[cfg_attr(test, serde(rename_all = "camelCase"))] +enum TaskType { + IndexCreation, + IndexUpdate, + IndexDeletion, + DocumentAddition, + DocumentPartial, + DocumentDeletion, + SettingsUpdate, + ClearAll, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize)] +#[cfg_attr(test, derive(serde::Serialize))] +pub enum IndexDocumentsMethod { + /// Replace the previous document with the new one, + /// removing all the already known attributes. + ReplaceDocuments, + + /// Merge the previous version of the document with the new version, + /// replacing old attributes values with the new ones and add the new attributes. + UpdateDocuments, +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Eq)] +#[cfg_attr(test, derive(serde::Serialize))] +pub enum DocumentDeletion { + Clear, + Ids(Vec), +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Eq)] +#[cfg_attr(test, derive(serde::Serialize))] +pub enum TaskEvent { + Created(#[serde(with = "time::serde::rfc3339")] OffsetDateTime), + Batched { + #[serde(with = "time::serde::rfc3339")] + timestamp: OffsetDateTime, + batch_id: BatchId, + }, + Processing(#[serde(with = "time::serde::rfc3339")] OffsetDateTime), + Succeded { + result: TaskResult, + #[serde(with = "time::serde::rfc3339")] + timestamp: OffsetDateTime, + }, + Failed { + error: ResponseError, + #[serde(with = "time::serde::rfc3339")] + timestamp: OffsetDateTime, + }, +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Eq)] +#[cfg_attr(test, derive(serde::Serialize))] +pub enum TaskResult { + DocumentAddition { indexed_documents: u64 }, + DocumentDeletion { deleted_documents: u64 }, + ClearAll { deleted_documents: u64 }, + Other, +} + +#[derive(Debug, Deserialize, Clone, PartialEq, Eq)] +#[cfg_attr(test, derive(serde::Serialize))] +#[serde(rename_all = "camelCase")] +pub struct ResponseError { + pub message: String, + #[serde(rename = "code")] + pub error_code: String, + #[serde(rename = "type")] + pub error_type: String, + #[serde(rename = "link")] + pub error_link: String, +} + +impl Task { + /// Return true when a task is finished. + /// A task is finished when its last state is either `Succeeded` or `Failed`. + pub fn is_finished(&self) -> bool { + self.events.last().map_or(false, |event| { + matches!(event, TaskEvent::Succeded { .. } | TaskEvent::Failed { .. }) + }) + } + + /// Return the content_uuid of the `Task` if there is one. + pub fn get_content_uuid(&self) -> Option { + match self { + Task { + content: TaskContent::DocumentAddition { content_uuid, .. }, + .. + } => Some(*content_uuid), + _ => None, + } + } +} + +impl IndexUid { + pub fn into_inner(self) -> String { + self.0 + } + + /// Return a reference over the inner str. + pub fn as_str(&self) -> &str { + &self.0 + } +} + +impl std::ops::Deref for IndexUid { + type Target = str; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Debug)] +#[cfg_attr(test, derive(serde::Serialize))] +#[cfg_attr(test, serde(rename_all = "camelCase"))] +pub struct TaskView { + uid: TaskId, + index_uid: String, + status: TaskStatus, + #[cfg_attr(test, serde(rename = "type"))] + task_type: TaskType, + #[cfg_attr(test, serde(skip_serializing_if = "Option::is_none"))] + details: Option, + #[cfg_attr(test, serde(skip_serializing_if = "Option::is_none"))] + error: Option, + #[cfg_attr(test, serde(serialize_with = "serialize_duration"))] + duration: Option, + #[cfg_attr(test, serde(serialize_with = "time::serde::rfc3339::serialize"))] + enqueued_at: OffsetDateTime, + #[cfg_attr( + test, + serde(serialize_with = "time::serde::rfc3339::option::serialize") + )] + started_at: Option, + #[cfg_attr( + test, + serde(serialize_with = "time::serde::rfc3339::option::serialize") + )] + finished_at: Option, + #[cfg_attr(test, serde(skip_serializing_if = "Option::is_none"))] + batch_uid: Option>, +} + +impl From for TaskView { + fn from(task: Task) -> Self { + let Task { + id, + index_uid, + content, + events, + } = task; + + let (task_type, mut details) = match content { + TaskContent::DocumentAddition { + merge_strategy, + documents_count, + .. + } => { + let details = TaskDetails::DocumentAddition { + received_documents: documents_count, + indexed_documents: None, + }; + + let task_type = match merge_strategy { + IndexDocumentsMethod::UpdateDocuments => TaskType::DocumentPartial, + IndexDocumentsMethod::ReplaceDocuments => TaskType::DocumentAddition, + _ => unreachable!("Unexpected document merge strategy."), + }; + + (task_type, Some(details)) + } + TaskContent::DocumentDeletion(DocumentDeletion::Ids(ids)) => ( + TaskType::DocumentDeletion, + Some(TaskDetails::DocumentDeletion { + received_document_ids: ids.len(), + deleted_documents: None, + }), + ), + TaskContent::DocumentDeletion(DocumentDeletion::Clear) => ( + TaskType::ClearAll, + Some(TaskDetails::ClearAll { + deleted_documents: None, + }), + ), + TaskContent::IndexDeletion => ( + TaskType::IndexDeletion, + Some(TaskDetails::ClearAll { + deleted_documents: None, + }), + ), + TaskContent::SettingsUpdate { settings, .. } => ( + TaskType::SettingsUpdate, + Some(TaskDetails::Settings { settings }), + ), + TaskContent::IndexCreation { primary_key } => ( + TaskType::IndexCreation, + Some(TaskDetails::IndexInfo { primary_key }), + ), + TaskContent::IndexUpdate { primary_key } => ( + TaskType::IndexUpdate, + Some(TaskDetails::IndexInfo { primary_key }), + ), + }; + + // An event always has at least one event: "Created" + let (status, error, finished_at) = match events.last().unwrap() { + TaskEvent::Created(_) => (TaskStatus::Enqueued, None, None), + TaskEvent::Batched { .. } => (TaskStatus::Enqueued, None, None), + TaskEvent::Processing(_) => (TaskStatus::Processing, None, None), + TaskEvent::Succeded { timestamp, result } => { + match (result, &mut details) { + ( + TaskResult::DocumentAddition { + indexed_documents: num, + .. + }, + Some(TaskDetails::DocumentAddition { + ref mut indexed_documents, + .. + }), + ) => { + indexed_documents.replace(*num); + } + ( + TaskResult::DocumentDeletion { + deleted_documents: docs, + .. + }, + Some(TaskDetails::DocumentDeletion { + ref mut deleted_documents, + .. + }), + ) => { + deleted_documents.replace(*docs); + } + ( + TaskResult::ClearAll { + deleted_documents: docs, + }, + Some(TaskDetails::ClearAll { + ref mut deleted_documents, + }), + ) => { + deleted_documents.replace(*docs); + } + _ => (), + } + (TaskStatus::Succeeded, None, Some(*timestamp)) + } + TaskEvent::Failed { timestamp, error } => { + match details { + Some(TaskDetails::DocumentDeletion { + ref mut deleted_documents, + .. + }) => { + deleted_documents.replace(0); + } + Some(TaskDetails::ClearAll { + ref mut deleted_documents, + .. + }) => { + deleted_documents.replace(0); + } + Some(TaskDetails::DocumentAddition { + ref mut indexed_documents, + .. + }) => { + indexed_documents.replace(0); + } + _ => (), + } + (TaskStatus::Failed, Some(error.clone()), Some(*timestamp)) + } + }; + + let enqueued_at = match events.first() { + Some(TaskEvent::Created(ts)) => *ts, + _ => unreachable!("A task must always have a creation event."), + }; + + let started_at = events.iter().find_map(|e| match e { + TaskEvent::Processing(ts) => Some(*ts), + _ => None, + }); + + let duration = finished_at.zip(started_at).map(|(tf, ts)| (tf - ts)); + + let batch_uid = if true { + let id = events.iter().find_map(|e| match e { + TaskEvent::Batched { batch_id, .. } => Some(*batch_id), + _ => None, + }); + Some(id) + } else { + None + }; + + Self { + uid: id, + index_uid: index_uid.into_inner(), + status, + task_type, + details, + error, + duration, + enqueued_at, + started_at, + finished_at, + batch_uid, + } + } +} + +/// 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. +fn serialize_duration( + duration: &Option, + serializer: S, +) -> Result { + 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(), + } +} diff --git a/dump/src/reader/v5/mod.rs b/dump/src/reader/v5/mod.rs index c252550d5..5f50cee7e 100644 --- a/dump/src/reader/v5/mod.rs +++ b/dump/src/reader/v5/mod.rs @@ -141,7 +141,6 @@ impl V5Reader { .join("updates") .join("updates_files") .join(uuid.to_string()); - dbg!(&update_file_path); Ok((task, Some(File::open(update_file_path).unwrap()))) } else { Ok((task, None)) diff --git a/dump/src/writer.rs b/dump/src/writer.rs index fc0e44ba0..848121de0 100644 --- a/dump/src/writer.rs +++ b/dump/src/writer.rs @@ -304,7 +304,6 @@ pub(crate) mod test { for (task, mut expected) in tasks_queue.lines().zip(create_test_tasks()) { // TODO: This can be removed once `Duration` from the `TaskView` is implemented. expected.0.duration = None; - dbg!(&task); assert_eq!(serde_json::from_str::(task).unwrap(), expected.0); if let Some(expected_update) = expected.1 { diff --git a/dump/tests/assets/v4.dump b/dump/tests/assets/v4.dump new file mode 100644 index 0000000000000000000000000000000000000000..9dd27624367db57f6f902a03df58fbd93c46fcf7 GIT binary patch literal 64636 zcmV(nK=QvIiwFP!00000|LpzQa_d-=B?!lLKLzV_TPib)R1n~NJ}WDuNX_#so$9h^ zkN`=DAV6UtMP+_w`3_w-zghQfo8=4iRo~^CPG8_#t(nMpCpQ{ zuvGft*Z$EjqW}AE5BE!&E%?WZX4fB1d=RE=u2S+9RM|E5uYGyjIsGFw0VA%pnu zeE$F8i=E8ocTO0&f&b;}m%I9x@)vuM{h+#Swzf_Bq3heuOMh_fzB*sN8r4>f_GsIz zSF7bO$()vuQ~WzzzE8iUOPN*EtQgfwwRvNhUmLZr_0G0YGdk@~v)cLg<*z^d`R5P+ zFRL>CeyY|7!jH{%0r)vE03;+Cr`EQV~@aO#h0e?1`{~zsOn9LkMjyPKIK6{Vv zpa1o0?cMorR%^9C&;LK-&p(J?Pu{sTbnS{2hQX4YeczhFWAD<>N?r9c+lhu>zW$dl z-BL7hXEP^s{c&j=I*woBua%-WbgXGqii4#U+9j(*BRh8D($bA5u3ri*f9#Yj-!2W3 zFp8rR{md#w^UzwAf>8#Ks*cwht*4To#T8bx*y7G4E9G955 zaXI%>`p?vfN_D%=*DTrA%%U6Pk!_bE5pTs0VZQmjC0BOnSM)S4J)iHq(9e?4SNEB@ zQ6x7~?>qObXhOHj-5c*JmmLLRX<~WwVo_<93@5lMKIr;x>{?!WTe_bgeC8p({MYiA zi94P+;ZL5ka6HV#{e(t;6(psh#UtZJYRr-dvty}$MP3l&f;E~J{%#2m$Jde`7>|8s&Q;`>veI)b-8mpgTmzTUj zv&X+Le^H}smcPgf{3%||@ex^^*bzKxU%vi>`kVT|`kq?fP4urM#B2U&&D|g6YZZJg z`jJ2USAEr=toSF4t)A=hPrTa=&A-n3m{&((@>U%$`&;FHGMwASSuI2@ylU?gX{*}8@_O&1Rqkk>uzDtWK z_g(qd2j9gn@fi7EV@t_&DSP?X|K*Jt70d9(j$Z2@YD4-+`*uPXrfoV%1_RH@%wUfO zHKKp}P6->2_P?`cM$6d1L-|m=4AKup>&7;okU_Ds?>2ZV;x@c@X|p@wcB!k4*NhIs zQj`pb4sE{xCXe0;8#7*&B5MJQ5>H_H=;ko8WC@yi8;b3CliDEKrrPwbTaRXGsm)f% z&(R>GBPQf;O}qJb<&t4!KdJFgu>Xx#_R~83bj{2(ziVdx>mOhzs=vQM7*4E^ZkwF| z|M11Yv)D~#0Zjgzfgjf7uPwG3%@1}vz3P)5i1e{~Yd`#O$LGKM!JdzTa7ea1%&vW% z{iI;ur=QPn*Z~(aj3+)_owpR-H}Dot zYH*S{eE^%>aXzpq+ofG>S+N+j#mV_9%>&mb>tVgPvt-8C9VTH&TPXW&lo|opfCl>y z?0BdZ^t*XCR>7mLKa98)E}nv%p2h~QO>INhh77Iiuiz)Ub3E{po`}YoZu2vqOuICp zdHi)g%=^u64c_nl?%*?9KljZKcK*<#mmcP9ivD-Ord6NK@A2zXa4=`O*N{Rmuuc0+;dKpT!?6Av2* z+Z7)ynbpcDpp7w124oCw{n+(*C(B->$R#T09*L$lW-G*JK8+RKu5^Q4KsQzwPCXp5 zO+&;}*nmEpFI*VGB)tqCal7=^B&hqA)z5tLSR!}qTXEig@w+Soe=Ftz3q}%&@gr`R zFpjj10_{)HgTgP$K7wHY>|wECu@c)QUkw|!tAG6dWjPEi&0(foU##$qM!e*X`1d)V z%%ES0>-X+u*_YyLSW9xZe7Sb+5|QUP_&q<2uOCf4&O8k4WT?W^C`crxq;Q!-a7-2~ zol&JRA**7iW?p$xzlEjayKv}6ui*^&Ek`anwUO&#DJjSZmG}y5AW8U3tuEkDbfGkO#P7-6Mcx(9m@v_@y&+4jRMP*OW+6nI{+#HI=6m?#C=$U{f! z*rn6m!P7(PCF;YPt6^Opx?`9`kXJpXR= zU9A4U|NFo1J|}elVX13d^H>}r`l*;E{75XwAh1`sFz%wh;P?wS41Cz3GO)BDi5Zr2 zY2tY2jYAoXr85}dzXK;;I*u|)v=?asMq&f;Mg!PPdSWsbPPoK=mWx<|)h!7`!$oK` z$VfT#3?80`ueG$iY5F_$TD=LMCEfYlp}>VUaQarXk^KmBt31@~6Z~u2AqN|#*y2|& zc|kBOdG3?~Uvk|%vU7AN{^rCXM~~ipss1}6*PNbeq-=jVvwZyVMc@wUA`{D{GqLPc)LDIyr<}SmINEK$D`KT8ODQV{@$_ZfQr(Uh7CYI zwzZ6~IQ%h|1f7{!8j~P|dBCBb8$w@QXvr5!=Z7qftCnX-`-4m(0BUmO0XEy@5;C$e zptU)60-qL68E0NNDqW9K1IX^Fd>w|D39nUIM~=aLV8`r61H zx_Df6HC6==wO!hCDWdUK<)RD2r@_&dUUp(FV0r(eL1xBJBKo>c2nq#Xb0C!44aoSrWfF_HW+&_`{Qb{rms!@Q&;hUpw4= zcV>B&QRu?&pSbhf8PC5e?d5-^kNONFBpa+eW}0dO7>8Jb14j;dW&39i*#`>UDH55m z!=A=6Z$1b+5;@KrxZ+UbnvNt}Ca|IOar(_joz8R>f(H&M6>H*(6^MG62^`h*?K-l3-hhmRHPRze`~@Y+tM(5VJS@R;}j{XTzOU~AFv zlcnQP$tR(5PaAPS_A6caS%8y`efJ=+zmHUy^`p#LZ z!|U%NocQo35Kg)OjDGK|zoN1!r@7x~ld`j7I}wR{q0Ni<7w|3MGRwE=e6bzR6(j;T zLE<8_DK`?gVmE(eQ_9%l6Y;~M=fowb(*vYJXTMW=vR2BY7+A5maP%lccSPO_xv@j# zXG#ph<_$K358s6w5gurlRm2?9x8`!hPXan?W5!0T5*a852RUAyfJC{Texm>V3%wLQ zGd;mFz+dbTqh*&=D3bUtd)>+lqg1_L8Yi@@|MxHW4!%yg*9!b&`jSm>#0VZkD0nNQ zr}T5OxZCuF)IX&+w5Hi`;_3=XUefcgDkB5rG}q!4L~!#Z5r-I+zDSQFGca&yIGlIzp1uU<_9B{v zD3(Q=I|-SCf$PCfAHapAhb3q&bczuDLik@De4hmW{g0>(h0AFzC0TFh7Rc`f{eOIl zzC*o!{UK`pTRKGld!K^8xwHKC04q4%I1-QE9c|b8$7?w~3DiIWio`Y{fyI$H@TlzY zqLa%{G~%!%2#Dc}br}3(M|mBVak4C*+)S`FIXbM;34X(2Ej>0lHfq}|+?&>l2Xuvk zMX_!49d@S)nFHhCobI0tA_9%T{B*vvQf-Ov*iQ=)}kbvH7(qqG5QD4Rk)nDVOd!I1$rLP#pnW zGcv2}tF4{W0?SH>kskrgP?MpT>oB#$#WO588$!dzAHqy{>T~>SS#NS%G&ihAkkzm7jr+zS;(u5~Infqwy28kN~^k~N^>(1ObbL3=m z@E~FFk)OjiQESFe!NK=4B;PhO7YEX7WRfK2<%sYKXhUw&GE-|z}`IPrm;0d3dF2^Zuks%5M^`D9K$qAcAln zDe(H#vE5{bpI_rFDNPcJTLRa9e>`b+i#r=6MYLO5l!-@v{LCGOytX`dL@P10T!m4p z6peaPy}RDTBtW-0w3|AFP5~+%WiV|1C0>Ksz;)Jtoxa-N;_3RQWWl1hM&XUo|2ynC zIuV7Er(FMnGtpAOY<4V4_L<@UdgPffWLKHZ5QNrt6z?aSGk3Hen7D6Kzv?MUf2&_D zpq&%Y$dYw+=Uy(go;_jy5dx+hsncqvNzF($a87_M9~~%Z$b%p2Z_`W3F!;{jz0U;c zH=nGrA`u)iYg|qrIV$+aGkES~6=d>rTxvE;`pNMyPA;xi&yvN|NaZEMXrEglO<6(- zQTRA=W&w+%6T}X}6swa<5@4yWrDCQmX@lU+c18%N@MN4fQtS;{MzNb8W4KFnvWzLj z1c*U5v~_HgUJx@xnQw<5Rx+R%1#xBY>ai6~G10K3w5lC#=ktqzlYSaqlo^NbqOB;M z9wQ_K1-hWCM=|`OVBl~(&1+9hBhpC={m7IWDBS?Spz{NHM1Aex%`)WZT&2mfd!`V{ z+c5C$Lg4YsN35EIX4WgX7LG_aumunmYDE$`>ro*>!bp=ivQc2qKmd&q|2*|Qv&4y5 zO8f0*gY{8t5)DDc629UP6yy(Prbb|j#hCju-mb%kQCJ((~vL(I9L{IdAhh%ivy zB(9FQ8hC=lXP*SyP+hdFLhE;*Vat<#=pZTj409m#htkLtZn+u_KArj3Ypgz8ZH?Hg zzaSiv%qL2%ZNXWQ6i@-Pcv|8k`)w4&%(du^ZoCo?%9Mb>xotM%-;**t1rSSjxDx5g_$C6p4 z|1sM1KmW0fC*|TqRLuQHLckPe@Q?IY`H$nZi3H^oUMDez;8~SO1l1)<{ z(%CD(85RV%P{{vBr5>(L=REH%x*I*PAR%wE{`jGQVrbJ`FzCMj?OA9TdXD6QK-tRH|OdSJL>69%?a%1Z8tWQQ_h zTxvlVnZTJ#vpdD?wmKGdZ zH~;QcCRpGp2ZT1;5g$M*8V@a6oYC9ob=61dR9OX_Ga|DG%AIh>(p1~qMN){umJ6W9 zAautdKIt!J$QKNE=pYcucvyw-U(qFCPIN{pJqYv%Fg(0K%X(gCQpgN{-9xs_bDXz@ z%+|ZwzN|hELDQj5qgf!2&Ggr0bbb9#b#P)pGwxZ6PJXZ{ZUmf|@pOfWr`;EwTOFp0 z$RAOH65rGGHTKup;5RxPr0IetkNx`B6wSOv5PWZSTD4kqd(8 zyIBX`0D&i&l8q(57KeH3HQL$Xuh>Zhr*A`2#F@sF24)ce0v zx0=#5j&f2X`y#@2@#vXfg6YJHQ{Rdd$07?S%qe&dkqpv^y z=(5yDJhKPeK{);~{&D`}l3=wTU;JQx*m#JiE2DjSF*&YRhu^yU-Sa!=Zu@CIrb{8j zTDZ=VLHj|lLM$kl4+nfg7S5Fzab-Rj9Z^Eb8wvpeW(ceHWb>;dzSR$u-rdQM|l#!V#s%F^tf3K%BixSl@&(Gb=$P zsJJCF*S3`!4JE5Rge*f;Ca1-N9C&%I<8wV5!FShA`emcjsZw0NK12-hpeWp{An#?_ zH0hG`lMK#2+OD6pMz8RV^c-@;ip+O=x^gIVwD zCqcl6f+~VV_FVaGwg4KY*=!rR1+W_pQAu|d1hY+MzkL!J@wevl)Bfvd|KMq7r{zx0 z{ZVUF9lW3YYxf1ETufSwP^!uN+8H6K3ue8QhHgmqSb&o#gHs$g#pDI{;C!6a@!@w% zSFjR7ffh7sMMG9^)?2ma`jFjBLKjRw44NLj144y^@Vi52G@Es^og2CX!fom96wA^p zFsY*-TI0a~%AmvnEj^lja-lqXBjdApsXtCe)&AW(l7Ki@4DlJqDu>*Di1Ee@~B>T~~7B2m7;xjHQtAfuS zT|6mglq*RH+M9&G9h2pv1xv6l@T*>!%m!u7c}Mh>0i95&!axNFGl5h+gyJJaz^(Xt z$PHTl67}Y)d~0Ia!Ey!581dGkm)P~(Cu>r=pp30ka%N5l99bw$1hGsUSc@2yAGlip z${xL6$QeR4H?DAoTa8Y$y*|Tz<@HgplAq2_qhsc$vmb<(GgG;%(}1So zYw0W)xUsbr(Qc>Pz}_E5N`39~JKH_E>Uo#5cKI~Ce)!&r37A>K zl2^e1%!iX8p`TvI1ibk)^6wvu(phw@6pafx4Yh{((aO(81rA21nI9Ee4#6g`YdbgR z-Qnqda&Yr>etU5_pDc$LdwZjs%hw_=iXc%eY0sI1o{zdBmP5c{NP(+OIB19(Km?(* znwf+;LPhYz@d! z6^*49k&Ie6O;;i4pH?hoCIFLHxQs)Yi$Y(GLWVahF@IR6w&nwOEk6m}1@rKuuaxZ> zLEUmyy0^kjW~Fr>t$LHolig9g=l0L>KE{jiVrNW~~QY-fL*da#Fjcx$5Av2?n5m*~B5$UyX&uAtx z0AyPtoVb=BL|~-bPBa(f9Ti?->h%%+Z_Th?;Hn1}J94PaCw~;ri^OF_eo8v7*Kxq5 z^++O9j!9VJ33dt+Dp-Z=h>ByPd=g%jD$&3f z!P&7=9(ZRd>P>@|?T70&^_@O;nd*&p%dF+r@9jp%Wb$I#x5m?@+3g*kUQb?kYO9M* z?be>Z4&SfUgCrVADhtCsa!UKwK%9;)oo5z!3HPoS!K}l1(Efp~nfoqReo(Gu5@0r+ z7s?;Sig=&+b1xaoO#9HAl3-2Qi0#r&!lV_Sy&|mKhL4X zvB?yTmR0lo%g}3Y{Z&z5heg~n3?STS zTRvOOgWmF@{>^B3waGl{Ucc6?BpL7CH{Z|s3HAgJ{ORyAAax z<4%J~&c0wMftDV~PXOn2nc{{JoQq>IQBUN7`VJX(u+`WWlKqY-=Sm@USvJ;0uPK@b zwuOl=+uO03+7r_yvU(JjB3qHGMHqLf*_6s+T0mD8M7KfipM#{tRG(7FpR$G_MI!28 zB9A;=>2jcfJ1$)yqf(;?8luY!S;@G@CoIlj(yKI6=t@vRU+Wbg+_1 zQG-}wwj6zB>LUnM_0L;4qeiqD;!Nr%Dp*2Bz0)$=^^e1!{3^`r?{_^CTe>pLZP6`|o=5I@N@WSE>1UQd)YCfln{iO05 zqJ(7x&SQcCGdoGkoShNPNE~Gdja|AS-=7w)b~5gTr01~jDd)MSVu)W4^W#(htx-w+fPCmsZ#UK;*f}Ei%BkgJKGZ9yAEH0kM(f0NzkRcV z%bnfFoki2N+fmXwoSjsI-k|?}ot$zd{3!)=_%BWPlMW9iT!=PuQY27_t`<`?NOH2i=1GEdg#XRyi=X+4154cqwh=-+D{b8t7vb z1cn20<1=$a^G0!shIfHqTAD*W1?b4^B{KG50)~~mCZvlM>uZpRTjFL2_6p+*M44a~ z+RhXS(Z5$(cLU49VG9Rx9F9PX33-G(&!V#W2OS*?C#_KBQB}t&Tu#YCaJdAa1_!pI z&@#|E=zI*NDg-s}{5MAt%&e0liRJezeUMs;#>JD7Zo*oZjG$nrcbOEOMYI+>LN zP1142rlpe*zhPmikOCgC89la2@Qgk?7^l29Fc<{9&#e5~Yd6|PKFx9E*fhTtW0T=n z++BqC{`l_59F88l{&2^!FXL6S)AvRnc_e8mr8OrL;jK_26cO-g+1lpQo~M(ZvO=L7 zfgy_#xjp8c7fQUZn443=lT`7vTtTm_g&QW3;N%2=hZgO?6=N`#up<+IXD({#nV8Ag zmK!tX&Qx|w`vp%TJjN{0Q zD{Xe#f&-YBsi=2yEy-@XToC$-oIuce3iLQU2pnTT3rFw-5n54mryhU8 zrdy>;#KiLzFvgQ$uCk6Y{w%K~Qz(@!*I=hYRSt|o!Vn4Lc3BMrNH+_rvh(0_Jq6nJ zX#EHVF=GyQmdtle)fJ@Vt^6jBj9P-P2p1SMtrFgn6Qwhefe7u&P@*M~R&`}2$ zhsI4K^sXv8hJGo%<=k@F0P@2QSvEAyzi{m{{B9l00D7u~QokJ7c2Qi?sI{w|=0^$6 z_XkYNXjJolW*>QjV6~aiiw>5Z@r@nSYyQzm+nleefpv90eRwzv-|zBWdf(CxuLd59 zlkP>IsmTxvfO`>$8m6+1!(dMHVMQgSgTb{%0w=3k8e5{P%?EQ94ANl%oL{PeLZ$+U zBt(?smDAYSY8zvc_)?)$(UUd9e6AQa=iWzxK=27j+h@vmsB=qm-WoDe2V|u)rB89kEs&etIGuvpw(^y*vX@)lS zIdUtZT5UG+uEr^llnYkQh)P$Y(6P};cyqTuP3kA^;B|j}eZmVB9vV1Wnr{uR;tbRNCo<%1h6561RGvk1PV(`2G*E!j5*9mTR2e5jFG zrcAc`swBD;C8b>I#vYC`h4U@7YJ<|G2Golpoi$6&iG|99EC@|3!phP)ONWce0Nz0h zVz%PUI>&`{jzthJK8IL7=e8J4@@b00H(NRwl7**5Gj{?M2dcAOAz!MRhELaR6qot7 znl-IcO#kW9Yf;j-mxKsI=$2?mVtJzqoBXo;UEbPZA#N=~2hcUa(s5H917^o}epNhs zb0<04yeDj!##-+3z?u`H*XNX7pWec}-DHu}W7~Hw&Zp<+ughfrV)wjzFmK;>>x<`? z_lu;5e9{=8^bNM~zT*uzZO-K{34?wUwq#te3m8~MViSkE!BJc&DEFLPAi9*7Vm;fX z10-*PmD-F1+hPT&WyQM|;T-a5j;Cdov&}13d?e_y0yZL!6NRUiRvQ8mP0QRD(mq|I zVT!Y(4k3QSHvp|X6}<{!yEA5sa7h*SRs^$}-6XCnjWSr)5y5c*vr@5nRlv(`)QsF1 z+8`W5hq*Iw9<2zfnPXx>f0QVC94LVYyuIuDKwv2Gi_9WFaum?^~!F=EKHJiexpI{i(I@RVHpt?sF{t+VQl3MyoD}bKCXJ3BL zp|x>%xXq%4@cQY6N8U!!wSVZ({r%q4Id=+x@v(jLkON(Tg%%aS9Il-#y`i+sH@hsvxH7M5hp;TAfMZ)@|q#EPY1T(~U71@g>Hq(liz3N7VL>;%3~tDZ)Esve@}ttjl+;huov z#gMnhSFn>nQtNc=U$wTvXvL z;NhF-9#f z`}3$zQEb@CVXL_?i7+(=`qAE3$2|s8rl-s z?I4UkOW`VX0kZA=E=NV6rBThp*T=Ne{L&Ru2@f1Ma$Gv2p(P+0m%a0=-MihZjn>J* zX|j6q7H5acNzdx~(s(4~yjwSze8*bP^SCJ=08 z3h7(CeaPY~6Y4_Ij&ez^B28N<5UrdpUV^KwH)4$POK|}OV!Gx$#SP#fIQf|J8`?l+ z)vqGT$q~(XB?{ESi!dc*vtNts3N6B}(m(14$w2v;>5m-0a$XjT6^fjL><9*LEk?<& z!HEfU-ta(uyv}qL>@G?y!oWGqk3V}<3M77lI`(EY=K?>pfPi< zYHhG}+#wLE=bBhr%x0Pz!XR>`k&~XH5gmRB>Z+NK;>1_-i!UrHYWe z=&$m7{5z23vu`cjF;|caBOaRVSzjUc2FMx{IN>`Kv!O!SESKQ`M8t3zP{d|gI>|1G@GrrRF#Zco%7UEFS&?s zU8}V_4Rg&chP?;$vBSE-Lb&gRx}0V|L@}7Rv^Hbjk4{fRuRZE_##g<)qeb*my-xO) z)7|r6{(hxWV237o`Fjadk?SdJkkc9APX%iw3>qgsEgG0Xfv!E)5spM~gsWFj>hj-c z92dfAkP|slW&c>vK7mXcw_(^>WeFH~=03~T+)_Q5$t6_uf0UNWU1oaL)nPwTECOYD z`7!8(0opF^1sQX1K)zm|T@@R|fzkmz_5!Cw_CraDaVZlvjN?pYiROF+zMnA2vBH)X z=eP4i$WJ0?jU;}R`>QkYXU>ug=nyy$=hJa9YfM31q_;#F-Gi1^m{t(ks_sPbhiDM7 z+_y_7Xr098De4)x6u}D=;O_-1idH2_tjiG`mh_ngI(Q+go(_=;u6b#fIez!GFgsv) zMAR@Cwi*K^spS>dCYS_-H^0Xh{?Oa0bvlh!Cl85vZft!ePo8s@T^fjuYP?>q?k91* z-81jn4`-v-#f39j^}O>%-*?~d2#(45hG~;W_Ao(6fseA1j&%XY+QPk|>}VpYA#9NG z3#brt9vi8Zu}=oCtiOO$keP#I(hqGQ?b17jLm;k835L38;7xQ-=0&MZsh(RFxS||5 z4Of&l8si><>K7Y6Yc1QUg-e_lI$BcZ-iK&*yWKHAI-0*no<&jV8mNtFD}DP~I-Da; zES)%GYq(Lw-@JR-U7YW=F8jeE+HZzI=lpKWg=x&G`o42p>DL^!VIT``EC?wc-HIX;AL7{jgoKeqv7bh^JE{N zf4e+t6=bTc&=U@Oxg?EL6pGufMtYc-v?>`01s9^{prlkCoB>LCJ3mD!qta!vp~D1N z3M&U{r_s5`*#LBt5#}rRsR6MQx_r@!JQ8Ff%95x~{DA`wBhwuNVCEhc0V7;b9ByL9 zr3Dq{)ee&}ry5-Vn@l%2bzWQ+wZm!Rad5&$Bnl*N%g-f;Yd8*a{W6>v(aCY8wH#B2 zFlrEGV$huMEm3l(wNU}1a*V<0?{?|P!C^#LFVcZf7c7RbTa?Ip0|&xMDdJf*%vQ@> zGu_Plc@f{{S|}7LB@qb2MW9T`rx(z2cKX~{&9Bam2KRG&((70kPjmalI6W2`G9=*7d8tBt2E z8D?tEKOX498Llxv@$+Sb-}P?|qg`7gO6{YB&$Y5hfL_ab*~rj`t;Ouyle_x1?1b~9 z)5|0KVg59Bd*{>62OMpcDrRp@rLN4T`k|FO39Nyg4A~*N028frnZWH^=T|eN7xJX9 zVfg-pAur^zO%yRF!qz1`Tluaz(37ycw`|sIi;i-b8yGV z)p8mh>)rK~M`&lAJw|Q_)`Vz+>4k9qGGb7Wd`fWK`{Fdf`N#_130MXLhnLw(QHX~Q z*CB9BYE;WKXdD;dFPW9F<-vRPTx&*I zYdqEdT?r=R@01M@0s;LkBXJcZ_Q@(X>vXHCNXvB*ns0P`hR#iLv@^YZ zy1RESu11rCZ^`3ma<*&S6qKwpmz=Uz73_ctA=@aoBqIfNH!O9|{i^IZn>StGA4GGP zJE^ns;EKc4kxNHy1gK0#SyFkrmVux%sS4wk$3Sr3kgu?(Dq7c-f`3&-CJaz)FChOq zql%zJ%83}Uf-{Ag;#T%)#q>&=HnO9J)LdHCJ5d_1^bMT$pGD-aSjX8ZO0cw=)mDB6 zZ+&E_=}g~b?&3u+?!LBP8}VMfW3`?;S62s%7x(7rbx~Ngm=lm-YmY&>klP~9K~pdR zxpPSSj;6;ZS7pexR%#M`)r+k=AX{`4Oi*OYJRa9y1e5^fvn81?IWnjFm?m#2a%jVh zxQ|K@nmI@Q7IX4riK0D)_qdQUm)7=79+I}ak*p0B)gUDwVll`$%+UvOtF9sns!lzg z;?v{=M`)dra~BB(gpJDk_b;yjtB!9$>yu1V^fSvUTmwG?u(s%?~#6dyI5PFnL=TrF#uZz*7J*Ob{tp!@R&X6Y^dG_$|1t((1Y&73N2gC0b^?WD%Y(nQC?~vlKbgYs%HKo0T z6__`o+U+gQZ*IMB;o(c~#Gbtxqn^=R9#_4YQ^YTR3RXxlrUawJmrj0K!ka=#OR|!j z;F5V&vb1p`2f zr3vadX_Or>%xq^7NFX8F;jwTuvf6#&;&P#~4evwsu1AqX5)MUSJ7?b`BqJl%CV`3t z+bww#NxtCZ)^xlZ@^sFu7>U(*vJypNKR3ylpoWGVa@7}WZ1)t>1SI3}0O1H_1XOi< z=fx+SSQSF^d5_R~QCwhAuaURY{(0apzelT$T0UqzPUhgkB$R7-vNj40Ur%hOHFfSU zpYOZX-qGXTYxSXVb$9$^zh4?_chRKaIc-Z!)D4zB*I?KyAgeQ=INnShvWjs)@LOqz z#(!#s0$%e+M!N6D{&wkZo4j>vYLSIcVU7q+}mUTuO3ey%;oU7x6=*H`t9nX-+Ecjo~#@H1Elor%0qfERYGAi#0{zF zV8KQLAHW(p@k%*_BKA@)oly+V`a9*+i&;+}SxQov<$rJ}u>}J&e#OP3jH_I4A8a3P z?{1%L_qWS$VmTUVUx{^73=Lf&PTYE4E|JX?;&fA>DrU;P>)ePT%k9$jT-nalFqEY_ zM7H*jVsY-ssfsUEfb<{U&uOhj4nm{(8Gfj-R^*I%JwfcGM&}BN69HHDIIqYhxw&6% z1gi$Yta15xX|67BPLB=;{$eoQIq3`!f&v=pBO8)QFc=W#ut)x1K>J~g_tkxgTp#%% zmAJ{7AjuhFn7QWm?+Z%Y@(;kGAnrof6-e+F zTW%O>X0Vm0u9M0rl(pN*Fz=q zC>m8qrJ5H@)-8*cxM@3)gJ8U5cILHkq-d$#yYX-`3~9xyW(Bw&Do(ml?zSun6Eg6O z$4g!5xui{(Z!58R4EP@OxS%MZ7{>v~6LATI3?SN5D#|J+^6&y_GD1A;XbUBg@r1MO ztSgdM`7v1Oa?rdbRhu4#%!LcD(QdZezbKVQOQZ9~{l0U@F0-mPO5rt5uTS^ytnu05 z+2Q@_oAqKpomk;b|Fl-*b1(Eg6vKakxL~H!+A5&2;eZ3zum-^9iqGC!+h~1UE&4vB ztCK6UX0L)crLbrnHM$qT$)$(Szw}|86nelD@V7@HsPqp~kK1zy0xPU3tI$bZR zhKxWJfg|!pic&LH*AGccWGqtc1_gO@Cq~sYyc>Qv!ySF1vLloMlt$0OF zSZ!=ng{fQ#Ec1NTL0NCN)SfG)%5>L4R!JNSxha-D0}}`2#THFtm2#IgbCZa&z)Lmj z%{6-FPCy|R9{y@#&7;8H2-)@y5@Q*kS;N=8i~YU+P59KiesxYRZjR52lqXq2YLBv1 zAaAsoPI z;(MHS_@nL7Q9xk#EM0t=;4A9+^Q1%gYs=UbR&)BxYE6?#O(sy zp=RH!m)VNenFbVt*lgFMc;z`cRy^U7h*@F9pbV*XHRbM1EJg8$RDX`88ETyCf4!oG z{ICD?fAYdtXq2_eBOUTG8Mpx`n$0n!Tq4C$<&MLJ!B4eTf14y1o^SaB|BlvXt65vK zko{mx!*Nf~br5W(J~IF2w_8`OaXdTi?eCwrj~%N!`qrO3oQ#XiB;AN(UVSSXr!XKW z(Q|SHf~7)OGmTgpcN7m=?ED5}jv1vCI^qncUn2i}SRzQEGDDtRY1=6MO!f^G7b0>{ z9}?^O*a@d+Kwq(?^0JsuOF%-s7?1z1;3XU9wGE7pKopJnnCVT1IeB?~eQ}@07l)6n z$a~#eoCfj5qP=`N?iPfZ98PZaumr8N&-Ns3$Jm{rt2+ugSL~-%3-=Ub64ox+$#B645+{pA$7f4=PD`m}j`oy3mTa7?#8{>$-8Oa# zqDrX~Y{N*)yQmsm+zx@5mJXsP;Ka2R={jygSefywydDT?h6{1K)${vYNfzZEEZ^#U zZN89)ivk3vUD25}1uTZ|J;OFsW`S_P**PM|9b33ErQIF5;#BD}QFTs2 zKa;XTp7o@f2dB@t6{)gkVW4mQooZ&Jr_xWg0+3%9ig3u8m~@_a<`pL;q_D}cHCIFt z?5#p<#b`r<@dGB}TW5rV%+8uL+MVNXCXZ|;XZQ8{!FcE4#BA(z2M_xv{>k%0%eY<^ zal#jHr^Xhi0eH__9uAsaKD2iD$WYW8=`S{OB;wAc=*d5$;qiptYz>iuR}BX>V5+iH zZ2kF#V1*WL;PFt4P)+J6h$eQlnho3lH96M-+>41SlG3czBA5JQ4M;1Mg5}BpT`K88 z1WbhtR(i`!K?SOAUp`kWUQ`-fuh!o&Z*J%C>o%Ft$;CHwXa!5-qIcGBJ`?;q8vD(w zn+JPVkoBD)H98k2($cX!ppB^i57Cg3b=|ot1~M!bXekcdh%hDfB5+!2i2;hkDsfOj zVnrngHIwIz4AUc0$aBu;BvK-n@h?F34AJtOvwwMQ)H%f09Y;qnsT3YKca$U2o$GKD z=OvXy6$CVb07BnEmXk}6QcMEHN?G9`&pQ{RWO@OLD+%Zzb4vy&P7Y?99ay=&+N_a} zn)d?y7)n#A$9!)^^G#-&GUb=E>)P`9_1V9?KRh`&8P@lr-O=#j;{8k?B|6(W(KHu{ z#U;>&K428*^y-jhqqN)gkRtffiN)FpY0V>LadlM>EscPKXJ)eNb#cIKcY3$}O4XA0 zaVDXF>|zCkb>U=~4y`vj$7jrhoN38J7DQr-4fq4)^=KvSO3Er7QmVtQ8ygBPYhWrZ zLs1kECaE$-;r^^Po6TP$Q&>Qbsx=I=oiFG;N<#L9g~|Vwa#A>Mxx~xf%u)|K4=>B* z+S@6L7EwN$=fp_lM#I44wflw-*k?kAL^m*k&8FUvw{*fxc9zp~h*Zq(XuYxZ_HC$y7nG=uKL>5JWK z-Bjb6{X&E0-=3`o!^P38dr;uIQ=k=c21c5AbCx25c4#6~Z=L;_(*U`nahc0P1`w6O z>W7}ALIBCk%MxZfxJ+JuIzX>`IQlnxI!8JI=way1C=2abxi9`0(c9p#80Y19qLAyD0VoU?2=4$BD~neS>n0 z(HS%q1LBl-tfaD8n6MHF>yaFzDE?YPXH~xN|JG{I+QsVhwXY@0N~vSb#98m7b~4{< zERsX*G<&s>#b}xxqxEJ(A8?jK(k!Y7tldRa9Z=UEU_x+$y9WsF?ry=|Ex5b8OK^90 zyAa&nor}A}#oe`h{a4%7K|AQ`{EYVKtbN||yh7=?CclkA}Qa(tV(1s9|Qv2Jn;r z>;Z2jX2Epy!N6WrUT6UAC=~VfruZQIQk`?_X)^oc1O;A&YLOsO!j{VU_0>^rp zKM10-$XRw6%**rU#^hcwYbwB-)6KB=gx2i9&|dzZntbc2oY=sK7*5OvDxsPh{5AZ2 z?uq!CxoZZV%3HN07>-pz;bQ@BP{o5Rrp7C08bOXvCO)~ulpNo(g{=1VT`R$~Yxh#2 z(6#n3Klc=KbOID5;AfU8eqzFG>=OKxVaJp*V7rzA`ER9gXW@WbQU-CPtB=|XnckCz2KNCm9qNo2th1jr|(Bns6L!Tks_c|IfhCpWM)}?NYKX*acT9mPl@kJl3tWhr5v$1|`)=BEe^{Ca;{E3b;JXiqBut@187V#?|MRj3tjR~E?tPCd; z`wwwkAM-aPZ-F(F2UKYWljprgc4@E-QrS3pA?m3Rrjq3As+{5(I+#`gwiu&GW#_M# zDvf)gNd7#-{z)>Ss@h6t5_B{sl>8Vt-Xqj9C9iQ_MO?{q zR-`F4M!d}3#4S03(vnl1lESf&9F?^ofj5`s5u=joF3XOYcA%S@O+%rSJ-o9=8a67I zN?;sVd_w$?rs0cdDoCs({$X*h46f7Y3fQ!$NrxNHA?<8OIbcSt0KSc}c$U5op19gy zAVJ*&CmURs=<*0R+67_gEaC;b5OB2rZO!qFkBgt=xrxzoiZyzsERw{XM$1gFI`R z_+98ikt?#Bhm?!o2_hZ&h5ERhK_+rMYF_kb|H$JL>b;xCU_t3j$$~Hc0-9+jXzw~y zcfLD;^=3vM{*I?+FR?FTiDq78tJmFtq?d0r?Nh&y#q2%#4n+&ta${3}PLyAJyM!#s zmTSM}muuN+@gX(e@$}Vd_fCggqPTr&b=czT%-VVgTa-Jkpe!BO0 zshs^z5FUnv{Rh71=e{A1U#jQZ-^IKsOIynPed548&2Nl5kw2(b_kW`y$gKpFi&031 zeCP@NtZK%PNey&d82|tWzb}}D<_t-*taen*15`{T!{hjsY2wXjT#dP-ky^(gd*~-; zwK!8HKitUvblg++wx$Ovqu(bE+WL8JAY8hgFT|Mfv$`T-#tt_V#XZ@A6J-Z(?-plF z$&Xr3U6E5f3ma=MuDG$57-@z6TN_#Vr?aAzd}8dps!V8=)d4*VLcKfG$(Pt2Tqo3{ z=BK3_N+SVHsE3|ahImnS?f2aLYfm#yEUfF{irbk*AE>KXPYe~|;8HT?UJ)$!h-kGJ zv^tsl9adE~&K+!`DmqyqhFBpW#FvOb-%*tGt-Ph(X`v&wetecxDxu)UK@Ff1JTmW( zfdgJYXT9jBTO(6%f<5F}h<*%F5qMHlOgMs->ADTq{(2GQ!qevNV}1Y9URl4l_J? zP!Vm5@k?dba5C8x4nevz0>2-@7$REil1`fg zXGASy>ir+33^fB|^gc`*S?4zKBeu3k`^sim^n^^hN_<-IK>>wEeL071Am*t%Ogz=_ zj;8N^tK6-pSyL&KOM6Q%`z2g{KDrZ_dO|ml%`G{l)Gog>?b8DqJR&GCbRoDv&}=V<=TQTm)Mb1qRh*M#zQau2XdJy@1cEE-mf#^+sy?Z>5FD zVxNTtA&tJ$#__d~RFz2l%+xg_NZc@#%>oUkPGsbKVF<$pF@<803l5niY84B^-DPL4;LO{hXS(^q;@gs9{=r^mL9UUlH*u#7y zvo?YBG@?r3?6vBjj}bf4r?9BvPn><4cz}NJg*I+09}->OT5kOCb#lX$S%@t1Pe2@b zpWe=^!{2ndAH||5n5kQP&odi6p%$6z;o{K_$2!M05$@a7dWxlggxx9@JYQ1-t|u)g zC!o#vU?ObLBV==JA9EQHu_lk-4vt)}S)J&Kv_1~rmg%Fii>+${kG_k$4TVC^`ef0g z1>jfcsr^rpx=$1I~>gp>T#$%0)rLV}|psfpvh$=cmmw(K0Y zJ&Iaww=R~nkH03*Ty*15=5=53vko@MR64)NJK1nHz3%1cK4@rPJTXhaDgC_p9O6v! z+Ub7tldImr1VzjW*H}T(P<@aE2Q%x8<^@lcmLtJ{>_RSbR2@E0)P6EHMXvB8XffMi z_jqt2Iq`Km5r}-YQ6!BfI!J5Rn1H<&4`PCAaS(5!GG2RSwW7%kq&^pFQ`{{ct26{i9`aOn%L7 zQU@O)s+O~SXAc^QPf+J5M3nNww!J6I=jWsQ1wT8p<5Zr|7<`c`zc4N7hwvULYm(Bx zdD_wK$IOXDpF$yDx$5Dz$l2b>*$wLj$&whr_aKm)I`NlbubXo=Q=Z8x}qgjb? zw8(``C}h>8LcYSgsJL^O#A+=z8C^BJ2tq*}qmQFKRx5%5Se11oJ=9KAvMB@lW@kCn zG-Pts?+(*!h1*5$%VnHuk-fqGR#M8Dt4?awzenx{YxzX8L-&Ei&k_{fxoq(D+cnj$m3o)Tr(L+wp5Wp`O*{_<(*3V`OnLtg)N#ga)Gr#+< zNtXuhXeR$9IS2p!$6~RS!c*!5$oTX(_GUF$gyayM)Gpkzo8(mxS*z3bL*|{;IIRK@ z%i^%UT``X#G!mUPWcE3rx5)>=BR_oc=klb&L0elD@Nr%QBbon`Sr5Q;D_W~y*XFYU zxG;rDGqMIj%DV69__(u%|IbI~4#4`}SV{*sDhh|M2gtERW3g`|2j z?_bA#`It$)vK4zJb> z;QAvzNwpHE_mY&7LcrGlR=ULBq>6UX@5t8itBifNybmpE5ztjlWsqgWn`|tR$nvEm z5Qkt4&Mf~H>UA47SD#%;aceltSLtUZvymEl{fEGpX0~6+rmS@efy_y8iv-F+NOGE% zDy)~*m9&GXx6OV3x)L8gTSj0zz8a)B@5dJ}$3*)=89c$n@m)f07POv1Kz;vBPO?py z-#8ucd+g2rJ-9d=8y6pHF%qquobjaN@1f9yjb8DICaVvrZz!EwxU;>S{S0!HxFd2Y zie@!-(qLv?OmacV@o&N@Ob|mtsX{(&o~^#K{4K19?rY%yF7E;@pk+J%y4u2R(>H1B zagZ@Hx)H85sONj}o}Ibf5YV)>_UC%%eT>iR@f3_(*yA+P_tl=gQeeRxH8VvnQUg}ClM0T1bK7`gm?>EA(B z51SWUi%;LC`h`#(K`(#Wt1?6lI`z0_s|ST|o#XGfMT@|TwwZn!{%wJPH4EwYp!x?p z9d`x8Wq4>#<_}3IZgLNytrQLj%)HY7;F2=^yqT7oAxx-t&KW+N*^=Mh!M9I_JV?z( zNu?RUe5iU$GJ$Qq=6Rr4BtYXJHXdR#t!O8U(~KHI#j(1WJcIWXFltqQH`c2A0+Esf z`#g7)BW~vAfR(@$xg0E0A01{r(+PQ-q2wie>((^RiF}Kc zL_Fd!sCVQ2R(5O)UmM`fU5D^x%0+SImL*XUiu^Ftprb;5cQ67*n&}Ty6i#ES@HtyM(sB-h{Q4=EF#aEZ_BrjMlE3(`v5_5obwpUwL7Bi`(R%vWNR}0N9 z3g&D7N8JFdcC#Fsj+d|kh#LWnuc)zSR$P!F@FqQ$I&oVWy!wHYch=jP8*1mz7e{@W z@^(Qa;Te6eaKQrv?!eACe@N|s`?T#ju)?wE`r^irBn(E9?B$dOJJJ!FtKEB49I0iB zTTWF7!cd~5ao;)&>6{Ho^E2E**9)5=4?IHM*6(0&N(8?BWg{PI9x8|%-klh~WT zluxNiVbm#hn57@KrKH|vdT_gxvc1coYX+*Soz zzw4lj@#a&L33mXG0XMej*;PZY^%oxXxXq91)6GJl1E7a@>&Iee1p?fC02lf^2DTf$ zHsyw#Hk*+Y3A;Noomu(1c^x%DOTR%N_d2pq+&?lT+BI=^CwKR0q^p+xAK+Dc;a!fP!rHODwW-LW48|zyPCbG)l7sN&~7A}{a)_l(8c4|lFg17 z5m&<*Z7r*?IEY1;TIU5)nA73HwzeSQ_AxI@Yubvs`9NPf3XhD+mW*MmDUC7*{Jpkv z`SCJa(MgS?aECOFN_`2+Gs@V%XYCFn^pOd+Y53atw9m>R6I=kI9c$)HyqR;?=`6RP z!oJ%rpYZ1JA$F0~1TFPSoQGjAE6QR~%+`}*7K^2T=5!@#>BRh74cR=G#s+OGiqxV7 z%*){}!f3t;L|(76)ynR;+wUW`I20(wd`PHnY_Fh0<3nDzD{@p=(VfSr9L)bULP6~s zP2;{W|EzM`kk^aJvg(I#L#kfF#=nTgf_$Fq6!lK&mFh%Jv;tdf>-%T4T2pCkYEgA4 zvtAhM6r@1)J7_n%;f5dqVL3mFU4>mq5)MmNpFx8HW)N&D%F1^3>${;n<`(2;Xlwx) zTRRW!g)fP`b-lCChF z!@}z#T`gydLg0+bB0?;(+yIU z#ktrs*&_KDH-x*lv&MO-6njAAzZ3^qN&^&X4WEP zm;Dnyt!PagujmbC9s6Y8sNq1$H;nPDPI;Y0QZ2P~c?1K(ui-%tB4))}yiVf#gTTo( zg_~Yar0S55=mcBsWG6(3$|?`n<5AedxO(pe@Ir{ZDIjGO5C#&nm0Fur(0a$z8uva$ z(>ll{D8U=su!+YCp;?sEob+PsAlZ+r?cQ<@*s<{7^*DXLSEi-^G%b$eMkXsHm8}vZ zO>NYlHWIv0AMa5NIA&~BY^L4Ir6#AW=py;1AK(i@4`CgT!xGeT&_-#Fp@<+WX&8sa zM9owKkgDj-T`D10X=T)PAG<9w3K8dEY728Um>T}^9k!6RLDbl39`OWOlsZ=hGLj6Z z|FtIL5g;k<>6C_UFjT0}Fts4%b$f99kyI=4E8A(?rj|rJh6JbBMU?2z#gU87Yf-{i z$kcAxIG^=Vyt5^9-Q{u(NkV>0YpT;}7qMFI!Y)Njwoue4HtSpJ!TqRxpVF%5i#0;5 zg>gVSY*;;)2o`tp;R!v9)YrBKk2k%6*|=ls?{X+LzP9nNp2hJ*NO(mPkO;5pu|s=(p}D7DYQ=CbjJuy;az zZKhgSd2GMcge|~qu=1~iwN$9iCe=V`q;zt1dj&Ub_WU}GWn$04Xm;OD*qLh%lK6kS z20HQ-DpLE5 z;+tks_6hC;z&BO((Uea|FSdJ83vrgCH8dEOC*XuqFgB}Ati~TmeLSgndy(fUck?#QR#q~{>o5qz@)P~L%+UA%n8b&)u zjATzS^f7%o?$Rr1NZiCC&~F4G2d~Kx;^R7KvI-`JnLivV&vggJ#m1d&r*a-Icsg&h zhx;phwgl?Bwr6C%RFq_G)WAPmvLm!idACe(bi$2QwnpE}$1ZQM6j`2PsD=SdYoD{Bpliu1jGevV!jDhuC!`ItA{PD4lv(wv;@~c(D3}SiCSBN;k}BalV{@pr z|8OI_;jSCQk$646Kb^hQ2|Dm)yQAN%n%$Jtu8>T+^NyWzaY z5;tg-D%Vs46fAPwtKs2&C*Epkgt(hJt1cL9lllSxIBqx~rGb0T3< zEK z>&M>G#`SIb(qm4{1)_ux1&_!0S?8b^~hN|U(|vu_?({BFc~PKyY_ zK7A#~Ru-^XQ*(}C*0Tja=VfQVgFV>?nP0Tre&bT^3hZAncPcN!wS%Y+hkkmT_;aI* zmC(3a1sWibGmuqGlTp0Xv5=U-n0Z6U)RKEqSg`0vN4`P7rMV$VXJr4)pxE9A0LRVw?E}kux$k@bZK0V zlDyP}2MgM2m_(bZB_$(Tw(-|AzDk0r&nZT!T-W;uPvxT8roCAv9xYFq20r-4M(@iSSI}t*#0{(CU#l?3YAeqiWUYd78?$YmI&X zqE)#^z&MudIz~?O%eLV>brHDZ&I!vCRw#b6yEJ7Ay(nb=F7z%Sf~GiHmMXo(vP_Ei zOIxud*p{Q0Z_W9Myt;_yN>dHGr?q$tJf8vWmw72SKC_KSr`Z_y*;P_?!RZIot^Yb>x_3uGm*mTz3g{$_Z(b!9d{o8h zU93h8bxMl&YFTNf+t`8~&ci>F{ZVDH_{+3~@;n{zEmsx5W!1U;IR{2h%wb`v{#Z0n zA=vpMJ9N;c7WxAk)hhB?H!pW)Z0c->3JZN|>*&?MX@@$S zEn01xX^uKGvRp#%B53M6;zoHSHF66wp;wX|4gU2#n+KS>7;_~D=n|-&^vA2# zu1;#1FqX05HY#oqvStwp39tDUU~~fMe9LebEbH|?1BO=2*(#ntZ;1M=e1Gnz_^R18 zT1FFHovO_}<-hb3T~P!y$(7&r*Sr^QlD%Jc#|wO3LG~SkL3Mk-hcTePil_|Nwmaar zeaFvPM)CVR=uYYWO^&-*zMVP}uLSulfsIkQ?)Bp-qw8dZZT?<-%@ zH^jEP!0X=$7lbupY{a7VBCNl^dq)Hc*%cUx?OeIg2bhmcBSYVMb?q*F2KrTdkMT`j zz4*}?fwikZkmJO^>o7t4UmmM$Qynq%txLxA{R|9kBDjp^t2h^PGPuStQn zoEzl%Ko=ZyTTfNYm{@lO;vKm@u@6v+1`G4fQZR%9Yg^k zLYaKL-MBBAp0)U~q#S@r0jqX4=!nn026kjn*yxdg_YEbR%8%;auBMFw)M+$kObqRn z--cr*o2l+cwLs&PLEy>l=!HfZo_L9Sc!<_bEIKP9b?}3Gl;`VaS^w)U=}~^_)3J4( z5Lt=ox7ynNzfTz1Dk2+~fCv-KIq3&$HFfQNff%M`8fCkiVaZ^y$op z{M%_a&eY-O@yT8OQV@$j7-@Sk51t(o6wTr=!Hulj(KpwG1^MPB)F{zl1xQNF=qFRh z;an1ff}b<-V(Yf^{2BvfM(qyjCy-8k(6)ikHTtvSg=02rA3C!gMe;uddRt9@%~0(Q zI=)RLm{0~dGMSjaDrVHt%6xyh#12V{iz4?9g`Iq_wk#t5n=xJGkWs8(H$)>|TwC_T zqcN^o|MmAa~{*YwcDyq=kE3CmhIr&!hX!9T8BYX#`z+&G=KBZnn3rE_T{4c zS`nMxr&IJNNquL)f~}olqlZ{ChdyuxHD&FcQrc|8atUuzO&MVD{BGA2?!e_T{aBf{ zm=63+VTN@WB#x9GGmemG&Rh1I&TJAiuIg)F*Jyl+Zz1w+n^7%kjUVH6)GYUG~w^ZITc-mBtbGdbe zU0N&8KiK+ol}@8Cz0hn4&J4K__Uk8G_vj9`@Nrx#^-Gm*xwT5M|8&#tYt;W2e7;Mz z?tMzbiibej9GnT*@`dHtdWBaO2)Ip@bxwmwa1@KTi@y}RoyY#L5i z#!7TpYRWg98>q&0+MId~Ps#u6!6zo-5ENTUVY7fh{Rev5^()yKnl=UoY~9V9qc-et zi|rUO>$CX`_r*tr52kdgTl7VI7pFPFGZm&Q+~E_3jWh5NBw6=Zioh{IQy;hBMO(WL(SuC19Li=(pKPp)x5kv4Gy@js=>hiW!0Ld{+!7OKqxmX%dMTScQQm z8dr?1WRiRCx2+3h)ykvniYl6alxQ%?8~b7vcZSPEdCLXH#uOsw1)F1ClTnMxpOe3a z9GUbrc6ynrqB6)u7-iRm~FZb%=$lMQaV7>x# z)`?Wx!&ssrIOK`G2be2yyB2Rgpr7#-&RwWzxTiZdx#Gy4ga&}-#<*Pih>Mgjv;H>t zR{ypex&#;%rt6QKn`T#eFIclh7#M02vdLir(ihpB9yyBNBTt(nf#&Some-4m-E9wD zSUTc296zUPxt!rs*i!9o>udq~l5%N}ORrCwmaWm>aJDDAvb<1oN*fvGip09dQCMC# zssHHeH6mhK61)|oKQDIdz7%Kv!iN~viQ!JDXgBYy-_S3jkbWX9zA;%soBph0mq3Ln~$bxc%gB>lzQ2GLiv63 zwwj#Y;V9PaXtI5s0`e(o;w6i;SZju1`~~`LV&zkOM{}C{*4$4Jw3HM-?Ed+g^sAcS{mSe88S;%G=xSWxS^uk=YyIuhTzjm; zFJtAtRY1V(^EBG+p?lGs{o_k*?fLR+6aIgv(|q_`72mU(*r(fxAE@|tVE)chtNWS@PQ`}&FWbcD zK7jQW@`%5o7;9CJIDPD`?={@DE_HM1nB+*K#iF~kSdV^QFjbl#gfB)^6oh%7ZZMCs8%2duVQzxx6(zs*{NR`S`Isv$Fzf|*n z#~&pq!m?KstY(E(FpPg`!WM$>2nrbN1mU?>XKxB~Tee@v)l*xB)6fR7|8WpWONR6?~PbUtEV$FulnGn%T^U;7o#HffURHpUpa^`X*% z=>~E$HUWL>O_e@e`dtLKtT}KeGgg=umxNAPP6hqi=U9CcU9N7&d-5>0igdbxdiqe4 zTh=&H<`3GgbLH4W{!v2x7b>)sCaT=`a{ZR9+L$I$e#%Bycz}Qt*UW^$d$~s7sy)JX z=6xv8+->5XZRTwPS7EC}G>__W!w8lo6sYNy_#x~T`)k3yzb=uNmJAoO%#DXw+L2lf z+4I4khRDpEEZ*{CfO>#zt&z)m&$cNgR*vMWHK5JpCw1lr($a@OUastIAAvyQ1RC?w zT5&%LtI3R)9C`jPK$ZmkW1$mCS6dtI)}MDr6Yq8iF7=Cwr$gB!YN+X5_8RUrg9aMi z?*hdPuZbPT;%feVaiCR!5X8r`Vts`sw->U+w>*iMCbJm$R#k|Dtek`E=#Qn#&0RA$ z6_?Bu8GvqS{~sF(wnaA;#Ktve0X81e(#_!59hDQ(QdJA2Q$(^Ry!&`2MyqJKcdT^P zeM#RfgoV6;(vofbVd(_ph^;dZpx|M^Xp8YUfpxT7Fm*|`GO!?DI1ke#8Z zt^e&_z6$LAoIIl7gSGtgL56x-f4JzFW2s5`oMr7x`hB$nkpwj4e17^Ufm(?`b)c7* ztOD1OtK2oC8yl_%{>NwB!Lj=( zH=PXzP=(~#G@nQ3rp4UxjCBy=NJV5&~M%m1v2fLpMyeJ;k(T81_ z20(nc&K=EvVoPLRER#QhqrR_RebRnZUi;l|<(j*F9~C5-@f_5fj)<) zx1Vymp+SBX3%k!8nUkJ#?xmk%l*_r#xdPjOZeczI!ZV38WFR z6Yd_5ndj=s#GEedG++2WzD>2#oYmu3{oLns#gdBUT1p8LmQ=A9YF~mvBMt289NCfH zGhFlu`Pnt^pm7w%8H&!t9U`dRq+y+Ie>5dgaA#2%^cZn>2N0(d;BHGDD#xJ5w@VZ{ z83I1}&ORd9=475HR=A;Vgz|khxZF1AS4qW^NJnIT&k9y7H_Im#-MT3j%4Wq)0Y+$C z3E1o(?4rpnCjM0YGt9^>)vp&krzyuL#PX}SpT`L}rgbkrb1nx||Fbo9ce!kg0PR}^k71UI@lPnW0o7GP6+C-t%cpZYriL-iobY}9#k^}b2`YVG z0)F()P05XCXT5%9Wx3r2UY9Q58_G2e_)%d(HhnO;Hicj25jW!2zgxgz0oSX*pTqG^ zHi=0@r#7n5nfDx>VAKp!O0)+7)fx9{=h3^~U8-NwcEkI^u!uxDpe;+)n0l1#zO);+9|LQEECwQN!~81)-hw{53p<5aEl0E8)z%x>a6Lrd99 zq3a{)HXeD4B9HqQo+lQqNQ^Ik$G!|9SAkPXt-9>|@y3~F@jng1GZ6dr;|2c�(fZ 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

9~MwmKUTbR9)Pl&gWX z469NCUkJuBxv>;FqPCEa6P9D%A#2_PmAC}mfx!H1Y{b^K__r0*lF4YVR}aW1hag9K zoYQFRPz*&^JU9(%cN&HHc-1lQ2@$WH(Je4`B|yb)X=7r zaFxL;8_dN*C@t32CbOKkp15(u6e&sENUIse8$#SpIp$W)hXy{Qhq>#>#c1g)M*vYH z6FF)W3;Uk!X%@-ZF-}Rn*x0dme1#9eqko!RMy>JGTe!W(>!7wRO&;&x2W88=%wIj; zua6HchbB<85IKTU@8Mge!J`3%3lKda4%1n{kgf_t);$bXZOF&ttr1RvS?I(=8C9v< zqzc;wPis!2qwP9jR@1bRW=fHJ%H6U`U*=+byB;+Mm$zT9t={+jL-_peJWqyMcWdNm zuVa{dGB7M=ZLyQknHEoi2xmE5^K$%E7LudNJ8l6l?HQ(NDC!ZjTquvD?3#eR#bzs8c_Hpf^U2p@3e@>N0PWNsz2Nt6#r!@nF?)=N7t&lQ~+ z&4~>u>)MND36AH7Ms!*LiurceH#X+&=WX+S-rf|J!N=zA>1KLU%5X7yVl#i0_b6eH z*kaT#r=Gd%;!dWtIY?>Bb!~RPy)YQU-~r--T`FLJA?P!gIujY6sxD(;`^tgf z!nVf9=jp(4l|Bz#IMGK0YUc|utAN)!W^PoX(F0b53F#N)5Raik^`%R^rgRN*eXMS? zm>{jj8Zv9}X){%VS^93YE3{i&P7&9Dc=)#34@OZ4Ze&Ow^Iz*{RA$DqSbR$S6|_S{ z>^?ApP~~u8oY#=yQu?zr?Mh>3;0ZC^X_N5A=JR7%4BVH^yguw~>a+XHl5y3&ynD}h zMGN^t?8nR1Qm$pcI!m;0Gaimm3+^hr3(eMy2gXA0;XhQ@d6uh}chg~YX~v(;e_wqX zB8ApEMoyq#Wj;=A;{W^KKXU^be)6F2Omud%cZG_m(lMxy{dRo1SbT8kAdHY%?$)`< z3_a=;X6)XSTq3^lr(8$!qtm?3*nRyktnF>Sf3S-oO(hNZZ8Gw(h@ctCcV&DJkFKBnK_@RR2O+T605ceZf!A=9^dij7EnkD=BMfOUK=J;v4Ore;27ao0X=o+WRTW+l8?Su0f z-3Hgjy3lJ@3qi{?2kr9H{HS9wZOP}Cum)yJtQ{*;I=ZQ$fJfym0h7~c!6RD`GDo0o zB^FuHLQFq3iH7AY#>k3HO__j!h_cJVkAj(dN2ai55ZNxYRS8QuN_0AKC1O@&7+*`G zMM_dSK<}kfje`qhz$bnLhJwxrgcR>f^`n>cRb$$u1ST6RV1hm0mm=q<3WmOQ8{Yy6 zXBRs?)EF@sZR9QunzAylXhf$gp1#AyD2PCQz^qoo)yaE29>^HL){FTAlJW7KqD=F| zQhKlGQq^D*N?sz(WyBR(=oX3lWQOb};c6{gk^-RuXwgQ!%TPkSUr}5-(^YOBQ`p}gOi{HGmgIcT0Ubi?!o3R2F??rf)#bKq`m0ETcm-i%)F z5w1(mH64`{u8|u=o<*r}4dFPegi#*QANN-**K74g`A-6j(^STMrIao$hcqyyex8kP zbaOz9*)pt?Cdu>g>vi}rLxz+2aP#@l^IrR2G+EW!?->OqG@#T8P^HmD(;V=A8^&fA z3E0_m?N3@xfuHL_jw`|3+BRrlZ`hUCqBk8R-6ec4d8Y}%os$HJVMgG#`Km&Mzjej z&&ITaAtU!vk2J~^Mt5-^sN%+2+Br@e+Wy7EhRpoK-2AAuVUc^@*Y<(Us9EvzRzMH^i}KIOQN@s_ea4tY5)R z`=MsfA?L=P)*M#VKw9Id zHtsSkONW;`(ouq=p4wF)*PGcI