From 7034803712c94ff43b9d4c94f9a63b58d2bfd765 Mon Sep 17 00:00:00 2001 From: Tamo Date: Wed, 12 Oct 2022 16:10:28 +0200 Subject: [PATCH] move the API key in meilisearch_types --- Cargo.lock | 28 +- dump/src/writer.rs | 2 +- index-scheduler/src/autobatcher.rs | 4 +- meilisearch-auth/src/error.rs | 34 +- meilisearch-auth/src/key.rs | 201 ---------- meilisearch-auth/src/lib.rs | 5 +- meilisearch-auth/src/store.rs | 3 +- .../src/extractors/authentication/mod.rs | 4 +- meilisearch-http/src/routes/api_key.rs | 3 +- meilisearch-types/Cargo.toml | 2 + meilisearch-types/src/keys.rs | 366 ++++++++++++++++++ meilisearch-types/src/lib.rs | 1 + 12 files changed, 407 insertions(+), 246 deletions(-) delete mode 100644 meilisearch-auth/src/key.rs create mode 100644 meilisearch-types/src/keys.rs diff --git a/Cargo.lock b/Cargo.lock index 2834d50c3..c9936c960 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1221,13 +1221,33 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "enum-iterator" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eeac5c5edb79e4e39fe8439ef35207780a11f69c52cbe424ce3dfad4cb78de6" +dependencies = [ + "enum-iterator-derive 0.7.0", +] + [[package]] name = "enum-iterator" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45a0ac4aeb3a18f92eaf09c6bb9b3ac30ff61ca95514fc58cbead1c9a6bf5401" dependencies = [ - "enum-iterator-derive", + "enum-iterator-derive 1.1.0", +] + +[[package]] +name = "enum-iterator-derive" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c134c37760b27a871ba422106eedbb8247da973a09e82558bf26d619c882b159" +dependencies = [ + "proc-macro2 1.0.46", + "quote 1.0.21", + "syn 1.0.101", ] [[package]] @@ -2262,7 +2282,7 @@ dependencies = [ name = "meilisearch-auth" version = "0.29.1" dependencies = [ - "enum-iterator", + "enum-iterator 1.1.3", "hmac", "meilisearch-types", "milli 0.34.0", @@ -2363,6 +2383,7 @@ dependencies = [ "actix-web", "csv", "either", + "enum-iterator 0.7.0", "insta", "meili-snap", "milli 0.33.4", @@ -2370,6 +2391,7 @@ dependencies = [ "proptest-derive", "serde", "serde_json", + "thiserror", "time", "tokio", "uuid 1.1.2", @@ -4034,7 +4056,7 @@ checksum = "73ba753d713ec3844652ad2cb7eb56bc71e34213a14faddac7852a10ba88f61e" dependencies = [ "anyhow", "cfg-if", - "enum-iterator", + "enum-iterator 1.1.3", "getset", "git2", "rustversion", diff --git a/dump/src/writer.rs b/dump/src/writer.rs index e5675ec30..c018b93d4 100644 --- a/dump/src/writer.rs +++ b/dump/src/writer.rs @@ -5,8 +5,8 @@ use std::{ }; use flate2::{write::GzEncoder, Compression}; -use meilisearch_auth::Key; use meilisearch_types::{ + keys::Key, settings::{Checked, Settings}, tasks::Task, }; diff --git a/index-scheduler/src/autobatcher.rs b/index-scheduler/src/autobatcher.rs index 897199be8..0ba81fde7 100644 --- a/index-scheduler/src/autobatcher.rs +++ b/index-scheduler/src/autobatcher.rs @@ -1,7 +1,7 @@ use meilisearch_types::milli::update::IndexDocumentsMethod::{ self, ReplaceDocuments, UpdateDocuments, }; -use meilisearch_types::tasks::{Kind, TaskId}; +use meilisearch_types::tasks::TaskId; use std::ops::ControlFlow::{self, Break, Continue}; use crate::KindWithContent; @@ -50,7 +50,7 @@ impl From for AutobatchKind { KindWithContent::IndexDeletion { .. } => AutobatchKind::IndexDeletion, KindWithContent::IndexCreation { .. } => AutobatchKind::IndexCreation, KindWithContent::IndexUpdate { .. } => AutobatchKind::IndexUpdate, - KindWithContent::IndexSwap { lhs, rhs } => AutobatchKind::IndexSwap, + KindWithContent::IndexSwap { .. } => AutobatchKind::IndexSwap, KindWithContent::CancelTask { .. } => AutobatchKind::CancelTask, KindWithContent::DeleteTasks { .. } => AutobatchKind::DeleteTasks, KindWithContent::DumpExport { .. } => AutobatchKind::DumpExport, diff --git a/meilisearch-auth/src/error.rs b/meilisearch-auth/src/error.rs index 46c244a5a..ecd4dbff8 100644 --- a/meilisearch-auth/src/error.rs +++ b/meilisearch-auth/src/error.rs @@ -1,37 +1,18 @@ use std::error::Error; use meilisearch_types::error::{Code, ErrorCode}; -use meilisearch_types::internal_error; -use serde_json::Value; +use meilisearch_types::{internal_error, keys}; pub type Result = std::result::Result; #[derive(Debug, thiserror::Error)] pub enum AuthControllerError { - #[error("`{0}` field is mandatory.")] - MissingParameter(&'static str), - #[error("`actions` field value `{0}` is invalid. It should be an array of string representing action names.")] - InvalidApiKeyActions(Value), - #[error( - "`{0}` is not a valid index uid. It should be an array of string representing index names." - )] - InvalidApiKeyIndexes(Value), - #[error("`expiresAt` field value `{0}` is invalid. It should follow the RFC 3339 format to represents a date or datetime in the future or specified as a null value. e.g. 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'.")] - InvalidApiKeyExpiresAt(Value), - #[error("`description` field value `{0}` is invalid. It should be a string or specified as a null value.")] - InvalidApiKeyDescription(Value), - #[error( - "`name` field value `{0}` is invalid. It should be a string or specified as a null value." - )] - InvalidApiKeyName(Value), - #[error("`uid` field value `{0}` is invalid. It should be a valid UUID v4 string or omitted.")] - InvalidApiKeyUid(Value), #[error("API key `{0}` not found.")] ApiKeyNotFound(String), #[error("`uid` field value `{0}` is already an existing API key.")] ApiKeyAlreadyExists(String), - #[error("The `{0}` field cannot be modified for the given resource.")] - ImmutableField(String), + #[error(transparent)] + ApiKey(#[from] keys::Error), #[error("Internal error: {0}")] Internal(Box), } @@ -46,16 +27,9 @@ internal_error!( impl ErrorCode for AuthControllerError { fn error_code(&self) -> Code { match self { - Self::MissingParameter(_) => Code::MissingParameter, - Self::InvalidApiKeyActions(_) => Code::InvalidApiKeyActions, - Self::InvalidApiKeyIndexes(_) => Code::InvalidApiKeyIndexes, - Self::InvalidApiKeyExpiresAt(_) => Code::InvalidApiKeyExpiresAt, - Self::InvalidApiKeyDescription(_) => Code::InvalidApiKeyDescription, - Self::InvalidApiKeyName(_) => Code::InvalidApiKeyName, + Self::ApiKey(e) => e.error_code(), Self::ApiKeyNotFound(_) => Code::ApiKeyNotFound, - Self::InvalidApiKeyUid(_) => Code::InvalidApiKeyUid, Self::ApiKeyAlreadyExists(_) => Code::ApiKeyAlreadyExists, - Self::ImmutableField(_) => Code::ImmutableField, Self::Internal(_) => Code::Internal, } } diff --git a/meilisearch-auth/src/key.rs b/meilisearch-auth/src/key.rs deleted file mode 100644 index 5ff8f8ac5..000000000 --- a/meilisearch-auth/src/key.rs +++ /dev/null @@ -1,201 +0,0 @@ -use crate::action::Action; -use crate::error::{AuthControllerError, Result}; -use crate::store::KeyId; - -use meilisearch_types::index_uid::IndexUid; -use meilisearch_types::star_or::StarOr; -use serde::{Deserialize, Serialize}; -use serde_json::{from_value, Value}; -use time::format_description::well_known::Rfc3339; -use time::macros::{format_description, time}; -use time::{Date, OffsetDateTime, PrimitiveDateTime}; -use uuid::Uuid; - -#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)] -pub struct Key { - #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, - #[serde(skip_serializing_if = "Option::is_none")] - 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, -} - -impl Key { - pub fn create_from_value(value: Value) -> Result { - let name = match value.get("name") { - None | Some(Value::Null) => None, - Some(des) => from_value(des.clone()) - .map(Some) - .map_err(|_| AuthControllerError::InvalidApiKeyName(des.clone()))?, - }; - - let description = match value.get("description") { - None | Some(Value::Null) => None, - Some(des) => from_value(des.clone()) - .map(Some) - .map_err(|_| AuthControllerError::InvalidApiKeyDescription(des.clone()))?, - }; - - let uid = value.get("uid").map_or_else( - || Ok(Uuid::new_v4()), - |uid| { - from_value(uid.clone()) - .map_err(|_| AuthControllerError::InvalidApiKeyUid(uid.clone())) - }, - )?; - - let actions = value - .get("actions") - .map(|act| { - from_value(act.clone()) - .map_err(|_| AuthControllerError::InvalidApiKeyActions(act.clone())) - }) - .ok_or(AuthControllerError::MissingParameter("actions"))??; - - let indexes = value - .get("indexes") - .map(|ind| { - from_value(ind.clone()) - .map_err(|_| AuthControllerError::InvalidApiKeyIndexes(ind.clone())) - }) - .ok_or(AuthControllerError::MissingParameter("indexes"))??; - - let expires_at = value - .get("expiresAt") - .map(parse_expiration_date) - .ok_or(AuthControllerError::MissingParameter("expiresAt"))??; - - let created_at = OffsetDateTime::now_utc(); - let updated_at = created_at; - - Ok(Self { - name, - description, - uid, - actions, - indexes, - expires_at, - created_at, - updated_at, - }) - } - - pub fn update_from_value(&mut self, value: Value) -> Result<()> { - if let Some(des) = value.get("description") { - let des = from_value(des.clone()) - .map_err(|_| AuthControllerError::InvalidApiKeyDescription(des.clone())); - self.description = des?; - } - - if let Some(des) = value.get("name") { - let des = from_value(des.clone()) - .map_err(|_| AuthControllerError::InvalidApiKeyName(des.clone())); - self.name = des?; - } - - if value.get("uid").is_some() { - return Err(AuthControllerError::ImmutableField("uid".to_string())); - } - - if value.get("actions").is_some() { - return Err(AuthControllerError::ImmutableField("actions".to_string())); - } - - if value.get("indexes").is_some() { - return Err(AuthControllerError::ImmutableField("indexes".to_string())); - } - - if value.get("expiresAt").is_some() { - return Err(AuthControllerError::ImmutableField("expiresAt".to_string())); - } - - if value.get("createdAt").is_some() { - return Err(AuthControllerError::ImmutableField("createdAt".to_string())); - } - - if value.get("updatedAt").is_some() { - return Err(AuthControllerError::ImmutableField("updatedAt".to_string())); - } - - self.updated_at = OffsetDateTime::now_utc(); - - Ok(()) - } - - pub(crate) fn default_admin() -> Self { - let now = OffsetDateTime::now_utc(); - let uid = Uuid::new_v4(); - Self { - name: Some("Default Admin API Key".to_string()), - description: Some("Use it for anything that is not a search operation. Caution! Do not expose it on a public frontend".to_string()), - uid, - actions: vec![Action::All], - indexes: vec![StarOr::Star], - expires_at: None, - created_at: now, - updated_at: now, - } - } - - pub(crate) fn default_search() -> Self { - let now = OffsetDateTime::now_utc(); - let uid = Uuid::new_v4(); - Self { - name: Some("Default Search API Key".to_string()), - description: Some("Use it to search from the frontend".to_string()), - uid, - actions: vec![Action::Search], - indexes: vec![StarOr::Star], - expires_at: None, - created_at: now, - updated_at: now, - } - } -} - -fn parse_expiration_date(value: &Value) -> Result> { - match value { - Value::String(string) => OffsetDateTime::parse(string, &Rfc3339) - .or_else(|_| { - PrimitiveDateTime::parse( - string, - format_description!( - "[year repr:full base:calendar]-[month repr:numerical]-[day]T[hour]:[minute]:[second]" - ), - ).map(|datetime| datetime.assume_utc()) - }) - .or_else(|_| { - PrimitiveDateTime::parse( - string, - format_description!( - "[year repr:full base:calendar]-[month repr:numerical]-[day] [hour]:[minute]:[second]" - ), - ).map(|datetime| datetime.assume_utc()) - }) - .or_else(|_| { - Date::parse(string, format_description!( - "[year repr:full base:calendar]-[month repr:numerical]-[day]" - )).map(|date| PrimitiveDateTime::new(date, time!(00:00)).assume_utc()) - }) - .map_err(|_| AuthControllerError::InvalidApiKeyExpiresAt(value.clone())) - // check if the key is already expired. - .and_then(|d| { - if d > OffsetDateTime::now_utc() { - Ok(d) - } else { - Err(AuthControllerError::InvalidApiKeyExpiresAt(value.clone())) - } - }) - .map(Option::Some), - Value::Null => Ok(None), - _otherwise => Err(AuthControllerError::InvalidApiKeyExpiresAt(value.clone())), - } -} diff --git a/meilisearch-auth/src/lib.rs b/meilisearch-auth/src/lib.rs index 43183d4cf..1cbdb13e0 100644 --- a/meilisearch-auth/src/lib.rs +++ b/meilisearch-auth/src/lib.rs @@ -1,7 +1,5 @@ -mod action; mod dump; pub mod error; -mod key; mod store; use std::collections::{HashMap, HashSet}; @@ -9,14 +7,13 @@ use std::ops::Deref; use std::path::Path; use std::sync::Arc; +use meilisearch_types::keys::{Action, Key}; use serde::{Deserialize, Serialize}; use serde_json::Value; use time::OffsetDateTime; use uuid::Uuid; -pub use action::{actions, Action}; use error::{AuthControllerError, Result}; -pub use key::Key; use meilisearch_types::star_or::StarOr; use store::generate_key_as_hexa; pub use store::open_auth_store_env; diff --git a/meilisearch-auth/src/store.rs b/meilisearch-auth/src/store.rs index 847af9d36..b1a2e9520 100644 --- a/meilisearch-auth/src/store.rs +++ b/meilisearch-auth/src/store.rs @@ -10,6 +10,7 @@ use std::str; use std::sync::Arc; use hmac::{Hmac, Mac}; +use meilisearch_types::keys::KeyId; use meilisearch_types::star_or::StarOr; use milli::heed::types::{ByteSlice, DecodeIgnore, SerdeJson}; use milli::heed::{Database, Env, EnvOpenOptions, RwTxn}; @@ -26,8 +27,6 @@ const AUTH_DB_PATH: &str = "auth"; const KEY_DB_NAME: &str = "api-keys"; const KEY_ID_ACTION_INDEX_EXPIRATION_DB_NAME: &str = "keyid-action-index-expiration"; -pub type KeyId = Uuid; - #[derive(Clone)] pub struct HeedAuthStore { env: Arc, diff --git a/meilisearch-http/src/extractors/authentication/mod.rs b/meilisearch-http/src/extractors/authentication/mod.rs index 4107a6194..aeae56abb 100644 --- a/meilisearch-http/src/extractors/authentication/mod.rs +++ b/meilisearch-http/src/extractors/authentication/mod.rs @@ -138,9 +138,9 @@ pub mod policies { use uuid::Uuid; use crate::extractors::authentication::Policy; - use meilisearch_auth::{Action, AuthController, AuthFilter, SearchRules}; + use meilisearch_auth::{AuthController, AuthFilter, SearchRules}; // reexport actions in policies in order to be used in routes configuration. - pub use meilisearch_auth::actions; + pub use meilisearch_types::keys::{actions, Action}; fn tenant_token_validation() -> Validation { let mut validation = Validation::default(); diff --git a/meilisearch-http/src/routes/api_key.rs b/meilisearch-http/src/routes/api_key.rs index 7605fa644..0b71eeac2 100644 --- a/meilisearch-http/src/routes/api_key.rs +++ b/meilisearch-http/src/routes/api_key.rs @@ -6,8 +6,9 @@ use serde_json::Value; use time::OffsetDateTime; use uuid::Uuid; -use meilisearch_auth::{error::AuthControllerError, Action, AuthController, Key}; +use meilisearch_auth::{error::AuthControllerError, AuthController}; use meilisearch_types::error::{Code, ResponseError}; +use meilisearch_types::keys::{Action, Key}; use crate::extractors::{ authentication::{policies::*, GuardedData}, diff --git a/meilisearch-types/Cargo.toml b/meilisearch-types/Cargo.toml index 05691e49b..42ee8e54b 100644 --- a/meilisearch-types/Cargo.toml +++ b/meilisearch-types/Cargo.toml @@ -9,11 +9,13 @@ actix-web = { version = "4.2.1", default-features = false } csv = "1.1.6" either = { version = "1.6.1", features = ["serde"] } milli = { git = "https://github.com/meilisearch/milli.git", tag = "v0.33.4", default-features = false } +enum-iterator = "0.7.0" proptest = { version = "1.0.0", optional = true } proptest-derive = { version = "0.3.0", optional = true } serde = { version = "1.0.145", features = ["derive"] } serde_json = "1.0.85" time = { version = "0.3.7", features = ["serde-well-known", "formatting", "parsing", "macros"] } +thiserror = "1.0.30" tokio = "1.0" uuid = { version = "1.1.2", features = ["serde", "v4"] } diff --git a/meilisearch-types/src/keys.rs b/meilisearch-types/src/keys.rs new file mode 100644 index 000000000..1a9b2b19e --- /dev/null +++ b/meilisearch-types/src/keys.rs @@ -0,0 +1,366 @@ +use crate::error::{Code, ErrorCode}; +use crate::index_uid::IndexUid; +use crate::star_or::StarOr; +use enum_iterator::IntoEnumIterator; +use serde::{Deserialize, Serialize}; +use serde_json::{from_value, Value}; +use std::hash::Hash; +use time::format_description::well_known::Rfc3339; +use time::macros::{format_description, time}; +use time::{Date, OffsetDateTime, PrimitiveDateTime}; +use uuid::Uuid; + +type Result = std::result::Result; + +pub type KeyId = Uuid; + +#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)] +pub struct Key { + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(skip_serializing_if = "Option::is_none")] + 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, +} + +impl Key { + pub fn create_from_value(value: Value) -> Result { + let name = match value.get("name") { + None | Some(Value::Null) => None, + Some(des) => from_value(des.clone()) + .map(Some) + .map_err(|_| Error::InvalidApiKeyName(des.clone()))?, + }; + + let description = match value.get("description") { + None | Some(Value::Null) => None, + Some(des) => from_value(des.clone()) + .map(Some) + .map_err(|_| Error::InvalidApiKeyDescription(des.clone()))?, + }; + + let uid = value.get("uid").map_or_else( + || Ok(Uuid::new_v4()), + |uid| from_value(uid.clone()).map_err(|_| Error::InvalidApiKeyUid(uid.clone())), + )?; + + let actions = value + .get("actions") + .map(|act| { + from_value(act.clone()).map_err(|_| Error::InvalidApiKeyActions(act.clone())) + }) + .ok_or(Error::MissingParameter("actions"))??; + + let indexes = value + .get("indexes") + .map(|ind| { + from_value(ind.clone()).map_err(|_| Error::InvalidApiKeyIndexes(ind.clone())) + }) + .ok_or(Error::MissingParameter("indexes"))??; + + let expires_at = value + .get("expiresAt") + .map(parse_expiration_date) + .ok_or(Error::MissingParameter("expiresAt"))??; + + let created_at = OffsetDateTime::now_utc(); + let updated_at = created_at; + + Ok(Self { + name, + description, + uid, + actions, + indexes, + expires_at, + created_at, + updated_at, + }) + } + + pub fn update_from_value(&mut self, value: Value) -> Result<()> { + if let Some(des) = value.get("description") { + let des = + from_value(des.clone()).map_err(|_| Error::InvalidApiKeyDescription(des.clone())); + self.description = des?; + } + + if let Some(des) = value.get("name") { + let des = from_value(des.clone()).map_err(|_| Error::InvalidApiKeyName(des.clone())); + self.name = des?; + } + + if value.get("uid").is_some() { + return Err(Error::ImmutableField("uid".to_string())); + } + + if value.get("actions").is_some() { + return Err(Error::ImmutableField("actions".to_string())); + } + + if value.get("indexes").is_some() { + return Err(Error::ImmutableField("indexes".to_string())); + } + + if value.get("expiresAt").is_some() { + return Err(Error::ImmutableField("expiresAt".to_string())); + } + + if value.get("createdAt").is_some() { + return Err(Error::ImmutableField("createdAt".to_string())); + } + + if value.get("updatedAt").is_some() { + return Err(Error::ImmutableField("updatedAt".to_string())); + } + + self.updated_at = OffsetDateTime::now_utc(); + + Ok(()) + } + + pub fn default_admin() -> Self { + let now = OffsetDateTime::now_utc(); + let uid = Uuid::new_v4(); + Self { + name: Some("Default Admin API Key".to_string()), + description: Some("Use it for anything that is not a search operation. Caution! Do not expose it on a public frontend".to_string()), + uid, + actions: vec![Action::All], + indexes: vec![StarOr::Star], + expires_at: None, + created_at: now, + updated_at: now, + } + } + + pub fn default_search() -> Self { + let now = OffsetDateTime::now_utc(); + let uid = Uuid::new_v4(); + Self { + name: Some("Default Search API Key".to_string()), + description: Some("Use it to search from the frontend".to_string()), + uid, + actions: vec![Action::Search], + indexes: vec![StarOr::Star], + expires_at: None, + created_at: now, + updated_at: now, + } + } +} + +fn parse_expiration_date(value: &Value) -> Result> { + match value { + Value::String(string) => OffsetDateTime::parse(string, &Rfc3339) + .or_else(|_| { + PrimitiveDateTime::parse( + string, + format_description!( + "[year repr:full base:calendar]-[month repr:numerical]-[day]T[hour]:[minute]:[second]" + ), + ).map(|datetime| datetime.assume_utc()) + }) + .or_else(|_| { + PrimitiveDateTime::parse( + string, + format_description!( + "[year repr:full base:calendar]-[month repr:numerical]-[day] [hour]:[minute]:[second]" + ), + ).map(|datetime| datetime.assume_utc()) + }) + .or_else(|_| { + Date::parse(string, format_description!( + "[year repr:full base:calendar]-[month repr:numerical]-[day]" + )).map(|date| PrimitiveDateTime::new(date, time!(00:00)).assume_utc()) + }) + .map_err(|_| Error::InvalidApiKeyExpiresAt(value.clone())) + // check if the key is already expired. + .and_then(|d| { + if d > OffsetDateTime::now_utc() { + Ok(d) + } else { + Err(Error::InvalidApiKeyExpiresAt(value.clone())) + } + }) + .map(Option::Some), + Value::Null => Ok(None), + _otherwise => Err(Error::InvalidApiKeyExpiresAt(value.clone())), + } +} + +#[derive(IntoEnumIterator, Copy, Clone, Serialize, 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, +} + +impl Action { + pub const fn from_repr(repr: u8) -> Option { + use actions::*; + match repr { + ALL => Some(Self::All), + SEARCH => Some(Self::Search), + DOCUMENTS_ALL => Some(Self::DocumentsAll), + DOCUMENTS_ADD => Some(Self::DocumentsAdd), + DOCUMENTS_GET => Some(Self::DocumentsGet), + DOCUMENTS_DELETE => Some(Self::DocumentsDelete), + INDEXES_ALL => Some(Self::IndexesAll), + INDEXES_CREATE => Some(Self::IndexesAdd), + INDEXES_GET => Some(Self::IndexesGet), + INDEXES_UPDATE => Some(Self::IndexesUpdate), + INDEXES_DELETE => Some(Self::IndexesDelete), + TASKS_ALL => Some(Self::TasksAll), + TASKS_GET => Some(Self::TasksGet), + SETTINGS_ALL => Some(Self::SettingsAll), + SETTINGS_GET => Some(Self::SettingsGet), + SETTINGS_UPDATE => Some(Self::SettingsUpdate), + STATS_ALL => Some(Self::StatsAll), + STATS_GET => Some(Self::StatsGet), + METRICS_ALL => Some(Self::MetricsAll), + METRICS_GET => Some(Self::MetricsGet), + DUMPS_ALL => Some(Self::DumpsAll), + DUMPS_CREATE => Some(Self::DumpsCreate), + VERSION => Some(Self::Version), + KEYS_CREATE => Some(Self::KeysAdd), + KEYS_GET => Some(Self::KeysGet), + KEYS_UPDATE => Some(Self::KeysUpdate), + KEYS_DELETE => Some(Self::KeysDelete), + _otherwise => None, + } + } + + pub const fn repr(&self) -> u8 { + *self as u8 + } +} + +pub mod actions { + use super::Action::*; + + pub(crate) const ALL: u8 = All.repr(); + pub const SEARCH: u8 = Search.repr(); + pub const DOCUMENTS_ALL: u8 = DocumentsAll.repr(); + pub const DOCUMENTS_ADD: u8 = DocumentsAdd.repr(); + pub const DOCUMENTS_GET: u8 = DocumentsGet.repr(); + pub const DOCUMENTS_DELETE: u8 = DocumentsDelete.repr(); + pub const INDEXES_ALL: u8 = IndexesAll.repr(); + pub const INDEXES_CREATE: u8 = IndexesAdd.repr(); + pub const INDEXES_GET: u8 = IndexesGet.repr(); + pub const INDEXES_UPDATE: u8 = IndexesUpdate.repr(); + pub const INDEXES_DELETE: u8 = IndexesDelete.repr(); + pub const TASKS_ALL: u8 = TasksAll.repr(); + pub const TASKS_GET: u8 = TasksGet.repr(); + pub const SETTINGS_ALL: u8 = SettingsAll.repr(); + pub const SETTINGS_GET: u8 = SettingsGet.repr(); + pub const SETTINGS_UPDATE: u8 = SettingsUpdate.repr(); + pub const STATS_ALL: u8 = StatsAll.repr(); + pub const STATS_GET: u8 = StatsGet.repr(); + pub const METRICS_ALL: u8 = MetricsAll.repr(); + pub const METRICS_GET: u8 = MetricsGet.repr(); + pub const DUMPS_ALL: u8 = DumpsAll.repr(); + pub const DUMPS_CREATE: u8 = DumpsCreate.repr(); + pub const VERSION: u8 = Version.repr(); + pub const KEYS_CREATE: u8 = KeysAdd.repr(); + pub const KEYS_GET: u8 = KeysGet.repr(); + pub const KEYS_UPDATE: u8 = KeysUpdate.repr(); + pub const KEYS_DELETE: u8 = KeysDelete.repr(); +} + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("`{0}` field is mandatory.")] + MissingParameter(&'static str), + #[error("`actions` field value `{0}` is invalid. It should be an array of string representing action names.")] + InvalidApiKeyActions(Value), + #[error("`indexes` field value `{0}` is invalid. It should be an array of string representing index names.")] + InvalidApiKeyIndexes(Value), + #[error("`expiresAt` field value `{0}` is invalid. It should follow the RFC 3339 format to represents a date or datetime in the future or specified as a null value. e.g. 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'.")] + InvalidApiKeyExpiresAt(Value), + #[error("`description` field value `{0}` is invalid. It should be a string or specified as a null value.")] + InvalidApiKeyDescription(Value), + #[error( + "`name` field value `{0}` is invalid. It should be a string or specified as a null value." + )] + InvalidApiKeyName(Value), + #[error("`uid` field value `{0}` is invalid. It should be a valid UUID v4 string or omitted.")] + InvalidApiKeyUid(Value), + #[error("The `{0}` field cannot be modified for the given resource.")] + ImmutableField(String), +} + +impl ErrorCode for Error { + fn error_code(&self) -> Code { + match self { + Self::MissingParameter(_) => Code::MissingParameter, + Self::InvalidApiKeyActions(_) => Code::InvalidApiKeyActions, + Self::InvalidApiKeyIndexes(_) => Code::InvalidApiKeyIndexes, + Self::InvalidApiKeyExpiresAt(_) => Code::InvalidApiKeyExpiresAt, + Self::InvalidApiKeyDescription(_) => Code::InvalidApiKeyDescription, + Self::InvalidApiKeyName(_) => Code::InvalidApiKeyName, + Self::InvalidApiKeyUid(_) => Code::InvalidApiKeyUid, + Self::ImmutableField(_) => Code::ImmutableField, + } + } +} diff --git a/meilisearch-types/src/lib.rs b/meilisearch-types/src/lib.rs index 674bf24ac..44dd67af7 100644 --- a/meilisearch-types/src/lib.rs +++ b/meilisearch-types/src/lib.rs @@ -1,6 +1,7 @@ pub mod document_formats; pub mod error; pub mod index_uid; +pub mod keys; pub mod settings; pub mod star_or; pub mod tasks;