mirror of
https://github.com/meilisearch/meilisearch.git
synced 2024-11-25 11:35:05 +08:00
Compare commits
26 Commits
4f561a8612
...
b4bf73d650
Author | SHA1 | Date | |
---|---|---|---|
|
b4bf73d650 | ||
|
057fcb3993 | ||
|
7882819795 | ||
|
c2b14b8f8f | ||
|
ccd79b07f7 | ||
|
146c87bd6f | ||
|
7e45125e7a | ||
|
46fc6bbc2e | ||
|
ba045a0000 | ||
|
b89fd49d63 | ||
|
544961372c | ||
|
0419b93032 | ||
|
e4745a61a8 | ||
|
04d86a4d9e | ||
|
e9668eff79 | ||
|
0403ec0a56 | ||
|
56b5289b2f | ||
|
4b8dd6dd6b | ||
|
3d3ae5aa0c | ||
|
a80bb8f77e | ||
|
f95bd11db2 | ||
|
e54fbb0d1e | ||
|
b6c5a57932 | ||
|
6688604a93 | ||
|
75969f9f68 | ||
|
07e7ddbc5b |
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -3507,6 +3507,7 @@ version = "1.11.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"bitflags 2.6.0",
|
||||||
"convert_case 0.6.0",
|
"convert_case 0.6.0",
|
||||||
"csv",
|
"csv",
|
||||||
"deserr",
|
"deserr",
|
||||||
|
@ -105,7 +105,7 @@ impl HeedAuthStore {
|
|||||||
|
|
||||||
let mut actions = HashSet::new();
|
let mut actions = HashSet::new();
|
||||||
for action in &key.actions {
|
for action in &key.actions {
|
||||||
match action {
|
match *action {
|
||||||
Action::All => actions.extend(enum_iterator::all::<Action>()),
|
Action::All => actions.extend(enum_iterator::all::<Action>()),
|
||||||
Action::DocumentsAll => {
|
Action::DocumentsAll => {
|
||||||
actions.extend(
|
actions.extend(
|
||||||
@ -128,23 +128,11 @@ impl HeedAuthStore {
|
|||||||
Action::SettingsAll => {
|
Action::SettingsAll => {
|
||||||
actions.extend([Action::SettingsGet, Action::SettingsUpdate].iter());
|
actions.extend([Action::SettingsGet, Action::SettingsUpdate].iter());
|
||||||
}
|
}
|
||||||
Action::DumpsAll => {
|
|
||||||
actions.insert(Action::DumpsCreate);
|
|
||||||
}
|
|
||||||
Action::SnapshotsAll => {
|
|
||||||
actions.insert(Action::SnapshotsCreate);
|
|
||||||
}
|
|
||||||
Action::TasksAll => {
|
Action::TasksAll => {
|
||||||
actions.extend([Action::TasksGet, Action::TasksDelete, Action::TasksCancel]);
|
actions.extend([Action::TasksGet, Action::TasksDelete, Action::TasksCancel]);
|
||||||
}
|
}
|
||||||
Action::StatsAll => {
|
|
||||||
actions.insert(Action::StatsGet);
|
|
||||||
}
|
|
||||||
Action::MetricsAll => {
|
|
||||||
actions.insert(Action::MetricsGet);
|
|
||||||
}
|
|
||||||
other => {
|
other => {
|
||||||
actions.insert(*other);
|
actions.insert(other);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -293,18 +281,24 @@ impl HeedAuthStore {
|
|||||||
/// optionally on a specific index, for a given key.
|
/// optionally on a specific index, for a given key.
|
||||||
pub struct KeyIdActionCodec;
|
pub struct KeyIdActionCodec;
|
||||||
|
|
||||||
|
impl KeyIdActionCodec {
|
||||||
|
fn action_parts_to_32bits([p1, p2, p3, p4]: &[u8; 4]) -> u32 {
|
||||||
|
((*p1 as u32) << 24) | ((*p2 as u32) << 16) | ((*p3 as u32) << 8) | (*p4 as u32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> milli::heed::BytesDecode<'a> for KeyIdActionCodec {
|
impl<'a> milli::heed::BytesDecode<'a> for KeyIdActionCodec {
|
||||||
type DItem = (KeyId, Action, Option<&'a [u8]>);
|
type DItem = (KeyId, Action, Option<&'a [u8]>);
|
||||||
|
|
||||||
fn bytes_decode(bytes: &'a [u8]) -> StdResult<Self::DItem, BoxedError> {
|
fn bytes_decode(bytes: &'a [u8]) -> StdResult<Self::DItem, BoxedError> {
|
||||||
let (key_id_bytes, action_bytes) = try_split_array_at(bytes).ok_or(SliceTooShortError)?;
|
let (key_id_bytes, action_bytes) = try_split_array_at(bytes).ok_or(SliceTooShortError)?;
|
||||||
let (&action_byte, index) =
|
let (action_bits, index) =
|
||||||
match try_split_array_at(action_bytes).ok_or(SliceTooShortError)? {
|
match try_split_array_at::<u8, 4>(action_bytes).ok_or(SliceTooShortError)? {
|
||||||
([action], []) => (action, None),
|
(action_parts, []) => (Self::action_parts_to_32bits(action_parts), None),
|
||||||
([action], index) => (action, Some(index)),
|
(action_parts, index) => (Self::action_parts_to_32bits(action_parts), Some(index)),
|
||||||
};
|
};
|
||||||
let key_id = Uuid::from_bytes(*key_id_bytes);
|
let key_id = Uuid::from_bytes(*key_id_bytes);
|
||||||
let action = Action::from_repr(action_byte).ok_or(InvalidActionError { action_byte })?;
|
let action = Action::from_bits(action_bits).ok_or(InvalidActionError { action_bits })?;
|
||||||
|
|
||||||
Ok((key_id, action, index))
|
Ok((key_id, action, index))
|
||||||
}
|
}
|
||||||
@ -317,7 +311,7 @@ impl<'a> milli::heed::BytesEncode<'a> for KeyIdActionCodec {
|
|||||||
let mut bytes = Vec::new();
|
let mut bytes = Vec::new();
|
||||||
|
|
||||||
bytes.extend_from_slice(key_id.as_bytes());
|
bytes.extend_from_slice(key_id.as_bytes());
|
||||||
let action_bytes = u8::to_be_bytes(action.repr());
|
let action_bytes = u32::to_be_bytes(action.bits());
|
||||||
bytes.extend_from_slice(&action_bytes);
|
bytes.extend_from_slice(&action_bytes);
|
||||||
if let Some(index) = index {
|
if let Some(index) = index {
|
||||||
bytes.extend_from_slice(index);
|
bytes.extend_from_slice(index);
|
||||||
@ -332,9 +326,9 @@ impl<'a> milli::heed::BytesEncode<'a> for KeyIdActionCodec {
|
|||||||
pub struct SliceTooShortError;
|
pub struct SliceTooShortError;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
#[error("cannot construct a valid Action from {action_byte}")]
|
#[error("cannot construct a valid Action from {action_bits}")]
|
||||||
pub struct InvalidActionError {
|
pub struct InvalidActionError {
|
||||||
pub action_byte: u8,
|
pub action_bits: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_key_as_hexa(uid: Uuid, master_key: &[u8]) -> String {
|
pub fn generate_key_as_hexa(uid: Uuid, master_key: &[u8]) -> String {
|
||||||
|
@ -38,6 +38,7 @@ time = { version = "0.3.36", features = [
|
|||||||
] }
|
] }
|
||||||
tokio = "1.38"
|
tokio = "1.38"
|
||||||
uuid = { version = "1.10.0", features = ["serde", "v4"] }
|
uuid = { version = "1.10.0", features = ["serde", "v4"] }
|
||||||
|
bitflags = "2.6.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
insta = "1.39.0"
|
insta = "1.39.0"
|
||||||
|
@ -2,10 +2,11 @@ use std::convert::Infallible;
|
|||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use deserr::{DeserializeError, Deserr, MergeWithError, ValuePointerRef};
|
use bitflags::{bitflags, Flags};
|
||||||
|
use deserr::{take_cf_content, DeserializeError, Deserr, MergeWithError, ValuePointerRef};
|
||||||
use enum_iterator::Sequence;
|
use enum_iterator::Sequence;
|
||||||
use milli::update::Setting;
|
use milli::update::Setting;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
use time::format_description::well_known::Rfc3339;
|
use time::format_description::well_known::Rfc3339;
|
||||||
use time::macros::{format_description, time};
|
use time::macros::{format_description, time};
|
||||||
use time::{Date, OffsetDateTime, PrimitiveDateTime};
|
use time::{Date, OffsetDateTime, PrimitiveDateTime};
|
||||||
@ -179,193 +180,284 @@ fn parse_expiration_date(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Serialize, Deserialize, Debug, Eq, PartialEq, Hash, Sequence, Deserr)]
|
bitflags! {
|
||||||
#[repr(u8)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
|
||||||
pub enum Action {
|
#[repr(transparent)]
|
||||||
#[serde(rename = "*")]
|
// NOTE: For `Sequence` impl to work, the values of these must be in ascending order
|
||||||
#[deserr(rename = "*")]
|
pub struct Action: u32 {
|
||||||
All = 0,
|
const Search = 1;
|
||||||
#[serde(rename = "search")]
|
// Documents
|
||||||
#[deserr(rename = "search")]
|
const DocumentsAdd = 1 << 1;
|
||||||
Search,
|
const DocumentsGet = 1 << 2;
|
||||||
#[serde(rename = "documents.*")]
|
const DocumentsDelete = 1 << 3;
|
||||||
#[deserr(rename = "documents.*")]
|
const DocumentsAll = Self::DocumentsAdd.bits() | Self::DocumentsGet.bits() | Self::DocumentsDelete.bits();
|
||||||
DocumentsAll,
|
// Indexes
|
||||||
#[serde(rename = "documents.add")]
|
const IndexesAdd = 1 << 4;
|
||||||
#[deserr(rename = "documents.add")]
|
const IndexesGet = 1 << 5;
|
||||||
DocumentsAdd,
|
const IndexesUpdate = 1 << 6;
|
||||||
#[serde(rename = "documents.get")]
|
const IndexesDelete = 1 << 7;
|
||||||
#[deserr(rename = "documents.get")]
|
const IndexesSwap = 1 << 8;
|
||||||
DocumentsGet,
|
const IndexesAll = Self::IndexesAdd.bits() | Self::IndexesGet.bits() | Self::IndexesUpdate.bits() | Self::IndexesDelete.bits() | Self::IndexesSwap.bits();
|
||||||
#[serde(rename = "documents.delete")]
|
// Tasks
|
||||||
#[deserr(rename = "documents.delete")]
|
const TasksCancel = 1 << 9;
|
||||||
DocumentsDelete,
|
const TasksDelete = 1 << 10;
|
||||||
#[serde(rename = "indexes.*")]
|
const TasksGet = 1 << 11;
|
||||||
#[deserr(rename = "indexes.*")]
|
const TasksAll = Self::TasksCancel.bits() | Self::TasksDelete.bits() | Self::TasksGet.bits();
|
||||||
IndexesAll,
|
// Settings
|
||||||
#[serde(rename = "indexes.create")]
|
const SettingsGet = 1 << 12;
|
||||||
#[deserr(rename = "indexes.create")]
|
const SettingsUpdate = 1 << 13;
|
||||||
IndexesAdd,
|
const SettingsAll = Self::SettingsGet.bits() | Self::SettingsUpdate.bits();
|
||||||
#[serde(rename = "indexes.get")]
|
// Stats
|
||||||
#[deserr(rename = "indexes.get")]
|
const StatsGet = 1 << 14;
|
||||||
IndexesGet,
|
const StatsAll = Self::StatsGet.bits();
|
||||||
#[serde(rename = "indexes.update")]
|
// Metrics
|
||||||
#[deserr(rename = "indexes.update")]
|
const MetricsGet = 1 << 15;
|
||||||
IndexesUpdate,
|
const MetricsAll = Self::MetricsGet.bits();
|
||||||
#[serde(rename = "indexes.delete")]
|
// Dumps
|
||||||
#[deserr(rename = "indexes.delete")]
|
const DumpsCreate = 1 << 16;
|
||||||
IndexesDelete,
|
const DumpsAll = Self::DumpsCreate.bits();
|
||||||
#[serde(rename = "indexes.swap")]
|
// Snapshots
|
||||||
#[deserr(rename = "indexes.swap")]
|
const SnapshotsCreate = 1 << 17;
|
||||||
IndexesSwap,
|
const SnapshotsAll = Self::SnapshotsCreate.bits();
|
||||||
#[serde(rename = "tasks.*")]
|
// Keys without an "all" version
|
||||||
#[deserr(rename = "tasks.*")]
|
const Version = 1 << 18;
|
||||||
TasksAll,
|
const KeysAdd = 1 << 19;
|
||||||
#[serde(rename = "tasks.cancel")]
|
const KeysGet = 1 << 20;
|
||||||
#[deserr(rename = "tasks.cancel")]
|
const KeysUpdate = 1 << 21;
|
||||||
TasksCancel,
|
const KeysDelete = 1 << 22;
|
||||||
#[serde(rename = "tasks.delete")]
|
const ExperimentalFeaturesGet = 1 << 23;
|
||||||
#[deserr(rename = "tasks.delete")]
|
const ExperimentalFeaturesUpdate = 1 << 24;
|
||||||
TasksDelete,
|
// All
|
||||||
#[serde(rename = "tasks.get")]
|
const All = 0xFFFFFFFF >> (32 - 1 - 24);
|
||||||
#[deserr(rename = "tasks.get")]
|
}
|
||||||
TasksGet,
|
|
||||||
#[serde(rename = "settings.*")]
|
|
||||||
#[deserr(rename = "settings.*")]
|
|
||||||
SettingsAll,
|
|
||||||
#[serde(rename = "settings.get")]
|
|
||||||
#[deserr(rename = "settings.get")]
|
|
||||||
SettingsGet,
|
|
||||||
#[serde(rename = "settings.update")]
|
|
||||||
#[deserr(rename = "settings.update")]
|
|
||||||
SettingsUpdate,
|
|
||||||
#[serde(rename = "stats.*")]
|
|
||||||
#[deserr(rename = "stats.*")]
|
|
||||||
StatsAll,
|
|
||||||
#[serde(rename = "stats.get")]
|
|
||||||
#[deserr(rename = "stats.get")]
|
|
||||||
StatsGet,
|
|
||||||
#[serde(rename = "metrics.*")]
|
|
||||||
#[deserr(rename = "metrics.*")]
|
|
||||||
MetricsAll,
|
|
||||||
#[serde(rename = "metrics.get")]
|
|
||||||
#[deserr(rename = "metrics.get")]
|
|
||||||
MetricsGet,
|
|
||||||
#[serde(rename = "dumps.*")]
|
|
||||||
#[deserr(rename = "dumps.*")]
|
|
||||||
DumpsAll,
|
|
||||||
#[serde(rename = "dumps.create")]
|
|
||||||
#[deserr(rename = "dumps.create")]
|
|
||||||
DumpsCreate,
|
|
||||||
#[serde(rename = "snapshots.*")]
|
|
||||||
#[deserr(rename = "snapshots.*")]
|
|
||||||
SnapshotsAll,
|
|
||||||
#[serde(rename = "snapshots.create")]
|
|
||||||
#[deserr(rename = "snapshots.create")]
|
|
||||||
SnapshotsCreate,
|
|
||||||
#[serde(rename = "version")]
|
|
||||||
#[deserr(rename = "version")]
|
|
||||||
Version,
|
|
||||||
#[serde(rename = "keys.create")]
|
|
||||||
#[deserr(rename = "keys.create")]
|
|
||||||
KeysAdd,
|
|
||||||
#[serde(rename = "keys.get")]
|
|
||||||
#[deserr(rename = "keys.get")]
|
|
||||||
KeysGet,
|
|
||||||
#[serde(rename = "keys.update")]
|
|
||||||
#[deserr(rename = "keys.update")]
|
|
||||||
KeysUpdate,
|
|
||||||
#[serde(rename = "keys.delete")]
|
|
||||||
#[deserr(rename = "keys.delete")]
|
|
||||||
KeysDelete,
|
|
||||||
#[serde(rename = "experimental.get")]
|
|
||||||
#[deserr(rename = "experimental.get")]
|
|
||||||
ExperimentalFeaturesGet,
|
|
||||||
#[serde(rename = "experimental.update")]
|
|
||||||
#[deserr(rename = "experimental.update")]
|
|
||||||
ExperimentalFeaturesUpdate,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Action {
|
impl Action {
|
||||||
pub const fn from_repr(repr: u8) -> Option<Self> {
|
const SERDE_MAP_ARR: [(&'static str, Self); 34] = [
|
||||||
use actions::*;
|
("search", Self::Search),
|
||||||
match repr {
|
("documents.add", Self::DocumentsAdd),
|
||||||
ALL => Some(Self::All),
|
("documents.get", Self::DocumentsGet),
|
||||||
SEARCH => Some(Self::Search),
|
("documents.delete", Self::DocumentsDelete),
|
||||||
DOCUMENTS_ALL => Some(Self::DocumentsAll),
|
("documents.*", Self::DocumentsAll),
|
||||||
DOCUMENTS_ADD => Some(Self::DocumentsAdd),
|
("indexes.create", Self::IndexesAdd),
|
||||||
DOCUMENTS_GET => Some(Self::DocumentsGet),
|
("indexes.get", Self::IndexesGet),
|
||||||
DOCUMENTS_DELETE => Some(Self::DocumentsDelete),
|
("indexes.update", Self::IndexesUpdate),
|
||||||
INDEXES_ALL => Some(Self::IndexesAll),
|
("indexes.delete", Self::IndexesDelete),
|
||||||
INDEXES_CREATE => Some(Self::IndexesAdd),
|
("indexes.swap", Self::IndexesSwap),
|
||||||
INDEXES_GET => Some(Self::IndexesGet),
|
("indexes.*", Self::IndexesAll),
|
||||||
INDEXES_UPDATE => Some(Self::IndexesUpdate),
|
("tasks.cancel", Self::TasksCancel),
|
||||||
INDEXES_DELETE => Some(Self::IndexesDelete),
|
("tasks.delete", Self::TasksDelete),
|
||||||
INDEXES_SWAP => Some(Self::IndexesSwap),
|
("tasks.get", Self::TasksGet),
|
||||||
TASKS_ALL => Some(Self::TasksAll),
|
("tasks.*", Self::TasksAll),
|
||||||
TASKS_CANCEL => Some(Self::TasksCancel),
|
("settings.get", Self::SettingsGet),
|
||||||
TASKS_DELETE => Some(Self::TasksDelete),
|
("settings.update", Self::SettingsUpdate),
|
||||||
TASKS_GET => Some(Self::TasksGet),
|
("settings.*", Self::SettingsAll),
|
||||||
SETTINGS_ALL => Some(Self::SettingsAll),
|
("stats.get", Self::StatsGet),
|
||||||
SETTINGS_GET => Some(Self::SettingsGet),
|
("stats.*", Self::StatsAll),
|
||||||
SETTINGS_UPDATE => Some(Self::SettingsUpdate),
|
("metrics.get", Self::MetricsGet),
|
||||||
STATS_ALL => Some(Self::StatsAll),
|
("metrics.*", Self::MetricsAll),
|
||||||
STATS_GET => Some(Self::StatsGet),
|
("dumps.create", Self::DumpsCreate),
|
||||||
METRICS_ALL => Some(Self::MetricsAll),
|
("dumps.*", Self::DumpsAll),
|
||||||
METRICS_GET => Some(Self::MetricsGet),
|
("snapshots.create", Self::SnapshotsCreate),
|
||||||
DUMPS_ALL => Some(Self::DumpsAll),
|
("snapshots.*", Self::SnapshotsAll),
|
||||||
DUMPS_CREATE => Some(Self::DumpsCreate),
|
("version", Self::Version),
|
||||||
SNAPSHOTS_CREATE => Some(Self::SnapshotsCreate),
|
("keys.create", Self::KeysAdd),
|
||||||
VERSION => Some(Self::Version),
|
("keys.get", Self::KeysGet),
|
||||||
KEYS_CREATE => Some(Self::KeysAdd),
|
("keys.update", Self::KeysUpdate),
|
||||||
KEYS_GET => Some(Self::KeysGet),
|
("keys.delete", Self::KeysDelete),
|
||||||
KEYS_UPDATE => Some(Self::KeysUpdate),
|
("experimental.get", Self::ExperimentalFeaturesGet),
|
||||||
KEYS_DELETE => Some(Self::KeysDelete),
|
("experimental.update", Self::ExperimentalFeaturesUpdate),
|
||||||
EXPERIMENTAL_FEATURES_GET => Some(Self::ExperimentalFeaturesGet),
|
("*", Self::All),
|
||||||
EXPERIMENTAL_FEATURES_UPDATE => Some(Self::ExperimentalFeaturesUpdate),
|
];
|
||||||
_otherwise => None,
|
|
||||||
}
|
fn get_action(v: &str) -> Option<Action> {
|
||||||
|
Self::SERDE_MAP_ARR
|
||||||
|
.iter()
|
||||||
|
.find(|(serde_name, _)| &v == serde_name)
|
||||||
|
.map(|(_, action)| *action)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn repr(&self) -> u8 {
|
fn get_action_serde_name(v: &Action) -> &'static str {
|
||||||
*self as u8
|
Self::SERDE_MAP_ARR
|
||||||
|
.iter()
|
||||||
|
.find(|(_, action)| v == action)
|
||||||
|
.map(|(serde_name, _)| serde_name)
|
||||||
|
.expect("an action is missing a matching serialized value")
|
||||||
|
}
|
||||||
|
|
||||||
|
// when we remove "all" flags, this will give us the exact index
|
||||||
|
fn get_potential_index(&self) -> usize {
|
||||||
|
if self.is_empty() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// most significant bit for u32
|
||||||
|
let msb = 1u32 << (31 - self.bits().leading_zeros());
|
||||||
|
|
||||||
|
// index of the single set bit
|
||||||
|
msb.trailing_zeros() as usize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod actions {
|
pub mod actions {
|
||||||
use super::Action::*;
|
use super::Action as A;
|
||||||
|
|
||||||
pub(crate) const ALL: u8 = All.repr();
|
pub const SEARCH: u32 = A::Search.bits();
|
||||||
pub const SEARCH: u8 = Search.repr();
|
pub const DOCUMENTS_ADD: u32 = A::DocumentsAdd.bits();
|
||||||
pub const DOCUMENTS_ALL: u8 = DocumentsAll.repr();
|
pub const DOCUMENTS_GET: u32 = A::DocumentsGet.bits();
|
||||||
pub const DOCUMENTS_ADD: u8 = DocumentsAdd.repr();
|
pub const DOCUMENTS_DELETE: u32 = A::DocumentsDelete.bits();
|
||||||
pub const DOCUMENTS_GET: u8 = DocumentsGet.repr();
|
pub const DOCUMENTS_ALL: u32 = A::DocumentsAll.bits();
|
||||||
pub const DOCUMENTS_DELETE: u8 = DocumentsDelete.repr();
|
pub const INDEXES_CREATE: u32 = A::IndexesAdd.bits();
|
||||||
pub const INDEXES_ALL: u8 = IndexesAll.repr();
|
pub const INDEXES_GET: u32 = A::IndexesGet.bits();
|
||||||
pub const INDEXES_CREATE: u8 = IndexesAdd.repr();
|
pub const INDEXES_UPDATE: u32 = A::IndexesUpdate.bits();
|
||||||
pub const INDEXES_GET: u8 = IndexesGet.repr();
|
pub const INDEXES_DELETE: u32 = A::IndexesDelete.bits();
|
||||||
pub const INDEXES_UPDATE: u8 = IndexesUpdate.repr();
|
pub const INDEXES_SWAP: u32 = A::IndexesSwap.bits();
|
||||||
pub const INDEXES_DELETE: u8 = IndexesDelete.repr();
|
pub const INDEXES_ALL: u32 = A::IndexesAll.bits();
|
||||||
pub const INDEXES_SWAP: u8 = IndexesSwap.repr();
|
pub const TASKS_CANCEL: u32 = A::TasksCancel.bits();
|
||||||
pub const TASKS_ALL: u8 = TasksAll.repr();
|
pub const TASKS_DELETE: u32 = A::TasksDelete.bits();
|
||||||
pub const TASKS_CANCEL: u8 = TasksCancel.repr();
|
pub const TASKS_GET: u32 = A::TasksGet.bits();
|
||||||
pub const TASKS_DELETE: u8 = TasksDelete.repr();
|
pub const TASKS_ALL: u32 = A::TasksAll.bits();
|
||||||
pub const TASKS_GET: u8 = TasksGet.repr();
|
pub const SETTINGS_GET: u32 = A::SettingsGet.bits();
|
||||||
pub const SETTINGS_ALL: u8 = SettingsAll.repr();
|
pub const SETTINGS_UPDATE: u32 = A::SettingsUpdate.bits();
|
||||||
pub const SETTINGS_GET: u8 = SettingsGet.repr();
|
pub const SETTINGS_ALL: u32 = A::SettingsAll.bits();
|
||||||
pub const SETTINGS_UPDATE: u8 = SettingsUpdate.repr();
|
pub const STATS_GET: u32 = A::StatsGet.bits();
|
||||||
pub const STATS_ALL: u8 = StatsAll.repr();
|
pub const STATS_ALL: u32 = A::StatsAll.bits();
|
||||||
pub const STATS_GET: u8 = StatsGet.repr();
|
pub const METRICS_GET: u32 = A::MetricsGet.bits();
|
||||||
pub const METRICS_ALL: u8 = MetricsAll.repr();
|
pub const METRICS_ALL: u32 = A::MetricsAll.bits();
|
||||||
pub const METRICS_GET: u8 = MetricsGet.repr();
|
pub const DUMPS_CREATE: u32 = A::DumpsCreate.bits();
|
||||||
pub const DUMPS_ALL: u8 = DumpsAll.repr();
|
pub const DUMPS_ALL: u32 = A::DumpsAll.bits();
|
||||||
pub const DUMPS_CREATE: u8 = DumpsCreate.repr();
|
pub const SNAPSHOTS_CREATE: u32 = A::SnapshotsCreate.bits();
|
||||||
pub const SNAPSHOTS_CREATE: u8 = SnapshotsCreate.repr();
|
pub const SNAPSHOTS_ALL: u32 = A::SnapshotsAll.bits();
|
||||||
pub const VERSION: u8 = Version.repr();
|
pub const VERSION: u32 = A::Version.bits();
|
||||||
pub const KEYS_CREATE: u8 = KeysAdd.repr();
|
pub const KEYS_CREATE: u32 = A::KeysAdd.bits();
|
||||||
pub const KEYS_GET: u8 = KeysGet.repr();
|
pub const KEYS_GET: u32 = A::KeysGet.bits();
|
||||||
pub const KEYS_UPDATE: u8 = KeysUpdate.repr();
|
pub const KEYS_UPDATE: u32 = A::KeysUpdate.bits();
|
||||||
pub const KEYS_DELETE: u8 = KeysDelete.repr();
|
pub const KEYS_DELETE: u32 = A::KeysDelete.bits();
|
||||||
pub const EXPERIMENTAL_FEATURES_GET: u8 = ExperimentalFeaturesGet.repr();
|
pub const EXPERIMENTAL_FEATURES_GET: u32 = A::ExperimentalFeaturesGet.bits();
|
||||||
pub const EXPERIMENTAL_FEATURES_UPDATE: u8 = ExperimentalFeaturesUpdate.repr();
|
pub const EXPERIMENTAL_FEATURES_UPDATE: u32 = A::ExperimentalFeaturesUpdate.bits();
|
||||||
|
pub const ALL: u32 = A::All.bits();
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: DeserializeError> Deserr<E> for Action {
|
||||||
|
fn deserialize_from_value<V: deserr::IntoValue>(
|
||||||
|
value: deserr::Value<V>,
|
||||||
|
location: deserr::ValuePointerRef<'_>,
|
||||||
|
) -> Result<Self, E> {
|
||||||
|
match value {
|
||||||
|
deserr::Value::String(s) => match Self::get_action(&s) {
|
||||||
|
Some(action) => Ok(action),
|
||||||
|
None => Err(deserr::take_cf_content(E::error::<std::convert::Infallible>(
|
||||||
|
None,
|
||||||
|
deserr::ErrorKind::UnknownValue {
|
||||||
|
value: &s,
|
||||||
|
accepted: &Self::SERDE_MAP_ARR.map(|(ser_action, _)| ser_action),
|
||||||
|
},
|
||||||
|
location,
|
||||||
|
))),
|
||||||
|
},
|
||||||
|
_ => Err(take_cf_content(E::error(
|
||||||
|
None,
|
||||||
|
deserr::ErrorKind::IncorrectValueKind {
|
||||||
|
actual: value,
|
||||||
|
accepted: &[deserr::ValueKind::String],
|
||||||
|
},
|
||||||
|
location,
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for Action {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_str(Self::get_action_serde_name(self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for Action {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
struct Visitor;
|
||||||
|
impl<'de> serde::de::Visitor<'de> for Visitor {
|
||||||
|
type Value = Action;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(formatter, "the name of a valid action (string)")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: serde::de::Error,
|
||||||
|
{
|
||||||
|
match Self::Value::get_action(s) {
|
||||||
|
Some(action) => Ok(action),
|
||||||
|
None => Err(E::invalid_value(serde::de::Unexpected::Str(s), &"a valid action")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deserializer.deserialize_str(Visitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Once "all" type flags are removed, simplify
|
||||||
|
// Essentially `get_potential_index` will give the exact index, +1 the exact next, -1 the exact previous
|
||||||
|
impl Sequence for Action {
|
||||||
|
const CARDINALITY: usize = Self::FLAGS.len();
|
||||||
|
|
||||||
|
fn next(&self) -> Option<Self> {
|
||||||
|
let mut potential_next_index = self.get_potential_index() + 1;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if let Some(next_flag) = Self::FLAGS.get(potential_next_index).map(|v| v.value()) {
|
||||||
|
if next_flag > self {
|
||||||
|
return Some(*next_flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
potential_next_index += 1;
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn previous(&self) -> Option<Self> {
|
||||||
|
// -2 because of "all" type flags that represent a single flag, otherwise -1 would suffice
|
||||||
|
let initial_potential_index = self.get_potential_index();
|
||||||
|
if initial_potential_index == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut potential_previous_index: usize =
|
||||||
|
if initial_potential_index == 1 { 0 } else { initial_potential_index - 2 };
|
||||||
|
|
||||||
|
let mut previous_item: Option<Self> = None;
|
||||||
|
let mut pre_previous_item: Option<Self> = None;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if let Some(next_flag) = Self::FLAGS.get(potential_previous_index).map(|v| v.value()) {
|
||||||
|
if next_flag > self {
|
||||||
|
return pre_previous_item;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre_previous_item = previous_item;
|
||||||
|
previous_item = Some(*next_flag);
|
||||||
|
potential_previous_index += 1;
|
||||||
|
} else {
|
||||||
|
return pre_previous_item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn first() -> Option<Self> {
|
||||||
|
Self::FLAGS.first().map(|v| *v.value())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn last() -> Option<Self> {
|
||||||
|
Self::FLAGS.last().map(|v| *v.value())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -171,7 +171,7 @@ pub mod policies {
|
|||||||
#[error("Could not decode tenant token, {0}.")]
|
#[error("Could not decode tenant token, {0}.")]
|
||||||
CouldNotDecodeTenantToken(jsonwebtoken::errors::Error),
|
CouldNotDecodeTenantToken(jsonwebtoken::errors::Error),
|
||||||
#[error("Invalid action `{0}`.")]
|
#[error("Invalid action `{0}`.")]
|
||||||
InternalInvalidAction(u8),
|
InternalInvalidAction(u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<jsonwebtoken::errors::Error> for AuthError {
|
impl From<jsonwebtoken::errors::Error> for AuthError {
|
||||||
@ -214,14 +214,14 @@ pub mod policies {
|
|||||||
Ok(api_key_uid)
|
Ok(api_key_uid)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_keys_action(action: u8) -> bool {
|
fn is_keys_action(action: u32) -> bool {
|
||||||
use actions::*;
|
use actions::*;
|
||||||
matches!(action, KEYS_GET | KEYS_CREATE | KEYS_UPDATE | KEYS_DELETE)
|
matches!(action, KEYS_GET | KEYS_CREATE | KEYS_UPDATE | KEYS_DELETE)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ActionPolicy<const A: u8>;
|
pub struct ActionPolicy<const A: u32>;
|
||||||
|
|
||||||
impl<const A: u8> Policy for ActionPolicy<A> {
|
impl<const A: u32> Policy for ActionPolicy<A> {
|
||||||
/// Attempts to grant authentication from a bearer token (that can be a tenant token or an API key), the requested Action,
|
/// Attempts to grant authentication from a bearer token (that can be a tenant token or an API key), the requested Action,
|
||||||
/// and a list of requested indexes.
|
/// and a list of requested indexes.
|
||||||
///
|
///
|
||||||
@ -255,7 +255,7 @@ pub mod policies {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// check that the indexes are allowed
|
// check that the indexes are allowed
|
||||||
let action = Action::from_repr(A).ok_or(AuthError::InternalInvalidAction(A))?;
|
let action = Action::from_bits(A).ok_or(AuthError::InternalInvalidAction(A))?;
|
||||||
let auth_filter = auth
|
let auth_filter = auth
|
||||||
.get_key_filters(key_uuid, search_rules)
|
.get_key_filters(key_uuid, search_rules)
|
||||||
.map_err(|_e| AuthError::InvalidApiKey)?;
|
.map_err(|_e| AuthError::InvalidApiKey)?;
|
||||||
@ -294,7 +294,7 @@ pub mod policies {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const A: u8> ActionPolicy<A> {
|
impl<const A: u32> ActionPolicy<A> {
|
||||||
fn authenticate_tenant_token(
|
fn authenticate_tenant_token(
|
||||||
auth: &AuthController,
|
auth: &AuthController,
|
||||||
token: &str,
|
token: &str,
|
||||||
|
@ -1733,46 +1733,51 @@ fn format_fields(
|
|||||||
// select the attributes to retrieve
|
// select the attributes to retrieve
|
||||||
let displayable_names =
|
let displayable_names =
|
||||||
displayable_ids.iter().map(|&fid| field_ids_map.name(fid).expect("Missing field name"));
|
displayable_ids.iter().map(|&fid| field_ids_map.name(fid).expect("Missing field name"));
|
||||||
permissive_json_pointer::map_leaf_values(&mut document, displayable_names, |key, value| {
|
permissive_json_pointer::map_leaf_values(
|
||||||
// To get the formatting option of each key we need to see all the rules that applies
|
&mut document,
|
||||||
// to the value and merge them together. eg. If a user said he wanted to highlight `doggo`
|
displayable_names,
|
||||||
// and crop `doggo.name`. `doggo.name` needs to be highlighted + cropped while `doggo.age` is only
|
|key, array_indices, value| {
|
||||||
// highlighted.
|
// To get the formatting option of each key we need to see all the rules that applies
|
||||||
// Warn: The time to compute the format list scales with the number of fields to format;
|
// to the value and merge them together. eg. If a user said he wanted to highlight `doggo`
|
||||||
// cumulated with map_leaf_values that iterates over all the nested fields, it gives a quadratic complexity:
|
// and crop `doggo.name`. `doggo.name` needs to be highlighted + cropped while `doggo.age` is only
|
||||||
// d*f where d is the total number of fields to display and f is the total number of fields to format.
|
// highlighted.
|
||||||
let format = formatting_fields_options
|
// Warn: The time to compute the format list scales with the number of fields to format;
|
||||||
.iter()
|
// cumulated with map_leaf_values that iterates over all the nested fields, it gives a quadratic complexity:
|
||||||
.filter(|(name, _option)| {
|
// d*f where d is the total number of fields to display and f is the total number of fields to format.
|
||||||
milli::is_faceted_by(name, key) || milli::is_faceted_by(key, name)
|
let format = formatting_fields_options
|
||||||
})
|
|
||||||
.map(|(_, option)| **option)
|
|
||||||
.reduce(|acc, option| acc.merge(option));
|
|
||||||
let mut infos = Vec::new();
|
|
||||||
|
|
||||||
// if no locales has been provided, we try to find the locales in the localized_attributes.
|
|
||||||
let locales = locales.or_else(|| {
|
|
||||||
localized_attributes
|
|
||||||
.iter()
|
.iter()
|
||||||
.find(|rule| rule.match_str(key))
|
.filter(|(name, _option)| {
|
||||||
.map(LocalizedAttributesRule::locales)
|
milli::is_faceted_by(name, key) || milli::is_faceted_by(key, name)
|
||||||
});
|
})
|
||||||
|
.map(|(_, option)| **option)
|
||||||
|
.reduce(|acc, option| acc.merge(option));
|
||||||
|
let mut infos = Vec::new();
|
||||||
|
|
||||||
*value = format_value(
|
// if no locales has been provided, we try to find the locales in the localized_attributes.
|
||||||
std::mem::take(value),
|
let locales = locales.or_else(|| {
|
||||||
builder,
|
localized_attributes
|
||||||
format,
|
.iter()
|
||||||
&mut infos,
|
.find(|rule| rule.match_str(key))
|
||||||
compute_matches,
|
.map(LocalizedAttributesRule::locales)
|
||||||
locales,
|
});
|
||||||
);
|
|
||||||
|
|
||||||
if let Some(matches) = matches_position.as_mut() {
|
*value = format_value(
|
||||||
if !infos.is_empty() {
|
std::mem::take(value),
|
||||||
matches.insert(key.to_owned(), infos);
|
builder,
|
||||||
|
format,
|
||||||
|
&mut infos,
|
||||||
|
compute_matches,
|
||||||
|
array_indices,
|
||||||
|
locales,
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(matches) = matches_position.as_mut() {
|
||||||
|
if !infos.is_empty() {
|
||||||
|
matches.insert(key.to_owned(), infos);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
);
|
||||||
|
|
||||||
let selectors = formatted_options
|
let selectors = formatted_options
|
||||||
.keys()
|
.keys()
|
||||||
@ -1790,13 +1795,14 @@ fn format_value(
|
|||||||
format_options: Option<FormatOptions>,
|
format_options: Option<FormatOptions>,
|
||||||
infos: &mut Vec<MatchBounds>,
|
infos: &mut Vec<MatchBounds>,
|
||||||
compute_matches: bool,
|
compute_matches: bool,
|
||||||
|
array_indices: &[usize],
|
||||||
locales: Option<&[Language]>,
|
locales: Option<&[Language]>,
|
||||||
) -> Value {
|
) -> Value {
|
||||||
match value {
|
match value {
|
||||||
Value::String(old_string) => {
|
Value::String(old_string) => {
|
||||||
let mut matcher = builder.build(&old_string, locales);
|
let mut matcher = builder.build(&old_string, locales);
|
||||||
if compute_matches {
|
if compute_matches {
|
||||||
let matches = matcher.matches();
|
let matches = matcher.matches(array_indices);
|
||||||
infos.extend_from_slice(&matches[..]);
|
infos.extend_from_slice(&matches[..]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1808,51 +1814,15 @@ fn format_value(
|
|||||||
None => Value::String(old_string),
|
None => Value::String(old_string),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::Array(values) => Value::Array(
|
// `map_leaf_values` makes sure this is only called for leaf fields
|
||||||
values
|
Value::Array(_) => unreachable!(),
|
||||||
.into_iter()
|
Value::Object(_) => unreachable!(),
|
||||||
.map(|v| {
|
|
||||||
format_value(
|
|
||||||
v,
|
|
||||||
builder,
|
|
||||||
format_options.map(|format_options| FormatOptions {
|
|
||||||
highlight: format_options.highlight,
|
|
||||||
crop: None,
|
|
||||||
}),
|
|
||||||
infos,
|
|
||||||
compute_matches,
|
|
||||||
locales,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
),
|
|
||||||
Value::Object(object) => Value::Object(
|
|
||||||
object
|
|
||||||
.into_iter()
|
|
||||||
.map(|(k, v)| {
|
|
||||||
(
|
|
||||||
k,
|
|
||||||
format_value(
|
|
||||||
v,
|
|
||||||
builder,
|
|
||||||
format_options.map(|format_options| FormatOptions {
|
|
||||||
highlight: format_options.highlight,
|
|
||||||
crop: None,
|
|
||||||
}),
|
|
||||||
infos,
|
|
||||||
compute_matches,
|
|
||||||
locales,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
),
|
|
||||||
Value::Number(number) => {
|
Value::Number(number) => {
|
||||||
let s = number.to_string();
|
let s = number.to_string();
|
||||||
|
|
||||||
let mut matcher = builder.build(&s, locales);
|
let mut matcher = builder.build(&s, locales);
|
||||||
if compute_matches {
|
if compute_matches {
|
||||||
let matches = matcher.matches();
|
let matches = matcher.matches(array_indices);
|
||||||
infos.extend_from_slice(&matches[..]);
|
infos.extend_from_slice(&matches[..]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -421,7 +421,7 @@ async fn error_add_api_key_invalid_parameters_actions() {
|
|||||||
meili_snap::snapshot!(code, @"400 Bad Request");
|
meili_snap::snapshot!(code, @"400 Bad Request");
|
||||||
meili_snap::snapshot!(meili_snap::json_string!(response, { ".createdAt" => "[ignored]", ".updatedAt" => "[ignored]" }), @r###"
|
meili_snap::snapshot!(meili_snap::json_string!(response, { ".createdAt" => "[ignored]", ".updatedAt" => "[ignored]" }), @r###"
|
||||||
{
|
{
|
||||||
"message": "Unknown value `doc.add` at `.actions[0]`: expected one of `*`, `search`, `documents.*`, `documents.add`, `documents.get`, `documents.delete`, `indexes.*`, `indexes.create`, `indexes.get`, `indexes.update`, `indexes.delete`, `indexes.swap`, `tasks.*`, `tasks.cancel`, `tasks.delete`, `tasks.get`, `settings.*`, `settings.get`, `settings.update`, `stats.*`, `stats.get`, `metrics.*`, `metrics.get`, `dumps.*`, `dumps.create`, `snapshots.*`, `snapshots.create`, `version`, `keys.create`, `keys.get`, `keys.update`, `keys.delete`, `experimental.get`, `experimental.update`",
|
"message": "Unknown value `doc.add` at `.actions[0]`: expected one of `search`, `documents.add`, `documents.get`, `documents.delete`, `documents.*`, `indexes.create`, `indexes.get`, `indexes.update`, `indexes.delete`, `indexes.swap`, `indexes.*`, `tasks.cancel`, `tasks.delete`, `tasks.get`, `tasks.*`, `settings.get`, `settings.update`, `settings.*`, `stats.get`, `stats.*`, `metrics.get`, `metrics.*`, `dumps.create`, `dumps.*`, `snapshots.create`, `snapshots.*`, `version`, `keys.create`, `keys.get`, `keys.update`, `keys.delete`, `experimental.get`, `experimental.update`, `*`",
|
||||||
"code": "invalid_api_key_actions",
|
"code": "invalid_api_key_actions",
|
||||||
"type": "invalid_request",
|
"type": "invalid_request",
|
||||||
"link": "https://docs.meilisearch.com/errors#invalid_api_key_actions"
|
"link": "https://docs.meilisearch.com/errors#invalid_api_key_actions"
|
||||||
|
@ -53,14 +53,14 @@ pub static AUTHORIZATIONS: Lazy<HashMap<(&'static str, &'static str), HashSet<&'
|
|||||||
("PUT", "/indexes/products/settings/sortable-attributes") => hashset!{"settings.update", "settings.*", "*"},
|
("PUT", "/indexes/products/settings/sortable-attributes") => hashset!{"settings.update", "settings.*", "*"},
|
||||||
("PUT", "/indexes/products/settings/stop-words") => hashset!{"settings.update", "settings.*", "*"},
|
("PUT", "/indexes/products/settings/stop-words") => hashset!{"settings.update", "settings.*", "*"},
|
||||||
("PUT", "/indexes/products/settings/synonyms") => hashset!{"settings.update", "settings.*", "*"},
|
("PUT", "/indexes/products/settings/synonyms") => hashset!{"settings.update", "settings.*", "*"},
|
||||||
("GET", "/indexes/products/stats") => hashset!{"stats.get", "stats.*", "*"},
|
("GET", "/indexes/products/stats") => hashset!{"stats.get", "*"},
|
||||||
("GET", "/stats") => hashset!{"stats.get", "stats.*", "*"},
|
("GET", "/stats") => hashset!{"stats.get", "*"},
|
||||||
("POST", "/dumps") => hashset!{"dumps.create", "dumps.*", "*"},
|
("POST", "/dumps") => hashset!{"dumps.create", "*"},
|
||||||
("POST", "/snapshots") => hashset!{"snapshots.create", "snapshots.*", "*"},
|
("POST", "/snapshots") => hashset!{"snapshots.create", "*"},
|
||||||
("GET", "/version") => hashset!{"version", "*"},
|
("GET", "/version") => hashset!{"version", "*"},
|
||||||
("GET", "/metrics") => hashset!{"metrics.get", "metrics.*", "*"},
|
("GET", "/metrics") => hashset!{"metrics.get", "*"},
|
||||||
("POST", "/logs/stream") => hashset!{"metrics.get", "metrics.*", "*"},
|
("POST", "/logs/stream") => hashset!{"metrics.get", "*"},
|
||||||
("DELETE", "/logs/stream") => hashset!{"metrics.get", "metrics.*", "*"},
|
("DELETE", "/logs/stream") => hashset!{"metrics.get", "*"},
|
||||||
("PATCH", "/keys/mykey/") => hashset!{"keys.update", "*"},
|
("PATCH", "/keys/mykey/") => hashset!{"keys.update", "*"},
|
||||||
("GET", "/keys/mykey/") => hashset!{"keys.get", "*"},
|
("GET", "/keys/mykey/") => hashset!{"keys.get", "*"},
|
||||||
("DELETE", "/keys/mykey/") => hashset!{"keys.delete", "*"},
|
("DELETE", "/keys/mykey/") => hashset!{"keys.delete", "*"},
|
||||||
|
@ -93,7 +93,7 @@ async fn create_api_key_bad_actions() {
|
|||||||
snapshot!(code, @"400 Bad Request");
|
snapshot!(code, @"400 Bad Request");
|
||||||
snapshot!(json_string!(response), @r###"
|
snapshot!(json_string!(response), @r###"
|
||||||
{
|
{
|
||||||
"message": "Unknown value `doggo` at `.actions[0]`: expected one of `*`, `search`, `documents.*`, `documents.add`, `documents.get`, `documents.delete`, `indexes.*`, `indexes.create`, `indexes.get`, `indexes.update`, `indexes.delete`, `indexes.swap`, `tasks.*`, `tasks.cancel`, `tasks.delete`, `tasks.get`, `settings.*`, `settings.get`, `settings.update`, `stats.*`, `stats.get`, `metrics.*`, `metrics.get`, `dumps.*`, `dumps.create`, `snapshots.*`, `snapshots.create`, `version`, `keys.create`, `keys.get`, `keys.update`, `keys.delete`, `experimental.get`, `experimental.update`",
|
"message": "Unknown value `doggo` at `.actions[0]`: expected one of `search`, `documents.add`, `documents.get`, `documents.delete`, `documents.*`, `indexes.create`, `indexes.get`, `indexes.update`, `indexes.delete`, `indexes.swap`, `indexes.*`, `tasks.cancel`, `tasks.delete`, `tasks.get`, `tasks.*`, `settings.get`, `settings.update`, `settings.*`, `stats.get`, `stats.*`, `metrics.get`, `metrics.*`, `dumps.create`, `dumps.*`, `snapshots.create`, `snapshots.*`, `version`, `keys.create`, `keys.get`, `keys.update`, `keys.delete`, `experimental.get`, `experimental.update`, `*`",
|
||||||
"code": "invalid_api_key_actions",
|
"code": "invalid_api_key_actions",
|
||||||
"type": "invalid_request",
|
"type": "invalid_request",
|
||||||
"link": "https://docs.meilisearch.com/errors#invalid_api_key_actions"
|
"link": "https://docs.meilisearch.com/errors#invalid_api_key_actions"
|
||||||
|
@ -208,7 +208,10 @@ async fn format_nested() {
|
|||||||
"doggos.name": [
|
"doggos.name": [
|
||||||
{
|
{
|
||||||
"start": 0,
|
"start": 0,
|
||||||
"length": 5
|
"length": 5,
|
||||||
|
"indices": [
|
||||||
|
0
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -105,6 +105,8 @@ impl FormatOptions {
|
|||||||
pub struct MatchBounds {
|
pub struct MatchBounds {
|
||||||
pub start: usize,
|
pub start: usize,
|
||||||
pub length: usize,
|
pub length: usize,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub indices: Option<Vec<usize>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Structure used to analyze a string, compute words that match,
|
/// Structure used to analyze a string, compute words that match,
|
||||||
@ -220,15 +222,20 @@ impl<'t, 'tokenizer> Matcher<'t, 'tokenizer, '_, '_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns boundaries of the words that match the query.
|
/// Returns boundaries of the words that match the query.
|
||||||
pub fn matches(&mut self) -> Vec<MatchBounds> {
|
pub fn matches(&mut self, array_indices: &[usize]) -> Vec<MatchBounds> {
|
||||||
match &self.matches {
|
match &self.matches {
|
||||||
None => self.compute_matches().matches(),
|
None => self.compute_matches().matches(array_indices),
|
||||||
Some((tokens, matches)) => matches
|
Some((tokens, matches)) => matches
|
||||||
.iter()
|
.iter()
|
||||||
.map(|m| MatchBounds {
|
.map(|m| MatchBounds {
|
||||||
start: tokens[m.get_first_token_pos()].byte_start,
|
start: tokens[m.get_first_token_pos()].byte_start,
|
||||||
// TODO: Why is this in chars, while start is in bytes?
|
// TODO: Why is this in chars, while start is in bytes?
|
||||||
length: m.char_count,
|
length: m.char_count,
|
||||||
|
indices: if array_indices.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(array_indices.to_owned())
|
||||||
|
},
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ fn contained_in(selector: &str, key: &str) -> bool {
|
|||||||
/// map_leaf_values(
|
/// map_leaf_values(
|
||||||
/// value.as_object_mut().unwrap(),
|
/// value.as_object_mut().unwrap(),
|
||||||
/// ["jean.race.name"],
|
/// ["jean.race.name"],
|
||||||
/// |key, value| match (value, key) {
|
/// |key, _array_indices, value| match (value, key) {
|
||||||
/// (Value::String(name), "jean.race.name") => *name = "patou".to_string(),
|
/// (Value::String(name), "jean.race.name") => *name = "patou".to_string(),
|
||||||
/// _ => unreachable!(),
|
/// _ => unreachable!(),
|
||||||
/// },
|
/// },
|
||||||
@ -66,17 +66,18 @@ fn contained_in(selector: &str, key: &str) -> bool {
|
|||||||
pub fn map_leaf_values<'a>(
|
pub fn map_leaf_values<'a>(
|
||||||
value: &mut Map<String, Value>,
|
value: &mut Map<String, Value>,
|
||||||
selectors: impl IntoIterator<Item = &'a str>,
|
selectors: impl IntoIterator<Item = &'a str>,
|
||||||
mut mapper: impl FnMut(&str, &mut Value),
|
mut mapper: impl FnMut(&str, &[usize], &mut Value),
|
||||||
) {
|
) {
|
||||||
let selectors: Vec<_> = selectors.into_iter().collect();
|
let selectors: Vec<_> = selectors.into_iter().collect();
|
||||||
map_leaf_values_in_object(value, &selectors, "", &mut mapper);
|
map_leaf_values_in_object(value, &selectors, "", &[], &mut mapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn map_leaf_values_in_object(
|
pub fn map_leaf_values_in_object(
|
||||||
value: &mut Map<String, Value>,
|
value: &mut Map<String, Value>,
|
||||||
selectors: &[&str],
|
selectors: &[&str],
|
||||||
base_key: &str,
|
base_key: &str,
|
||||||
mapper: &mut impl FnMut(&str, &mut Value),
|
array_indices: &[usize],
|
||||||
|
mapper: &mut impl FnMut(&str, &[usize], &mut Value),
|
||||||
) {
|
) {
|
||||||
for (key, value) in value.iter_mut() {
|
for (key, value) in value.iter_mut() {
|
||||||
let base_key = if base_key.is_empty() {
|
let base_key = if base_key.is_empty() {
|
||||||
@ -94,12 +95,12 @@ pub fn map_leaf_values_in_object(
|
|||||||
if should_continue {
|
if should_continue {
|
||||||
match value {
|
match value {
|
||||||
Value::Object(object) => {
|
Value::Object(object) => {
|
||||||
map_leaf_values_in_object(object, selectors, &base_key, mapper)
|
map_leaf_values_in_object(object, selectors, &base_key, array_indices, mapper)
|
||||||
}
|
}
|
||||||
Value::Array(array) => {
|
Value::Array(array) => {
|
||||||
map_leaf_values_in_array(array, selectors, &base_key, mapper)
|
map_leaf_values_in_array(array, selectors, &base_key, array_indices, mapper)
|
||||||
}
|
}
|
||||||
value => mapper(&base_key, value),
|
value => mapper(&base_key, array_indices, value),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,13 +110,24 @@ pub fn map_leaf_values_in_array(
|
|||||||
values: &mut [Value],
|
values: &mut [Value],
|
||||||
selectors: &[&str],
|
selectors: &[&str],
|
||||||
base_key: &str,
|
base_key: &str,
|
||||||
mapper: &mut impl FnMut(&str, &mut Value),
|
base_array_indices: &[usize],
|
||||||
|
mapper: &mut impl FnMut(&str, &[usize], &mut Value),
|
||||||
) {
|
) {
|
||||||
for value in values.iter_mut() {
|
// This avoids allocating twice
|
||||||
|
let mut array_indices = Vec::with_capacity(base_array_indices.len() + 1);
|
||||||
|
array_indices.extend_from_slice(base_array_indices);
|
||||||
|
array_indices.push(0);
|
||||||
|
|
||||||
|
for (i, value) in values.iter_mut().enumerate() {
|
||||||
|
*array_indices.last_mut().unwrap() = i;
|
||||||
match value {
|
match value {
|
||||||
Value::Object(object) => map_leaf_values_in_object(object, selectors, base_key, mapper),
|
Value::Object(object) => {
|
||||||
Value::Array(array) => map_leaf_values_in_array(array, selectors, base_key, mapper),
|
map_leaf_values_in_object(object, selectors, base_key, &array_indices, mapper)
|
||||||
value => mapper(base_key, value),
|
}
|
||||||
|
Value::Array(array) => {
|
||||||
|
map_leaf_values_in_array(array, selectors, base_key, &array_indices, mapper)
|
||||||
|
}
|
||||||
|
value => mapper(base_key, &array_indices, value),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -743,12 +755,14 @@ mod tests {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
map_leaf_values(value.as_object_mut().unwrap(), ["jean.race.name"], |key, value| {
|
map_leaf_values(
|
||||||
match (value, key) {
|
value.as_object_mut().unwrap(),
|
||||||
|
["jean.race.name"],
|
||||||
|
|key, _, value| match (value, key) {
|
||||||
(Value::String(name), "jean.race.name") => *name = S("patou"),
|
(Value::String(name), "jean.race.name") => *name = S("patou"),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
},
|
||||||
});
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
value,
|
value,
|
||||||
@ -775,7 +789,7 @@ mod tests {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let mut calls = 0;
|
let mut calls = 0;
|
||||||
map_leaf_values(value.as_object_mut().unwrap(), ["jean"], |key, value| {
|
map_leaf_values(value.as_object_mut().unwrap(), ["jean"], |key, _, value| {
|
||||||
calls += 1;
|
calls += 1;
|
||||||
match (value, key) {
|
match (value, key) {
|
||||||
(Value::String(name), "jean.race.name") => *name = S("patou"),
|
(Value::String(name), "jean.race.name") => *name = S("patou"),
|
||||||
@ -798,4 +812,52 @@ mod tests {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn map_array() {
|
||||||
|
let mut value: Value = json!({
|
||||||
|
"no_array": "peter",
|
||||||
|
"simple": ["foo", "bar"],
|
||||||
|
"nested": [
|
||||||
|
{
|
||||||
|
"a": [
|
||||||
|
["cat", "dog"],
|
||||||
|
["fox", "bear"],
|
||||||
|
],
|
||||||
|
"b": "hi",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"a": ["green", "blue"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
map_leaf_values(
|
||||||
|
value.as_object_mut().unwrap(),
|
||||||
|
["no_array", "simple", "nested"],
|
||||||
|
|_key, array_indices, value| {
|
||||||
|
*value = format!("{array_indices:?}").into();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
value,
|
||||||
|
json!({
|
||||||
|
"no_array": "[]",
|
||||||
|
"simple": ["[0]", "[1]"],
|
||||||
|
"nested": [
|
||||||
|
{
|
||||||
|
"a": [
|
||||||
|
["[0, 0, 0]", "[0, 0, 1]"],
|
||||||
|
["[0, 1, 0]", "[0, 1, 1]"],
|
||||||
|
],
|
||||||
|
"b": "[0]",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"a": ["[1, 0]", "[1, 1]"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user