askar_storage/
entry.rs

1//! Entry type definitions
2
3use std::{
4    fmt::{self, Debug, Formatter},
5    pin::Pin,
6    str::FromStr,
7};
8
9use futures_lite::stream::{Stream, StreamExt};
10use zeroize::Zeroize;
11
12use super::wql;
13use crate::{crypto::buffer::SecretBytes, error::Error};
14
15pub(crate) fn sorted_tags(tags: &[EntryTag]) -> Vec<&EntryTag> {
16    if tags.is_empty() {
17        Vec::new()
18    } else {
19        let mut tags = tags.iter().collect::<Vec<&EntryTag>>();
20        tags.sort();
21        tags
22    }
23}
24
25/// A record in the store
26#[derive(Clone, Debug, Eq)]
27pub struct Entry {
28    /// The entry kind discriminator
29    pub kind: EntryKind,
30
31    /// The category of the entry record
32    pub category: String,
33
34    /// The name of the entry record, unique within its category
35    pub name: String,
36
37    /// The value of the entry record
38    pub value: SecretBytes,
39
40    /// Tags associated with the entry record
41    pub tags: Vec<EntryTag>,
42}
43
44impl Entry {
45    /// Create a new `Entry`
46    #[inline]
47    pub fn new<C: Into<String>, N: Into<String>, V: Into<SecretBytes>>(
48        kind: EntryKind,
49        category: C,
50        name: N,
51        value: V,
52        tags: Vec<EntryTag>,
53    ) -> Self {
54        Self {
55            kind,
56            category: category.into(),
57            name: name.into(),
58            value: value.into(),
59            tags,
60        }
61    }
62
63    pub(crate) fn sorted_tags(&self) -> Vec<&EntryTag> {
64        sorted_tags(&self.tags)
65    }
66}
67
68impl PartialEq for Entry {
69    fn eq(&self, rhs: &Self) -> bool {
70        self.category == rhs.category
71            && self.name == rhs.name
72            && self.value == rhs.value
73            && self.sorted_tags() == rhs.sorted_tags()
74    }
75}
76
77/// Set of distinct entry kinds for separating records.
78#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
79pub enum EntryKind {
80    /// Key manager entry
81    Kms = 1,
82    /// General stored item
83    Item = 2,
84}
85
86impl TryFrom<usize> for EntryKind {
87    type Error = Error;
88
89    fn try_from(value: usize) -> Result<Self, Self::Error> {
90        match value {
91            1 => Ok(Self::Kms),
92            2 => Ok(Self::Item),
93            _ => Err(err_msg!("Unknown entry kind: {value}")),
94        }
95    }
96}
97
98/// Supported operations for entries in the store
99#[derive(Clone, Copy, Debug, PartialEq, Eq)]
100pub enum EntryOperation {
101    /// Insert a new `Entry`
102    Insert,
103    /// Replace an existing `Entry`
104    Replace,
105    /// Remove an existing `Entry`
106    Remove,
107}
108
109/// A tag on an entry record in the store
110#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Zeroize)]
111pub enum EntryTag {
112    /// An entry tag to be stored encrypted
113    Encrypted(String, String),
114    /// An entry tag to be stored in plaintext (for ordered comparison)
115    Plaintext(String, String),
116}
117
118impl EntryTag {
119    /// Accessor for the tag name
120    pub fn name(&self) -> &str {
121        match self {
122            Self::Encrypted(name, _) | Self::Plaintext(name, _) => name,
123        }
124    }
125
126    /// Create a new EntryTag using references to the name and value
127    pub fn map_ref(&self, f: impl FnOnce(&str, &str) -> (String, String)) -> Self {
128        match self {
129            Self::Encrypted(name, val) => {
130                let (name, val) = f(name.as_str(), val.as_str());
131                Self::Encrypted(name, val)
132            }
133            Self::Plaintext(name, val) => {
134                let (name, val) = f(name.as_str(), val.as_str());
135                Self::Plaintext(name, val)
136            }
137        }
138    }
139
140    /// Setter for the tag name
141    pub fn update_name(&mut self, f: impl FnOnce(&mut String)) {
142        match self {
143            Self::Encrypted(name, _) | Self::Plaintext(name, _) => f(name),
144        }
145    }
146
147    /// Accessor for the tag value
148    pub fn value(&self) -> &str {
149        match self {
150            Self::Encrypted(_, val) | Self::Plaintext(_, val) => val,
151        }
152    }
153
154    /// Unwrap the tag value
155    pub fn into_value(self) -> String {
156        match self {
157            Self::Encrypted(_, value) | Self::Plaintext(_, value) => value,
158        }
159    }
160}
161
162impl Debug for EntryTag {
163    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
164        match self {
165            Self::Encrypted(name, value) => f
166                .debug_tuple("Encrypted")
167                .field(&name)
168                .field(&value)
169                .finish(),
170            Self::Plaintext(name, value) => f
171                .debug_tuple("Plaintext")
172                .field(&name)
173                .field(&value)
174                .finish(),
175        }
176    }
177}
178
179#[derive(Clone, Debug, PartialEq, Eq)]
180pub(crate) struct EncEntryTag {
181    pub name: Vec<u8>,
182    pub value: Vec<u8>,
183    pub plaintext: bool,
184}
185
186/// A WQL filter used to restrict record queries
187#[derive(Clone, Debug, PartialEq, Eq)]
188#[repr(transparent)]
189pub struct TagFilter {
190    pub(crate) query: wql::Query,
191}
192
193impl TagFilter {
194    /// Combine multiple tag filters using the `AND` operator
195    #[inline]
196    pub fn all_of(each: Vec<TagFilter>) -> Self {
197        Self {
198            query: wql::Query::And(unsafe {
199                std::mem::transmute::<Vec<TagFilter>, Vec<wql::Query>>(each)
200            }),
201        }
202    }
203
204    /// Combine multiple tag filters using the `OR` operator
205    #[inline]
206    pub fn any_of(each: Vec<TagFilter>) -> Self {
207        Self {
208            query: wql::Query::Or(unsafe {
209                std::mem::transmute::<Vec<TagFilter>, Vec<wql::Query>>(each)
210            }),
211        }
212    }
213
214    /// Get the inverse of a tag filter
215    #[inline]
216    pub fn negate(filter: TagFilter) -> Self {
217        Self {
218            query: wql::Query::Not(Box::new(filter.query)),
219        }
220    }
221
222    /// Create an equality comparison tag filter
223    #[inline]
224    pub fn is_eq(name: impl Into<String>, value: impl Into<String>) -> Self {
225        Self {
226            query: wql::Query::Eq(name.into(), value.into()),
227        }
228    }
229
230    /// Create an inequality comparison tag filter
231    #[inline]
232    pub fn is_not_eq(name: impl Into<String>, value: impl Into<String>) -> Self {
233        Self {
234            query: wql::Query::Neq(name.into(), value.into()),
235        }
236    }
237
238    /// Create an greater-than comparison tag filter
239    #[inline]
240    pub fn is_gt(name: impl Into<String>, value: impl Into<String>) -> Self {
241        Self {
242            query: wql::Query::Gt(name.into(), value.into()),
243        }
244    }
245
246    /// Create an greater-than-or-equal comparison tag filter
247    #[inline]
248    pub fn is_gte(name: impl Into<String>, value: impl Into<String>) -> Self {
249        Self {
250            query: wql::Query::Gte(name.into(), value.into()),
251        }
252    }
253
254    /// Create an less-than comparison tag filter
255    #[inline]
256    pub fn is_lt(name: impl Into<String>, value: impl Into<String>) -> Self {
257        Self {
258            query: wql::Query::Lt(name.into(), value.into()),
259        }
260    }
261
262    /// Create an less-than-or-equal comparison tag filter
263    #[inline]
264    pub fn is_lte(name: impl Into<String>, value: impl Into<String>) -> Self {
265        Self {
266            query: wql::Query::Lte(name.into(), value.into()),
267        }
268    }
269
270    /// Create a LIKE comparison tag filter
271    #[inline]
272    pub fn is_like(name: impl Into<String>, value: impl Into<String>) -> Self {
273        Self {
274            query: wql::Query::Like(name.into(), value.into()),
275        }
276    }
277
278    /// Create an IN comparison tag filter for a set of tag values
279    #[inline]
280    pub fn is_in(name: impl Into<String>, values: Vec<String>) -> Self {
281        Self {
282            query: wql::Query::In(name.into(), values),
283        }
284    }
285
286    /// Create an EXISTS tag filter for a set of tag names
287    #[inline]
288    pub fn exist(names: Vec<String>) -> Self {
289        Self {
290            query: wql::Query::Exist(names),
291        }
292    }
293
294    /// Convert the tag filter to JSON format
295    pub fn to_string(&self) -> Result<String, Error> {
296        serde_json::to_string(&self.query).map_err(err_map!("Error encoding tag filter"))
297    }
298
299    /// Unwrap into a wql::Query
300    pub fn into_query(self) -> wql::Query {
301        self.query
302    }
303}
304
305impl From<wql::Query> for TagFilter {
306    fn from(query: wql::Query) -> Self {
307        Self { query }
308    }
309}
310
311impl FromStr for TagFilter {
312    type Err = Error;
313
314    fn from_str(query: &str) -> Result<Self, Error> {
315        let query = serde_json::from_str(query).map_err(err_map!("Error parsing tag query"))?;
316        Ok(Self { query })
317    }
318}
319
320/// An active record scan of a store backend
321pub struct Scan<'s, T> {
322    #[allow(clippy::type_complexity)]
323    stream: Option<Pin<Box<dyn Stream<Item = Result<Vec<T>, Error>> + Send + 's>>>,
324    page_size: usize,
325}
326
327impl<'s, T> Scan<'s, T> {
328    pub(crate) fn new<S>(stream: S, page_size: usize) -> Self
329    where
330        S: Stream<Item = Result<Vec<T>, Error>> + Send + 's,
331    {
332        Self {
333            stream: Some(stream.boxed()),
334            page_size,
335        }
336    }
337
338    /// Fetch the next set of result rows
339    pub async fn fetch_next(&mut self) -> Result<Option<Vec<T>>, Error> {
340        if let Some(mut s) = self.stream.take() {
341            match s.try_next().await? {
342                Some(val) => {
343                    if val.len() == self.page_size {
344                        self.stream.replace(s);
345                    }
346                    Ok(Some(val))
347                }
348                None => Ok(None),
349            }
350        } else {
351            Ok(None)
352        }
353    }
354}
355
356impl<S> Debug for Scan<'_, S> {
357    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
358        f.debug_struct("Scan")
359            .field("page_size", &self.page_size)
360            .finish()
361    }
362}