blueprint_core/
metadata.rs

1//! Metadata that can be included in a [`JobCall`] or [`JobResult`] to provide additional context.
2
3use core::convert::Infallible;
4use core::fmt;
5use core::str::FromStr;
6
7use alloc::borrow::Cow;
8use alloc::collections::BTreeMap;
9use alloc::collections::btree_map::{Entry, Iter, IterMut};
10use alloc::string::String;
11use alloc::vec::Vec;
12use bytes::{Bytes, BytesMut};
13
14/// A typed metadata map
15///
16/// # Type Parameters
17///
18/// * `T`: The type of values stored in the metadata map.
19#[derive(Clone, Debug, Default, PartialEq)]
20pub struct MetadataMap<T> {
21    map: BTreeMap<Cow<'static, str>, T>,
22}
23
24impl<T> MetadataMap<T> {
25    /// Creates a new empty `MetadataMap`.
26    pub fn new() -> Self {
27        Self {
28            map: BTreeMap::new(),
29        }
30    }
31
32    /// Returns the number of elements in the map.
33    pub fn len(&self) -> usize {
34        self.map.len()
35    }
36
37    /// Returns `true` if the map is empty.
38    pub fn is_empty(&self) -> bool {
39        self.map.is_empty()
40    }
41
42    /// Inserts a key-value pair into the map.
43    ///
44    /// If the map did not have this key present, [`None`] is returned.
45    ///
46    /// If the map did have this key present, the value is updated, and the old
47    /// value is returned. The key is not updated, though; it is retained as
48    /// is (verbatim).
49    pub fn insert<K, V>(&mut self, key: K, value: V) -> Option<T>
50    where
51        K: Into<Cow<'static, str>>,
52        V: Into<T>,
53    {
54        self.map.insert(key.into(), value.into())
55    }
56
57    /// Gets a reference to the value associated with the given key.
58    pub fn get<K>(&self, key: K) -> Option<&T>
59    where
60        K: AsRef<str>,
61    {
62        self.map.get(key.as_ref())
63    }
64
65    /// Gets a mutable reference to the value associated with the given key.
66    pub fn get_mut<K>(&mut self, key: &K) -> Option<&mut T>
67    where
68        K: AsRef<str>,
69    {
70        self.map.get_mut(key.as_ref())
71    }
72
73    /// Removes a key from the map, returning the value at the key if the key
74    /// was previously in the map.
75    pub fn remove<K>(&mut self, key: &K) -> Option<T>
76    where
77        K: AsRef<str>,
78    {
79        self.map.remove(key.as_ref())
80    }
81
82    /// Clears the map, removing all key-value pairs. Keeps the allocated memory for reuse.
83    pub fn clear(&mut self) {
84        self.map.clear();
85    }
86
87    /// Returns an iterator over the map's entries.
88    pub fn iter(&self) -> Iter<'_, Cow<'static, str>, T> {
89        self.map.iter()
90    }
91
92    /// Returns a mutable iterator over the map's entries.
93    pub fn iter_mut(&mut self) -> IterMut<'_, Cow<'static, str>, T> {
94        self.map.iter_mut()
95    }
96
97    /// Provides a view into a single entry in the map, which may or may not be present.
98    pub fn entry<K>(&mut self, key: K) -> Entry<'_, Cow<'static, str>, T>
99    where
100        K: Into<Cow<'static, str>>,
101    {
102        self.map.entry(key.into())
103    }
104
105    /// Extends the map with the key-value pairs from the given map.
106    pub fn extend(&mut self, other: Self) {
107        self.map.extend(other.map);
108    }
109}
110
111/// Represents a [`JobCall`] metadata field value.
112///
113/// To handle this, the `MetadataValue` is usable as a type and can be compared
114/// with strings and implements `Debug`. A `to_str` method is provided that returns
115/// an `Err` if the metadata value contains non-visible ASCII characters.
116#[derive(Clone, Default)]
117pub struct MetadataValue {
118    inner: Bytes,
119    is_sensitive: bool,
120}
121
122impl FromStr for MetadataValue {
123    type Err = Infallible;
124
125    fn from_str(s: &str) -> Result<Self, Self::Err> {
126        Ok(Self {
127            inner: Bytes::copy_from_slice(s.as_bytes()),
128            is_sensitive: false,
129        })
130    }
131}
132
133impl MetadataValue {
134    /// Create a new `MetadataValue` from a string.
135    pub fn from_bytes(value: Bytes) -> Self {
136        Self {
137            inner: value,
138            is_sensitive: false,
139        }
140    }
141
142    /// Create a new `MetadataValue` from a string.
143    pub fn from_sensitive_str(value: &str) -> Self {
144        Self {
145            inner: Bytes::copy_from_slice(value.as_bytes()),
146            is_sensitive: true,
147        }
148    }
149
150    /// Create a new `MetadataValue` from a string.
151    pub fn from_sensitive_bytes(value: Bytes) -> Self {
152        Self {
153            inner: value,
154            is_sensitive: true,
155        }
156    }
157
158    /// Returns true if the metadata value is sensitive.
159    pub fn is_sensitive(&self) -> bool {
160        self.is_sensitive
161    }
162
163    /// Returns the length of the metadata value.
164    pub fn len(&self) -> usize {
165        self.inner.len()
166    }
167
168    /// Returns true if the metadata value is empty.
169    pub fn is_empty(&self) -> bool {
170        self.inner.is_empty()
171    }
172
173    /// Converts the metadata value into a string if it contains valid UTF-8.
174    pub fn to_str(&self) -> Result<&str, core::str::Utf8Error> {
175        core::str::from_utf8(&self.inner)
176    }
177
178    /// Converts the metadata value into bytes.
179    pub fn into_bytes(self) -> Bytes {
180        self.inner
181    }
182
183    pub fn as_bytes(&self) -> &[u8] {
184        &self.inner[..]
185    }
186}
187
188impl AsRef<[u8]> for MetadataValue {
189    #[inline]
190    fn as_ref(&self) -> &[u8] {
191        self.inner.as_ref()
192    }
193}
194
195impl fmt::Debug for MetadataValue {
196    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
197        if self.is_sensitive {
198            f.write_str("Sensitive")
199        } else {
200            f.write_str("\"")?;
201            let mut from = 0;
202            let bytes = self.as_bytes();
203            for (i, &b) in bytes.iter().enumerate() {
204                if !is_visible_ascii(b) || b == b'"' {
205                    if from != i {
206                        f.write_str(
207                            core::str::from_utf8(&bytes[from..i]).map_err(|_| fmt::Error)?,
208                        )?;
209                    }
210                    if b == b'"' {
211                        f.write_str("\\\"")?;
212                    } else {
213                        write!(f, "\\x{b:x}")?;
214                    }
215                    from = i + 1;
216                }
217            }
218
219            if from != bytes.len() {
220                f.write_str(core::str::from_utf8(&bytes[from..]).map_err(|_| fmt::Error)?)?;
221            }
222            f.write_str("\"")
223        }
224    }
225}
226
227impl From<&str> for MetadataValue {
228    fn from(value: &str) -> Self {
229        Self::from_str(value).unwrap()
230    }
231}
232
233impl From<Bytes> for MetadataValue {
234    fn from(value: Bytes) -> Self {
235        Self::from_bytes(value)
236    }
237}
238
239impl From<&Bytes> for MetadataValue {
240    fn from(value: &Bytes) -> Self {
241        Self::from_bytes(value.clone())
242    }
243}
244
245impl From<BytesMut> for MetadataValue {
246    fn from(value: BytesMut) -> Self {
247        Self::from_bytes(value.freeze())
248    }
249}
250
251impl From<&BytesMut> for MetadataValue {
252    fn from(value: &BytesMut) -> Self {
253        Self::from_bytes(value.clone().freeze())
254    }
255}
256
257impl From<String> for MetadataValue {
258    fn from(value: String) -> Self {
259        Self::from_str(&value).unwrap()
260    }
261}
262
263impl From<&String> for MetadataValue {
264    fn from(value: &String) -> Self {
265        Self::from_str(value).unwrap()
266    }
267}
268
269impl From<&[u8]> for MetadataValue {
270    fn from(value: &[u8]) -> Self {
271        Self::from_bytes(Bytes::copy_from_slice(value))
272    }
273}
274
275impl From<Vec<u8>> for MetadataValue {
276    fn from(value: Vec<u8>) -> Self {
277        Self::from_bytes(Bytes::from(value))
278    }
279}
280
281impl From<&Vec<u8>> for MetadataValue {
282    fn from(value: &Vec<u8>) -> Self {
283        Self::from_bytes(Bytes::copy_from_slice(value))
284    }
285}
286
287impl<const N: usize> From<[u8; N]> for MetadataValue {
288    fn from(value: [u8; N]) -> Self {
289        Self::from_bytes(Bytes::copy_from_slice(&value))
290    }
291}
292
293impl<const N: usize> From<&[u8; N]> for MetadataValue {
294    fn from(value: &[u8; N]) -> Self {
295        Self::from_bytes(Bytes::copy_from_slice(value))
296    }
297}
298
299macro_rules! impl_from_numbers {
300    ($($t:ty),*) => {
301        $(
302            /// Converts a number into a metadata value by converting it to a big-endian byte array.
303            impl From<$t> for MetadataValue {
304                fn from(value: $t) -> Self {
305                    Self::from_bytes(Bytes::copy_from_slice(&value.to_be_bytes()))
306                }
307            }
308
309            impl From<&$t> for MetadataValue {
310                fn from(value: &$t) -> Self {
311                    Self::from_bytes(Bytes::copy_from_slice(&value.to_be_bytes()))
312                }
313            }
314        )*
315
316    };
317}
318
319macro_rules! impl_try_from_metadata_for_numbers {
320    ($($t:ty),*) => {
321        $(
322            /// Tries to convert a metadata value into a number by parsing it as a big-endian byte array.
323            impl core::convert::TryFrom<MetadataValue> for $t {
324                type Error = core::array::TryFromSliceError;
325
326                fn try_from(value: MetadataValue) -> Result<Self, Self::Error> {
327                    let bytes = value.as_bytes();
328                    let mut arr = [0; core::mem::size_of::<Self>()];
329                    arr.copy_from_slice(bytes);
330                    Ok(Self::from_be_bytes(arr))
331                }
332            }
333
334            /// Tries to convert a metadata value into a number by parsing it as a big-endian byte array.
335            impl core::convert::TryFrom<&MetadataValue> for $t {
336                type Error = core::array::TryFromSliceError;
337
338                fn try_from(value: &MetadataValue) -> Result<Self, Self::Error> {
339                    let bytes = value.as_bytes();
340                    let mut arr = [0; core::mem::size_of::<Self>()];
341                    arr.copy_from_slice(bytes);
342                    Ok(Self::from_be_bytes(arr))
343                }
344            }
345        )*
346    };
347}
348
349impl_from_numbers! { u16, u32, u64, u128, usize, i16, i32, i64, i128, isize }
350impl_try_from_metadata_for_numbers! { u16, u32, u64, u128, usize, i16, i32, i64, i128, isize }
351
352const fn is_visible_ascii(b: u8) -> bool {
353    b >= 32 && b < 127 || b == b'\t'
354}