diff --git a/dump/src/reader/v5/keys.rs b/dump/src/reader/v5/keys.rs new file mode 100644 index 000000000..bcb5d5247 --- /dev/null +++ b/dump/src/reader/v5/keys.rs @@ -0,0 +1,81 @@ +use serde::Deserialize; +use time::OffsetDateTime; +use uuid::Uuid; + +use super::meta::{IndexUid, StarOr}; + +pub type KeyId = Uuid; + +#[derive(Debug, Deserialize)] +pub struct Key { + pub description: Option, + pub name: Option, + pub uid: 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, Hash)] +#[repr(u8)] +pub enum Action { + #[serde(rename = "*")] + All = 0, + #[serde(rename = "search")] + Search, + #[serde(rename = "documents.*")] + DocumentsAll, + #[serde(rename = "documents.add")] + DocumentsAdd, + #[serde(rename = "documents.get")] + DocumentsGet, + #[serde(rename = "documents.delete")] + DocumentsDelete, + #[serde(rename = "indexes.*")] + IndexesAll, + #[serde(rename = "indexes.create")] + IndexesAdd, + #[serde(rename = "indexes.get")] + IndexesGet, + #[serde(rename = "indexes.update")] + IndexesUpdate, + #[serde(rename = "indexes.delete")] + IndexesDelete, + #[serde(rename = "tasks.*")] + TasksAll, + #[serde(rename = "tasks.get")] + TasksGet, + #[serde(rename = "settings.*")] + SettingsAll, + #[serde(rename = "settings.get")] + SettingsGet, + #[serde(rename = "settings.update")] + SettingsUpdate, + #[serde(rename = "stats.*")] + StatsAll, + #[serde(rename = "stats.get")] + StatsGet, + #[serde(rename = "metrics.*")] + MetricsAll, + #[serde(rename = "metrics.get")] + MetricsGet, + #[serde(rename = "dumps.*")] + DumpsAll, + #[serde(rename = "dumps.create")] + DumpsCreate, + #[serde(rename = "version")] + Version, + #[serde(rename = "keys.create")] + KeysAdd, + #[serde(rename = "keys.get")] + KeysGet, + #[serde(rename = "keys.update")] + KeysUpdate, + #[serde(rename = "keys.delete")] + KeysDelete, +} diff --git a/dump/src/reader/v5/meta.rs b/dump/src/reader/v5/meta.rs index f13c2bbef..07f55f5f8 100644 --- a/dump/src/reader/v5/meta.rs +++ b/dump/src/reader/v5/meta.rs @@ -1,14 +1,137 @@ -use serde::{Deserialize, Serialize}; +use std::{ + fmt::{self, Display, Formatter}, + marker::PhantomData, + str::FromStr, +}; + +use serde::{de::Visitor, Deserialize, Deserializer}; use uuid::Uuid; -#[derive(Serialize, Deserialize, Debug)] +use super::settings::{Settings, Unchecked}; + +#[derive(Deserialize, Debug)] pub struct IndexUuid { pub uid: String, pub index_meta: IndexMeta, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Deserialize, Debug)] pub struct IndexMeta { pub uuid: Uuid, pub creation_task_id: usize, } + +// There is one in each indexes under `meta.json`. +#[derive(Deserialize)] +pub struct DumpMeta { + pub settings: Settings, + pub primary_key: Option, +} + +#[derive(Deserialize, Debug, Clone, PartialEq, Eq)] +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)] +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/v5/mod.rs b/dump/src/reader/v5/mod.rs index 671f0f5d5..9194c81f7 100644 --- a/dump/src/reader/v5/mod.rs +++ b/dump/src/reader/v5/mod.rs @@ -46,13 +46,15 @@ use uuid::Uuid; use crate::{IndexMetadata, Result, Version}; use self::{ - meta::IndexUuid, + keys::Key, + meta::{DumpMeta, IndexUuid}, settings::{Checked, Settings, Unchecked}, tasks::Task, }; use super::{DumpReader, IndexReader}; +mod keys; mod meta; mod settings; mod tasks; @@ -75,27 +77,6 @@ pub struct V5Reader { index_uuid: Vec, } -struct V5IndexReader { - metadata: IndexMetadata, - - documents: BufReader, - settings: BufReader, -} - -impl V5IndexReader { - pub fn new(name: String, path: &Path) -> Result { - let metadata = File::open(path.join("metadata.json"))?; - - let ret = V5IndexReader { - metadata: serde_json::from_reader(metadata)?, - documents: BufReader::new(File::open(path.join("documents.jsonl"))?), - settings: BufReader::new(File::open(path.join("settings.json"))?), - }; - - Ok(ret) - } -} - impl V5Reader { pub fn open(dump: TempDir) -> Result { let meta_file = fs::read(dump.path().join("metadata.json"))?; @@ -124,8 +105,7 @@ impl DumpReader for V5Reader { type Task = Task; type UpdateFile = File; - // TODO: remove this - type Key = meilisearch_auth::Key; + type Key = Key; fn version(&self) -> Version { Version::V5 @@ -190,7 +170,6 @@ impl DumpReader for V5Reader { })) } - // TODO: do it fn keys(&mut self) -> Box> + '_> { Box::new( (&mut self.keys) @@ -200,6 +179,36 @@ impl DumpReader for V5Reader { } } +struct V5IndexReader { + metadata: IndexMetadata, + settings: Settings, + + documents: BufReader, +} + +impl V5IndexReader { + 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 = V5IndexReader { + metadata, + settings: meta.settings.check(), + documents: BufReader::new(File::open(path.join("documents.jsonl"))?), + }; + + Ok(ret) + } +} + impl IndexReader for V5IndexReader { type Document = serde_json::Map; type Settings = Settings; @@ -215,7 +224,6 @@ impl IndexReader for V5IndexReader { } fn settings(&mut self) -> Result { - let settings: Settings = serde_json::from_reader(&mut self.settings)?; - Ok(settings.check()) + Ok(self.settings.clone()) } } diff --git a/dump/src/reader/v5/tasks.rs b/dump/src/reader/v5/tasks.rs index 6683354d1..95e4ad54c 100644 --- a/dump/src/reader/v5/tasks.rs +++ b/dump/src/reader/v5/tasks.rs @@ -2,7 +2,10 @@ use serde::{Deserialize, Serialize}; use time::OffsetDateTime; use uuid::Uuid; -use super::settings::{Settings, Unchecked}; +use super::{ + meta::IndexUid, + settings::{Settings, Unchecked}, +}; pub type TaskId = u32; pub type BatchId = u32; @@ -56,9 +59,6 @@ pub enum TaskContent { }, } -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub struct IndexUid(String); - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum IndexDocumentsMethod { /// Replace the previous document with the new one,