iqdb_types/metadata.rs
1//! Scalar metadata attached to a stored vector.
2//!
3//! [`Metadata`] is an immutable, ordered map from string keys to scalar
4//! [`Value`]s. It carries the structured attributes a query filters on (an
5//! author, a timestamp encoded as an integer, a published flag). Construct it
6//! once from a map or an iterator; it has no in-place mutators.
7
8use std::collections::{BTreeMap, btree_map};
9
10/// A scalar metadata value.
11///
12/// A closed set of JSON-like scalars. It deliberately has no nesting — metadata
13/// is a flat map of scalars, which keeps filtering simple and predictable. It
14/// holds an `f64`, so it is [`PartialEq`] but not [`Eq`].
15///
16/// # Examples
17///
18/// ```
19/// use iqdb_types::Value;
20///
21/// let title = Value::String("intro".to_string());
22/// let year = Value::Int(2026);
23/// let score = Value::Float(0.5);
24/// let published = Value::Bool(true);
25/// let missing = Value::Null;
26///
27/// assert_eq!(year, Value::Int(2026));
28/// assert_ne!(title, missing);
29/// let _ = (score, published);
30/// ```
31#[derive(Debug, Clone, PartialEq)]
32#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
33pub enum Value {
34 /// A UTF-8 string value.
35 String(String),
36 /// A signed 64-bit integer value.
37 Int(i64),
38 /// A 64-bit floating-point value.
39 Float(f64),
40 /// A boolean value.
41 Bool(bool),
42 /// The absence of a value.
43 Null,
44}
45
46/// An immutable, ordered map of metadata keys to [`Value`]s.
47///
48/// Build one from a [`BTreeMap`] with [`From`], or collect it from an iterator
49/// of `(String, Value)` pairs. Read it with [`get`](Metadata::get),
50/// [`len`](Metadata::len), [`is_empty`](Metadata::is_empty), and
51/// [`iter`](Metadata::iter). There are no setters — to change metadata, build a
52/// new value.
53///
54/// # Examples
55///
56/// ```
57/// use iqdb_types::{Metadata, Value};
58///
59/// let meta: Metadata = [
60/// ("title".to_string(), Value::String("intro".to_string())),
61/// ("year".to_string(), Value::Int(2026)),
62/// ]
63/// .into_iter()
64/// .collect();
65///
66/// assert_eq!(meta.len(), 2);
67/// assert_eq!(meta.get("year"), Some(&Value::Int(2026)));
68/// assert_eq!(meta.get("missing"), None);
69/// ```
70#[derive(Debug, Clone, Default, PartialEq)]
71#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
72pub struct Metadata(BTreeMap<String, Value>);
73
74impl Metadata {
75 /// Returns the value for `key`, or `None` if the key is absent.
76 ///
77 /// # Examples
78 ///
79 /// ```
80 /// use iqdb_types::{Metadata, Value};
81 ///
82 /// let meta: Metadata =
83 /// [("year".to_string(), Value::Int(2026))].into_iter().collect();
84 /// assert_eq!(meta.get("year"), Some(&Value::Int(2026)));
85 /// assert_eq!(meta.get("nope"), None);
86 /// ```
87 #[inline]
88 #[must_use]
89 pub fn get(&self, key: &str) -> Option<&Value> {
90 self.0.get(key)
91 }
92
93 /// Returns the number of entries.
94 ///
95 /// # Examples
96 ///
97 /// ```
98 /// use iqdb_types::{Metadata, Value};
99 ///
100 /// let meta: Metadata =
101 /// [("a".to_string(), Value::Null)].into_iter().collect();
102 /// assert_eq!(meta.len(), 1);
103 /// ```
104 #[inline]
105 #[must_use]
106 pub fn len(&self) -> usize {
107 self.0.len()
108 }
109
110 /// Returns `true` if there are no entries.
111 ///
112 /// # Examples
113 ///
114 /// ```
115 /// use iqdb_types::Metadata;
116 ///
117 /// assert!(Metadata::default().is_empty());
118 /// ```
119 #[inline]
120 #[must_use]
121 pub fn is_empty(&self) -> bool {
122 self.0.is_empty()
123 }
124
125 /// Returns an iterator over the entries in key order.
126 ///
127 /// # Examples
128 ///
129 /// ```
130 /// use iqdb_types::{Metadata, Value};
131 ///
132 /// let meta: Metadata = [
133 /// ("b".to_string(), Value::Int(2)),
134 /// ("a".to_string(), Value::Int(1)),
135 /// ]
136 /// .into_iter()
137 /// .collect();
138 ///
139 /// // BTreeMap iterates in key order.
140 /// let keys: Vec<&String> = meta.iter().map(|(key, _)| key).collect();
141 /// assert_eq!(keys, vec!["a", "b"]);
142 /// ```
143 #[inline]
144 pub fn iter(&self) -> btree_map::Iter<'_, String, Value> {
145 self.0.iter()
146 }
147}
148
149impl From<BTreeMap<String, Value>> for Metadata {
150 #[inline]
151 fn from(map: BTreeMap<String, Value>) -> Self {
152 Self(map)
153 }
154}
155
156impl FromIterator<(String, Value)> for Metadata {
157 #[inline]
158 fn from_iter<I: IntoIterator<Item = (String, Value)>>(iter: I) -> Self {
159 Self(iter.into_iter().collect())
160 }
161}